diff options
Diffstat (limited to 'testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources')
11 files changed, 664 insertions, 0 deletions
diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/echo-worker.js b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/echo-worker.js new file mode 100644 index 0000000000..3e3ecb52e9 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/echo-worker.js @@ -0,0 +1,16 @@ +// On receiving a message from the parent Document, send back a message to the +// parent Document. This is used to wait for worker initialization and test +// that this worker is alive and working. + +// For dedicated workers. +self.addEventListener('message', event => { + postMessage(event.data); +}); + +// For shared workers. +onconnect = e => { + const port = e.ports[0]; + port.onmessage = event => { + port.postMessage(event.data); + } +}; diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/event-recorder.js b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/event-recorder.js new file mode 100644 index 0000000000..469286a399 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/event-recorder.js @@ -0,0 +1,54 @@ +// Recording events + +const params = new URLSearchParams(window.location.search); +const uuid = params.get('uuid'); + +// The recorded events are stored in localStorage rather than global variables +// to catch events fired just before navigating out. +function getPushedItems(key) { + return JSON.parse(localStorage.getItem(key) || '[]'); +} + +function pushItem(key, value) { + const array = getPushedItems(key); + array.push(value); + localStorage.setItem(key, JSON.stringify(array)); +} + +window.recordEvent = function(eventName) { + pushItem(uuid + '.observedEvents', eventName); +} + +window.getRecordedEvents = function() { + return getPushedItems(uuid + '.observedEvents'); +} + +// Records events fired on `window` and `document`, with names listed in +// `eventNames`. +function startRecordingEvents(eventNames) { + for (const eventName of eventNames) { + window.addEventListener(eventName, event => { + let result = eventName; + if (event.persisted) { + result += '.persisted'; + } + if (eventName === 'visibilitychange') { + result += '.' + document.visibilityState; + } + recordEvent('window.' + result); + }); + document.addEventListener(eventName, () => { + let result = eventName; + if (eventName === 'visibilitychange') { + result += '.' + document.visibilityState; + } + recordEvent('document.' + result); + }); + } +} + +// When a comma-separated list of event names are given as the `events` +// parameter in the URL, start record the events of the given names. +if (params.get('events')) { + startRecordingEvents(params.get('events').split(',')); +} diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/executor-pushstate.html b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/executor-pushstate.html new file mode 100644 index 0000000000..dcf4a798d0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/executor-pushstate.html @@ -0,0 +1,13 @@ +<!DOCTYPE HTML> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="event-recorder.js" type="module"></script> +<script src="worker-helper.js" type="module"></script> +<script type="module"> +// This is mostly the same as `executor.html`, except for +// `isLoadedFromPushState` is set here, in order to detect whether the page +// was loaded from `executor.html` or `executor-pushstate.html`. +// Full executor functionality is still needed to handle remote script +// execution requests etc. +window.isLoadedFromPushState = true; +</script> +<script src="executor.js" type="module"></script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/executor.html b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/executor.html new file mode 100644 index 0000000000..2d118bbe2b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/executor.html @@ -0,0 +1,5 @@ +<!DOCTYPE HTML> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="event-recorder.js" type="module"></script> +<script src="worker-helper.js" type="module"></script> +<script src="executor.js" type="module"></script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/executor.js b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/executor.js new file mode 100644 index 0000000000..67ce068130 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/executor.js @@ -0,0 +1,64 @@ +const params = new URLSearchParams(window.location.search); +const uuid = params.get('uuid'); + +// Executor and BFCache detection + +// When navigating out from this page, always call +// `prepareNavigation(callback)` synchronously from the script injected by +// `RemoteContext.execute_script()`, and trigger navigation on or after the +// callback is called. +// prepareNavigation() suspends task polling and avoid in-flight fetch +// requests during navigation that might evict the page from BFCache. +// +// When we navigate to the page again, task polling is resumed, either +// - (BFCache cases) when the pageshow event listener added by +// prepareNavigation() is executed, or +// - (Non-BFCache cases) when `Executor.execute()` is called again during +// non-BFCache page loading. +// +// In such scenarios, `assert_bfcached()` etc. in `helper.sub.js` can determine +// whether the page is restored from BFCache or not, by observing +// - `isPageshowFired`: whether the pageshow event listener added by the +// prepareNavigation() before navigating out, and +// - `loadCount`: whether this inline script is evaluated again. +// - `isPageshowPersisted` is used to assert that `event.persisted` is true +// when restored from BFCache. + +window.isPageshowFired = false; +window.isPageshowPersisted = null; +window.loadCount = parseInt(localStorage.getItem(uuid + '.loadCount') || '0') + 1; +localStorage.setItem(uuid + '.loadCount', loadCount); + +window.pageShowPromise = new Promise(resolve => + window.addEventListener('pageshow', resolve, {once: true})); + +const executor = new Executor(uuid); + +window.prepareNavigation = function(callback) { + window.addEventListener( + 'pageshow', + (event) => { + window.isPageshowFired = true; + window.isPageshowPersisted = event.persisted; + executor.resume(); + }, + {once: true}); + executor.suspend(callback); +} + +// Try to disable BFCache by acquiring and never releasing a Web Lock. +// This requires HTTPS. +// Note: This is a workaround depending on non-specified WebLock+BFCache +// behavior, and doesn't work on Safari. We might want to introduce a +// test-only BFCache-disabling API instead in the future. +// https://github.com/web-platform-tests/wpt/issues/16359#issuecomment-795004780 +// https://crbug.com/1298336 +window.disableBFCache = () => { + return new Promise(resolve => { + // Use page's UUID as a unique lock name. + navigator.locks.request(uuid, () => { + resolve(); + return new Promise(() => {}); + }); + }); +}; diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js new file mode 100644 index 0000000000..a1d18d108e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js @@ -0,0 +1,229 @@ +// Helpers called on the main test HTMLs. +// Functions in `RemoteContext.execute_script()`'s 1st argument are evaluated +// on the executors (`executor.html`), and helpers available on the executors +// are defined in `executor.html`. + +const originSameOrigin = + location.protocol === 'http:' ? + 'http://{{host}}:{{ports[http][0]}}' : + 'https://{{host}}:{{ports[https][0]}}'; +const originSameSite = + location.protocol === 'http:' ? + 'http://{{host}}:{{ports[http][1]}}' : + 'https://{{host}}:{{ports[https][1]}}'; +const originCrossSite = + location.protocol === 'http:' ? + 'http://{{hosts[alt][www]}}:{{ports[http][0]}}' : + 'https://{{hosts[alt][www]}}:{{ports[https][0]}}'; + +const executorPath = + '/html/browsers/browsing-the-web/back-forward-cache/resources/executor.html?uuid='; + +// Asserts that the executor `target` is (or isn't, respectively) +// restored from BFCache. These should be used in the following fashion: +// 1. Call prepareNavigation() on the executor `target`. +// 2. Navigate the executor to another page. +// 3. Navigate back to the executor `target`. +// 4. Call assert_bfcached() or assert_not_bfcached() on the main test HTML. +async function assert_bfcached(target) { + const status = await getBFCachedStatus(target); + assert_implements_optional(status === 'BFCached', + "Could have been BFCached but actually wasn't"); +} + +async function assert_not_bfcached(target) { + const status = await getBFCachedStatus(target); + assert_implements(status !== 'BFCached', + 'Should not be BFCached but actually was'); +} + +async function getBFCachedStatus(target) { + const [loadCount, isPageshowFired, isPageshowPersisted] = + await target.execute_script(() => [ + window.loadCount, window.isPageshowFired, window.isPageshowPersisted]); + + if (loadCount === 1 && isPageshowFired === true && + isPageshowPersisted === true) { + return 'BFCached'; + } else if (loadCount === 2 && isPageshowFired === false) { + return 'Not BFCached'; + } else { + // This can occur for example when this is called before first navigating + // away (loadCount = 1, isPageshowFired = false), e.g. when + // 1. sending a script for navigation and then + // 2. calling getBFCachedStatus() without waiting for the completion of + // the script on the `target` page. + assert_unreached( + `Got unexpected BFCache status: loadCount = ${loadCount}, ` + + `isPageshowFired = ${isPageshowFired}, ` + + `isPageshowPersisted = ${isPageshowPersisted}`); + } +} + +// Always call `await remoteContext.execute_script(waitForPageShow);` after +// triggering to navigation to the page, to wait for pageshow event on the +// remote context. +const waitForPageShow = () => window.pageShowPromise; + +// Run a test that navigates A->B->A: +// 1. Page A is opened by `params.openFunc(url)`. +// 2. `params.funcBeforeNavigation(params.argsBeforeNavigation)` is executed +// on page A. +// 3. The window is navigated to page B on `params.targetOrigin`. +// 4. The window is back navigated to page A (expecting BFCached). +// +// Events `params.events` (an array of strings) are observed on page A and +// `params.expectedEvents` (an array of strings) is expected to be recorded. +// See `event-recorder.js` for event recording. +// +// Parameters can be omitted. See `defaultParams` below for default. +function runEventTest(params, description) { + const defaultParams = { + openFunc(url) { + window.open( + `${url}&events=${this.events.join(',')}`, + '_blank', + 'noopener' + ) + }, + events: ['pagehide', 'pageshow', 'load'], + expectedEvents: [ + 'window.load', + 'window.pageshow', + 'window.pagehide.persisted', + 'window.pageshow.persisted' + ], + async funcAfterAssertion(pageA) { + assert_array_equals( + await pageA.execute_script(() => getRecordedEvents()), + this.expectedEvents); + } + } + // Apply defaults. + params = { ...defaultParams, ...params }; + + runBfcacheTest(params, description); +} + +async function navigateAndThenBack(pageA, pageB, urlB, + funcBeforeBackNavigation, + argsBeforeBackNavigation) { + await pageA.execute_script( + (url) => { + prepareNavigation(() => { + location.href = url; + }); + }, + [urlB] + ); + + await pageB.execute_script(waitForPageShow); + if (funcBeforeBackNavigation) { + await pageB.execute_script(funcBeforeBackNavigation, + argsBeforeBackNavigation); + } + await pageB.execute_script( + () => { + prepareNavigation(() => { history.back(); }); + } + ); + + await pageA.execute_script(waitForPageShow); +} + +function runBfcacheTest(params, description) { + const defaultParams = { + openFunc: url => window.open(url, '_blank', 'noopener'), + scripts: [], + funcBeforeNavigation: () => {}, + argsBeforeNavigation: [], + targetOrigin: originCrossSite, + funcBeforeBackNavigation: () => {}, + argsBeforeBackNavigation: [], + shouldBeCached: true, + funcAfterAssertion: () => {}, + } + // Apply defaults. + params = {...defaultParams, ...params }; + + promise_test(async t => { + const pageA = new RemoteContext(token()); + const pageB = new RemoteContext(token()); + + const urlA = executorPath + pageA.context_id; + const urlB = params.targetOrigin + executorPath + pageB.context_id; + + // So that tests can refer to these URLs for assertions if necessary. + pageA.url = originSameOrigin + urlA; + pageB.url = urlB; + + params.openFunc(urlA); + + await pageA.execute_script(waitForPageShow); + + for (const src of params.scripts) { + await pageA.execute_script((src) => { + const script = document.createElement("script"); + script.src = src; + document.head.append(script); + return new Promise(resolve => script.onload = resolve); + }, [src]); + } + + await pageA.execute_script(params.funcBeforeNavigation, + params.argsBeforeNavigation); + await navigateAndThenBack(pageA, pageB, urlB, + params.funcBeforeBackNavigation, + params.argsBeforeBackNavigation); + + if (params.shouldBeCached) { + await assert_bfcached(pageA); + } else { + await assert_not_bfcached(pageA); + } + + if (params.funcAfterAssertion) { + await params.funcAfterAssertion(pageA, pageB, t); + } + }, description); +} + +// Call clients.claim() on the service worker +async function claim(t, worker) { + const channel = new MessageChannel(); + const saw_message = new Promise(function(resolve) { + channel.port1.onmessage = t.step_func(function(e) { + assert_equals(e.data, 'PASS', 'Worker call to claim() should fulfill.'); + resolve(); + }); + }); + worker.postMessage({type: "claim", port: channel.port2}, [channel.port2]); + await saw_message; +} + +// Assigns the current client to a local variable on the service worker. +async function storeClients(t, worker) { + const channel = new MessageChannel(); + const saw_message = new Promise(function(resolve) { + channel.port1.onmessage = t.step_func(function(e) { + assert_equals(e.data, 'PASS', 'storeClients'); + resolve(); + }); + }); + worker.postMessage({type: "storeClients", port: channel.port2}, [channel.port2]); + await saw_message; +} + +// Call storedClients.postMessage("") on the service worker +async function postMessageToStoredClients(t, worker) { + const channel = new MessageChannel(); + const saw_message = new Promise(function(resolve) { + channel.port1.onmessage = t.step_func(function(e) { + assert_equals(e.data, 'PASS', 'postMessageToStoredClients'); + resolve(); + }); + }); + worker.postMessage({type: "postMessageToStoredClients", + port: channel.port2}, [channel.port2]); + await saw_message; +} diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/inflight-fetch-helper.js b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/inflight-fetch-helper.js new file mode 100644 index 0000000000..7832003b76 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/inflight-fetch-helper.js @@ -0,0 +1,47 @@ +// Delay after fetch start: +// - 0.0 seconds: before BFCache +// - 2.0 seconds: when in BFCache +// - 3.5 seconds: after restored from BFCache +function runTest(urlToFetch, hasCSP, shouldSucceed, description) { + runBfcacheTest({ + funcBeforeNavigation: async (urlToFetch, hasCSP) => { + if (hasCSP) { + // Set CSP. + const meta = document.createElement('meta'); + meta.setAttribute('http-equiv', 'Content-Security-Policy'); + meta.setAttribute('content', "connect-src 'self'"); + document.head.appendChild(meta); + } + + // Initiate a `fetch()`. + window.fetchPromise = fetch(urlToFetch); + + // Wait for 0.5 seconds to receive response headers for the fetch() + // before BFCache, if any. + await new Promise(resolve => setTimeout(resolve, 500)); + }, + argsBeforeNavigation: [urlToFetch, hasCSP], + funcBeforeBackNavigation: () => { + // Wait for 2 seconds before back navigating to pageA. + return new Promise(resolve => setTimeout(resolve, 2000)); + }, + funcAfterAssertion: async (pageA, pageB, t) => { + // Wait for fetch() completion and check the result. + const result = pageA.execute_script( + () => window.fetchPromise.then(r => r.text())); + if (shouldSucceed) { + assert_equals( + await result, + 'Body', + 'Fetch should complete successfully after restored from BFCache'); + } else { + await promise_rejects_js(t, TypeError, result, + 'Fetch should fail after restored from BFCache'); + } + } + }, 'Eligibility (in-flight fetch): ' + description); +} + +const url = new URL('../resources/slow.py', location); +const sameOriginUrl = url.href; +const crossSiteUrl = originCrossSite + url.pathname; diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js new file mode 100644 index 0000000000..80c164f560 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js @@ -0,0 +1,137 @@ +// A collection of helper functions that make use of the `remoteContextHelper` +// to test BFCache support and behavior. + +// Call `prepareForBFCache()` before navigating away from the page. This simply +// sets a variable in window. +async function prepareForBFCache(remoteContextHelper) { + await remoteContextHelper.executeScript(() => { + window.beforeBFCache = true; + }); +} + +// Call `getBeforeCache()` after navigating back to the page. This returns the +// value in window. +async function getBeforeBFCache(remoteContextHelper) { + return await remoteContextHelper.executeScript(() => { + return window.beforeBFCache; + }); +} + +// If the value in window is set to true, this means that the page was reloaded, +// i.e., the page was restored from BFCache. +// Call `prepareForBFCache()` before navigating away to call this function. +async function assertImplementsBFCacheOptional(remoteContextHelper) { + var beforeBFCache = await getBeforeBFCache(remoteContextHelper); + assert_implements_optional(beforeBFCache == true, 'BFCache not supported.'); +} + +// Subtracts set `b` from set `a` and returns the result. +function setMinus(a, b) { + const minus = new Set(); + a.forEach(e => { + if (!b.has(e)) { + minus.add(e); + } + }); + return minus; +} + +// Return a sorted Array from the iterable `s`. +function sorted(s) { + return Array.from(s).sort(); +} + +// Assert expected reasons and the reported reasons match. +function matchReasons(expectedNotRestoredReasonsSet, notRestoredReasonsSet) { + const missing = setMinus( + expectedNotRestoredReasonsSet, notRestoredReasonsSet, 'Missing reasons'); + const extra = setMinus( + notRestoredReasonsSet, expectedNotRestoredReasonsSet, 'Extra reasons'); + assert_true(missing.size + extra.size == 0, `Expected: ${sorted(expectedNotRestoredReasonsSet)}\n` + + `Got: ${sorted(notRestoredReasonsSet)}\n` + + `Missing: ${sorted(missing)}\n` + + `Extra: ${sorted(extra)}\n`); +} + +// This function takes a set of reasons and extracts reasons out of it and returns a set of strings. +// For example, if the input is [{"reason": "error-document"}, {"reason": "masked"}], +// the output is ["error-document", "masked"]. +function extractReason(reasonSet) { + let reasonsExtracted = new Set(); + for (let reason of reasonSet) { + reasonsExtracted.add(reason.reason); + } + return reasonsExtracted; +} + +// A helper function to assert that the page is not restored from BFCache by +// checking whether the `beforeBFCache` value from `window` is undefined +// due to page reload. +// This function also takes an optional `notRestoredReasons` list which +// indicates the set of expected reasons that make the page not restored. +// If the reasons list is undefined, the check will be skipped. Otherwise +// this check will use the `notRestoredReasons` API, to obtain the reasons +// in a tree structure, and flatten the reasons before making the order- +// insensitive comparison. +// If the API is not available, the function will terminate instead of marking +// the assertion failed. +// Call `prepareForBFCache()` before navigating away to call this function. +async function assertNotRestoredFromBFCache( + remoteContextHelper, notRestoredReasons) { + var beforeBFCache = await getBeforeBFCache(remoteContextHelper); + assert_equals(beforeBFCache, undefined, 'document unexpectedly BFCached'); + + // The reason is optional, so skip the remaining test if the + // `notRestoredReasons` is not set. + if (notRestoredReasons === undefined) { + return; + } + + let isFeatureEnabled = await remoteContextHelper.executeScript(() => { + return 'notRestoredReasons' in + performance.getEntriesByType('navigation')[0]; + }); + + // Return if the `notRestoredReasons` API is not available. + if (!isFeatureEnabled) { + return; + } + + let result = await remoteContextHelper.executeScript(() => { + return performance.getEntriesByType('navigation')[0].notRestoredReasons; + }); + + let expectedNotRestoredReasonsSet = new Set(notRestoredReasons); + let notRestoredReasonsSet = new Set(); + + // Flatten the reasons from the main frame and all the child frames. + const collectReason = (node) => { + for (let reason of node.reasons) { + notRestoredReasonsSet.add(reason.reason); + } + for (let child of node.children) { + collectReason(child); + } + }; + collectReason(result); + matchReasons(expectedNotRestoredReasonsSet, notRestoredReasonsSet); +} + +// A helper function that combines the steps of setting window property, +// navigating away and back, and making assertion on whether BFCache is +// supported. +// This function can be used to check if the current page is eligible for +// BFCache. +async function assertBFCacheEligibility( + remoteContextHelper, shouldRestoreFromBFCache) { + await prepareForBFCache(remoteContextHelper); + // Navigate away and back. + const newRemoteContextHelper = await remoteContextHelper.navigateToNew(); + await newRemoteContextHelper.historyBack(); + + if (shouldRestoreFromBFCache) { + await assertImplementsBFCacheOptional(remoteContextHelper); + } else { + await assertNotRestoredFromBFCache(remoteContextHelper); + } +} diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/service-worker.js b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/service-worker.js new file mode 100644 index 0000000000..ab9a3239ea --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/service-worker.js @@ -0,0 +1,58 @@ +self.addEventListener('message', function(event) { + if (event.data.type == "claim") { + self.clients.claim() + .then(function(result) { + if (result !== undefined) { + event.data.port.postMessage( + 'FAIL: claim() should be resolved with undefined'); + return; + } + event.data.port.postMessage('PASS'); + }) + .catch(function(error) { + event.data.port.postMessage('FAIL: exception: ' + error.name); + }); + } else if (event.data.type == "storeClients") { + self.clients.matchAll() + .then(function(result) { + self.storedClients = result; + event.data.port.postMessage("PASS"); + }); + } else if (event.data.type == "postMessageToStoredClients") { + for (let client of self.storedClients) { + client.postMessage("dummyValue"); + } + event.data.port.postMessage("PASS"); + } else if (event.data.type == 'storeMessagePort') { + let isCloseEventFired = false; + const port = event.ports[0]; + port.start(); + port.onmessage = (event) => { + if (event.data == 'Confirm the ports can communicate') { + port.postMessage('Receive message'); + } else if (event.data == 'Ask if the close event was fired') { + port.postMessage(isCloseEventFired); + } + }; + port.onclose = () => { + isCloseEventFired = true; + }; + } + }); + +self.addEventListener('fetch', e => { + if (e.request.url.match(/\/is-controlled/)) { + e.respondWith(new Response('controlled')); + } + else if (e.request.url.match(/\/get-clients-matchall/)) { + const options = { includeUncontrolled: true, type: 'all' }; + e.respondWith( + self.clients.matchAll(options) + .then(clients => { + const client_urls = []; + clients.forEach(client => client_urls.push(client.url)); + return new Response(JSON.stringify(client_urls)); + }) + ); + } + }); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/slow.py b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/slow.py new file mode 100644 index 0000000000..01bb3309b1 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/slow.py @@ -0,0 +1,13 @@ +import time + +def main(request, response): + delay_before_header = float(request.GET.first(b"delayBeforeHeader", 0)) / 1000 + delay_before_body = float(request.GET.first(b"delayBeforeBody", 0)) / 1000 + + time.sleep(delay_before_header) + if b"cors" in request.GET: + response.headers.set(b"Access-Control-Allow-Origin", b"*") + response.write_status_headers() + + time.sleep(delay_before_body) + response.writer.write_content(b"Body") diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/worker-helper.js b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/worker-helper.js new file mode 100644 index 0000000000..d5f3a0c814 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/worker-helper.js @@ -0,0 +1,28 @@ +// Worker-related helper file to be used from executor.html. + +// The class `WorkerHelper` is exposed to `globalThis` because this should be +// used via `eval()`. +globalThis.WorkerHelper = class { + static pingWorker(worker) { + return new Promise((resolve, reject) => { + const message = 'message ' + Math.random(); + const onmessage = e => { + if (e.data === message) { + resolve('PASS'); + } else { + reject('pingWorker: expected ' + message + ' but got ' + e.data); + } + }; + worker.onerror = reject; + if (worker instanceof Worker) { + worker.addEventListener('message', onmessage, {once: true}); + worker.postMessage(message); + } else if (worker instanceof SharedWorker) { + worker.port.onmessage = onmessage; + worker.port.postMessage(message); + } else { + reject('Unexpected worker type'); + } + }); + } +}; |