<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/utils.js"></script>
<script src="/common/dispatcher/dispatcher.js"></script>
<script src="../resources/utils.js"></script>
<script src="resources/utils.sub.js"></script>
<script src="/common/subset-tests-by-key.js"></script>
<meta name="variant" content="?include=defaultPredicate">
<meta name="variant" content="?include=hrefMatches">
<meta name="variant" content="?include=and">
<meta name="variant" content="?include=or">
<meta name="variant" content="?include=not">
<meta name="variant" content="?include=invalidPredicate">
<meta name="variant" content="?include=linkInShadowTree">
<meta name="variant" content="?include=linkHrefChanged">
<meta name="variant" content="?include=newRuleSetAdded">
<meta name="variant" content="?include=selectorMatches">
<meta name="variant" content="?include=selectorMatchesScopingRoot">
<meta name="variant" content="?include=selectorMatchesInShadowTree">
<meta name="variant" content="?include=selectorMatchesDisplayNone">
<meta name="variant" content="?include=selectorMatchesDisplayLocked">
<meta name="variant" content="?include=unslottedLink">
<meta name="variant" content="?include=immediateMutation">
<meta name="variant" content="?include=baseURLChangedBySameDocumentNavigation">
<meta name="variant" content="?include=baseURLChangedByBaseElement">
<meta name="variant" content="?include=linkToSelfFragment">
<body>
<script>
setup(() => assertSpeculationRulesIsSupported());
subsetTestByKey('defaultPredicate', promise_test, async t => {
const url = getPrefetchUrl();
addLink(url);
insertDocumentRule();
await new Promise(resolve => t.step_timeout(resolve, 2000));
assert_equals(await isUrlPrefetched(url), 1);
}, 'test document rule with no predicate');
subsetTestByKey('hrefMatches', promise_test, async t => {
insertDocumentRule({ href_matches: '*\\?uuid=*&foo=bar' });
const url_1 = getPrefetchUrl({foo: 'bar'});
addLink(url_1);
const url_2 = getPrefetchUrl({foo: 'buzz'});
addLink(url_2)
await new Promise(resolve => t.step_timeout(resolve, 2000));
assert_equals(await isUrlPrefetched(url_1), 1);
assert_equals(await isUrlPrefetched(url_2), 0);
}, 'test href_matches document rule');
subsetTestByKey('and', promise_test, async t => {
insertDocumentRule({
'and': [
{ href_matches: '*\\?*foo=bar*' },
{ href_matches: '*\\?*fizz=buzz*' }]
});
const url_1 = getPrefetchUrl({foo: 'bar'});
const url_2 = getPrefetchUrl({fizz: 'buzz'});
const url_3 = getPrefetchUrl({foo: 'bar', fizz: 'buzz'});
[url_1, url_2, url_3].forEach(url => addLink(url));
await new Promise(resolve => t.step_timeout(resolve, 2000));
assert_equals(await isUrlPrefetched(url_1), 0);
assert_equals(await isUrlPrefetched(url_2), 0);
assert_equals(await isUrlPrefetched(url_3), 1);
}, 'test document rule with conjunction predicate');
subsetTestByKey('or', promise_test, async t => {
insertDocumentRule({
'or': [
{ href_matches: '*\\?*foo=bar*' },
{ href_matches: '*\\?*fizz=buzz*' }]
});
const url_1 = getPrefetchUrl({ foo: 'buzz' });
const url_2 = getPrefetchUrl({ fizz: 'buzz' });
const url_3 = getPrefetchUrl({ foo: 'bar'});
[url_1, url_2, url_3].forEach(url => addLink(url));
await new Promise(resolve => t.step_timeout(resolve, 2000));
assert_equals(await isUrlPrefetched(url_1), 0);
assert_equals(await isUrlPrefetched(url_2), 1);
assert_equals(await isUrlPrefetched(url_3), 1);
}, 'test document rule with disjunction predicate');
subsetTestByKey('not', promise_test, async t => {
insertDocumentRule({ not: { href_matches: '*\\?uuid=*&foo=bar' } });
const url_1 = getPrefetchUrl({foo: 'bar'});
addLink(url_1);
const url_2 = getPrefetchUrl({foo: 'buzz'});
addLink(url_2)
await new Promise(resolve => t.step_timeout(resolve, 2000));
assert_equals(await isUrlPrefetched(url_1), 0);
assert_equals(await isUrlPrefetched(url_2), 1);
}, 'test document rule with negation predicate');
subsetTestByKey('invalidPredicate', promise_test, async t => {
const url = getPrefetchUrl();
addLink(url);
insertDocumentRule({invalid: 'predicate'});
await new Promise(resolve => t.step_timeout(resolve, 2000));
assert_equals(await isUrlPrefetched(url), 0);
}, 'invalid predicate should not throw error or start prefetch');
subsetTestByKey('linkInShadowTree', promise_test, async t => {
insertDocumentRule();
// Create shadow root.
const shadowHost = document.createElement('div');
document.body.appendChild(shadowHost);
const shadowRoot = shadowHost.attachShadow({mode: 'open'});
const url = getPrefetchUrl();
addLink(url, shadowRoot);
await new Promise(resolve => t.step_timeout(resolve, 2000));
assert_equals(await isUrlPrefetched(url), 1);
}, 'test that matching link in a shadow tree is prefetched');
subsetTestByKey('linkHrefChanged', promise_test, async t => {
insertDocumentRule({href_matches: "*\\?*foo=bar*"});
const url = getPrefetchUrl();
const link = addLink(url);
await new Promise(resolve => t.step_timeout(resolve, 2000));
assert_equals(await isUrlPrefetched(url), 0);
const matching_url = getPrefetchUrl({foo: 'bar'});
link.href = matching_url;
await new Promise(resolve => t.step_timeout(resolve, 2000));
assert_equals(await isUrlPrefetched(matching_url), 1);
}, 'test that changing the href of an invalid link to a matching value triggers a prefetch');
subsetTestByKey('newRuleSetAdded', promise_test, async t => {
insertDocumentRule({href_matches: "*\\?*foo=bar*"});
const url = getPrefetchUrl({fizz: "buzz"});
addLink(url);
await new Promise(resolve => t.step_timeout(resolve, 2000));
assert_equals(await isUrlPrefetched(url), 0);
insertDocumentRule({href_matches: "*\\?*fizz=buzz*"});
await new Promise(resolve => t.step_timeout(resolve, 2000));
assert_equals(await isUrlPrefetched(url), 1);
}, 'test that adding a second rule set triggers prefetch');
subsetTestByKey('selectorMatches', promise_test, async t => {
insertDocumentRule({ selector_matches: 'a.important-link' });
const url_1 = getPrefetchUrl({foo: 'bar'});
const importantLink = addLink(url_1);
importantLink.className = 'important-link';
const url_2 = getPrefetchUrl({foo: 'buzz'});
addLink(url_2)
await new Promise(resolve => t.step_timeout(resolve, 2000));
assert_equals(await isUrlPrefetched(url_1), 1);
assert_equals(await isUrlPrefetched(url_2), 0);
}, 'test selector_matches document rule');
subsetTestByKey('selectorMatchesScopingRoot', promise_test, async t => {
insertDocumentRule({ selector_matches: ':root > body > a' });
const url_1 = getPrefetchUrl({ foo: 'bar' });
addLink(url_1);
const url_2 = getPrefetchUrl({ foo: 'buzz' });
const extraContainer = document.createElement('div');
document.body.appendChild(extraContainer);
addLink(url_2, extraContainer);
await new Promise(resolve => t.step_timeout(resolve, 2000));
assert_equals(await isUrlPrefetched(url_1), 1);
assert_equals(await isUrlPrefetched(url_2), 0);
}, 'test selector_matches with :root');
// 'selector_matches' should use the shadowRoot as the scoping root when
// matching links inside a shadow tree.
subsetTestByKey('selectorMatchesInShadowTree', promise_test, async t => {
insertDocumentRule({ selector_matches: ':scope a.important-link' });
// Create shadow root.
const shadowHost = document.createElement('div');
document.body.appendChild(shadowHost);
const shadowRoot = shadowHost.attachShadow({ mode: 'open' });
const url = getPrefetchUrl();
const link = addLink(url, shadowRoot);
link.className = 'important-link';
await new Promise(resolve => t.step_timeout(resolve, 2000));
assert_equals(await isUrlPrefetched(url), 1);
}, 'test selector_matches with link inside shadow tree');
subsetTestByKey('selectorMatchesDisplayNone', promise_test, async t => {
const style = document.createElement('style');
style.innerText = ".important-section { display: none; }";
document.head.appendChild(style);
insertDocumentRule();
const importantSection = document.createElement('div');
importantSection.className = 'important-section';
document.body.appendChild(importantSection);
const url = getPrefetchUrl();
addLink(url, importantSection);
await new Promise(resolve => t.step_timeout(resolve, 2000));
assert_equals(await isUrlPrefetched(url), 0);
style.remove();
await new Promise(resolve => t.step_timeout(resolve, 2000));
assert_equals(await isUrlPrefetched(url), 1);
}, 'test selector_matches with link inside display:none container');
subsetTestByKey('selectorMatchesDisplayLocked', promise_test, async t => {
const style = document.createElement('style');
style.innerText = ".important-section { content-visibility: hidden; }";
document.head.appendChild(style);
insertDocumentRule({ selector_matches: '.important-section a' });
const importantSection = document.createElement('div');
importantSection.className = 'important-section';
document.body.appendChild(importantSection);
const url = getPrefetchUrl();
addLink(url, importantSection);
await new Promise(resolve => t.step_timeout(resolve, 2000));
assert_equals(await isUrlPrefetched(url), 0);
style.remove();
await new Promise(resolve => t.step_timeout(resolve, 2000));
assert_equals(await isUrlPrefetched(url), 1);
}, 'test selector_matches with link inside display locked container');
subsetTestByKey('unslottedLink', promise_test, async t => {
insertDocumentRule();
// Create shadow root.
const shadowHost = document.createElement('div');
document.body.appendChild(shadowHost);
const shadowRoot = shadowHost.attachShadow({ mode: 'open' });
// Add unslotted link.
const url = getPrefetchUrl();
addLink(url, shadowHost);
await new Promise(resolve => t.step_timeout(resolve, 2000));
assert_equals(await isUrlPrefetched(url), 0);
}, 'test that unslotted link never matches document rule');
subsetTestByKey('immediateMutation', promise_test, async t => {
// Add a link and allow it to get its style computed.
// (Double RAF lets this happen normally.)
const url = getPrefetchUrl();
const link = addLink(url, document.body);
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(() => resolve())));
// Add a document rule and then immediately change the DOM to make it match.
insertDocumentRule({ selector_matches: '.late-class *' });
document.body.className = 'late-class';
await new Promise(resolve => t.step_timeout(resolve, 2000));
assert_equals(await isUrlPrefetched(url), 1);
}, 'test that selector_matches predicates respect changes immediately');
const baseURLChangedTestFixture = (testName, modifyBaseURLFunc) => {
return subsetTestByKey(testName, promise_test, async t => {
const url = getPrefetchUrl();
const link = addLink(url);
const url_pattern_string = `prefetch.py${url.search}`;
// Insert a document rule with a url pattern predicate that uses a
// relative URL which will not match with |url|, due to |document.baseURI|
// being different from |url|'s path.
assert_false((new URLPattern(url_pattern_string, document.baseURI)).test(url));
insertDocumentRule({ href_matches: url_pattern_string });
await new Promise(resolve => t.step_timeout(resolve, 2000));
assert_equals(await isUrlPrefetched(url), 0);
// Change the baseURL of the document to |url|. |url| should now be
// prefetched.
modifyBaseURLFunc(url);
assert_true((new URLPattern(url_pattern_string, document.baseURI)).test(url));
await new Promise(resolve => t.step_timeout(resolve, 2000));
assert_equals(await isUrlPrefetched(url), 1);
});
}
baseURLChangedTestFixture('baseURLChangedBySameDocumentNavigation', url => {
history.pushState({}, "", url);
});
baseURLChangedTestFixture('baseURLChangedByBaseElement', url => {
const base = document.createElement('base');
base.href = url;
document.head.appendChild(base);
});
subsetTestByKey('linkToSelfFragment', promise_test, async t => {
const url = getPrefetchUrl();
history.pushState({}, "", url);
addLink(new URL('#fragment', url));
insertDocumentRule();
await new Promise(resolve => t.step_timeout(resolve, 2000));
assert_equals(await isUrlPrefetched(url), 0);
}, 'test that a fragment link to the current document does not prefetch');
</script>
</body>