diff options
Diffstat (limited to 'testing/web-platform/tests/speculation-rules/prefetch')
27 files changed, 1635 insertions, 0 deletions
diff --git a/testing/web-platform/tests/speculation-rules/prefetch/anonymous-client.https.html b/testing/web-platform/tests/speculation-rules/prefetch/anonymous-client.https.html new file mode 100644 index 0000000000..dfa48f02ab --- /dev/null +++ b/testing/web-platform/tests/speculation-rules/prefetch/anonymous-client.https.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.sub.js"></script> +<script> + promise_test(async t => { + assert_implements(HTMLScriptElement.supports('speculationrules'), "Speculation Rules not supported"); + + let agent = await spawnWindow(t); + let nextUrl = agent.getExecutorURL({ hostname: PREFETCH_PROXY_BYPASS_HOST, page: 2 }); + await agent.forceSinglePrefetch(nextUrl, { requires: ["anonymous-client-ip-when-cross-origin"] }); + await agent.navigate(nextUrl); + + let requestHeaders = await agent.getRequestHeaders(); + assert_in_array(requestHeaders.purpose, ["", "prefetch"], "The vendor-specific header Purpose, if present, must be 'prefetch'."); + assert_equals(requestHeaders.sec_purpose, "prefetch;anonymous-client-ip"); + }, "test anonymous-client url prefetch for cross origin pages"); +</script> diff --git a/testing/web-platform/tests/speculation-rules/prefetch/cross-origin-cookies.https.html b/testing/web-platform/tests/speculation-rules/prefetch/cross-origin-cookies.https.html new file mode 100644 index 0000000000..c3911919f0 --- /dev/null +++ b/testing/web-platform/tests/speculation-rules/prefetch/cross-origin-cookies.https.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src='/resources/testdriver-vendor.js'></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.sub.js"></script> +<script> + promise_test(async t => { + assert_implements(HTMLScriptElement.supports('speculationrules'), "Speculation Rules not supported"); + + await test_driver.delete_all_cookies(); + + let executor = 'cookies.py'; + let agent = await spawnWindow(t, { executor }); + let response_cookies = await agent.getResponseCookies(); + let request_cookies = await agent.getRequestCookies(); + assert_equals(request_cookies["count"], undefined); + assert_equals(request_cookies["type"], undefined); + assert_equals(response_cookies["count"], "1"); + assert_equals(response_cookies["type"], "navigate"); + + let nextUrl = agent.getExecutorURL({ executor, hostname: PREFETCH_PROXY_BYPASS_HOST, page: 2 }); + await agent.forceSinglePrefetch(nextUrl, { requires: ["anonymous-client-ip-when-cross-origin"] }); + await agent.forceSinglePrefetch(nextUrl); + await agent.navigate(nextUrl); + + response_cookies = await agent.getResponseCookies(); + request_cookies = await agent.getRequestCookies(); + assert_equals(request_cookies["count"], undefined); + assert_equals(request_cookies["type"], undefined); + assert_equals(response_cookies["count"], "1"); + assert_equals(response_cookies["type"], "prefetch"); + + let requestHeaders = await agent.getRequestHeaders(); + assert_equals(requestHeaders.sec_purpose, "prefetch;anonymous-client-ip"); + + }, "speculation rules based prefetch should not use cookies for cross origin urls."); +</script> diff --git a/testing/web-platform/tests/speculation-rules/prefetch/document-rules.https.html b/testing/web-platform/tests/speculation-rules/prefetch/document-rules.https.html new file mode 100644 index 0000000000..a5030f6938 --- /dev/null +++ b/testing/web-platform/tests/speculation-rules/prefetch/document-rules.https.html @@ -0,0 +1,136 @@ +<!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.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"> + +<body> +<script> + subsetTestByKey('defaultPredicate', promise_test, async t => { + assert_implements(HTMLScriptElement.supports('speculationrules'), + 'Speculation Rules not supported'); + + 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 => { + assert_implements(HTMLScriptElement.supports('speculationrules'), + 'Speculation Rules not supported'); + + 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 => { + assert_implements(HTMLScriptElement.supports('speculationrules'), + 'Speculation Rules not supported'); + + 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 => { + assert_implements(HTMLScriptElement.supports('speculationrules'), + 'Speculation Rules not supported'); + + 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 => { + assert_implements(HTMLScriptElement.supports('speculationrules'), + "Speculation Rules not supported"); + + 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 => { + assert_implements(HTMLScriptElement.supports('speculationrules'), + 'Speculation Rules not supported'); + + 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 => { + assert_implements(HTMLScriptElement.supports('speculationrules'), + 'Speculation Rules not supported'); + + 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'); + +</script> +</body> diff --git a/testing/web-platform/tests/speculation-rules/prefetch/duplicate-urls.https.html b/testing/web-platform/tests/speculation-rules/prefetch/duplicate-urls.https.html new file mode 100644 index 0000000000..179bbdfd68 --- /dev/null +++ b/testing/web-platform/tests/speculation-rules/prefetch/duplicate-urls.https.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.sub.js"></script> + +<script> + promise_test(async t => { + assert_implements(HTMLScriptElement.supports('speculationrules'), "Speculation Rules not supported"); + + let urls = Array(5).fill(getPrefetchUrlList(1)[0]); + insertSpeculationRules({ prefetch: [{ source: 'list', urls: urls }] }); + await new Promise(resolve => t.step_timeout(resolve, 2000)); + + let prefetched_count = (await Promise.all(urls.map(isUrlPrefetched))).reduce( + (count, was_prefetched) => count + (was_prefetched ? 1 : 0), 0); + + assert_equals(prefetched_count, 1, "url should be prefetched just once."); + }, "browser should remove duplicate urls from prefetch buffer."); +</script> diff --git a/testing/web-platform/tests/speculation-rules/prefetch/invalid-rules.https.html b/testing/web-platform/tests/speculation-rules/prefetch/invalid-rules.https.html new file mode 100644 index 0000000000..573f3c0b0f --- /dev/null +++ b/testing/web-platform/tests/speculation-rules/prefetch/invalid-rules.https.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.sub.js"></script> +<script> + promise_test(async t => { + assert_implements(HTMLScriptElement.supports('speculationrules'), "Speculation Rules not supported"); + + let agent = await spawnWindow(t); + let nextUrl = agent.getExecutorURL({ page: 2 }); + await agent.forceSinglePrefetch(nextUrl, { invalid_key: "value" }); + await agent.navigate(nextUrl); + + assert_not_prefetched(await agent.getRequestHeaders()); + }, "an unrecognized key in a prefetch rule should prevent it from being fetched"); +</script> diff --git a/testing/web-platform/tests/speculation-rules/prefetch/multiple-url.https.html b/testing/web-platform/tests/speculation-rules/prefetch/multiple-url.https.html new file mode 100644 index 0000000000..dd9916632f --- /dev/null +++ b/testing/web-platform/tests/speculation-rules/prefetch/multiple-url.https.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.sub.js"></script> + +<script> + promise_test(async t => { + assert_implements(HTMLScriptElement.supports('speculationrules'), "Speculation Rules not supported"); + + let urls = getPrefetchUrlList(5); + insertSpeculationRules({ prefetch: [{ source: 'list', urls: urls }] }); + await new Promise(resolve => t.step_timeout(resolve, 3000)); + + let prefetched_count = (await Promise.all(urls.map(isUrlPrefetched))).reduce( + (count, was_prefetched) => count + (was_prefetched ? 1 : 0), 0); + + assert_greater_than_equal(prefetched_count, 2, "At least two urls should be prefetched to pass the test."); + }, "browser should be able to prefetch multiple urls"); +</script> diff --git a/testing/web-platform/tests/speculation-rules/prefetch/navigation-timing-delivery-type.tentative.https.html b/testing/web-platform/tests/speculation-rules/prefetch/navigation-timing-delivery-type.tentative.https.html new file mode 100644 index 0000000000..cee8e55f12 --- /dev/null +++ b/testing/web-platform/tests/speculation-rules/prefetch/navigation-timing-delivery-type.tentative.https.html @@ -0,0 +1,59 @@ +<!-- TODO(crbug/1358591): Rename this file from "tentative" once +`WICG/nav-speculation#180` is merged. --> +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.sub.js"></script> + +<meta name="variant" content="?prefetch=true&bypass_cache=true"> +<meta name="variant" content="?prefetch=false&bypass_cache=true"> +<meta name="variant" content="?prefetch=true&bypass_cache=false"> +<meta name="variant" content="?prefetch=false&bypass_cache=false"> + +<script> +const prefetchEnabled = (Object.fromEntries( + new URLSearchParams(location.search)).prefetch === "true"); +const bypassCache = (Object.fromEntries( + new URLSearchParams(location.search)).bypass_cache === "true"); + +promise_test(async t => { + assert_implements(HTMLScriptElement.supports('speculationrules'), + "Speculation Rules not supported"); + + const agent = await spawnWindow(t); + // Some meaningless query param to avoid cached response. + const prefetchUrl = + bypassCache ? agent.getExecutorURL({ a: "b" }) : agent.getExecutorURL(); + + if (prefetchEnabled) + await agent.forceSinglePrefetch(prefetchUrl); + + await agent.navigate(prefetchUrl); + + if (prefetchEnabled) + assert_prefetched(await agent.getRequestHeaders(), + `Prefetch ${prefetchUrl.href} should work.`); + else + assert_not_prefetched(await agent.getRequestHeaders(), + `${prefetchUrl.href} should not be prefetched.`); + + await agent.execute_script( + () => window.entries = performance.getEntriesByType('navigation')); + + // Expects one entry, whose `deliveryType` is "navigational-prefetch" for + // the prefetched request, and "" for the non-prefetched. + // + // TODO(crbug/1317756): Currently the initial prefetch request bypasses the + // HTTP cache, making `deliveryType` always an empty string for non-prefetch + // request. Expand test coverage when `net::LOAD_DISABLE_CACHE` is removed. + assert_equals(await agent.execute_script(() => window.entries.length), 1, + 'Wrong number of entries'); + const deliveryType = + await agent.execute_script(() => window.entries[0].deliveryType); + const expectedDeliveryType = prefetchEnabled ? 'navigational-prefetch' : ''; + assert_equals(deliveryType, expectedDeliveryType); + + }, `PerformanceNavigationTiming.deliveryType test, same origin prefetch.`); +</script> diff --git a/testing/web-platform/tests/speculation-rules/prefetch/navigation-timing-requestStart-responseStart.https.html b/testing/web-platform/tests/speculation-rules/prefetch/navigation-timing-requestStart-responseStart.https.html new file mode 100644 index 0000000000..062d7265d8 --- /dev/null +++ b/testing/web-platform/tests/speculation-rules/prefetch/navigation-timing-requestStart-responseStart.https.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.sub.js"></script> + +<meta name="variant" content=""> +<meta name="variant" content="?prefetch=true"> + +<script> +const searchParams = new URLSearchParams(location.search); +const prefetchEnabled = searchParams.has('prefetch'); + +promise_test(async t => { + assert_implements(HTMLScriptElement.supports('speculationrules'), + "Speculation Rules not supported"); + + const agent = await spawnWindow(t); + // Some meaningless query param to avoid cached response. + const prefetchUrl = agent.getExecutorURL({ a: "b" }); + + if (prefetchEnabled) + await agent.forceSinglePrefetch(prefetchUrl); + + await agent.navigate(prefetchUrl); + + if (prefetchEnabled) { + assert_prefetched(await agent.getRequestHeaders(), + `Prefetch ${prefetchUrl.href} should work.`); + } else { + assert_not_prefetched(await agent.getRequestHeaders(), + `${prefetchUrl.href} should not be prefetched.`); + } + + const entries = await agent.execute_script( + () => performance.getEntriesByType('navigation')); + assert_equals(entries.length, 1, 'Wrong number of navigation entries'); + const entry = entries[0]; + + // Events timeline: + // ... -> connectEnd --> requestStart --> responseStart --> ... + if (prefetchEnabled) { + assert_equals(entry.connectEnd, entry.requestStart); + assert_equals(entry.requestStart, entry.responseStart); + } else { + assert_less_than_equal(entry.connectEnd, entry.requestStart); + assert_less_than_equal(entry.requestStart, entry.responseStart); + } + + }, "PerformanceNavigationTiming.requestStart/responseStart test, same origin prefetch."); +</script> diff --git a/testing/web-platform/tests/speculation-rules/prefetch/navigation-timing-sizes.https.html b/testing/web-platform/tests/speculation-rules/prefetch/navigation-timing-sizes.https.html new file mode 100644 index 0000000000..19c254ca1d --- /dev/null +++ b/testing/web-platform/tests/speculation-rules/prefetch/navigation-timing-sizes.https.html @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.sub.js"></script> + +<meta name="variant" content=""> +<meta name="variant" content="?bypass_cache=true"> +<meta name="variant" content="?prefetch=true"> +<meta name="variant" content="?prefetch=true&bypass_cache=true"> + +<script> +const searchParams = new URLSearchParams(location.search); +const prefetchEnabled = searchParams.has('prefetch'); +const bypassCache = searchParams.has('bypass_cache'); + +// Header size: https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-transfersize +const headerSize = 300; + +promise_test(async t => { + assert_implements(HTMLScriptElement.supports('speculationrules'), + "Speculation Rules not supported"); + + const agent = await spawnWindow(t); + // Some meaningless query param to avoid cached response. + const prefetchUrl = + bypassCache ? agent.getExecutorURL({ a: "b" }) : agent.getExecutorURL(); + + if (prefetchEnabled) + await agent.forceSinglePrefetch(prefetchUrl); + + await agent.navigate(prefetchUrl); + + if (prefetchEnabled) + assert_prefetched(await agent.getRequestHeaders(), + `Prefetch ${prefetchUrl.href} should work.`); + else + assert_not_prefetched(await agent.getRequestHeaders(), + `${prefetchUrl.href} should not be prefetched.`); + + await agent.execute_script( + () => window.entries = performance.getEntriesByType('navigation')); + + // TODO(crbug/1317756): Currently the initial prefetch request bypasses the + // HTTP cache. Expand test coverage for cache and cache+revalidation cases. + // + // We do not assert the exact size of `resources/executor.sub.html` since it + // would be a headache to update this test everytime executor.sub.html + // changes. + assert_equals(await agent.execute_script(() => window.entries.length), 1, + 'Wrong number of entries'); + const entry = + await agent.execute_script(() => window.entries[0]); + const bodySize = entry.encodedBodySize; + assert_greater_than(bodySize, 0); + assert_equals(entry.transferSize, headerSize + bodySize); + assert_equals(entry.decodedBodySize, bodySize); + }, `PerformanceNavigationTiming.transferSize/encodedBodySize/decodedBodySize test, same origin prefetch.`); +</script> diff --git a/testing/web-platform/tests/speculation-rules/prefetch/no-vary-search/README.txt b/testing/web-platform/tests/speculation-rules/prefetch/no-vary-search/README.txt new file mode 100644 index 0000000000..60ac226f8c --- /dev/null +++ b/testing/web-platform/tests/speculation-rules/prefetch/no-vary-search/README.txt @@ -0,0 +1 @@ +Web Platform Tests for No-Vary-Search support in prefetch cache. diff --git a/testing/web-platform/tests/speculation-rules/prefetch/no-vary-search/prefetch-single.https.html b/testing/web-platform/tests/speculation-rules/prefetch/no-vary-search/prefetch-single.https.html new file mode 100644 index 0000000000..543b46c535 --- /dev/null +++ b/testing/web-platform/tests/speculation-rules/prefetch/no-vary-search/prefetch-single.https.html @@ -0,0 +1,282 @@ +<!DOCTYPE html> +<title>Prefetched response including No-Vary-Search headers is used during navigation</title> +<meta charset="utf-8"> + +<meta name="variant" content="?1-1"> +<meta name="variant" content="?2-2"> +<meta name="variant" content="?3-3"> +<meta name="variant" content="?4-4"> +<meta name="variant" content="?5-5"> +<meta name="variant" content="?6-6"> +<meta name="variant" content="?7-7"> +<meta name="variant" content="?8-8"> +<meta name="variant" content="?9-9"> +<meta name="variant" content="?10-10"> +<meta name="variant" content="?11-11"> +<meta name="variant" content="?12-12"> +<meta name="variant" content="?13-13"> +<meta name="variant" content="?14-14"> +<meta name="variant" content="?15-15"> +<meta name="variant" content="?16-16"> +<meta name="variant" content="?17-17"> +<meta name="variant" content="?18-18"> +<meta name="variant" content="?19-19"> +<meta name="variant" content="?20-20"> +<meta name="variant" content="?21-21"> +<meta name="variant" content="?22-22"> +<meta name="variant" content="?23-23"> +<meta name="variant" content="?24-24"> +<meta name="variant" content="?25-25"> +<meta name="variant" content="?26-last"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/common/utils.js"></script> +<script src="../resources/utils.sub.js"></script> +<script src="/common/subset-tests.js"></script> + +<script> + function addNoVarySearchHeaderUsingPipe(url, value){ + // Use server pipes https://web-platform-tests.org/writing-tests/server-pipes.html + // to populate No-Vary-Search response header. + // The "," and ")" characters need to be escaped by using backslash + // (see https://web-platform-tests.org/writing-tests/server-pipes.html). + // E.g. params=("a") becomes params=("a"\), params=("a"),key-order becomes + // params=("a"\)\,key-order etc. + url.searchParams.append("pipe", + `header(No-Vary-Search,${value.replaceAll(/[,)]/g, '\\$&')})`); + } + + /* + remoteAgent: the RemoteContext instance used to communicate between the + test and the window where prefetch/navigation is happening + noVarySearchHeaderValue: the value of No-Vary-Search header to be populated + for the prefetched response + prefetchQuery: query params to be added to prefetchExecutor url and prefetched + navigateQuery: query params to be added to prefetchExecutor url and navigated to + */ + async function prefetchAndNavigate(remoteAgent, noVarySearchHeaderValue, prefetchQuery, navigateQuery){ + const nextUrl = remoteAgent.getExecutorURL(); + const navigateToUrl = new URL(nextUrl); + // Add query params to the url to be prefetched. + const additionalPrefetchedUrlSearchParams = new URLSearchParams(prefetchQuery); + addNoVarySearchHeaderUsingPipe(nextUrl, noVarySearchHeaderValue); + additionalPrefetchedUrlSearchParams.forEach((value, key) => { + nextUrl.searchParams.append(key, value); + }); + + await remoteAgent.forceSinglePrefetch(nextUrl); + + // Add new query params to navigateToUrl to match No-Vary-Search test case. + const additionalNavigateToUrlSearchParams = new URLSearchParams(navigateQuery); + addNoVarySearchHeaderUsingPipe(navigateToUrl, noVarySearchHeaderValue); + additionalNavigateToUrlSearchParams.forEach((value, key) => { + navigateToUrl.searchParams.append(key, value); + }); + await remoteAgent.navigate(navigateToUrl); + } + + function prefetch_no_vary_search_test(description, noVarySearch, prefetchQuery, navigateQuery, shouldUsePrefetch){ + promise_test(async t => { + assert_implements(HTMLScriptElement.supports('speculationrules'), "Speculation Rules not supported"); + const agent = await spawnWindow(t, {}); + await prefetchAndNavigate(agent, + noVarySearch, + prefetchQuery, + navigateQuery); + + if(shouldUsePrefetch){ + assert_prefetched(await agent.getRequestHeaders(), + "Navigation didn't use the prefetched response!"); + } + else{ + assert_not_prefetched(await agent.getRequestHeaders(), + "Navigation used the prefetched response!"); + } + }, description); + } + + // Test inputs: + // - description: a description of the test. + // - no-vary-search: No-Vary-Search header value for the response. + // - prefetch-query: added to query part of prefetch-executor when prefetching + // - navigate-query: added to query part of prefetch-executor when navigating + // - shouldUsePrefetch: if the test case expects the prefetched entry to be + // used or not. + [{description:"Use prefetched response as query parameter b has the same value.", + noVarySearch: 'params=("a")', + prefetchQuery: "a=2&b=3", + navigateQuery: "b=3", + shouldUsePrefetch: true}, + + {description:"Don't use prefetched response as query parameter b has different value.", + noVarySearch: 'params("a")', + prefetchQuery: "a=2&b=3", + navigateQuery: "b=2", + shouldUsePrefetch: false}, + + {description:"Use prefetched response as the URLs do not vary by a and b.", + noVarySearch: 'params=("a" "b")', + prefetchQuery: "a=2&b=3", + navigateQuery: "b=2", + shouldUsePrefetch: true}, + + {description:"Use prefetched response as the URLs do not vary on any query parameters.", + noVarySearch: "params", + prefetchQuery: "a=2&b=3", + navigateQuery: "b=4&c=5", + shouldUsePrefetch: true}, + + {description:"Use prefetched response as the URLs do not vary on any query parameters.", + noVarySearch: "params", + prefetchQuery: "", + navigateQuery: "b=4&c=5", + shouldUsePrefetch: true}, + + {description:"Don't use prefetched response as the URLs have different value for c.", + noVarySearch: "key-order", + prefetchQuery: "c=4&b=3&a=2", + navigateQuery: "a=2&c=5&b=3", + shouldUsePrefetch: false}, + + {description:"Don't use prefetched response as the URLs have the values in different order for a.", + noVarySearch: "key-order", + prefetchQuery: "b=5&a=3&a=4&d=6&c=5&b=3", + navigateQuery: "d=6&a=4&b=5&b=3&c=5&a=3", + shouldUsePrefetch: false}, + + {description:"Use prefetched response as the URLs have the same values for a.", + noVarySearch: "key-order", + prefetchQuery: "b=5&a=3&a=4&d=6&c=5&b=3", + navigateQuery: "d=6&a=3&b=5&b=3&c=5&a=4", + shouldUsePrefetch: true}, + + {description:"Use prefetched response as the URLs have the same values for a.", + noVarySearch: "key-order=?1", + prefetchQuery: "b=5&a=3&a=4&d=6&c=5&b=3", + navigateQuery: "d=6&a=3&b=5&b=3&c=5&a=4", + shouldUsePrefetch: true}, + + {description:"Don't use prefetched response as key-order is set to false and the URLs are not identical.", + noVarySearch: "key-order=?0", + prefetchQuery: "b=5&a=3&a=4&d=6&c=5&b=3", + navigateQuery: "d=6&a=3&b=5&b=3&c=5&a=4", + shouldUsePrefetch: false}, + + {description:"Use prefetched response as query parameter c can be ignored.", + noVarySearch: 'params=("c")', + prefetchQuery: "a=2&b=2&c=5", + navigateQuery: "a=2&c=3&b=2", + shouldUsePrefetch: true}, + + {description:"Use prefetched response as query parameter a can be ignored.", + noVarySearch: 'params=("a")', + prefetchQuery: "a=2", + navigateQuery: "", + shouldUsePrefetch: true}, + + {description:"Use prefetched response as query parameter a can be ignored.", + noVarySearch: 'params=("a")', + prefetchQuery: "", + navigateQuery: "a=2", + shouldUsePrefetch: true}, + + {description:"Use prefetched response as all query parameters except c can be ignored.", + noVarySearch: 'params, except=("c")', + prefetchQuery: "b=5&a=3&d=6&c=3", + navigateQuery: "a=1&b=2&c=3", + shouldUsePrefetch: true}, + + {description:"Use prefetched response as all query parameters except c can be ignored." + + " Only the last except matters.", + noVarySearch: 'params, except=("b"), except=("c")', + prefetchQuery: "b=5&a=3&d=6&c=3", + navigateQuery: "a=1&b=2&c=3", + shouldUsePrefetch: true}, + + {description:"Don't use prefetched response as even though all query parameters" + + " except c can be ignored, c has different value.", + noVarySearch: 'params, except=("c")', + prefetchQuery: "b=5&a=3&d=6&c=3", + navigateQuery: "a=1&b=2&c=5", + shouldUsePrefetch: false}, + + {description:"Use prefetched response as even though all query parameters" + + " except c and d can be ignored, c value matches and d value matches.", + noVarySearch: 'params, except=("c" "d")', + prefetchQuery: "b=5&a=3&d=6&c=5", + navigateQuery: "d=6&a=1&b=2&c=5", + shouldUsePrefetch: true}, + + {description:"Use prefetched response as even though all query parameters except" + + " c and d can be ignored, c value matches and d value matches." + + " Some query parameters to be ignored appear multiple times in the query.", + noVarySearch: 'params, except=("c" "d")', + prefetchQuery: "b=5&a=3&a=4&d=6&c=5", + navigateQuery: "d=6&a=1&a=2&b=2&b=3&c=5", + shouldUsePrefetch: true}, + + {description:"Use prefetched response as all query parameters except c can be ignored." + + " Allow extension via parameters.", + noVarySearch: 'params, except=("c";unknown)', + prefetchQuery: "b=5&a=3&d=6&c=3", + navigateQuery: "a=1&b=2&c=3", + shouldUsePrefetch: true}, + + {description:"Use prefetched response as query parameter c can be ignored." + + " Allow extension via parameters.", + noVarySearch: 'params=("c";unknown)', + prefetchQuery: "a=2&b=2&c=5", + navigateQuery: "a=2&c=3&b=2", + shouldUsePrefetch: true}, + + {description:"Use prefetched response as the URLs have the values in different order for a." + + " Allow extension via parameters.", + noVarySearch: "key-order;unknown", + prefetchQuery: "b=5&a=3&a=4&d=6&c=5&b=3", + navigateQuery: "d=6&a=3&b=5&b=3&c=5&a=4", + shouldUsePrefetch: true}, + + {description:"Use prefetched response as the URLs do not vary on any query parameters." + + " Allow extension via parameters.", + noVarySearch: "params;unknown", + prefetchQuery: "", + navigateQuery: "b=4&c=5", + shouldUsePrefetch: true}, + + {description:"Use prefetched response as all query parameters except c can be ignored." + + " Allow extension via parameters.", + noVarySearch: 'params;unknown, except=("c");unknown', + prefetchQuery: "b=5&a=3&d=6&c=3", + navigateQuery: "a=1&b=2&c=3", + shouldUsePrefetch: true}, + + {description:"Don't use the prefetched URL. Empty No-Vary-Search means default URL variance." + + " The prefetched and the navigated URLs have to be the same.", + noVarySearch: "", + prefetchQuery: "b=5&a=3&d=6&c=3", + navigateQuery: "a=1&b=2&c=3", + shouldUsePrefetch: false}, + + {description:"Use the prefetched URL. Empty No-Vary-Search means default URL variance." + + " The prefetched and the navigated URLs have to be the same.", + noVarySearch: "", + prefetchQuery: "b=5&a=3&d=6&c=3", + navigateQuery: "b=5&a=3&d=6&c=3", + shouldUsePrefetch: true}, + + {description:"Use the prefetched URL. Empty No-Vary-Search means default URL variance." + + " The prefetched and the navigated URLs have to be the same.", + noVarySearch: "", + prefetchQuery: "", + navigateQuery: "", + shouldUsePrefetch: true}, + + ].forEach(({description, noVarySearch, prefetchQuery, navigateQuery, shouldUsePrefetch}) => { + subsetTest(prefetch_no_vary_search_test, + description, noVarySearch, prefetchQuery, navigateQuery, + shouldUsePrefetch); + }); + +</script> diff --git a/testing/web-platform/tests/speculation-rules/prefetch/out-of-document-rule-set.https.html b/testing/web-platform/tests/speculation-rules/prefetch/out-of-document-rule-set.https.html new file mode 100644 index 0000000000..9f2c311715 --- /dev/null +++ b/testing/web-platform/tests/speculation-rules/prefetch/out-of-document-rule-set.https.html @@ -0,0 +1,152 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/common/subset-tests-by-key.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.sub.js"></script> + +<meta name="variant" content="?include=BaseCase"> +<meta name="variant" content="?include=FollowRedirect"> +<meta name="variant" content="?include=RelativeUrlForSpeculationRulesSet"> +<meta name="variant" content="?include=RelativeUrlForCandidate"> +<meta name="variant" content="?include=UseNonUTF8EncodingForSpeculationRulesSet"> +<meta name="variant" content="?include=FailCORS"> +<meta name="variant" content="?include=FailToParseSpeculationRulesHeader"> +<meta name="variant" content="?include=InnerListInSpeculationRulesHeader"> +<meta name="variant" content="?include=EmptyRuleSet"> +<meta name="variant" content="?include=FailToParseRuleSet"> +<meta name="variant" content="?include=InvalidUrlForSpeculationRulesSet"> +<meta name="variant" content="?include=StatusCode199"> +<meta name="variant" content="?include=StatusCode404"> +<meta name="variant" content="?include=InvalidMimeType"> + +<script> + async function runSpeculationRulesFetchTest(t, options) { + options = { + // Whether a prefetch is expected to succeed. + shouldPrefetch: true, + // Status code to be returned in the response. + status: 200, + // Whether a redirect must be followed to reach the rule set. + redirect: false, + // Whether to use relative URLs for the candidates in the rule set. + useRelativeUrlForCandidate: false, + // Whether to use relative URL for the rule set in SpeculationRules header. + useRelativeUrlForSpeculationRulesSet: false, + // Whether to use UTF-8 encoding for the rule set. + useUtf8EncodingForSpeculationRulesSet: true, + // Whether to force the response to cause a CORS failure. + failCors: false, + // Whether to use a valid SpeculationRules header format. + useValidSpeculationRulesHeaderValue: true, + // Whether to use an inner list of URLS in SpeculationRules header. + useInnerListInSpeculationRulesHeaderValue: false, + // Whether to return an empty response. + useEmptySpeculationRulesSet: false, + // Wheter to return a rule set with valid JSON format + useValidJsonForSpeculationRulesSet: true, + // Wheter to use a valid URL for the rule set in SpeculationRules header. + useValidUrlForSpeculationRulesSet: true, + // Wheter to use the valid "application/speculationrules-json" MIME type for the rule set. + useValidMimeTypeForSpeculationRulesSet: true, + ...options + }; + assert_implements(HTMLScriptElement.supports('speculationrules'), "Speculation Rules not supported."); + + let page = 2; + let uuid = token(); + let executor_url = new URL(`executor.sub.html`, SR_PREFETCH_UTILS_URL).toString(); + if (options.useRelativeUrlForCandidate) { + executor_url = `executor.sub.html`; + } + let speculation_rule_set_url = `ruleset.py?url=${executor_url}&uuid=${uuid}&page=${page}&status=${options.status}&valid_mime=${options.useValidMimeTypeForSpeculationRulesSet}&valid_json=${options.useValidJsonForSpeculationRulesSet}&empty_json=${options.useEmptySpeculationRulesSet}&fail_cors=${options.failCors}&valid_encoding=${options.useUtf8EncodingForSpeculationRulesSet}&redirect=${options.redirect}`; + if (!options.useRelativeUrlForSpeculationRulesSet) { + let base_url = new URL(SR_PREFETCH_UTILS_URL); + base_url.hostname = PREFETCH_PROXY_BYPASS_HOST; + speculation_rule_set_url = new URL(speculation_rule_set_url, base_url).toString(); + } + if (!options.useValidUrlForSpeculationRulesSet) { + speculation_rule_set_url = "http://:80/"; + } + + let speculation_rules_header = `header(Speculation-Rules,"${speculation_rule_set_url}")`; + if (!options.useValidSpeculationRulesHeaderValue) { + speculation_rules_header = `header(Speculation-Rules, x y z)`; + } + else if (options.useInnerListInSpeculationRulesHeaderValue) { + speculation_rules_header = `header(Speculation-Rules, \\("${speculation_rule_set_url}" "xyz.com/rule-set.json"\\))`; + } + + let agent = await spawnWindow(t, {pipe: speculation_rules_header}, uuid); + await new Promise(resolve => t.step_timeout(resolve, 2000)); + // Passing non-ascii character 'รท' as part of the next URL to check if we always decode the speculation rules set using utf-8 or not. This character is encoded differently in utf-8 and windows-1250 + let nextUrl = agent.getExecutorURL({ page, str: decodeURIComponent('%C3%B7')}); + await agent.navigate(nextUrl); + + await new Promise(resolve => t.step_timeout(resolve, 2000)); + + let test_case_desc = JSON.stringify(options); + if (options.shouldPrefetch) + assert_prefetched(await agent.getRequestHeaders(), `Prefetch should work for request ${test_case_desc}.`); + else + assert_not_prefetched(await agent.getRequestHeaders(), `Prefetch should not work for request ${test_case_desc}.`); + } + + subsetTestByKey('BaseCase', promise_test, async t => { + return runSpeculationRulesFetchTest(t, {}); + }, "Base case."); + + subsetTestByKey('FollowRedirect', promise_test, async t => { + return runSpeculationRulesFetchTest(t, {redirect: true}); + }, "It should follow redirects and fetch the speculation rules set."); + + subsetTestByKey('RelativeUrlForSpeculationRulesSet', promise_test, async t => { + return runSpeculationRulesFetchTest(t, {useRelativeUrlForSpeculationRulesSet: true}); + }, "It should fetch a speculation rules set using its relative URL."); + + subsetTestByKey('RelativeUrlForCandidate', promise_test, async t => { + return runSpeculationRulesFetchTest(t, {useRelativeUrlForCandidate: true, shouldPrefetch: false}); + }, "It should resolve the relative candidate URLs in the speculation rules set based on the speculation rules set's URL"); + + subsetTestByKey('UseNonUTF8EncodingForSpeculationRulesSet', promise_test, async t => { + return runSpeculationRulesFetchTest(t, {useUtf8EncodingForSpeculationRulesSet: false, shouldPrefetch: false}); + }, "The speculation rules set should always be encoded using UTF-8."); + + subsetTestByKey('FailCORS', promise_test, async t => { + return runSpeculationRulesFetchTest(t, {failCors: true, shouldPrefetch: false}); + }, "It should reject the speculation rules set if CORS fails."); + + subsetTestByKey('FailToParseSpeculationRulesHeader', promise_test, async t => { + return runSpeculationRulesFetchTest(t, {useValidSpeculationRulesHeaderValue: false, shouldPrefetch: false}); + }, "It should reject the speculation rules set if it fails to parse the SpeculationRules header."); + + subsetTestByKey('InnerListInSpeculationRulesHeader', promise_test, async t => { + return runSpeculationRulesFetchTest(t, {useInnerListInSpeculationRulesHeaderValue: true, shouldPrefetch: false}); + }, "It should reject the speculation rules passed as inner list in the SpeculationRules header."); + + subsetTestByKey('EmptyRuleSet', promise_test, async t => { + return runSpeculationRulesFetchTest(t, {useEmptySpeculationRulesSet: true, shouldPrefetch: false}); + }, "It should reject an empty speculation rules set."); + + subsetTestByKey('FailToParseRuleSet', promise_test, async t => { + return runSpeculationRulesFetchTest(t, {useValidJsonForSpeculationRulesSet: false, shouldPrefetch: false}); + }, "It should reject the speculation rules set if it cannot parse it."); + + subsetTestByKey('InvalidUrlForSpeculationRulesSet', promise_test, async t => { + return runSpeculationRulesFetchTest(t, {useValidUrlForSpeculationRulesSet: false, shouldPrefetch: false}); + }, "It should reject the speculation rules set with invalid URL."); + + subsetTestByKey('StatusCode199', promise_test, async t => { + return runSpeculationRulesFetchTest(t, {status: 199, shouldPrefetch: false}); + }, "It should reject the speculation rules set with unsuccessful status code."); + + subsetTestByKey('StatusCode404', promise_test, async t => { + return runSpeculationRulesFetchTest(t, {status: 404, shouldPrefetch: false}); + }, "It should reject the speculation rules set with unsuccessful status code."); + + subsetTestByKey('InvalidMimeType', promise_test, async t => { + return runSpeculationRulesFetchTest(t, {useValidMimeTypeForSpeculationRulesSet: false, shouldPrefetch: false}); + }, "It should reject the speculation rules set with invalid MIME type."); + +</script> diff --git a/testing/web-platform/tests/speculation-rules/prefetch/prefetch-single.https.html b/testing/web-platform/tests/speculation-rules/prefetch/prefetch-single.https.html new file mode 100644 index 0000000000..42f75d0c29 --- /dev/null +++ b/testing/web-platform/tests/speculation-rules/prefetch/prefetch-single.https.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.sub.js"></script> +<meta name="variant" content="?from_protocol=http&to_protocol=http"> +<meta name="variant" content="?from_protocol=http&to_protocol=https"> +<meta name="variant" content="?from_protocol=https&to_protocol=http"> +<meta name="variant" content="?from_protocol=https&to_protocol=https"> +<script> + // This is split across four test variants due to the test timeouts. + let { from_protocol, to_protocol } = Object.fromEntries(new URLSearchParams(location.search)); + promise_test(async t => { + assert_implements(HTMLScriptElement.supports('speculationrules'), "Speculation Rules not supported"); + + let agent = await spawnWindow(t, { protocol: from_protocol }); + let nextUrl = agent.getExecutorURL({ protocol: to_protocol, page: 2 }); + await agent.forceSinglePrefetch(nextUrl); + await agent.navigate(nextUrl); + + if (to_protocol == "https") { + assert_prefetched(await agent.getRequestHeaders(), "Prefetch should work for HTTPS urls."); + } else { + assert_not_prefetched(await agent.getRequestHeaders(), "Prefetch should not work for HTTP urls."); + } + }, `test single ${to_protocol} url prefetch from a ${from_protocol} url`); +</script> diff --git a/testing/web-platform/tests/speculation-rules/prefetch/prefetch-status.https.html b/testing/web-platform/tests/speculation-rules/prefetch/prefetch-status.https.html new file mode 100644 index 0000000000..6835a55ee9 --- /dev/null +++ b/testing/web-platform/tests/speculation-rules/prefetch/prefetch-status.https.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.sub.js"></script> + +<meta name="variant" content="?status=200&should_prefetch=true"> +<meta name="variant" content="?status=250&should_prefetch=true"> +<meta name="variant" content="?status=299&should_prefetch=true"> +<meta name="variant" content="?status=400&should_prefetch=false"> +<meta name="variant" content="?status=500&should_prefetch=false"> + +<script> + // This is split across four test variants due to the test timeouts. + let { status, should_prefetch } = Object.fromEntries(new URLSearchParams(location.search)); + promise_test(async t => { + assert_implements(HTMLScriptElement.supports('speculationrules'), "Speculation Rules not supported"); + + let agent = await spawnWindow(t); + let nextUrl = agent.getExecutorURL({ page: 2, pipe: `status(${status})` }); + await agent.forceSinglePrefetch(nextUrl); + await agent.navigate(nextUrl); + + if (should_prefetch == 'true') + assert_prefetched(await agent.getRequestHeaders(), `Prefetch should work for request status:${status}.`); + else + assert_not_prefetched(await agent.getRequestHeaders(), `Prefetch should not work for request statue:${status}.`); + }, "Check that only prefetched requests with status in 200-299 range are used."); +</script> diff --git a/testing/web-platform/tests/speculation-rules/prefetch/redirect-url.https.html b/testing/web-platform/tests/speculation-rules/prefetch/redirect-url.https.html new file mode 100644 index 0000000000..07db405dc3 --- /dev/null +++ b/testing/web-platform/tests/speculation-rules/prefetch/redirect-url.https.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.sub.js"></script> + +<script> + promise_test(async t => { + assert_implements(HTMLScriptElement.supports('speculationrules'), "Speculation Rules not supported"); + + let url = getRedirectUrl(); + insertSpeculationRules({ prefetch: [{ source: 'list', urls: [url] }] }); + await new Promise(resolve => t.step_timeout(resolve, 2000)); + + assert_equals(await isUrlPrefetched(url), 1, "redirected url should be prefetched"); + }, "browser should be able to prefetch redirected urls"); +</script> diff --git a/testing/web-platform/tests/speculation-rules/prefetch/referrer-policy-from-rules.https.html b/testing/web-platform/tests/speculation-rules/prefetch/referrer-policy-from-rules.https.html new file mode 100644 index 0000000000..bbb0343509 --- /dev/null +++ b/testing/web-platform/tests/speculation-rules/prefetch/referrer-policy-from-rules.https.html @@ -0,0 +1,143 @@ +<!DOCTYPE html> +<title>Prefetch with the referrer policy specified in speculation rules</title> + +<!--Split test cases due to the use of timeouts in speculation rules test utilities.--> +<meta name="variant" content="?1-1"> +<meta name="variant" content="?2-2"> +<meta name="variant" content="?3-3"> +<meta name="variant" content="?4-4"> +<meta name="variant" content="?5-5"> +<meta name="variant" content="?6-6"> +<meta name="variant" content="?7-last"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/common/subset-tests.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.sub.js"></script> + +<script> +"use strict"; + +subsetTest(promise_test, async t => { + assert_implements(HTMLScriptElement.supports('speculationrules'), "Speculation Rules not supported"); + + const agent = await spawnWindow(t); + await agent.setReferrerPolicy("strict-origin-when-cross-origin"); + const expectedReferrer = agent.getExecutorURL().origin + "/"; + + const nextURL = agent.getExecutorURL({ page: 2 }); + await agent.forceSinglePrefetch(nextURL, { referrer_policy: "strict-origin" }); + await agent.navigate(nextURL); + + const headers = await agent.getRequestHeaders(); + assert_prefetched(headers, "must be prefetched"); + assert_equals(headers.referer, expectedReferrer, "must send the origin as the referrer"); +}, 'with "strict-origin" referrer policy in rule set overriding "strict-origin-when-cross-origin" of referring page'); + +subsetTest(promise_test, async t => { + assert_implements(HTMLScriptElement.supports('speculationrules'), "Speculation Rules not supported"); + + const agent = await spawnWindow(t); + const next_url = agent.getExecutorURL({ page: 2 }); + await agent.execute_script((url) => { + const a = addLink(url); + a.referrerPolicy = 'no-referrer'; + insertDocumentRule(undefined, { referrer_policy: 'strict-origin' }); + }, [next_url]); + await new Promise(resolve => t.step_timeout(resolve, 2000)); + await agent.navigate(next_url); + + const headers = await agent.getRequestHeaders(); + assert_prefetched(headers, 'must be prefetched'); + const expected_referrer = next_url.origin + '/'; + assert_equals(headers.referer, expected_referrer, 'must send the origin as the referrer'); +}, 'with "strict-origin" referrer policy in rule set override "no-referrer" of link'); + +subsetTest(promise_test, async t => { + assert_implements(HTMLScriptElement.supports('speculationrules'), "Speculation Rules not supported"); + + const agent = await spawnWindow(t); + await agent.setReferrerPolicy("unsafe-url"); + + const nextURL = agent.getExecutorURL({ hostname: PREFETCH_PROXY_BYPASS_HOST, page: 2 }); + await agent.forceSinglePrefetch( + nextURL, { referrer_policy: "no-referrer", requires: ["anonymous-client-ip-when-cross-origin"] }); + await agent.navigate(nextURL); + + // This referring page's referrer policy would not be eligible for + // cross-site prefetching, but setting a sufficiently strict policy in the + // rule allows for prefetching. + const headers = await agent.getRequestHeaders(); + assert_prefetched(headers, "must be prefetched"); + assert_equals(headers.referer, '', "must send no referrer"); +}, 'with "no-referrer" referrer policy in rule set overriding "unsafe-url" of cross-site referring page'); + +subsetTest(promise_test, async t => { + assert_implements(HTMLScriptElement.supports('speculationrules'), "Speculation Rules not supported"); + + const agent = await spawnWindow(t); + await agent.setReferrerPolicy("strict-origin-when-cross-origin"); + + const nextURL = agent.getExecutorURL({ page: 2 }); + await agent.forceSinglePrefetch(nextURL, { referrer_policy: "no-referrrrrrrer" }); + await agent.navigate(nextURL); + + const headers = await agent.getRequestHeaders(); + assert_not_prefetched(headers, "must not be prefetched"); +}, 'unrecognized policies invalidate the rule'); + +subsetTest(promise_test, async t => { + assert_implements(HTMLScriptElement.supports('speculationrules'), "Speculation Rules not supported"); + + const agent = await spawnWindow(t); + await agent.setReferrerPolicy("strict-origin-when-cross-origin"); + + const nextURL = agent.getExecutorURL({ page: 2 }); + await agent.forceSinglePrefetch(nextURL, { referrer_policy: "never" }); + await agent.navigate(nextURL); + + const headers = await agent.getRequestHeaders(); + assert_not_prefetched(headers, "must not be prefetched"); +}, 'treat legacy referrer policy values as invalid'); + +subsetTest(promise_test, async t => { + assert_implements(HTMLScriptElement.supports('speculationrules'), "Speculation Rules not supported"); + + const agent = await spawnWindow(t); + await agent.setReferrerPolicy("strict-origin"); + const expectedReferrer = agent.getExecutorURL().origin + "/"; + + const nextURL = agent.getExecutorURL({ hostname: PREFETCH_PROXY_BYPASS_HOST, page: 2 }); + await agent.forceSinglePrefetch( + nextURL, { referrer_policy: "unsafe-url", requires: ["anonymous-client-ip-when-cross-origin"] }); + await agent.navigate(nextURL); + + // This referring page's referrer policy would normally make it eligible for + // cross-site prefetching, but setting an unacceptable policy in the rule + // makes it ineligible. + const headers = await agent.getRequestHeaders(); + assert_not_prefetched(headers, "must not be prefetched"); + assert_equals(headers.referer, expectedReferrer, "must send the origin as the referrer"); +}, 'with "unsafe-url" referrer policy in rule set overriding "strict-origin" of cross-site referring page'); + +subsetTest(promise_test, async t => { + assert_implements(HTMLScriptElement.supports('speculationrules'), "Speculation Rules not supported"); + + const agent = await spawnWindow(t); + await agent.setReferrerPolicy("strict-origin"); + const expectedReferrer = agent.getExecutorURL().origin + "/"; + + const nextURL = agent.getExecutorURL({ page: 2 }); + // The empty string is a valid value for "referrer_policy" and will be + // treated as if the key were omitted. + await agent.forceSinglePrefetch(nextURL, { referrer_policy: "" }); + await agent.navigate(nextURL); + + const headers = await agent.getRequestHeaders(); + assert_prefetched(headers, "must be prefetched"); + assert_equals(headers.referer, expectedReferrer, "must send the origin as the referrer"); +}, 'with empty string referrer policy in rule set defaulting to "strict-origin" of referring page'); + +</script> diff --git a/testing/web-platform/tests/speculation-rules/prefetch/referrer-policy-not-accepted.https.html b/testing/web-platform/tests/speculation-rules/prefetch/referrer-policy-not-accepted.https.html new file mode 100644 index 0000000000..d7c003b3ca --- /dev/null +++ b/testing/web-platform/tests/speculation-rules/prefetch/referrer-policy-not-accepted.https.html @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<title>Prefetch attempts with an unacceptable referrer policy</title> + +<!--Split test cases due to the use of timeouts in speculation rules test utilities.--> +<meta name="variant" content="?1-1"> +<meta name="variant" content="?2-last"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/common/subset-tests.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.sub.js"></script> + +<script> +"use strict"; + +subsetTest(promise_test, async t => { + assert_implements(HTMLScriptElement.supports('speculationrules'), "Speculation Rules not supported"); + + const agent = await spawnWindow(t); + await agent.setReferrerPolicy("unsafe-url"); + const expectedReferrer = agent.getExecutorURL().href; + + const nextURL = agent.getExecutorURL({ page: 2 }); + await agent.forceSinglePrefetch(nextURL); + await agent.navigate(nextURL); + + const headers = await agent.getRequestHeaders(); + // The referrer policy restriction does not apply to same-site prefetch. + assert_prefetched(headers, "must be prefetched"); + assert_equals(headers.referer, expectedReferrer, "must send the full URL as the referrer"); +}, 'with "unsafe-url" referrer policy on same-site referring page'); + +subsetTest(promise_test, async t => { + assert_implements(HTMLScriptElement.supports('speculationrules'), "Speculation Rules not supported"); + + const agent = await spawnWindow(t); + await agent.setReferrerPolicy("unsafe-url"); + const expectedReferrer = agent.getExecutorURL().href; + + const nextURL = agent.getExecutorURL({ hostname: PREFETCH_PROXY_BYPASS_HOST, page: 2 }); + // This prefetch attempt should be ignored. + await agent.forceSinglePrefetch( + nextURL, { requires: ["anonymous-client-ip-when-cross-origin"] }); + await agent.navigate(nextURL); + + const headers = await agent.getRequestHeaders(); + assert_not_prefetched(headers, "must not be prefetched"); + assert_equals(headers.referer, expectedReferrer, "must send the full URL as the referrer"); +}, 'with "unsafe-url" referrer policy on cross-site referring page'); + +</script> diff --git a/testing/web-platform/tests/speculation-rules/prefetch/referrer-policy.https.html b/testing/web-platform/tests/speculation-rules/prefetch/referrer-policy.https.html new file mode 100644 index 0000000000..1987d2e2ff --- /dev/null +++ b/testing/web-platform/tests/speculation-rules/prefetch/referrer-policy.https.html @@ -0,0 +1,88 @@ +<!DOCTYPE html> +<title>Prefetch is done with the referring page's referrer policy</title> + +<!--Split test cases due to the use of timeouts in speculation rules test utilities.--> +<meta name="variant" content="?1-1"> +<meta name="variant" content="?2-2"> +<meta name="variant" content="?3-3"> +<meta name="variant" content="?4-last"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/common/subset-tests.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.sub.js"></script> + +<script> +"use strict"; + +subsetTest(promise_test, async t => { + assert_implements(HTMLScriptElement.supports('speculationrules'), "Speculation Rules not supported"); + + const agent = await spawnWindow(t); + await agent.setReferrerPolicy("strict-origin-when-cross-origin"); + const expectedReferrer = agent.getExecutorURL().href; + + const nextURL = agent.getExecutorURL({ page: 2 }); + await agent.forceSinglePrefetch(nextURL); + await agent.navigate(nextURL); + + const headers = await agent.getRequestHeaders(); + assert_prefetched(headers, "must be prefetched"); + assert_equals(headers.referer, expectedReferrer, "must send the full URL as the referrer"); +}, 'with "strict-origin-when-cross-origin" referrer policy'); + +subsetTest(promise_test, async t => { + assert_implements(HTMLScriptElement.supports('speculationrules'), "Speculation Rules not supported"); + + const agent = await spawnWindow(t); + await agent.setReferrerPolicy("strict-origin"); + const expectedReferrer = agent.getExecutorURL().origin + "/"; + + const nextURL = agent.getExecutorURL({ page: 2 }); + await agent.forceSinglePrefetch(nextURL); + await agent.navigate(nextURL); + + const headers = await agent.getRequestHeaders(); + assert_prefetched(headers, "must be prefetched"); + assert_equals(headers.referer, expectedReferrer, "must send the origin as the referrer"); +}, 'with "strict-origin" referrer policy'); + +subsetTest(promise_test, async t => { + assert_implements(HTMLScriptElement.supports('speculationrules'), "Speculation Rules not supported"); + + const agent = await spawnWindow(t); + await agent.setReferrerPolicy("no-referrer"); + + const nextURL = agent.getExecutorURL({ page: 2 }); + await agent.forceSinglePrefetch(nextURL); + await agent.navigate(nextURL); + + const headers = await agent.getRequestHeaders(); + assert_prefetched(headers, "must be prefetched"); + assert_equals(headers.referer, '', "must send no referrer"); +}, 'with "no-referrer" referrer policy'); + +subsetTest(promise_test, async t => { + assert_implements(HTMLScriptElement.supports('speculationrules'), "Speculation Rules not supported"); + + const agent = await spawnWindow(t); + await agent.setReferrerPolicy("no-referrer"); + + const next_url = agent.getExecutorURL({ page: 2 }); + await agent.execute_script((url) => { + const a = addLink(url); + a.referrerPolicy = 'strict-origin'; + insertDocumentRule(); + }, [next_url]); + await new Promise(resolve => t.step_timeout(resolve, 2000)); + await agent.navigate(next_url); + + const headers = await agent.getRequestHeaders(); + const expected_referrer = next_url.origin + '/'; + assert_prefetched(headers, 'must be prefetched'); + assert_equals(headers.referer, expected_referrer); +}, 'with "strict-origin" link referrer policy overriding "no-referrer" of referring page'); + +</script> diff --git a/testing/web-platform/tests/speculation-rules/prefetch/resources/authenticate.py b/testing/web-platform/tests/speculation-rules/prefetch/resources/authenticate.py new file mode 100644 index 0000000000..037a7c144e --- /dev/null +++ b/testing/web-platform/tests/speculation-rules/prefetch/resources/authenticate.py @@ -0,0 +1,32 @@ + +def main(request, response): + def fmt(x): + return f'"{x.decode("utf-8")}"' if x is not None else "undefined" + + purpose = request.headers.get("Purpose", b"").decode("utf-8") + sec_purpose = request.headers.get("Sec-Purpose", b"").decode("utf-8") + + headers = [(b"Content-Type", b"text/html"), (b'WWW-Authenticate', 'Basic')] + status = 200 if request.auth.username is not None or sec_purpose.startswith( + "prefetch") else 401 + + content = f''' + <!DOCTYPE html> + <script src="/common/dispatcher/dispatcher.js"></script> + <script src="utils.sub.js"></script> + <script> + window.requestHeaders = {{ + purpose: "{purpose}", + sec_purpose: "{sec_purpose}" + }}; + + window.requestCredentials = {{ + username: {fmt(request.auth.username)}, + password: {fmt(request.auth.password)} + }}; + + const uuid = new URLSearchParams(location.search).get('uuid'); + window.executor = new Executor(uuid); + </script> + ''' + return status, headers, content diff --git a/testing/web-platform/tests/speculation-rules/prefetch/resources/cookies.py b/testing/web-platform/tests/speculation-rules/prefetch/resources/cookies.py new file mode 100644 index 0000000000..3c2299aa3a --- /dev/null +++ b/testing/web-platform/tests/speculation-rules/prefetch/resources/cookies.py @@ -0,0 +1,41 @@ + +def main(request, response): + def get_cookie(key): + key = key.encode("utf-8") + if key in request.cookies: + return f'"{request.cookies[key].value.decode("utf-8")}"' + else: + return "undefined" + + purpose = request.headers.get("Purpose", b"").decode("utf-8") + sec_purpose = request.headers.get("Sec-Purpose", b"").decode("utf-8") + + cookie_count = int( + request.cookies[b"count"].value) if b"count" in request.cookies else 0 + response.set_cookie("count", f"{cookie_count+1}", + secure=True, samesite="None") + response.set_cookie( + "type", "prefetch" if sec_purpose.startswith("prefetch") else "navigate") + + headers = [(b"Content-Type", b"text/html")] + + content = f''' + <!DOCTYPE html> + <script src="/common/dispatcher/dispatcher.js"></script> + <script src="utils.sub.js"></script> + <script> + window.requestHeaders = {{ + purpose: "{purpose}", + sec_purpose: "{sec_purpose}" + }}; + + window.requestCookies = {{ + count: {get_cookie("count")}, + type: {get_cookie("type")} + }}; + + const uuid = new URLSearchParams(location.search).get('uuid'); + window.executor = new Executor(uuid); + </script> + ''' + return headers, content diff --git a/testing/web-platform/tests/speculation-rules/prefetch/resources/executor.sub.html b/testing/web-platform/tests/speculation-rules/prefetch/resources/executor.sub.html new file mode 100644 index 0000000000..ba1b3acb0c --- /dev/null +++ b/testing/web-platform/tests/speculation-rules/prefetch/resources/executor.sub.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="utils.sub.js"></script> +<script> +window.requestHeaders = { + purpose: "{{header_or_default(Purpose, )}}", + sec_purpose: "{{header_or_default(Sec-Purpose, )}}", + referer: "{{header_or_default(Referer, )}}", +}; + +const uuid = new URLSearchParams(location.search).get('uuid'); +window.executor = new Executor(uuid); +</script> diff --git a/testing/web-platform/tests/speculation-rules/prefetch/resources/prefetch.py b/testing/web-platform/tests/speculation-rules/prefetch/resources/prefetch.py new file mode 100644 index 0000000000..4a0a7a3602 --- /dev/null +++ b/testing/web-platform/tests/speculation-rules/prefetch/resources/prefetch.py @@ -0,0 +1,16 @@ +from wptserve.handlers import json_handler + +@json_handler +def main(request, response): + uuid = request.GET[b"uuid"] + prefetch = request.headers.get( + "Sec-Purpose", b"").decode("utf-8").startswith("prefetch") + + n = request.server.stash.take(uuid) + if n is None: + n = 0 + if prefetch: + n += 1 + request.server.stash.put(uuid, n) + + return n diff --git a/testing/web-platform/tests/speculation-rules/prefetch/resources/redirect.py b/testing/web-platform/tests/speculation-rules/prefetch/resources/redirect.py new file mode 100644 index 0000000000..de7a4af987 --- /dev/null +++ b/testing/web-platform/tests/speculation-rules/prefetch/resources/redirect.py @@ -0,0 +1,3 @@ +def main(request, response): + new_url = request.url.replace("redirect", "prefetch").encode("utf-8") + return 301, [(b"Location", new_url)], b"" diff --git a/testing/web-platform/tests/speculation-rules/prefetch/resources/ruleset.py b/testing/web-platform/tests/speculation-rules/prefetch/resources/ruleset.py new file mode 100644 index 0000000000..97de1cc1a0 --- /dev/null +++ b/testing/web-platform/tests/speculation-rules/prefetch/resources/ruleset.py @@ -0,0 +1,49 @@ +def main(request, response): + url = request.GET[b"url"].decode("utf-8") + uuid = request.GET[b"uuid"].decode("utf-8") + page = request.GET[b"page"].decode("utf-8") + valid_json = request.GET[b"valid_json"].decode("utf-8") + empty_json = request.GET[b"empty_json"].decode("utf-8") + fail_cors = request.GET[b"fail_cors"].decode("utf-8") + valid_encoding = request.GET[b"valid_encoding"].decode("utf-8") + redirect = request.GET[b"redirect"].decode("utf-8") + sec_fetch_dest = request.headers[b"Sec-Fetch-Dest"].decode( + "utf-8").lower() if b"Sec-Fetch-Dest" in request.headers else None + content_type = b"application/speculationrules+json" if request.GET[ + b"valid_mime"].decode("utf-8") == "true" else b"application/json" + status = int(request.GET[b"status"]) + + if redirect == "true": + new_url = request.url.replace("redirect=true", + "redirect=false").encode("utf-8") + return 301, [(b"Location", new_url), + (b'Access-Control-Allow-Origin', b'*')], b"" + + encoding = "utf-8" if valid_encoding == "true" else "windows-1250" + content_type += f'; charset={encoding}'.encode('utf-8') + strparam = b'\xc3\xb7'.decode('utf-8') + + content = f''' + {{ + "prefetch": [ + {{ + "source":"list", + "urls":["{url}?uuid={uuid}&page={page}&str={strparam}"], + "requires":["anonymous-client-ip-when-cross-origin"] + }} + ] + }} + ''' + if empty_json == "true": + content = "" + elif valid_json != "true": + content = "invalid json" + elif sec_fetch_dest is None or sec_fetch_dest != "script": + content = "normal document" + + headers = [(b"Content-Type", content_type)] + if fail_cors != "true": + origin = request.headers[ + b"Origin"] if b"Origin" in request.headers else b'*' + headers.append((b'Access-Control-Allow-Origin', origin)) + return status, headers, content.encode(encoding) diff --git a/testing/web-platform/tests/speculation-rules/prefetch/resources/utils.sub.js b/testing/web-platform/tests/speculation-rules/prefetch/resources/utils.sub.js new file mode 100644 index 0000000000..ea70939aff --- /dev/null +++ b/testing/web-platform/tests/speculation-rules/prefetch/resources/utils.sub.js @@ -0,0 +1,175 @@ +/** + * Utilities for initiating prefetch via speculation rules. + */ + +// Resolved URL to find this script. +const SR_PREFETCH_UTILS_URL = new URL(document.currentScript.src, document.baseURI); +// Hostname for cross origin urls. +const PREFETCH_PROXY_BYPASS_HOST = "{{hosts[alt][]}}"; + +class PrefetchAgent extends RemoteContext { + constructor(uuid, t) { + super(uuid); + this.t = t; + } + + getExecutorURL(options = {}) { + let {hostname, username, password, protocol, executor, ...extra} = options; + let params = new URLSearchParams({uuid: this.context_id, ...extra}); + if(executor === undefined) { + executor = "executor.sub.html"; + } + let url = new URL(`${executor}?${params}`, SR_PREFETCH_UTILS_URL); + if(hostname !== undefined) { + url.hostname = hostname; + } + if(username !== undefined) { + url.username = username; + } + if(password !== undefined) { + url.password = password; + } + if(protocol !== undefined) { + url.protocol = protocol; + url.port = protocol === "https" ? "{{ports[https][0]}}" : "{{ports[http][0]}}"; + } + return url; + } + + // Requests prefetch via speculation rules. + // + // In the future, this should also use browser hooks to force the prefetch to + // occur despite heuristic matching, etc., and await the completion of the + // prefetch. + async forceSinglePrefetch(url, extra = {}) { + await this.execute_script((url, extra) => { + insertSpeculationRules({ prefetch: [{source: 'list', urls: [url], ...extra}] }); + }, [url, extra]); + return new Promise(resolve => this.t.step_timeout(resolve, 2000)); + } + + async navigate(url) { + await this.execute_script((url) => { + window.executor.suspend(() => { + location.href = url; + }); + }, [url]); + url.username = ''; + url.password = ''; + assert_equals( + await this.execute_script(() => location.href), + url.toString(), + "expected navigation to reach destination URL"); + await this.execute_script(() => {}); + } + + async getRequestHeaders() { + return this.execute_script(() => requestHeaders); + } + + async getResponseCookies() { + return this.execute_script(() => { + let cookie = {}; + document.cookie.split(/\s*;\s*/).forEach((kv)=>{ + let [key, value] = kv.split(/\s*=\s*/); + cookie[key] = value; + }); + return cookie; + }); + } + + async getRequestCookies() { + return this.execute_script(() => window.requestCookies); + } + + async getRequestCredentials() { + return this.execute_script(() => window.requestCredentials); + } + + async setReferrerPolicy(referrerPolicy) { + return this.execute_script(referrerPolicy => { + const meta = document.createElement("meta"); + meta.name = "referrer"; + meta.content = referrerPolicy; + document.head.append(meta); + }, [referrerPolicy]); + } + + async getDeliveryType(){ + return this.execute_script(() => { + return performance.getEntriesByType("navigation")[0].deliveryType; + }); + } +} + +// Produces a URL with a UUID which will record when it's prefetched. +// |extra_params| can be specified to add extra search params to the generated +// URL. +function getPrefetchUrl(extra_params={}) { + let params = new URLSearchParams({ uuid: token(), ...extra_params }); + return new URL(`prefetch.py?${params}`, SR_PREFETCH_UTILS_URL); +} + +// Produces n URLs with unique UUIDs which will record when they are prefetched. +function getPrefetchUrlList(n) { + return Array.from({ length: n }, () => getPrefetchUrl()); +} + +function getRedirectUrl() { + let params = new URLSearchParams({uuid: token()}); + return new URL(`redirect.py?${params}`, SR_PREFETCH_UTILS_URL); +} + +async function isUrlPrefetched(url) { + let response = await fetch(url, {redirect: 'follow'}); + assert_true(response.ok); + return response.json(); +} + +// Must also include /common/utils.js and /common/dispatcher/dispatcher.js to use this. +async function spawnWindow(t, options = {}, uuid = token()) { + let agent = new PrefetchAgent(uuid, t); + let w = window.open(agent.getExecutorURL(options), options); + t.add_cleanup(() => w.close()); + return agent; +} + +function insertSpeculationRules(body) { + let script = document.createElement('script'); + script.type = 'speculationrules'; + script.textContent = JSON.stringify(body); + document.head.appendChild(script); +} + +// Creates and appends <a href=|href|> to |insertion point|. If +// |insertion_point| is not specified, document.body is used. +function addLink(href, insertion_point=document.body) { + const a = document.createElement('a'); + a.href = href; + insertion_point.appendChild(a); + return a; +} + +// Inserts a prefetch document rule with |predicate|. |predicate| can be +// undefined, in which case the default predicate will be used (i.e. all links +// in document will match). +function insertDocumentRule(predicate, extra_options={}) { + insertSpeculationRules({ + prefetch: [{ + source: 'document', + where: predicate, + ...extra_options + }] + }); +} + +function assert_prefetched (requestHeaders, description) { + assert_in_array(requestHeaders.purpose, ["", "prefetch"], "The vendor-specific header Purpose, if present, must be 'prefetch'."); + assert_in_array(requestHeaders.sec_purpose, + ["prefetch", "prefetch;anonymous-client-ip"], description); +} + +function assert_not_prefetched (requestHeaders, description){ + assert_equals(requestHeaders.purpose, "", description); + assert_equals(requestHeaders.sec_purpose, "", description); +} diff --git a/testing/web-platform/tests/speculation-rules/prefetch/same-origin-cookies.https.html b/testing/web-platform/tests/speculation-rules/prefetch/same-origin-cookies.https.html new file mode 100644 index 0000000000..1d60a4bee0 --- /dev/null +++ b/testing/web-platform/tests/speculation-rules/prefetch/same-origin-cookies.https.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src='/resources/testdriver-vendor.js'></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.sub.js"></script> +<script> + promise_test(async t => { + assert_implements(HTMLScriptElement.supports('speculationrules'), "Speculation Rules not supported"); + + await test_driver.delete_all_cookies(); + + let executor = 'cookies.py'; + let agent = await spawnWindow(t, { executor }); + let response_cookies = await agent.getResponseCookies(); + let request_cookies = await agent.getRequestCookies(); + assert_equals(request_cookies["count"], undefined); + assert_equals(request_cookies["type"], undefined); + assert_equals(response_cookies["count"], "1"); + assert_equals(response_cookies["type"], "navigate"); + + let nextUrl = agent.getExecutorURL({ executor, page: 2 }); + await agent.forceSinglePrefetch(nextUrl); + await agent.navigate(nextUrl); + + response_cookies = await agent.getResponseCookies(); + request_cookies = await agent.getRequestCookies(); + assert_equals(request_cookies["count"], "1"); + assert_equals(request_cookies["type"], "navigate"); + assert_equals(response_cookies["count"], "2"); + assert_equals(response_cookies["type"], "prefetch"); + + assert_prefetched(await agent.getRequestHeaders()); + + }, "speculation rules based prefetch should use cookies for same origin urls."); +</script> diff --git a/testing/web-platform/tests/speculation-rules/prefetch/user-pass.https.html b/testing/web-platform/tests/speculation-rules/prefetch/user-pass.https.html new file mode 100644 index 0000000000..94748f1eac --- /dev/null +++ b/testing/web-platform/tests/speculation-rules/prefetch/user-pass.https.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.sub.js"></script> +<meta name="variant" content="?cross-origin=true"> +<meta name="variant" content="?cross-origin=false"> +<script> + let cross_origin = Object.fromEntries(new URLSearchParams(location.search))["cross-origin"] === "true"; + promise_test(async t => { + assert_implements(HTMLScriptElement.supports('speculationrules'), "Speculation Rules not supported"); + + let executor = "authenticate.py"; + let credentials = { username: "user", password: "pass" }; + let agent = await spawnWindow(t, { executor, ...credentials }); + let request_credentials = await agent.getRequestCredentials(); + assert_equals(request_credentials.username, credentials.username); + assert_equals(request_credentials.password, credentials.password); + + let host = cross_origin ? { hostname: PREFETCH_PROXY_BYPASS_HOST } : {}; + let nextUrl = agent.getExecutorURL({ page: 2, executor, ...host }); + await agent.forceSinglePrefetch(nextUrl, { requires: ["anonymous-client-ip-when-cross-origin"] }); + await agent.navigate(nextUrl); + + let requestHeaders = await agent.getRequestHeaders(); + request_credentials = await agent.getRequestCredentials(); + if (cross_origin) { + assert_equals(request_credentials.username, undefined); + assert_equals(request_credentials.password, undefined); + + assert_in_array(requestHeaders.purpose, ["", "prefetch"]); + assert_equals(requestHeaders.sec_purpose, "prefetch;anonymous-client-ip"); + } + else { + assert_equals(request_credentials.username, credentials.username); + assert_equals(request_credentials.password, credentials.password); + + assert_prefetched(await agent.getRequestHeaders()); + } + + }, "test www-authenticate basic does not forward credentials to cross-origin pages."); +</script> |