diff options
Diffstat (limited to 'testing/web-platform/tests/html/browsers')
1102 files changed, 34529 insertions, 0 deletions
diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/README.md b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/README.md new file mode 100644 index 0000000000..5f10361d5c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/README.md @@ -0,0 +1,52 @@ +# How to write back-forward cache tests + +In the back-forward cache tests, the main test HTML usually: + +1. Opens new executor Windows using `window.open()` + `noopener` option, + because less isolated Windows (e.g. iframes and `window.open()` without + `noopener` option) are often not eligible for back-forward cache (e.g. + in Chromium). +2. Injects scripts to the executor Windows and receives the results via + `RemoteContext.execute_script()` by + [/common/dispatcher](../../../../common/dispatcher/README.md). + Follow the semantics and guideline described there. + +Back-forward cache specific helpers are in: + +- [resources/executor.html](resources/executor.html): + The BFCache-specific executor and contains helpers for executors. +- [resources/helper.sub.js](resources/helper.sub.js): + Helpers for main test HTMLs. + +We must ensure that injected scripts are evaluated only after page load +(more precisely, the first `pageshow` event) and not during navigation, +to prevent unexpected interference between injected scripts, in-flight fetch +requests behind `RemoteContext.execute_script()`, navigation and back-forward +cache. To ensure this, + +- Call `await remoteContext.execute_script(waitForPageShow)` before any + other scripts are injected to the remote context, and +- Call `prepareNavigation(callback)` synchronously from the script injected + by `RemoteContext.execute_script()`, and trigger navigation on or after the + callback is called. + +In typical A-B-A scenarios (where we navigate from Page A to Page B and then +navigate back to Page A, assuming Page A is (or isn't) in BFCache), + +- Call `prepareNavigation()` on the executor, and then navigate to B, and then + navigate back to Page A. +- Call `assert_bfcached()` or `assert_not_bfcached()` on the main test HTML, to + check the BFCache status. This is important to do to ensure the test would + not fail normally and instead result in `PRECONDITION_FAILED` if the page is + unexpectedly bfcached/not bfcached. +- Check other test expectations on the main test HTML, + +as in [events.html](./events.html) and `runEventTest()` in +[resources/helper.sub.js](resources/helper.sub.js). + +# Asserting PRECONDITION_FAILED for unexpected BFCache eligibility + +To distinguish failures due to unexpected BFCache ineligibility (which might be +acceptable due to different BFCache eligibility criteria across browsers), +`assert_bfcached()` and `assert_not_bfcached()` results in +`PRECONDITION_FAILED` rather than ordinal failures. diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/eligibility/broadcast-channel.html b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/eligibility/broadcast-channel.html new file mode 100644 index 0000000000..bc04a5ed7f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/eligibility/broadcast-channel.html @@ -0,0 +1,23 @@ +<!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/helper.sub.js"></script> +<script> +// Check whether the page is BFCached when there are open BroadcastChannels. +// See https://github.com/whatwg/html/issues/7219 for other related scenarios. +runEventTest( + {funcBeforeNavigation: () => { + window.bc = new BroadcastChannel('foo'); + }}, + 'Eligibility (BroadcastChannel)'); + +// Same as above, but the BroadcastChannels are closed in the pagehide event. +runEventTest( + {funcBeforeNavigation: () => { + window.bc = new BroadcastChannel('foo'); + window.addEventListener('pagehide', () => window.bc.close()); + }}, + 'Eligibility (BroadcastChannel closed in the pagehide event)'); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/eligibility/dedicated-worker.html b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/eligibility/dedicated-worker.html new file mode 100644 index 0000000000..b08588a8bd --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/eligibility/dedicated-worker.html @@ -0,0 +1,25 @@ +<!doctype html> +<meta name="timeout" content="long"> +<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/helper.sub.js"></script> +<script> +// Check whether the page is BFCached when there are dedicated workers that are +// already loaded. +runBfcacheTest({ + funcBeforeNavigation: async () => { + globalThis.worker = new Worker('../resources/echo-worker.js'); + // Make sure the worker starts before navigation. + await WorkerHelper.pingWorker(globalThis.worker); + }, + funcAfterAssertion: async (pageA) => { + // Confirm that the worker is still there. + assert_equals( + await pageA.execute_script(() => WorkerHelper.pingWorker(globalThis.worker)), + 'PASS', + 'Worker should still work after restored from BFCache'); + } +}, 'Eligibility: dedicated workers'); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/eligibility/inflight-fetch-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/eligibility/inflight-fetch-1.html new file mode 100644 index 0000000000..6a48d0657b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/eligibility/inflight-fetch-1.html @@ -0,0 +1,18 @@ +<!doctype html> +<meta name="timeout" content="long"> +<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/helper.sub.js"></script> +<script src="../resources/inflight-fetch-helper.js"></script> +<script> +// Check whether the page is BFCached when there are in-flight network requests +// at the time of navigation. + +// Successful fetch completion with header received before BFCached. +runTest(sameOriginUrl + '?delayBeforeBody=2000', false, true, + 'Header received before BFCache and body received when in BFCache'); +runTest(sameOriginUrl + '?delayBeforeBody=3500', false, true, + 'Header received before BFCache and body received after BFCache'); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/eligibility/inflight-fetch-2.html b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/eligibility/inflight-fetch-2.html new file mode 100644 index 0000000000..a767c4fa83 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/eligibility/inflight-fetch-2.html @@ -0,0 +1,22 @@ +<!doctype html> +<meta name="timeout" content="long"> +<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/helper.sub.js"></script> +<script src="../resources/inflight-fetch-helper.js"></script> +<script> +// Check whether the page is BFCached when there are in-flight network requests +// at the time of navigation. + +// Successful fetch completion with header received when in BFCache or after +// BFCache. +runTest(sameOriginUrl + '?delayBeforeHeader=2000', false, true, + 'Header and body received when in BFCache'); +runTest(sameOriginUrl + '?delayBeforeHeader=2000&delayBeforeBody=1500', + false, true, + 'Header received when in BFCache and body received after BFCache'); +runTest(sameOriginUrl + '?delayBeforeHeader=3500', false, true, + 'Header and body received after BFCache'); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/eligibility/inflight-fetch-cors.html b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/eligibility/inflight-fetch-cors.html new file mode 100644 index 0000000000..c04089a5e2 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/eligibility/inflight-fetch-cors.html @@ -0,0 +1,18 @@ +<!doctype html> +<meta name="timeout" content="long"> +<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/helper.sub.js"></script> +<script src="../resources/inflight-fetch-helper.js"></script> +<script> +// Check whether the page is BFCached when there are in-flight network requests +// at the time of navigation. + +// CORS and failing fetch. +runTest(crossSiteUrl + '?delayBeforeHeader=2000&cors=yes', false, true, + 'CORS succeeded when in BFCache'); +runTest(crossSiteUrl + '?delayBeforeHeader=2000', false, false, + 'CORS failed when in BFCache'); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/eligibility/inflight-fetch-redirects.html b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/eligibility/inflight-fetch-redirects.html new file mode 100644 index 0000000000..b0b49d5f12 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/eligibility/inflight-fetch-redirects.html @@ -0,0 +1,34 @@ +<!doctype html> +<meta name="timeout" content="long"> +<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/helper.sub.js"></script> +<script src="../resources/inflight-fetch-helper.js"></script> +<script> +// Check whether the page is BFCached when there are in-flight network requests +// at the time of navigation. + +// Redirects and CSP. +runTest( + '/common/slow-redirect.py?delay=2&location=' + + encodeURIComponent(sameOriginUrl), + false, true, + 'Redirect header received when in BFCache'); +runTest( + '/common/slow-redirect.py?delay=2&location=' + + encodeURIComponent(sameOriginUrl), + true, true, + 'Redirect header received when in BFCache w/ CSP passing'); +runTest( + '/common/slow-redirect.py?delay=2&location=' + + encodeURIComponent(crossSiteUrl + '?cors=yes'), + false, true, + 'Cross-origin redirect header received when in BFCache'); +runTest( + '/common/slow-redirect.py?delay=2&location=' + + encodeURIComponent(crossSiteUrl + '?cors=yes'), + true, false, + 'Cross-origin redirect header received when in BFCache w/ CSP failing'); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/eligibility/shared-worker.html b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/eligibility/shared-worker.html new file mode 100644 index 0000000000..77139fd08a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/eligibility/shared-worker.html @@ -0,0 +1,25 @@ +<!doctype html> +<meta name="timeout" content="long"> +<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/helper.sub.js"></script> +<script> +// Check whether the page is BFCached when there are shared workers that are +// already loaded. +runBfcacheTest({ + funcBeforeNavigation: async () => { + globalThis.worker = new SharedWorker('../resources/echo-worker.js'); + // Make sure the worker starts before navigation. + await WorkerHelper.pingWorker(globalThis.worker); + }, + funcAfterAssertion: async (pageA) => { + // Confirm that the worker is still there. + assert_equals( + await pageA.execute_script(() => WorkerHelper.pingWorker(globalThis.worker)), + 'PASS', + 'SharedWorker should still work after restored from BFCache'); + } +}, 'Eligibility: shared workers'); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/events.html b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/events.html new file mode 100644 index 0000000000..4b1d3e408e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/events.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/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="resources/helper.sub.js"></script> +<script> +// Basic event tests. +runEventTest( + {targetOrigin: originSameOrigin}, + 'SameOrigin'); + +runEventTest( + {targetOrigin: originSameSite}, + 'SameSite'); + +runEventTest( + {}, + 'CrossSite'); + +// beforeunload. +runEventTest({ + events: ['pagehide', 'pageshow', 'load', 'beforeunload'], + expectedEvents: [ + 'window.load', + 'window.pageshow', + 'window.beforeunload', + 'window.pagehide.persisted', + 'window.pageshow.persisted' + ]}, + 'beforeunload'); + +// unload. +runEventTest({ + events: ['pagehide', 'pageshow', 'load', 'unload'], + expectedEvents: [ + 'window.load', + 'window.pageshow', + 'window.pagehide.persisted', + 'window.pageshow.persisted' + ]}, + 'unload'); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/focus.html b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/focus.html new file mode 100644 index 0000000000..3901a5417d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/focus.html @@ -0,0 +1,73 @@ +<!DOCTYPE HTML> +<meta name="timeout" content="long"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#focused-area-of-the-document"> +<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/helper.sub.js"></script> +<script> +// Focus should remain the same and thus blur/focus events shouldn't be fired +// when page gets into and out of BFCache, as explicitly noted in the spec: +// https://html.spec.whatwg.org/multipage/interaction.html#focused-area-of-the-document +// "Even if a document is not fully active and not shown to the user, it can still +// have a focused area of the document. If a document's fully active state changes, +// its focused area of the document will stay the same." +runBfcacheTest({ + openFunc: (url) => window.open(url + '&events=pagehide,pageshow,load', + '_blank', 'noopener'), + funcBeforeNavigation: () => { + // Create and focus on an <input> before navigation. + // Focus/blur events on the <input> are recorded. + const textInput = document.createElement('input'); + textInput.setAttribute('type', 'text'); + textInput.setAttribute('id', 'toBeFocused'); + textInput.onfocus = () => { + recordEvent('input.focus'); + }; + textInput.onblur = () => { + recordEvent('input.blur'); + }; + document.body.appendChild(textInput); + textInput.focus(); + window.activeElementBeforePageHide = document.activeElement; + window.addEventListener('pagehide', () => { + window.activeElementOnPageHide = document.activeElement; + }); + }, + funcAfterAssertion: async (pageA) => { + assert_true( + await pageA.execute_script(() => { + return window.activeElementBeforePageHide === + document.querySelector('#toBeFocused'); + }), + 'activeElement before pagehide'); + + assert_true( + await pageA.execute_script(() => { + return window.activeElementOnPageHide === + document.querySelector('#toBeFocused'); + }), + 'activeElement on pagehide'); + + assert_true( + await pageA.execute_script(() => { + return document.activeElement === + document.querySelector('#toBeFocused'); + }), + 'activeElement after navigation'); + + assert_array_equals( + await pageA.execute_script(() => getRecordedEvents()), + [ + 'window.load', + 'window.pageshow', + 'input.focus', + 'window.pagehide.persisted', + 'window.pageshow.persisted' + ], + 'blur/focus events should not be fired ' + + 'when page gets into and out of BFCache'); + } +}, 'Focus should be kept when page gets into and out of BFCache'); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/pushstate.https.html b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/pushstate.https.html new file mode 100644 index 0000000000..218562254a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/pushstate.https.html @@ -0,0 +1,63 @@ +<!DOCTYPE HTML> +<meta name="timeout" content="long"> +<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/helper.sub.js"></script> +<script> +// Tests what happens when doing history navigation to an entry that's created +// via pushState, with and without BFCache: +// 1. Navigate to `urlA`. +// 2. `pushState(urlPushState)`. +// 3. Navigate to `urlB`. +// 4. Do a back navigation. +// With BFCache, the page loaded at Step 1 is restored from BFCache, +// and is not reloaded from `urlPushState` nor `urlA`. +// Without BFCache, a page is loaded from `urlPushState`, not from `urlA`. +// In both cases, `location` and `history.state` are set to those set by +// `pushState()` in Step 2. +// See https://github.com/whatwg/html/issues/6207 for more discussion on the +// specified, implemented and desired behaviors. While this test contradicts +// the current spec but matches the desired behavior, and the spec will be +// fixed as part of https://github.com/whatwg/html/pull/6315. +for (const bfcacheDisabled of [false, true]) { + const pushStateExecutorPath = + '/html/browsers/browsing-the-web/back-forward-cache/resources/executor-pushstate.html'; + + runBfcacheTest({ + funcBeforeNavigation: async (bfcacheDisabled, pushStateExecutorPath) => { + const urlPushState = new URL(location.href); + urlPushState.pathname = pushStateExecutorPath; + if (bfcacheDisabled) { + await disableBFCache(); + } + + // `pushState(..., urlPushState)` on `urlA`, + history.pushState('blue', '', urlPushState.href); + }, + argsBeforeNavigation: [bfcacheDisabled, pushStateExecutorPath], + shouldBeCached: !bfcacheDisabled, + funcAfterAssertion: async (pageA) => { + // We've navigated to `urlB` and back again + // (already done within `runBfcacheTest()`). + // After the back navigation, `location` etc. should point to + // `urlPushState` and the state that's pushed. + const urlPushState = location.origin + pushStateExecutorPath + + '?uuid=' + pageA.context_id; + assert_equals(await pageA.execute_script(() => location.href), + urlPushState, 'url'); + assert_equals(await pageA.execute_script(() => history.state), + 'blue', 'history.state'); + + if (bfcacheDisabled) { + // When the page is not restored from BFCache, the HTML page is loaded + // from `urlPushState` (not from `urlA`). + assert_true(await pageA.execute_script(() => isLoadedFromPushState), + 'document should be loaded from urlPushState'); + } + } + }, 'back navigation to pushState()d page (' + + (bfcacheDisabled ? 'not ' : '') + 'in BFCache)'); +} +</script> 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..53bcdccf7c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js @@ -0,0 +1,103 @@ +// 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.'); +} + +// 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); + + // 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); + } + for (let child of node.children) { + collectReason(child); + } + } + collectReason(result); + + assert_equals(notRestoredReasonsSet.length, + expectedNotRestoredReasonsSet.length); + + for (let reason of expectedNotRestoredReasonsSet) { + assert_true(notRestoredReasonsSet.has(reason)); + } +} + +// 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..df9ce65acd --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/resources/service-worker.js @@ -0,0 +1,44 @@ +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"); + } + }); + +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'); + } + }); + } +}; diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/service-worker-client-postmessage.https.html b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/service-worker-client-postmessage.https.html new file mode 100644 index 0000000000..acc682a073 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/service-worker-client-postmessage.https.html @@ -0,0 +1,71 @@ +<!doctype html> +<meta name="timeout" content="long"> +<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/helper.sub.js"></script> +<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> +<script> +// When a service worker is unregistered when a controlled page is in BFCache, +// the page can be still restored from BFCache and remain controlled by the +// service worker. +promise_test(async t => { + // Register a service worker and make this page controlled. + const workerUrl = + 'resources/service-worker.js?pipe=header(Service-Worker-Allowed,../)'; + const registration = + await service_worker_unregister_and_register(t, workerUrl, './'); + t.add_cleanup(_ => registration.unregister()); + await wait_for_state(t, registration.installing, 'activated'); + const controllerChanged = new Promise( + resolve => navigator.serviceWorker.oncontrollerchange = resolve); + await claim(t, registration.active); + await controllerChanged; + + const pageA = new RemoteContext(token()); + const pageB = new RemoteContext(token()); + + const urlA = location.origin + executorPath + pageA.context_id; + const urlB = originCrossSite + executorPath + pageB.context_id; + + // Open `urlA`. + window.open(urlA, '_blank', 'noopener'); + await pageA.execute_script(waitForPageShow); + + assert_true( + await pageA.execute_script( + () => (navigator.serviceWorker.controller !== null)), + 'pageA should be controlled before navigation'); + + await storeClients(t, registration.active); + + // Navigate to `urlB`. + await pageA.execute_script( + (url) => prepareNavigation(() => { + location.href = url; + }), + [urlB]); + await pageB.execute_script(waitForPageShow); + + // Posting a message to a client should evict it from the bfcache. + await postMessageToStoredClients(t, registration.active); + + // Back navigate and check whether the page is restored from BFCache. + await pageB.execute_script( + () => { + prepareNavigation(() => { history.back(); }); + } + ); + await pageA.execute_script(waitForPageShow); + await assert_not_bfcached(pageA); + + await pageA.execute_script(() => navigator.serviceWorker.ready); + + assert_true( + await pageA.execute_script( + () => (navigator.serviceWorker.controller !== null)), + 'pageA should be controlled after history navigation'); + +}, 'Client.postMessage while a controlled page is in BFCache'); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/service-worker-clients-claim.https.html b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/service-worker-clients-claim.https.html new file mode 100644 index 0000000000..d9540c221b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/service-worker-clients-claim.https.html @@ -0,0 +1,71 @@ +<!doctype html> +<meta name="timeout" content="long"> +<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/helper.sub.js"></script> +<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> +<script> +// Calling Clients.claim() on the service worker when a controlled page is in +// BFCache should evict the page from BFCache, as per +// https://github.com/w3c/ServiceWorker/issues/1038#issuecomment-291028845. +promise_test(async t => { + const pageA = new RemoteContext(token()); + const pageB = new RemoteContext(token()); + + const urlA = location.origin + executorPath + pageA.context_id; + const urlB = originCrossSite + executorPath + pageB.context_id; + + window.open(urlA, '_blank', 'noopener'); + await pageA.execute_script(waitForPageShow); + + // Register a service worker after `pageA` is loaded to make `pageA` + // uncontrolled at this time. + const workerUrl = + 'resources/service-worker.js?pipe=header(Service-Worker-Allowed,../)'; + const registration = + await service_worker_unregister_and_register(t, workerUrl, './'); + t.add_cleanup(_ => registration.unregister()); + await wait_for_state(t, registration.installing, 'activated'); + + // Navigate to `urlB`. + await pageA.execute_script( + (url) => { + prepareNavigation(() => { location.href = url; }); + }, + [urlB]); + await pageB.execute_script(waitForPageShow); + + // Call Clients.claim() on the service worker when `pageA` is in BFCache. + const controllerChanged = new Promise( + resolve => navigator.serviceWorker.oncontrollerchange = resolve); + await claim(t, registration.active); + await controllerChanged; + + // `pageA` doesn't appear in matchAll(). + const clients1 = await (await fetch('/get-clients-matchall')).json(); + assert_true(clients1.indexOf(urlA) < 0, + '1: matchAll() before back navigation'); + + // Back navigate and check that the page was evicted from BFCache. + await pageB.execute_script( + () => { + prepareNavigation(() => { history.back(); }); + } + ); + await pageA.execute_script(waitForPageShow); + await assert_not_bfcached(pageA); + + // After back navigation, `pageA` appear in matchAll(), because it was newly + // loaded and controlled by the service worker. + const clients2 = await (await fetch('/get-clients-matchall')).json(); + const controlled2 = await pageA.execute_script( + () => (navigator.serviceWorker.controller !== null)); + assert_true(clients2.indexOf(urlA) >= 0, + '2: matchAll() just after back navigation'); + assert_true(controlled2, + '2: pageA should be controlled just after back navigation'); + +}, 'Clients.claim() evicts pages that would be affected from BFCache'); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/service-worker-clients-matchall.https.html b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/service-worker-clients-matchall.https.html new file mode 100644 index 0000000000..069529dbe4 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/service-worker-clients-matchall.https.html @@ -0,0 +1,76 @@ +<!doctype html> +<meta name="timeout" content="long"> +<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/helper.sub.js"></script> +<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> +<script> +promise_test(async t => { + // Register a service worker and make this page controlled. + const workerUrl = + 'resources/service-worker.js?pipe=header(Service-Worker-Allowed,../)'; + const registration = + await service_worker_unregister_and_register(t, workerUrl, './'); + t.add_cleanup(_ => registration.unregister()); + await wait_for_state(t, registration.installing, 'activated'); + const controllerChanged = new Promise( + resolve => navigator.serviceWorker.oncontrollerchange = resolve); + await claim(t, registration.active); + await controllerChanged; + + const pageA = new RemoteContext(token()); + const pageB = new RemoteContext(token()); + + const urlA = location.origin + executorPath + pageA.context_id; + const urlB = originCrossSite + executorPath + pageB.context_id; + + // Open `urlA`. + window.open(urlA, '_blank', 'noopener'); + await pageA.execute_script(waitForPageShow); + + // Get Clients.matchAll() and check whether `pageA` is controlled. + // Actual `assert_*()` is called after `assert_bfcached()` below. + const clients1 = await (await fetch('/get-clients-matchall')).json(); + const controlled1 = await pageA.execute_script( + () => (navigator.serviceWorker.controller !== null)); + + // Navigate to `urlB` and get Clients.matchAll() when `urlA` is in BFCache. + await pageA.execute_script( + (url) => prepareNavigation(() => { + location.href = url; + }), + [urlB]); + await pageB.execute_script(waitForPageShow); + const clients2 = await (await fetch('/get-clients-matchall')).json(); + + // Back navigate and check whether the page is restored from BFCache. + await pageB.execute_script( + () => { + prepareNavigation(() => { history.back(); }); + } + ); + await pageA.execute_script(waitForPageShow); + await assert_bfcached(pageA); + + // Get Clients.matchAll() and check whether `pageA` is controlled. + const clients3 = await (await fetch('/get-clients-matchall')).json(); + const controlled3 = await pageA.execute_script( + () => (navigator.serviceWorker.controller !== null)); + + // Clients.matchAll() should not list `urlA` when it is in BFCache. + assert_true(clients1.indexOf(urlA) >= 0, + '1: matchAll() before navigation'); + assert_true(clients2.indexOf(urlA) < 0, + '2: matchAll() before back navigation'); + assert_true(clients3.indexOf(urlA) >= 0, + '3: matchAll() after back navigation'); + + // `pageA` should be controlled before/after BFCached. + assert_true(controlled1, + 'pageA should be controlled before BFCached'); + assert_true(controlled3, + 'pageA should be controlled after restored'); +}, 'Clients.matchAll() should not list pages in BFCache'); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/service-worker-controlled-after-restore.https.html b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/service-worker-controlled-after-restore.https.html new file mode 100644 index 0000000000..a937eb85ac --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/service-worker-controlled-after-restore.https.html @@ -0,0 +1,54 @@ +<!doctype html> +<meta name="timeout" content="long"> +<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/helper.sub.js"></script> +<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> +<script> +promise_test(async t => { + const pageA = new RemoteContext(token()); + const pageB = new RemoteContext(token()); + + const urlA = location.origin + executorPath + pageA.context_id; + const urlB = originCrossSite + executorPath + pageB.context_id; + + // Register a service worker. + const workerUrl = + 'resources/service-worker.js?pipe=header(Service-Worker-Allowed,../)'; + const registration = + await service_worker_unregister_and_register(t, workerUrl, './'); + t.add_cleanup(_ => registration.unregister()); + await wait_for_state(t, registration.installing, 'activated'); + + window.open(urlA, '_blank', 'noopener'); + await pageA.execute_script(waitForPageShow); + + assert_true( + await pageA.execute_script( + () => (navigator.serviceWorker.controller !== null)), + 'pageA should be controlled before navigation'); + + await navigateAndThenBack(pageA, pageB, urlB); + await assert_bfcached(pageA); + + assert_true( + await pageA.execute_script( + () => (navigator.serviceWorker.controller !== null)), + 'navigator.serviceWorker.controller should be non-null ' + + 'after restored from BFCache'); + + const isControlled = await pageA.execute_script( + () => fetch('/is-controlled').then(r => r.text())); + + assert_true( + await pageA.execute_script( + () => (navigator.serviceWorker.controller !== null)), + 'navigator.serviceWorker.controller should be non-null ' + + 'after restored from BFCache and after fetch'); + + assert_equals(isControlled, 'controlled', + 'fetch should be intercepted after restored from BFCache'); +}, 'Pages should remain controlled after restored from BFCache'); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/service-worker-unregister.https.html b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/service-worker-unregister.https.html new file mode 100644 index 0000000000..1c3f81153c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/service-worker-unregister.https.html @@ -0,0 +1,67 @@ +<!doctype html> +<meta name="timeout" content="long"> +<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/helper.sub.js"></script> +<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> +<script> +// When a service worker is unregistered when a controlled page is in BFCache, +// the page can be still restored from BFCache and remain controlled by the +// service worker. +promise_test(async t => { + // Register a service worker and make this page controlled. + const workerUrl = + 'resources/service-worker.js?pipe=header(Service-Worker-Allowed,../)'; + const registration = + await service_worker_unregister_and_register(t, workerUrl, './'); + t.add_cleanup(_ => registration.unregister()); + await wait_for_state(t, registration.installing, 'activated'); + const controllerChanged = new Promise( + resolve => navigator.serviceWorker.oncontrollerchange = resolve); + await claim(t, registration.active); + await controllerChanged; + + const pageA = new RemoteContext(token()); + const pageB = new RemoteContext(token()); + + const urlA = location.origin + executorPath + pageA.context_id; + const urlB = originCrossSite + executorPath + pageB.context_id; + + // Open `urlA`. + window.open(urlA, '_blank', 'noopener'); + await pageA.execute_script(waitForPageShow); + + assert_true( + await pageA.execute_script( + () => (navigator.serviceWorker.controller !== null)), + 'pageA should be controlled before navigation'); + + // Navigate to `urlB`. + await pageA.execute_script( + (url) => prepareNavigation(() => { + location.href = url; + }), + [urlB]); + await pageB.execute_script(waitForPageShow); + + // Unregister the service worker when the controlled `pageA` is in BFCache. + await registration.unregister(); + + // Back navigate and check whether the page is restored from BFCache. + await pageB.execute_script( + () => { + prepareNavigation(() => { history.back(); }); + } + ); + await pageA.execute_script(waitForPageShow); + await assert_not_bfcached(pageA); + + assert_true( + await pageA.execute_script( + () => (navigator.serviceWorker.controller === null)), + 'pageA should not be controlled'); + +}, 'Unregister service worker while a controlled page is in BFCache'); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/storage-events.html b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/storage-events.html new file mode 100644 index 0000000000..6957496c30 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/storage-events.html @@ -0,0 +1,101 @@ +<!DOCTYPE HTML> +<meta name="timeout" content="long"> +<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/helper.sub.js"></script> +<script> +// When localStorage (`key1`) is modified when a page (`pageA`) is in BFCache, +// storage events should not be fired for the page after becoming active. +// https://github.com/whatwg/storage/issues/119#issuecomment-1115844532 +promise_test(async t => { + const pageA = new RemoteContext(token()); + const pageB = new RemoteContext(token()); + const pageC = new RemoteContext(token()); + + const urlA = executorPath + pageA.context_id + '&events=pagehide,pageshow,load'; + const urlB = originCrossSite + executorPath + pageB.context_id; + const urlC = executorPath + pageC.context_id + '&events=pagehide,pageshow,load'; + + // localStorage key to set while pageA is in BFCache. + const key1 = token(); + // localStorage key to set after pageA is restored from BFCache. + const key2 = token(); + + const startRecordingStorageEvent = (key1, key2) => { + window.key1EventFired = new Promise(resolve => { + window.addEventListener('storage', e => { + if (e.key === key1) { + recordEvent('storage1'); + resolve(); + } + }); + }); + window.key2EventFired = new Promise(resolve => { + window.addEventListener('storage', e => { + if (e.key === key2) { + recordEvent('storage2'); + resolve(); + } + }); + }); + }; + + window.open(urlA, '_blank', 'noopener'); + await pageA.execute_script(waitForPageShow); + await pageA.execute_script(startRecordingStorageEvent, [key1, key2]); + + // Window C is an unrelated window kept open without navigation, to confirm + // that storage events are fired as expected in non-BFCache-related scenario + // and not blocked due to non-BFCache-related reasons. + window.open(urlC, '_blank'); + await pageC.execute_script(waitForPageShow); + await pageC.execute_script(startRecordingStorageEvent, [key1, key2]); + + // Navigate A to B. + await pageA.execute_script((url) => { + prepareNavigation(() => { + location.href = url; + }); + }, [urlB]); + await pageB.execute_script(waitForPageShow); + + // Update `key1` while pageA is in BFCache. + localStorage.setItem(key1, 'value'); + + // Wait for a storage event is fired on PageC and a while, + // to prevent race conditions between event processing + // triggered by `setItem()` and the following operations. + await pageC.execute_script(() => window.key1EventFired); + await new Promise(resolve => t.step_timeout(resolve, 1000)); + + // Back navigate to pageA, to be restored from BFCache. + await pageB.execute_script( + () => { + prepareNavigation(() => { history.back(); }); + } + ); + await pageA.execute_script(waitForPageShow); + await assert_bfcached(pageA); + + // Update `key2` after pageA is restored from BFCache. + localStorage.setItem(key2, 'value'); + + // Wait for a storage event for `key2` is fired on PageA. + await pageA.execute_script(() => window.key2EventFired); + + // Confirm that a storage event for `key1` is not fired on PageA. + assert_array_equals( + await pageA.execute_script(() => getRecordedEvents()), + [ + 'window.load', + 'window.pageshow', + 'window.pagehide.persisted', + 'window.pageshow.persisted', + 'storage2', + ], + 'pageA should not receive storage events for updates while in BFCache'); + +}, 'Storage events should not be fired for BFCached pages after becoming active'); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/timers.html b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/timers.html new file mode 100644 index 0000000000..aab650f36e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/back-forward-cache/timers.html @@ -0,0 +1,68 @@ +<!doctype html> +<meta name="timeout" content="long"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers:fully-active"> +<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/helper.sub.js"></script> +<script> +// Timers should be paused when the Document is not fully active. +// This test is checking this by measuring the actual elapsed time for a timer +// started before a page is stored into BFCache, staying for a while in BFCache, +// and fired after the page is restored from BFCache. + +const delayMain = 18000; +const delayBeforeForwardNavigation = 6000; +const delayBeforeBackNavigation = 5000; +// `delayBeforeForwardNavigation` and `delayBeforeBackNavigation` are set +// sufficiently large in order to distinguish the expected case from other +// scenarios listed in `funcAfterAssertion()`, and to allow some delays outside +// timers (e.g. due to communication between Windows). The additional delays +// can be large (e.g. ~4 seconds), so the delays above should be sufficiently +// large. + +const startTime = performance.now(); + +runBfcacheTest({ + funcBeforeNavigation: async (delayMain, delayBeforeForwardNavigation) => { + // Set `promiseMainTimer` that is resolved after a timeout of `delayMain` + // ms. + window.promiseMainTimer = new Promise(resolve => { + setTimeout(resolve, delayMain); + }); + // Then navigate to another page after `delayBeforeForwardNavigation` ms. + await new Promise(resolve => + setTimeout(resolve, delayBeforeForwardNavigation)); + }, + argsBeforeNavigation: [delayMain, delayBeforeForwardNavigation], + funcBeforeBackNavigation: async (delayBeforeBackNavigation) => { + // Back navigate after `delayBeforeBackNavigation` ms. + await new Promise(resolve => + setTimeout(resolve, delayBeforeBackNavigation)); + }, + argsBeforeBackNavigation: [delayBeforeBackNavigation], + funcAfterAssertion: async (pageA) => { + // Wait for `promiseMainTimer` resolution and check its timing. + await pageA.execute_script(() => window.promiseMainTimer); + const actualDelay = performance.now() - startTime; + + if (actualDelay >= delayMain + delayBeforeBackNavigation + + delayBeforeForwardNavigation) { + assert_unreached( + "The timer is fired too late. " + + "Maybe the timer is reset when restored from BFCache and " + + "waits from the beginning again"); + } else if (actualDelay >= delayMain + delayBeforeBackNavigation) { + // Expected: The timer is paused when the page is in BFCache. + } else if (actualDelay >= delayMain) { + assert_unreached( + "The timer is fired too early. " + + "Maybe the time isn't paused when the page is in BFCache"); + } else { + assert_unreached( + "The timer is fired too early, even earlier than delayMain."); + } + } +}, 'Timers should be paused when the page is in BFCache'); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/001-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/001-1.html new file mode 100644 index 0000000000..cadcf126f5 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/001-1.html @@ -0,0 +1,11 @@ +<!doctype html> +001-1 +<script> +addEventListener("pageshow", + function(e) { + parent.events.push(e); + if (parent.events.length == 2) { + parent.do_test(); + } + }, false); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/001-2.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/001-2.html new file mode 100644 index 0000000000..6387bc89c8 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/001-2.html @@ -0,0 +1,5 @@ +<!doctype html> +001-2 +<script> +onload = function() {setTimeout(function() {history.go(-1)}, 500)} +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/001.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/001.html new file mode 100644 index 0000000000..336ede4cb6 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/001.html @@ -0,0 +1,35 @@ +<!doctype html> +<title>pageshow event from traversal</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<iframe src="001-1.html"></iframe> +<script> +var t = async_test(); +var events = []; +var iframe = document.getElementsByTagName("iframe")[0]; + +onload = t.step_func(function() { + setTimeout(t.step_func( + function() { + assert_equals(iframe.contentDocument.readyState, "complete") + iframe.src = "001-2.html"; + }), 500); + onload = null; +}) + +do_test = t.step_func(function() { + assert_equals(events.length, 2); + events.forEach(function(e, i) { + phase = i ? "after" : "before"; + assert_equals(e.type, "pageshow", "type " + phase + " navigation"); + + // https://github.com/whatwg/html/issues/6794 + assert_equals(e.bubbles, true, "bubbles " + phase + " navigation"); + assert_equals(e.cancelable, true, "cancelable " + phase + " navigation"); + + assert_equals(e.persisted, i == 0 ? false : true, "persisted " + phase + " navigation"); + t.done(); + }); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/PopStateEvent.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/PopStateEvent.html new file mode 100644 index 0000000000..5774c150f4 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/PopStateEvent.html @@ -0,0 +1,38 @@ +<!doctype html> +<meta charset=utf-8> +<title>Synthetic popstate events</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +test(function() { + assert_throws_js( + TypeError, + () => PopStateEvent(''), + "Calling PopStateEvent constructor without 'new' must throw" + ); +}, "PopStateEvent constructor called as normal function"); + +test(function () { + assert_false('initPopStateEvent' in PopStateEvent.prototype, + 'There should be no PopStateEvent#initPopStateEvent'); +}, 'initPopStateEvent'); + +test(function () { + var popStateEvent = new PopStateEvent("popstate"); + assert_equals(popStateEvent.state, null, "the PopStateEvent.state"); +}, "Initial value of PopStateEvent.state must be null"); + +test(function () { + var state = history.state; + var data; + window.addEventListener('popstate', function (e) { + data = e.state; + }); + window.dispatchEvent(new PopStateEvent('popstate', { + 'state': {testdata:true} + })); + assert_true(data.testdata,'state data was corrupted'); + assert_equals(history.state, state, "history.state was NOT set by dispatching the event"); +}, 'Dispatching a synthetic PopStateEvent'); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/api-availability.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/api-availability.html new file mode 100644 index 0000000000..2f7d3fafdf --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/api-availability.html @@ -0,0 +1,22 @@ +<!doctype html> +<title>API availability following history traversal</title> +<meta charset=utf-8> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<p>Test requires popup blocker disabled</p> +<div id=log></div> +<script> +var t = async_test(); +var hasNavigated = false; +var child; +t.step(function() { + child = window.open("resources/api-availability-1.html"); + t.add_cleanup(function() { + child.close(); + }); +}); +navigate = t.step_func(function() { + hasNavigated = true; + child.location = child.location.href.replace("api-availability-1.html", "api-availability-2.html"); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name-0.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name-0.html new file mode 100644 index 0000000000..5cbab71a5e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name-0.html @@ -0,0 +1,35 @@ +<iframe id="test"></iframe> +<script> +var opener = window.opener; +var t = opener.t; +var f = document.getElementById("test"); +var l = opener.document.getElementById("step_log"); + +log = function(t) {l.textContent += ("\n" + t)} +var navigated = false; +var steps = [ + () => f.src = "browsing_context_name-1.html", + () => { + navigated = true; + opener.assert_equals(f.contentWindow.name, "test", "Initial load"); + f.src = "browsing_context_name-2.html" + }, + () => { + opener.assert_equals(f.contentWindow.name, "test1"); + opener.assert_equals(history.length, 2); + history.back() + }, + () => { + opener.assert_equals(f.contentWindow.name, "test1", "After navigation"); + t.done(); + } +].map((x, i) => t.step_func(() => {log("Step " + (i+1)); x()})); + +next = () => steps.shift()(); + +onload = () => { + log("page load"); + f.onload = () => {log("iframe onload"); next()}; + setTimeout(next, 0); +}; +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name-1.html new file mode 100644 index 0000000000..85748a2ebc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name-1.html @@ -0,0 +1,6 @@ +document 1 +<script> +if (!parent.navigated) { + window.name = "test"; +} +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name-2.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name-2.html new file mode 100644 index 0000000000..b0c869046b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name-2.html @@ -0,0 +1,4 @@ +document 2 +<script> +window.name = "test1"; +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name-3.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name-3.html new file mode 100644 index 0000000000..e0c239744f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name-3.html @@ -0,0 +1,6 @@ +document 3 +<script> +if (!parent.navigated) { + window.name = "test3"; +} +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name-4.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name-4.html new file mode 100644 index 0000000000..5d2dfa6bb8 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name-4.html @@ -0,0 +1,6 @@ +document 4 +<script> +if (!parent.navigated) { + window.name = "test4"; +} +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name.html new file mode 100644 index 0000000000..60a8acb098 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name.html @@ -0,0 +1,13 @@ +<!doctype html> +<title>Retaining window.name on history traversal</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<pre id="step_log"></pre> + +<script> +var t = async_test(); +t.step(() => { + win = window.open("browsing_context_name-0.html"); + t.add_cleanup(() => win.close()); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name_cross_origin-0.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name_cross_origin-0.html new file mode 100644 index 0000000000..9e91722714 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name_cross_origin-0.html @@ -0,0 +1,34 @@ +<iframe id="test"></iframe> +<script> +var t = opener.t; +var f = document.getElementById("test"); +var l = opener.document.getElementById("step_log"); + +var log = function(t) {l.textContent += ("\n" + t)} +var navigated = false; +var steps = [ + () => f.src = "browsing_context_name-1.html", + () => { + navigated = true; + opener.assert_equals(f.contentWindow.name, "test", "Initial load"); + f.src = f.src.replace("http://", "http://www.").replace("browsing_context_name-1", "browsing_context_name-2"); + }, + () => { + // Can't test .name easily here because it's cross-origin + opener.assert_equals(history.length, 2); + history.back() + }, + () => { + opener.assert_equals(f.contentWindow.name, "test", "After navigation"); + t.done(); + } +].map((x, i) => t.step_func(() => {log("Step " + (i+1)); x()})); + +next = () => steps.shift()(); + +onload = () => { + log("page load"); + f.onload = () => {log("iframe onload"); next()}; + setTimeout(next, 0); +}; +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name_cross_origin.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name_cross_origin.html new file mode 100644 index 0000000000..caa0bce3eb --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name_cross_origin.html @@ -0,0 +1,13 @@ +<!doctype html> +<title>Restoring window.name on cross-origin history traversal</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<pre id="step_log"></pre> + +<script> +var t = async_test(); +t.step(() => { + var win = window.open("browsing_context_name_cross_origin-0.html"); + t.add_cleanup(() => win.close()); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name_cross_origin_2.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name_cross_origin_2.html new file mode 100644 index 0000000000..8202a892a3 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name_cross_origin_2.html @@ -0,0 +1,43 @@ +<!doctype html> +<title>Restoring window.name on cross-origin history traversal</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<pre id="step_log"></pre> +<iframe id="test"></iframe> +<script> +var t = async_test(); +var f = document.getElementById("test"); +var l = document.getElementById("step_log"); + +log = function(t) {l.textContent += ("\n" + t)} + +var steps = [ + function() {f.src = "browsing_context_name-1.html"}, + function() { + assert_equals(f.contentWindow.name, "test", "Initial load"); + setTimeout(next, 0); + }, + function() {f.src = "browsing_context_name-3.html"}, + function() { + assert_equals(f.contentWindow.name, "test3", "After navigation 1"); + setTimeout(next, 0); + }, + function() {f.src = f.src.replace("http://", "http://www.").replace("browsing_context_name-3", "browsing_context_name-2");}, + function() { + setTimeout(next, 0); + }, + function() {history.go(-2); setTimeout(next, 500)}, + function() { + assert_equals(f.contentWindow.name, "test3", "After navigation 2"); + t.done(); + } +].map(function(x) {return t.step_func(function() {log("Step " + step + " " + f.contentWindow.location); x()})}); + +var step = 0; +next = t.step_func(function() {steps[step++]()}); + +f.onload=next; + +onload = function() { setTimeout(next, 0); }; +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name_cross_origin_3.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name_cross_origin_3.html new file mode 100644 index 0000000000..b6a35680dd --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/browsing_context_name_cross_origin_3.html @@ -0,0 +1,44 @@ +<!doctype html> +<title>Restoring window.name on cross-origin history traversal</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<pre id="step_log"></pre> +<iframe id="test"></iframe> +<script> +var t = async_test(); +var f = document.getElementById("test"); +var l = document.getElementById("step_log"); + +log = function(t) {l.textContent += ("\n" + t)} + +var steps = [ + function() {f.src = "browsing_context_name-1.html"}, + function() { + assert_equals(f.contentWindow.name, "test", "Initial load"); + setTimeout(next, 0); + }, + function() {f.src = "browsing_context_name-3.html"}, + function() { + assert_equals(f.contentWindow.name, "test3", "After navigation 1"); + setTimeout(next, 0); + }, + function() {f.src = f.src.replace("http://", "http://www.").replace("browsing_context_name-1", "browsing_context_name-2");}, + function() {f.src = f.src.replace("http://www.", "http://").replace("browsing_context_name-2", "browsing_context_name-4");}, + function() { + assert_equals(f.contentWindow.name, "test3", "After navigation 2"); + history.go(-3); setTimeout(next, 500) + }, + function() { + assert_equals(f.contentWindow.name, "test3", "After navigation 3"); + t.done(); + } +].map(function(x) {return t.step_func(function() {log("Step " + step + " " + f.contentWindow.location); x()})}); + +var step = 0; +next = t.step_func(function() {steps[step++]()}); + +f.onload=next; + +onload = function() { setTimeout(next, 0); }; +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/document-state.https.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/document-state.https.html new file mode 100644 index 0000000000..609ce9b850 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/document-state.https.html @@ -0,0 +1,135 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Test the properties of a session history entry's document state</title> +<link rel="help" href="https://html.spec.whatwg.org/#document-state"> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/common/utils.js"></script> +<script src="/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js"></script> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<body> +<script> +// In this test, we create an auxiliary window with a session history A -> B, +// where the document on site B is the current active document. Bf-cache is +// disabled via `Cache-Control: no-store` headers. We then `history.back()` to +// site A, and perform `location.replace(B)`. This makes the first document in +// the session history now same-origin/site with the URL in the subsequent +// session history entry's document state. +// +// We then perform `history.forward()` in the first document, which loads the +// second document (from network). We confirm that the resulting navigation +// request was made with the expected state, stored on the history entry's +// document state. The consequences of this are: +// - The navigation is made with the `Sec-Fetch-Site: cross-site` header, +// indicating that the *original* document state's initiator origin was +// preserved +// - The navigation is made with a cross-origin `Referer` header, indicating +// that the *original* document state's referrer was preserved +// - The resulting document has a cross-origin `document.referrer`, indicating +// the same as above +promise_test(async t => { + const rcHelper = new RemoteContextHelper(); + const A = await rcHelper.addWindow(); + + // Create B on a new origin (with bf-cache disabled). + const B = await A.navigateToNew({ + origin: 'HTTPS_NOTSAMESITE_ORIGIN', + headers: [['Cache-Control', 'no-store']], + }); + + // This is the origin we're going to navigate A to, so that it becomes + // same-origin with B. + const originB = new URL(await B.executeScript(() => location.href)).origin; + await B.historyBack(); + + // Make A navigate to the same document but in origin B: + const urlA = await A.executeScript(() => location.href); + const originA = new URL(urlA).origin; + assert_not_equals(originA, originB, 'Contexts A and B are cross-origin'); + + // Load A's current document but on origin B. + const newUrlOnOriginB = urlA.replace(originA, originB); + await A.navigate((url) => { + location.replace(url); + }, [newUrlOnOriginB]); + + // Assert that A and B are now same-origin: + const newUrlA = await A.executeScript(() => { + return location.href; + }); + + // Now the session history looks like: + // B -> B (initiator origin: A) + assert_equals(new URL(newUrlA).origin, originB); + + // This means that when we navigate forward, we should request the second + // document with the history entry's document state, which mostly preserves + // parameters from the original initiator (a cross-site document), despite a + // now-same-origin document initiating this navigation via history. + await A.historyForward(); + + const secFetchSite = await B.executeScript(() => window.requestHeaders['sec-fetch-site']); + const referrer = await B.executeScript(() => window.requestHeaders['referer']); + const documentReferrer = await B.executeScript(() => document.referrer); + + assert_equals(secFetchSite, 'cross-site', + 'Same-origin forward history navigation to a document whose original ' + + 'initiator was cross-site, ends up with Sec-Fetch-Dest: cross-site ' + + 'header'); + assert_equals(referrer, originA + '/', + 'Same-origin forward history navigation to a document whose original ' + + 'initiator was cross-site ends up with the Referer header that is the ' + + 'original cross-site initiator'); + assert_equals(documentReferrer, originA + '/', + 'Same-origin forward history navigation to a document whose original ' + + 'initiator was cross-site ends up with document.referrer that is the ' + + 'original cross-site initiator'); +}, "A navigation's initiator origin and referrer are stored in the document " + + "state and used in the document repopulation case"); + +// This test is similar to the above, but instead of testing for the true +// history entry -> document state -> document repopulation case, we stay on [B] +// (the document who was navigated to from [A]) and run `location.reload()` to +// confirm that the initiator information from the [A] -> [B] navigation is used +// when reloading [B], not [B]'s own same-origin information. +promise_test(async t => { + const rcHelper = new RemoteContextHelper(); + const A = await rcHelper.addWindow(); + + const originA = new URL(await A.executeScript(() => location.href)).origin; + + // Create B on a new origin. + const B = await A.navigateToNew({ + origin: 'HTTPS_NOTSAMESITE_ORIGIN', + }); + + const originB = new URL(await B.executeScript(() => location.href)).origin; + assert_not_equals(originA, originB, 'Contexts A and B are cross-origin'); + + // Reload B. + await B.navigate(() => { + location.reload(); + }, []); + + const secFetchSite = await B.executeScript(() => window.requestHeaders['sec-fetch-site']); + const referrer = await B.executeScript(() => window.requestHeaders['referer']); + const documentReferrer = await B.executeScript(() => document.referrer); + + assert_equals(secFetchSite, 'cross-site', + 'Same-origin forward history navigation to a document whose original ' + + 'initiator was cross-site, ends up with Sec-Fetch-Dest: cross-site ' + + 'header'); + assert_equals(referrer, originA + '/', + 'Same-origin forward history navigation to a document whose original ' + + 'initiator was cross-site ends up with the Referer header that is the ' + + 'original cross-site initiator'); + assert_equals(documentReferrer, originA + '/', + 'Same-origin forward history navigation to a document whose original ' + + 'initiator was cross-site ends up with document.referrer that is the ' + + 'original cross-site initiator'); +}, "A navigation's initiator origin and referrer are stored in the document " + + "state and used on location.reload()"); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/after-load-hash-twice.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/after-load-hash-twice.html new file mode 100644 index 0000000000..75889ef517 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/after-load-hash-twice.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Popstate/hashchange/load event ordering</title> + +<script> +// Set these up super-early before we hit the network for the test harness, just in case. +window.eventOrder = []; +window.onhashchange = () => window.eventOrder.push("hashchange"); +window.onpopstate = () => window.eventOrder.push("popstate"); +window.onload = () => window.eventOrder.push("load"); +</script> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> +async_test(t => { + assert_array_equals(window.eventOrder, []); + + // 0 timeout is necessary because if we do location.hash assignment before load is finished firing it counts as a replacement. + window.addEventListener("load", () => t.step_timeout(() => { + assert_array_equals(window.eventOrder, ["load"]); + + window.addEventListener("hashchange", t.step_func(() => { + assert_array_equals(window.eventOrder, ["load", "popstate", "popstate", "hashchange"]); + + window.addEventListener("hashchange", t.step_func_done(() => { + assert_array_equals(window.eventOrder, ["load", "popstate", "popstate", "hashchange", "hashchange"]); + })); + }), { once: true }); + + location.hash = "#1"; + assert_array_equals(window.eventOrder, ["load", "popstate"]); + location.hash = "#2"; + assert_array_equals(window.eventOrder, ["load", "popstate", "popstate"]); + }, 0)); +}, "when changing hash, after the load event"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/after-load-hash.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/after-load-hash.html new file mode 100644 index 0000000000..f74d716d91 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/after-load-hash.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Popstate/hashchange/load event ordering</title> + +<script> +// Set these up super-early before we hit the network for the test harness, just in case. +window.eventOrder = []; +window.onhashchange = () => window.eventOrder.push("hashchange"); +window.onpopstate = () => window.eventOrder.push("popstate"); +window.onload = () => window.eventOrder.push("load"); +</script> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> +async_test(t => { + assert_array_equals(window.eventOrder, []); + + // 0 timeout is necessary because if we do location.hash assignment before load is finished firing it counts as a replacement. + window.addEventListener("load", () => t.step_timeout(() => { + assert_array_equals(window.eventOrder, ["load"]); + + window.addEventListener("hashchange", t.step_func_done(() => { + assert_array_equals(window.eventOrder, ["load", "popstate", "hashchange"]); + })); + + location.hash = "#1"; + assert_array_equals(window.eventOrder, ["load", "popstate"]); + }, 0)); +}, "when changing hash, after the load event"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/after-load-pushState.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/after-load-pushState.html new file mode 100644 index 0000000000..4f9f3dad47 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/after-load-pushState.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Popstate/hashchange/load event ordering</title> + +<script> +// Set these up super-early before we hit the network for the test harness, just in case. +window.eventOrder = []; +window.onhashchange = () => window.eventOrder.push("hashchange"); +window.onpopstate = () => window.eventOrder.push("popstate"); +window.onload = () => window.eventOrder.push("load"); +</script> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> +async_test(t => { + assert_array_equals(window.eventOrder, []); + + // 0 timeout is necessary because if we do pushState before load is finished firing it counts as a replacement. + window.addEventListener("load", () => t.step_timeout(() => { + assert_array_equals(window.eventOrder, ["load"]); + + t.step_timeout(t.step_func_done(() => { + assert_array_equals(window.eventOrder, ["load"]); + }), 100); + + history.pushState({ state: "new state" }, ""); + }, 0)); +}, "when pushing state, after the load event"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/after-load-replaceState.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/after-load-replaceState.html new file mode 100644 index 0000000000..28148ff7b2 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/after-load-replaceState.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Popstate/hashchange/load event ordering</title> + +<script> +// Set these up super-early before we hit the network for the test harness, just in case. +window.eventOrder = []; +window.onhashchange = () => window.eventOrder.push("hashchange"); +window.onpopstate = () => window.eventOrder.push("popstate"); +window.onload = () => window.eventOrder.push("load"); +</script> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> +async_test(t => { + assert_array_equals(window.eventOrder, []); + + window.addEventListener("load", t.step_func(() => { + assert_array_equals(window.eventOrder, ["load"]); + + t.step_timeout(t.step_func_done(() => { + assert_array_equals(window.eventOrder, ["load"]); + }), 100); + + history.replaceState({ state: "new state" }, ""); + })); +}, "when replacing state, after the load event"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/before-load-hash-twice.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/before-load-hash-twice.html new file mode 100644 index 0000000000..7c8df11843 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/before-load-hash-twice.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Popstate/hashchange/load event ordering</title> + +<script> +// Set these up super-early before we hit the network for the test harness, just in case. +window.eventOrder = []; +window.onhashchange = () => window.eventOrder.push("hashchange"); +window.onpopstate = () => window.eventOrder.push("popstate"); +window.onload = () => window.eventOrder.push("load"); +</script> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> +async_test(t => { + assert_array_equals(window.eventOrder, []); + + window.addEventListener("load", t.step_func_done(() => { + assert_array_equals(window.eventOrder, ["popstate", "popstate", "hashchange", "hashchange", "load"]); + })); + + location.hash = "#1"; + assert_array_equals(window.eventOrder, ["popstate"]); + location.hash = "#2"; + assert_array_equals(window.eventOrder, ["popstate", "popstate"]); +}, "when changing hash twice, before load"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/before-load-hash.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/before-load-hash.html new file mode 100644 index 0000000000..97c4636fad --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/before-load-hash.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Popstate/hashchange/load event ordering</title> + +<script> +// Set these up super-early before we hit the network for the test harness, just in case. +window.eventOrder = []; +window.onhashchange = () => window.eventOrder.push("hashchange"); +window.onpopstate = () => window.eventOrder.push("popstate"); +window.onload = () => window.eventOrder.push("load"); +</script> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> +async_test(t => { + assert_array_equals(window.eventOrder, []); + + window.addEventListener("load", t.step_func_done(() => { + assert_array_equals(window.eventOrder, ["popstate", "hashchange", "load"]); + })); + + location.hash = "#1"; + assert_array_equals(window.eventOrder, ["popstate"]); +}, "when changing hash, before load"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/before-load-pushState.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/before-load-pushState.html new file mode 100644 index 0000000000..a08afa474f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/before-load-pushState.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Popstate/hashchange/load event ordering</title> + +<script> +// Set these up super-early before we hit the network for the test harness, just in case. +window.eventOrder = []; +window.onhashchange = () => window.eventOrder.push("hashchange"); +window.onpopstate = () => window.eventOrder.push("popstate"); +window.onload = () => window.eventOrder.push("load"); +</script> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> +async_test(t => { + assert_array_equals(window.eventOrder, []); + + window.addEventListener("load", t.step_func(() => { + t.step_timeout(t.step_func_done(() => { + assert_array_equals(window.eventOrder, ["load"]); + }), 100); + })); + + history.pushState({ state: "new state" }, ""); +}, "when pushing state, before load"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/before-load-replaceState.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/before-load-replaceState.html new file mode 100644 index 0000000000..10d30038fb --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/before-load-replaceState.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Popstate/hashchange/load event ordering</title> + +<script> +// Set these up super-early before we hit the network for the test harness, just in case. +window.eventOrder = []; +window.onhashchange = () => window.eventOrder.push("hashchange"); +window.onpopstate = () => window.eventOrder.push("popstate"); +window.onload = () => window.eventOrder.push("load"); +</script> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> +async_test(t => { + assert_array_equals(window.eventOrder, []); + + t.step_timeout(t.step_func_done(() => { + assert_array_equals(window.eventOrder, ["load"]); + }), 100); + + history.replaceState({ state: "new state" }, ""); +}, "when replacing state, before load"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/pushState-inside-popstate.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/pushState-inside-popstate.html new file mode 100644 index 0000000000..35ada116ed --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/pushState-inside-popstate.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +test(t => { + let popstate_called = false; + window.onpopstate = t.step_func(e => { + popstate_called = true; + history.pushState(2, null, "#2"); + assert_not_equals(history.state, e.state); + }); + location.hash = "#1"; + assert_true(popstate_called); +}, "pushState inside popstate") +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/same-document-traverse-immediate.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/same-document-traverse-immediate.html new file mode 100644 index 0000000000..51ea20b289 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/same-document-traverse-immediate.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Popstate/hashchange/load event ordering</title> + +<script> +// Set these up super-early before we hit the network for the test harness, just in case. +window.eventOrder = []; +window.onhashchange = () => window.eventOrder.push("hashchange"); +window.onpopstate = () => window.eventOrder.push("popstate"); +window.onload = () => window.eventOrder.push("load"); +</script> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> +async_test(t => { + assert_array_equals(window.eventOrder, []); + + // 0 timeout is necessary because if we do location.hash assignment before load is finished firing it counts as a replacement. + window.addEventListener("load", () => t.step_timeout(() => { + assert_array_equals(window.eventOrder, ["load"]); + + window.addEventListener("hashchange", t.step_func(() => { + assert_array_equals(window.eventOrder, ["load", "popstate", "hashchange"]); + + window.addEventListener("hashchange", t.step_func_done(() => { + assert_array_equals(window.eventOrder, ["load", "popstate", "hashchange", "popstate", "hashchange"]); + })); + }), { once: true }); + + location.hash = "#1"; + assert_array_equals(window.eventOrder, ["load", "popstate"]); + history.back(); + assert_array_equals(window.eventOrder, ["load", "popstate"]); + }, 0)); +}, "when traversing back, before hashchange"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/same-document-traverse-wait.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/same-document-traverse-wait.html new file mode 100644 index 0000000000..39bc760ff7 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/event-order/same-document-traverse-wait.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Popstate/hashchange/load event ordering</title> + +<script> +// Set these up super-early before we hit the network for the test harness, just in case. +window.eventOrder = []; +window.onhashchange = () => window.eventOrder.push("hashchange"); +window.onpopstate = () => window.eventOrder.push("popstate"); +window.onload = () => window.eventOrder.push("load"); +</script> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> +async_test(t => { + assert_array_equals(window.eventOrder, []); + + // 0 timeout is necessary because if we do location.hash assignment before load is finished firing it counts as a replacement. + window.addEventListener("load", () => t.step_timeout(() => { + assert_array_equals(window.eventOrder, ["load"]); + + window.addEventListener("hashchange", t.step_func(() => { + assert_array_equals(window.eventOrder, ["load", "popstate", "hashchange"]); + + window.addEventListener("hashchange", t.step_func_done(() => { + assert_array_equals(window.eventOrder, ["load", "popstate", "hashchange", "popstate", "hashchange"]); + })); + + history.back(); + assert_array_equals(window.eventOrder, ["load", "popstate", "hashchange"]); + }), { once: true }); + + location.hash = "#1"; + assert_array_equals(window.eventOrder, ["load", "popstate"]); + }, 0)); +}, "when traversing back, after hashchange"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/events.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/events.html new file mode 100644 index 0000000000..d5ff83fac0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/events.html @@ -0,0 +1,151 @@ +<!doctype html> +<title> PageTransitionEffect Event </title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +test(function() { + var e = new PageTransitionEvent("pageshow", {persisted:false, cancelable:false, bubbles:false}); + assert_true(e instanceof PageTransitionEvent); + assert_equals(e.type, "pageshow"); + assert_false(e.bubbles, "bubbles"); + assert_false(e.cancelable, "cancelable"); + assert_false(e.persisted, "persisted"); +}, "Constructing pageshow event"); + +test(function() { + var e = new PageTransitionEvent("pagehide", {persisted:false, cancelable:false, bubbles:false}); + assert_true(e instanceof PageTransitionEvent); + assert_equals(e.type, "pagehide"); + assert_false(e.persisted, "persisted"); + assert_false(e.bubbles, "bubbles"); + assert_false(e.cancelable, "cancelable"); +}, "Constructing pagehide event"); + +test(function() { + var e = new PageTransitionEvent("pageshow", {persisted:true}); + assert_true(e instanceof PageTransitionEvent); + assert_equals(e.type, "pageshow"); + assert_true(e.persisted, "persisted"); + assert_false(e.bubbles, "bubbles"); + assert_false(e.cancelable, "cancelable"); +}, "Constructing pageshow event, persisted true"); + +test(function() { + var e = new PageTransitionEvent("pagehide", {persisted:true}); + assert_true(e instanceof PageTransitionEvent); + assert_equals(e.type, "pagehide"); + assert_true(e.persisted, "persisted"); + assert_false(e.bubbles, "bubbles"); + assert_false(e.cancelable, "cancelable"); +}, "Constructing pagehide event, persisted true"); + +test(function() { + var e = new PageTransitionEvent("pageshow", {}); + assert_true(e instanceof PageTransitionEvent); + assert_equals(e.type, "pageshow"); + assert_false(e.persisted, "persisted"); + assert_false(e.bubbles, "bubbles"); + assert_false(e.cancelable, "cancelable"); +}, "Constructing pageshow event, empty options"); + +test(function() { + var e = new PageTransitionEvent("pagehide", {}); + assert_true(e instanceof PageTransitionEvent); + assert_equals(e.type, "pagehide"); + assert_false(e.persisted, "persisted"); + assert_false(e.bubbles, "bubbles"); + assert_false(e.cancelable, "cancelable"); +}, "Constructing pagehide event, empty options"); + +test(function() { + var e = new PageTransitionEvent("pageshow"); + assert_true(e instanceof PageTransitionEvent); + assert_equals(e.type, "pageshow"); + assert_false(e.persisted, "persisted"); + assert_false(e.bubbles, "bubbles"); + assert_false(e.cancelable, "cancelable"); +}, "Constructing pageshow event, missing options"); + +test(function() { + var e = new PageTransitionEvent("pagehide"); + assert_true(e instanceof PageTransitionEvent); + assert_equals(e.type, "pagehide"); + assert_false(e.persisted, "persisted"); + assert_false(e.bubbles, "bubbles"); + assert_false(e.cancelable, "cancelable"); +}, "Constructing pagehide event, missing options"); + +test(function() { + var e = new PageTransitionEvent("pageshow", {persisted:null}); + assert_true(e instanceof PageTransitionEvent); + assert_equals(e.type, "pageshow"); + assert_false(e.persisted, "persisted"); + assert_false(e.bubbles, "bubbles"); + assert_false(e.cancelable, "cancelable"); +}, "Constructing pageshow event, persisted:null"); + +test(function() { + var e = new PageTransitionEvent("pagehide", {persisted:null}); + assert_true(e instanceof PageTransitionEvent); + assert_equals(e.type, "pagehide"); + assert_false(e.persisted, "persisted"); + assert_false(e.bubbles, "bubbles"); + assert_false(e.cancelable, "cancelable"); +}, "Constructing pagehide event, persisted:null"); + +test(function() { + var e = new PageTransitionEvent("pageshow", {persisted:undefined}); + assert_true(e instanceof PageTransitionEvent); + assert_equals(e.type, "pageshow"); + assert_false(e.persisted, "persisted"); + assert_false(e.bubbles, "bubbles"); + assert_false(e.cancelable, "cancelable"); +}, "Constructing pageshow event, persisted:undefined"); + +test(function() { + var e = new PageTransitionEvent("pagehide", {persisted:undefined}); + assert_true(e instanceof PageTransitionEvent); + assert_equals(e.type, "pagehide"); + assert_false(e.persisted, "persisted"); + assert_false(e.bubbles, "bubbles"); + assert_false(e.cancelable, "cancelable"); +}, "Constructing pagehide event, persisted:undefined"); + +test(function() { + var e = new PageTransitionEvent("pageshow", {bubbles:true}); + assert_true(e instanceof PageTransitionEvent); + assert_equals(e.type, "pageshow"); + assert_false(e.persisted, "persisted"); + assert_true(e.bubbles, "bubbles"); + assert_false(e.cancelable, "cancelable"); +}, "Constructing pageshow event, bubbles:true"); + +test(function() { + var e = new PageTransitionEvent("pagehide", {bubbles:true}); + assert_true(e instanceof PageTransitionEvent); + assert_equals(e.type, "pagehide"); + assert_false(e.persisted, "persisted"); + assert_true(e.bubbles, "bubbles"); + assert_false(e.cancelable, "cancelable"); +}, "Constructing pagehide event, bubbles:true"); + +test(function() { + var e = new PageTransitionEvent("pageshow", {cancelable:true}); + assert_true(e instanceof PageTransitionEvent); + assert_equals(e.type, "pageshow"); + assert_false(e.persisted, "persisted"); + assert_false(e.bubbles, "bubbles"); + assert_true(e.cancelable, "cancelable"); +}, "Constructing pageshow event, cancelable:true"); + +test(function() { + var e = new PageTransitionEvent("pagehide", {cancelable:true}); + assert_true(e instanceof PageTransitionEvent); + assert_equals(e.type, "pagehide"); + assert_false(e.persisted, "persisted"); + assert_false(e.bubbles, "bubbles"); + assert_true(e.cancelable, "cancelable"); +}, "Constructing pagehide event, cancelable:true"); + +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/hashchange_event.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/hashchange_event.html new file mode 100644 index 0000000000..b7111255f8 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/hashchange_event.html @@ -0,0 +1,49 @@ +<!doctype html> +<title>Queue a task to fire hashchange event</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +t = async_test(); +window.onload = t.step_func(function () { + if (location.href.toString().indexOf("#") > -1) { + location.href = location.href.replace(/#.*$/,''); + return; + } + var root = location.href; + var oldURLs = []; + var newURLs = []; + + var timer = null; + + location.hash = 'foo'; + window.onhashchange = t.step_func(function (e) { + assert_true(e.isTrusted, "isTrusted"); + assert_equals(e.target, window, "target"); + assert_equals(e.type, "hashchange", "type"); + assert_true(e instanceof HashChangeEvent, "is HashChangeEvent"); + assert_false(e.bubbles, "bubbles"); + assert_false(e.cancelable, "cancelable"); + oldURLs.push(e.oldURL); + newURLs.push(e.newURL); + if (newURLs.length === 2) { + check_result(); + } else if (timer === null) { + timer = setTimeout(function() {check_result()}, 500); + } + }) + + check_result = t.step_func(function() { + clearTimeout(timer); + try { + assert_array_equals([root, root+"#foo"], oldURLs, "e.newURL"); + assert_array_equals([root+"#foo", root+"#bar"], newURLs, "e.newURL"); + t.done(); + } finally { + location.hash = ""; + } + }); + + location.hash = 'bar'; +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/history-traversal-navigate-parent-while-child-loading.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/history-traversal-navigate-parent-while-child-loading.html new file mode 100644 index 0000000000..2b70375a14 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/history-traversal-navigate-parent-while-child-loading.html @@ -0,0 +1,30 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i"></iframe> +<body> +<script> +async_test(t => { + let starting_history_length = history.length; + let iframe_url = (new URL("/common/blank.html", location.href)).href; + i.src = iframe_url; + + history.pushState("a", "", "#a"); + assert_equals(history.length, starting_history_length + 1, "First history length"); + + i.onload = t.step_func(() => { + assert_equals(history.length, starting_history_length + 1, "Second history length"); + assert_equals(i.contentWindow.location.href, iframe_url); + assert_equals(location.hash, "#a"); + history.back(); + // Wait a while for a back navigation. Since all of the possible outcomes + // are either same-document or navigating to about:blank, this doesn't need + // to wait terribly long. + t.step_timeout(t.step_func_done(() => { + assert_equals(location.hash, "", "top frame should have navigated back"); + assert_equals(i.contentWindow.location.href, iframe_url, "iframe should not have navigated"); + }), 100); + }); +}, "pushState() in parent while child is doing initial navigation, then go back"); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/history-traversal-navigates-multiple-frames.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/history-traversal-navigates-multiple-frames.html new file mode 100644 index 0000000000..4f2429fbfd --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/history-traversal-navigates-multiple-frames.html @@ -0,0 +1,29 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = () => t.step_timeout(t.step_func(() => { + let starting_history_length = history.length; + location.hash = "#a"; + assert_equals(starting_history_length + 1, history.length); + i.contentWindow.location.hash = "#b"; + assert_equals(starting_history_length + 2, history.length); + + let popstateCount = 0; + const popstateCalled = t.step_func(() => { + popstateCount++; + if (popstateCount < 2) + return; + assert_equals(location.hash, ""); + assert_equals(i.contentWindow.location.hash, ""); + t.done(); + }); + + window.onpopstate = popstateCalled; + i.contentWindow.onpopstate = popstateCalled; + history.go(-2); + }), 0); +}, "A history traversal should be able to navigate a parent and child simultaneously"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/resources/blank1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/resources/blank1.html new file mode 100644 index 0000000000..6b4df1ef2f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/resources/blank1.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<style> +body { + height: 2000px; + width: 2000px; +} +</style> +<body> Blank 1 </body>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/resources/blank2.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/resources/blank2.html new file mode 100644 index 0000000000..def2139667 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/resources/blank2.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<style> +body { + height: 2000px; + width: 2000px; +} +</style> +<body> Blank 2 </body> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/resources/page-with-fragment.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/resources/page-with-fragment.html new file mode 100644 index 0000000000..11737661d0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/resources/page-with-fragment.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<style> +body { + height: 2000px; + width: 2000px; +} +#fragment { + position: absolute; + top: 800px; + background-color: #faa; + display: block; + height: 100px; + width: 100px; +} + +</style> +<body> +Page with fragment + <a id="fragment" name="fragment" class='box'></a> +</body>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/resources/post_name_on_load.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/resources/post_name_on_load.html new file mode 100644 index 0000000000..1e9b10d1ee --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/resources/post_name_on_load.html @@ -0,0 +1,7 @@ +<!doctype html> +<script> +addEventListener('load', _ => { + let params = new URLSearchParams(window.location.search); + window.opener.postMessage(params.get('name'), '*'); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/resume-timer-on-history-back.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/resume-timer-on-history-back.html new file mode 100644 index 0000000000..77602b2d42 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/resume-timer-on-history-back.html @@ -0,0 +1,146 @@ +<!doctype html> +<title>Verify history.back() on a persisted page resumes timers</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script type="text/javascript"> + +function make_post_back_url(name) { + return new URL('resources/post_name_on_load.html?name=' + name, + window.location).href; +} + +function wait_for_message(name) { + return new Promise(resolve => { + addEventListener('message', function onMsg(evt) { + if (evt.data !== name) { + return; + } + removeEventListener('message', onMsg); + resolve(); + }); + }); +} + +function with_window_by_name(name) { + let win = window.open(make_post_back_url(name)); + return wait_for_message(name).then(_ => { + return win; + }); +} + +function with_nested_frame(win, url) { + return new Promise(resolve => { + let frame = win.document.createElement('iframe'); + frame.addEventListener('load', function onLoad(evt) { + removeEventListener('load', onLoad); + resolve(frame); + }); + frame.src = url; + win.document.body.appendChild(frame); + }); +} + +function delay(win, delay) { + return new Promise(resolve => { + win.setTimeout(_ => { + resolve(win); + }, delay); + }); +} + +function navigate_by_name(win, name) { + win.location = make_post_back_url(name); + return wait_for_message(name).then(_ => { + return win; + }); +} + +function go_back(win) { + return new Promise(resolve => { + win.onpagehide = e => resolve(win); + win.history.back(); + }); +} + +let DELAY = 500; + +promise_test(t => { + // Create a new window so we can navigate it later. + return with_window_by_name('foo').then(win => { + // Schedule a timer within the new window. Our intent is + // to navigate the window before the timer fires. + let delayFired = false; + let innerDelay = delay(win, DELAY); + innerDelay.then(_ => { + delayFired = true; + }); + + return navigate_by_name(win, 'bar').then(_ => { + // Since the window has navigated the timer should not + // fire. We set a timer on our current test window + // to verify the other timer is not received. + assert_false(delayFired); + return delay(window, DELAY * 2); + }).then(_ => { + // The navigated window's timer should not have fired. + assert_false(delayFired); + // Now go back to the document that set the timer. + return go_back(win); + }).then(_ => { + // We wait for one of two conditions here. For browsers + // with a bfcache the original suspended timer will fire. + // Alternatively, if the browser reloads the page the original + // message will be sent again. Wait for either of these + // two events. + return Promise.race([wait_for_message('foo'), innerDelay]); + }).then(_ => { + win.close(); + }); + }); +}, 'history.back() handles top level page timer correctly'); + +promise_test(t => { + let win; + // Create a new window so we can navigate it later. + return with_window_by_name('foo').then(w => { + win = w; + + // Create a nested frame so we check if navigation and history.back() + // properly handle child window state. + return with_nested_frame(win, 'about:blank'); + + }).then(frame => { + // Schedule a timer within the nested frame contained by the new window. + // Our intent is to navigate the window before the timer fires. + let delayFired = false; + let innerDelay = delay(frame.contentWindow, DELAY); + innerDelay.then(_ => { + delayFired = true; + }); + + return navigate_by_name(win, 'bar').then(_ => { + // Since the window has navigated the timer should not + // fire. We set a timer on our current test window + // to verify the other timer is not received. + assert_false(delayFired); + return delay(window, DELAY * 2); + }).then(_ => { + // The navigated window's timer should not have fired. + assert_false(delayFired); + // Now go back to the document containing the frame that set the timer. + return go_back(win); + }).then(_ => { + // We wait for one of two conditions here. For browsers + // with a bfcache the original suspended timer will fire. + // Alternatively, if the browser reloads the page the original + // message will be sent again. Wait for either of these + // two events. + return Promise.race([wait_for_message('foo'), innerDelay]); + }).then(_ => { + win.close(); + }); + }); +}, 'history.back() handles nested iframe timer correctly'); + +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-basic.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-basic.html new file mode 100644 index 0000000000..e47cd9c383 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-basic.html @@ -0,0 +1,34 @@ +<!doctype html> +<title>Verify existence and basic read/write function of history.scrollRestoration</title> + +<style> + body { + height: 2000px; + width: 2000px; + } +</style> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script type="text/javascript"> + 'use strict'; + + test(function() { + assert_equals(history.scrollRestoration, 'auto'); + }, 'Default value is "auto"'); + + test(function() { + history.scrollRestoration = 'manual'; + assert_equals(history.scrollRestoration, 'manual', 'should be able to set "manual"'); + history.scrollRestoration = 'auto'; + assert_equals(history.scrollRestoration, 'auto', 'should be able to set "auto"'); + }, 'It is writable'); + + test(function() { + history.scrollRestoration = 'auto'; + for (var v of [3.1415, {}, 'bogus']) { + history.scrollRestoration = v; + assert_equals(history.scrollRestoration, 'auto', `setting to invalid value (${v}) should be ignored`); + } + }, 'Invalid values are ignored'); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-fragment-scrolling-cross-origin.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-fragment-scrolling-cross-origin.html new file mode 100644 index 0000000000..fec801e94b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-fragment-scrolling-cross-origin.html @@ -0,0 +1,71 @@ +<!DOCTYPE html> +<meta name=timeout content=long> +<title>Precedence of scroll restoration mode over fragment scrolling in cross-origin history traversal</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<style> + iframe { + height: 300px; + width: 300px; + } +</style> +<div id="log"></div> +<script> + 'use strict'; + + var next; + function frameOnload() { + if (next) { + next(); + } else { + // The test does the following navigation steps for iframe + // 1. load page-with-fragment.html#fragment + // 2. load blank1 + // 3. go back to page-with-fragment.html + async_test(function(t) { + var iframe = document.querySelector('iframe'); + var hostInfo = get_host_info(); + var basePath = location.pathname.substring(0, location.pathname.lastIndexOf('/')); + var localURL = hostInfo.HTTP_ORIGIN + basePath + '/resources/page-with-fragment.html#fragment'; + var remoteURL = hostInfo.HTTP_REMOTE_ORIGIN + basePath + "/resources/blank1.html" + + var steps = [ + function() { + assert_equals(iframe.contentWindow.location.href, localURL, 'should be on page-with-fragment page'); + // wait one animation frame to ensure layout is run and fragment scrolling is complete + iframe.contentWindow.requestAnimationFrame(function() { + assert_approx_equals(iframe.contentWindow.scrollY, 800, 5, 'should scroll to fragment'); + + iframe.contentWindow.history.scrollRestoration = 'manual'; + assert_equals(iframe.contentWindow.history.scrollRestoration, 'manual'); + setTimeout(next, 0); + }); + }, function() { + // navigate to a new page from a different origin + iframe.src = remoteURL; + }, function() { + // going back causes the iframe to traverse back + history.back(); + }, function() { + // coming back from history, scrollRestoration should be set to manual and respected + assert_equals(iframe.contentWindow.location.href, localURL, 'should be back on page-with-fragment page'); + iframe.contentWindow.requestAnimationFrame(t.step_func_done(function() { + assert_equals(iframe.contentWindow.history.scrollRestoration, 'manual', 'navigating back should retain scrollRestoration value'); + assert_equals(iframe.contentWindow.scrollX, 0, 'should not scroll to fragment'); + assert_equals(iframe.contentWindow.scrollY, 0, 'should not scroll to fragment'); + })); + } + ]; + + var stepCount = 0; + next = t.step_func(function() { + steps[stepCount++](); + }); + next(); + }, 'Manual scroll restoration should take precedent over scrolling to fragment in cross origin navigation'); + } + } +</script> +<iframe src="resources/page-with-fragment.html#fragment" onload="frameOnload()"></iframe> + diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-fragment-scrolling-samedoc.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-fragment-scrolling-samedoc.html new file mode 100644 index 0000000000..073e0f6e06 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-fragment-scrolling-samedoc.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<meta name="viewport" content="width=device-width, initial-scale=1.0"> +<style> + body { + height: 2000px; + width: 2000px; + } + + #fragment { + position: absolute; + top: 800px; + background-color: #faa; + display: block; + height: 100px; + width: 100px; + } +</style> + +<body> + <a id="fragment" name="fragment" class='box'></a> +</body> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script type="text/javascript"> + 'use strict'; + + async_test(function(t) { + history.scrollRestoration = 'manual'; + assert_equals(history.scrollRestoration, 'manual'); + + location.hash = '#fragment'; + assert_equals(window.scrollY, 800, 'new navigations should scroll to fragment'); + + // create a new entry and reset the scroll before verification + history.pushState(null, null, '#done'); + window.scrollTo(0, 0); + assert_equals(window.scrollY, 0, 'should reset scroll before verification'); + + setTimeout(function() { + // setup verification + window.addEventListener('hashchange', t.step_func(function() { + assert_equals(location.hash, '#fragment'); + assert_equals(history.scrollRestoration, 'manual'); + // navigating back should give precedent to history restoration which is 'manual' + assert_equals(window.scrollX, 0, 'should not scroll to fragment'); + assert_equals(window.scrollY, 0, 'should not scroll to fragment'); + t.done(); + })); + // kick off verification + window.history.back(); + }, 0); + + }, 'Manual scroll restoration should take precedent over scrolling to fragment in cross doc navigation'); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-navigation-cross-origin.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-navigation-cross-origin.html new file mode 100644 index 0000000000..87a337b2da --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-navigation-cross-origin.html @@ -0,0 +1,71 @@ +<!DOCTYPE html> +<meta name=timeout content=long> +<title>Correct behaviour of scroll restoration mode is cross origin history traversal</title> + +<style> + iframe { + height: 300px; + width: 300px; + } +</style> + +<body> + <iframe></iframe> +</body> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script type="text/javascript"> + 'use strict'; + + // The test does the following navigation steps for iframe + // 1. load blank1 + // 2. load blank2 + // 3. go back to blank1 + async_test(function(t) { + var iframe = document.querySelector('iframe'); + var baseURL = location.href.substring(0, location.href.lastIndexOf('/')); + + var steps = [ + function() { + iframe.src = 'resources/blank1.html'; + }, + function() { + assert_equals(iframe.contentWindow.location.href, baseURL + '/resources/blank1.html', 'should be on first blank page'); + iframe.contentWindow.history.scrollRestoration = 'manual'; + assert_equals(iframe.contentWindow.history.scrollRestoration, 'manual'); + iframe.contentWindow.scrollTo(500, 500); + assert_equals(iframe.contentWindow.scrollX, 500, 'scripted scrolling should take effect'); + assert_equals(iframe.contentWindow.scrollY, 500, 'scripted scrolling should take effect'); + setTimeout(next, 0); + }, + function() { + // navigate to new page + iframe.src = 'resources/blank2.html'; + }, + function() { + assert_equals(iframe.contentWindow.location.href, baseURL + '/resources/blank2.html', 'should be on second blank page'); + assert_equals(iframe.contentWindow.history.scrollRestoration, 'auto', 'new page loads should set scrollRestoration to "auto"'); + setTimeout(next, 0); + }, function() { + iframe.contentWindow.history.back(); + }, function() { + // coming back scrollRestoration should be restored to 'manual' and respected + assert_equals(iframe.contentWindow.location.href, baseURL + '/resources/blank1.html', 'should be back on first blank page'); + assert_equals(iframe.contentWindow.history.scrollRestoration, 'manual', 'navigating back should retain scrollRestoration value'); + assert_equals(iframe.contentWindow.scrollX, 0, 'horizontal scroll offset should not be restored'); + assert_equals(iframe.contentWindow.scrollY, 0, 'vertical scroll offset should not be restored'); + t.done(); + } + ]; + + var stepCount = 0; + var next = t.step_func(function() { + steps[stepCount++](); + }); + + iframe.onload = next; + next(); + }, 'Navigating to new page should reset to "auto" and navigating back should restore and respect scroll restoration mode'); + +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-navigation-samedoc.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-navigation-samedoc.html new file mode 100644 index 0000000000..46d40eedc6 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-navigation-samedoc.html @@ -0,0 +1,81 @@ +<!DOCTYPE html> +<title>Correct behaviour of scroll restoration mode in same document history traversals</title> + +<style> + body { + height: 10000px; + width: 10000px; + } +</style> + +<body></body> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script type="text/javascript"> + 'use strict'; + + async_test(function(t) { + history.scrollRestoration = 'auto'; + window.scrollTo(0, 0); + + // create history entries and then verify the impact of scrollRestoration + // when they are popped + var entries = { + /* For scroll restoration mode 'auto', the spec does not require scroll + position to be restored at any particular value. */ + '#1': {type: 'push', expectedScroll: null, scrollRestoration: 'auto'}, + '#2': {type: 'replace', expectedScroll: null, scrollRestoration: 'auto'}, + /* For scroll restoration mode 'manual', the spec requires scroll position + not to be restored. So we expect [555,555] which is the latest position + before navigation. */ + '#3': {type: 'push', expectedScroll: [555, 555], scrollRestoration: 'manual'}, + '#4': {type: 'replace', expectedScroll: [555, 555], scrollRestoration: 'manual'} + }; + + // setup entries + for (var key in entries) { + var entry = entries[key], + beforeValue = history.scrollRestoration, + newValue = entry.scrollRestoration; + + var args = [{key: key}, '', key]; + if (entry.type == 'push') { + history.pushState.apply(history, args); + } else { + history.pushState(null, '', key); + history.replaceState.apply(history, args); + } + assert_equals(history.scrollRestoration, beforeValue, `history.scrollRestoration value is retained after pushing new state`); + history.scrollRestoration = newValue; + assert_equals(history.scrollRestoration, newValue, `Setting scrollRestoration to ${newValue} works as expected`); + window.scrollBy(50, 100); + } + + // setup verification + window.addEventListener('hashchange', t.step_func(function() { + var key = location.hash, + entry = entries[key]; + + if (key === '') { + t.done(); + return; + } + assert_equals(history.state.key, key, `state should have key: ${key}`); + assert_equals(history.scrollRestoration, entry.scrollRestoration, 'scrollRestoration is updated correctly'); + if (entry.expectedScroll) { + assert_equals(window.scrollX, entry.expectedScroll[0], `scrollX is correct for ${key}`); + assert_equals(window.scrollY, entry.expectedScroll[1], `scrollY is correct for ${key}`); + } + + window.history.back(); + })); + + // reset the scroll and kick off the verification + setTimeout(function() { + history.pushState(null, null, '#done'); + window.scrollTo(555, 555); + window.history.back(); + }, 0); + + }, 'history.{push,replace}State retain scroll restoration mode and navigation in the same document respects it'); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/popstate_event.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/popstate_event.html new file mode 100644 index 0000000000..a41fabf968 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/popstate_event.html @@ -0,0 +1,47 @@ +<!doctype html> +<title>Queue a task to fire popstate event</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +t = async_test(); +window.onload = t.step_func(function () { + var states = []; + + var timer = null; + + history.pushState("a", "State a", "/a"); + history.pushState("b", "State b", "/b"); + + history.back(); + window.onpopstate = t.step_func(function (e) { + assert_true(e.isTrusted, "isTrusted"); + assert_equals(e.target, window, "target"); + assert_equals(e.type, "popstate", "type"); + assert_true(e instanceof PopStateEvent, "is PopStateEvent"); + assert_false(e.bubbles, "bubbles"); + assert_false(e.cancelable, "cancelable"); + + states.push(e.state); + + if (states.length === 2) { + check_result(); + } else if (timer === null) { + timer = setTimeout(function() {check_result()}, 500); + } + }) + + check_result = t.step_func(function() { + clearTimeout(timer); + try { + assert_array_equals(states, ["a", null]); + t.done(); + } finally { + location.hash = ""; + } + }); + + setTimeout(function() {history.back()}, 0); + +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/resources/a.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/resources/a.html new file mode 100644 index 0000000000..55b73e1153 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/resources/a.html @@ -0,0 +1 @@ +Welcome to A.
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/resources/api-availability-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/resources/api-availability-1.html new file mode 100644 index 0000000000..2c31168750 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/resources/api-availability-1.html @@ -0,0 +1,31 @@ +<!doctype html> +<title>API availability following history traversal - 1</title> +<script> +var controller = opener; +var t = controller.t; +var assert_not_equals = controller.assert_not_equals; + +t.step(function() { + // If this document is discarded as a result of navigation, then this script + // will be executed a second time. The semantics this test intends to verify + // cannot be observed under these conditions, the discarding is not itself a + // violation. Silently pass the test in that case. + if (controller.hasNavigated) { + t.done(); + return; + } + + t.step_timeout(function() { + assert_not_equals(window.history, null, 'history'); + assert_not_equals(window.localStorage, null, 'localStorage'); + assert_not_equals(window.location, null, 'location'); + assert_not_equals(window.navigator, null, 'navigator'); + assert_not_equals(window.opener, null, 'opener'); + assert_not_equals(window.sessionStorage, null, 'sessionStorage'); + + t.done(); + }, 1000); + + controller.navigate(); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/resources/api-availability-2.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/resources/api-availability-2.html new file mode 100644 index 0000000000..420e5092bc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/resources/api-availability-2.html @@ -0,0 +1,3 @@ +<!doctype html> +<title>API availability following history traversal - 2</title> +<body onload="history.back()"></body> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/resources/b.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/resources/b.html new file mode 100644 index 0000000000..8f2fc900dd --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/resources/b.html @@ -0,0 +1 @@ +Welcome to B.
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/resources/c.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/resources/c.html new file mode 100644 index 0000000000..db494e878e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/resources/c.html @@ -0,0 +1 @@ +Welcome to C.
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/resources/unset_context_name-1.sub.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/resources/unset_context_name-1.sub.html new file mode 100644 index 0000000000..97918a1f99 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/resources/unset_context_name-1.sub.html @@ -0,0 +1,45 @@ +<!doctype html> +<!-- test must be run in a top level browsing context --> +<title>window.name test helper</title> +<script> +const search = window.location.search.replace("?", ""); +const steps = search.split("|"); + +async function proceedTest() { + while (steps.length) { + const step = steps.shift(); + + if (step.startsWith("report=")) { + const id = step.split("=")[1]; + const stashURL = new URL("unset_context_name_stash.py", location); + stashURL.searchParams.set('id', id); + stashURL.searchParams.set('value', window.name); + + await fetch(stashURL, { method: "POST" }); + continue; + } + + if (step === "close") { + window.close(); + break; + } + + if (step === "navigate") { + const url = new URL(window.location); + url.host = "{{hosts[][www]}}:{{ports[http][0]}}"; + url.search = "?" + steps.join("|"); + window.location = url.href; + break; + } + + if (step.startsWith("set=")) { + window.name = step.split("=")[1]; + continue; + } + + throw new Error("Unsupported step!"); + } +} + +proceedTest(); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/resources/unset_context_name_stash.py b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/resources/unset_context_name_stash.py new file mode 100644 index 0000000000..411a4587bc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/resources/unset_context_name_stash.py @@ -0,0 +1,13 @@ +def main(request, response): + key = request.GET.first(b"id") + if request.method == "POST": + value = request.GET.first(b"value") + request.server.stash.take(key) + request.server.stash.put(key, value) + return b"OK" + else: + value = request.server.stash.take(key) + if value is not None: + return value + else: + return b"NONE" diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/same-url.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/same-url.html new file mode 100644 index 0000000000..bcca5ed90c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/same-url.html @@ -0,0 +1,50 @@ +<title>Test same-URL navigation and its effects on history</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<div id=log></div> +<iframe src=resources/a.html></iframe> +<script> +async_test((t) => { + let state = "begin" + self[0].frameElement.onload = t.step_func(() => { + if(state === "b first") { + assert_equals(history.length, 2) + + state = "c first" + navigateFrameAfterDelay(t, "resources/c.html") + } else if (state === "c first") { + assert_equals(history.length, 3) + + state = "a second" + history.back(2) + } else if (state === "a second") { + assert_equals(history.length, 3) + + state = "a third" + navigateFrameAfterDelay(t, "resources/a.html") + } else if (state === "a third") { + assert_equals(history.length, 3) + t.done() + } + }) + onload = t.step_func(() => { + assert_equals(state, "begin") + assert_equals(history.length, 1) + + state = "b first" + + navigateFrameAfterDelay(t, "resources/b.html") + }) +}) + +function navigateFrameAfterDelay(t, url) { + // Delay to avoid triggering the "replace" behavior which occurs if + // the page isn't yet completely loaded, which only occurs after the + // load event handlers have finished: + // https://html.spec.whatwg.org/#location-object-navigate + // https://html.spec.whatwg.org/#the-end:completely-finish-loading + t.step_timeout(() => { + self[0].location = url + }, 0) +} +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/scroll-restoration-order.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/scroll-restoration-order.html new file mode 100644 index 0000000000..8fe7d9f977 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/scroll-restoration-order.html @@ -0,0 +1,74 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>History restoration order test</title> +<meta name="assert" content="https://html.spec.whatwg.org/multipage/browsing-the-web.html#history-traversal"> +<meta name="assert" content="Traversing history should restore scroll position after dispatching popstate and before dispatching hashchange"> + +<style> + body { + height: 200vh; + width: 200vw; + } +</style> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + 'use strict'; + async_test(function(t) { + window.addEventListener('load', t.step_func(function() { + // Allow 1px epsilon for fractional scrolling. + assert_array_approx_equals(scrollPosition(), [0, 0], 1); + + history.pushState('#1', '', '#1'); + window.scrollTo(50, 100); + assert_array_approx_equals(scrollPosition(), [50, 100], 1); + + history.pushState('#2', '', '#2'); + window.scrollTo(100, 200); + assert_array_approx_equals(scrollPosition(), [100, 200], 1); + + setTimeout(t.step_func(function(){ + history.pushState(null, null, '#done'); + window.scrollTo(555, 555); + assert_array_approx_equals(scrollPosition(), [555, 555], 1); + // Kick off the verification. + window.history.back(); + }), 0); + })); + + window.addEventListener('popstate', t.step_func(function() { + // Verify that scroll position is *not* restored before popstate. + const key = location.hash; + const expected_scroll_position = expectedScrollPositionForKey(key); + assert_not_equals(scrollPosition()[0], expected_scroll_position[0], `scroll is restored before popstate for ${key}`); + assert_not_equals(scrollPosition()[1], expected_scroll_position[1], `scroll is restored before popstate for ${key}`); + + if (key == '') + t.done(); + else + setTimeout(t.step_func(function(){ window.history.back(); }), 0); + })); + + window.addEventListener('hashchange', t.step_func(function() { + // Verify that scroll position is restored before hashchange. + var key = location.hash; + const expected_scroll_position = expectedScrollPositionForKey(key); + assert_array_approx_equals(scrollPosition(), expected_scroll_position, 1, `scroll is restored before hashchange for ${key}`); + })); + + function scrollPosition() { + return [window.pageXOffset, window.pageYOffset]; + } + + function expectedScrollPositionForKey(key) { + switch (key) { + case '#2': return [100, 200]; + case '#1': return [50, 100]; + case '' : return [0, 0]; + default: assert_unreached(); + } + } + + }, 'Traversing history should restore scroll position after dispatching popstate and before dispatching hashchange'); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/srcdoc/consecutive-srcdoc.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/srcdoc/consecutive-srcdoc.html new file mode 100644 index 0000000000..9aab36b986 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/srcdoc/consecutive-srcdoc.html @@ -0,0 +1,85 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>changing srcdoc to a different srcdoc</title> +<link rel="help" href="https://github.com/whatwg/html/issues/6809#issuecomment-905677979"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../resources/helpers.js"></script> + +<script> +"use strict"; + +// Note: bfcache won't mess with any windows with openers, so it doesn't +// interfere with these tests. + +promise_test(async t => { + // Set up a window whose iframe contains [normal page, srcdoc] entries. + const w = await openWindow("../../resources/has-iframe.html", t); + const iframe = w.document.querySelector("iframe"); + + await waitToAvoidReplace(t); + iframe.srcdoc = srcdocThatPostsParentOpener("srcdoc1"); + await waitForMessage(iframe.contentWindow); + + assert_equals(w.history.length, 2); + + // Now navigate to a different srcdoc + iframe.srcdoc = srcdocThatPostsParentOpener("srcdoc2"); + + // Test that it's a replace. + await waitForMessage(iframe.contentWindow); + assert_equals(w.history.length, 2, + "history.length must not change since it was a replace"); + assert_equals( + iframe.contentDocument.querySelector("p").textContent, + "srcdoc2", + "Sanity check: the srcdoc document did indeed update" + ); +}, "changing srcdoc does a replace navigation since the URL is still " + + "about:srcdoc"); + +promise_test(async t => { + // Set up a window whose iframe contains [normal page, srcdoc] entries. + const w = await openWindow("../../resources/has-iframe.html", t); + const iframe = w.document.querySelector("iframe"); + + await waitToAvoidReplace(t); + iframe.srcdoc = srcdocThatPostsParentOpener("srcdoc1"); + await waitForMessage(iframe.contentWindow); + + assert_equals(w.history.length, 2); + + // Now navigate to about:srcdoc#yo + iframe.contentWindow.location.href = "about:srcdoc#yo"; + assert_equals(iframe.contentWindow.location.href, "about:srcdoc#yo"); + assert_equals(w.history.length, 3); + + // Now navigate to a different srcdoc + iframe.srcdoc = srcdocThatPostsParentOpener("srcdoc2"); + + // Test that it's a push back to about:srcdoc. + await waitForMessage(iframe.contentWindow); + assert_equals( + w.history.length, + 4, + "history.length must increase since it was a push" + ); + assert_equals(iframe.contentWindow.location.href, "about:srcdoc"); + assert_equals( + iframe.contentDocument.querySelector("p").textContent, + "srcdoc2", + "Sanity check: the srcdoc document did indeed update" + ); + + // Test that we can go back to about:srcdoc#yo. + w.history.back(); + await waitForMessage(iframe.contentWindow); + assert_equals(iframe.contentWindow.location.href, "about:srcdoc#yo"); + assert_equals( + iframe.contentDocument.querySelector("p").textContent, + "srcdoc1", + "srcdoc content must be restored from history" + ); +}, "changing srcdoc to about:srcdoc#yo then another srcdoc does two push " + + "navigations and we can navigate back"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/srcdoc/srcdoc-history-entries.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/srcdoc/srcdoc-history-entries.html new file mode 100644 index 0000000000..09f4094c5f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/srcdoc/srcdoc-history-entries.html @@ -0,0 +1,95 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>srcdoc history entries</title> +<link rel="help" href="https://github.com/whatwg/html/issues/6809"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../resources/helpers.js"></script> + +<script> +"use strict"; + +// Note: bfcache won't mess with any windows with openers, so it doesn't +// interfere with these tests. + +promise_test(async t => { + // Set up a window whose iframe contains + // [normal page, srcdoc, normal page, srcdoc] entries. + const w = await openWindow("/common/blank.html", t); + const iframe = await addIframe("/common/blank.html?iframe", w.document); + + assert_equals(w.history.length, 1); + + await waitToAvoidReplace(t); + iframe.srcdoc = srcdocThatPostsParentOpener("srcdoc1"); + assert_equals(w.history.length, 1, "srcdoc navigation must not be sync"); + + await waitForMessage(iframe.contentWindow); + assert_equals(w.history.length, 2); + + await waitToAvoidReplace(t); + const middleURL = (new URL( + "../../resources/post-top-opener-on-load.html", location.href)).href; + iframe.contentWindow.location.href = middleURL; + + await waitForMessage(iframe.contentWindow); + assert_equals(w.history.length, 3); + + await waitToAvoidReplace(t); + iframe.srcdoc = srcdocThatPostsParentOpener("srcdoc2"); + assert_equals(w.history.length, 3, "srcdoc navigation must not be sync"); + + await waitForMessage(iframe.contentWindow); + assert_equals(w.history.length, 4); + + // Now test traversal. + w.history.back(); + await waitForMessage(iframe.contentWindow); + assert_equals(iframe.contentWindow.location.href, middleURL); + + await waitToAvoidReplace(t); + + w.history.back(); + await waitForMessage(iframe.contentWindow); + assert_equals(iframe.contentWindow.location.href, "about:srcdoc"); + assert_equals( + iframe.contentDocument.querySelector("p").textContent, + "srcdoc1", + "srcdoc contents must be restored from history, not from the current " + + "value ('srcdoc2') of the content attribute" + ); +}, "srcdoc history entries: the iframe itself navigates"); + +promise_test(async t => { + // Set up a window whose iframe contains [normal page, srcdoc] entries. + const w = await openWindow("../../resources/has-iframe.html", t); + const iframe = w.document.querySelector("iframe"); + + assert_equals(w.history.length, 1); + + await waitToAvoidReplace(t); + iframe.srcdoc = srcdocThatPostsParentOpener("srcdoc1"); + assert_equals(w.history.length, 1, "srcdoc navigation must not be sync"); + + await waitForMessage(iframe.contentWindow); + assert_equals(w.history.length, 2); + + // Now navigate the window itself. + w.location.href = "../../resources/post-top-opener-on-load.html"; + await waitForMessage(w); + assert_equals(w.history.length, 3); + + // Now test traversal. + w.history.back(); + await waitForMessage(w); + const iframeAgain = w.document.querySelector("iframe"); + + assert_equals(iframeAgain.contentWindow.location.href, "about:srcdoc"); + assert_equals( + iframeAgain.contentDocument?.querySelector("p")?.textContent, + "srcdoc1", + "srcdoc contents must be restored from history, not from the current " + + "value of the (not-existing) content attribute" + ); +}, "srcdoc history entries: the container window navigates"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/support/window-name-after-cross-origin-main-frame-navigation-popup.sub.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/support/window-name-after-cross-origin-main-frame-navigation-popup.sub.html new file mode 100644 index 0000000000..e13d191658 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/support/window-name-after-cross-origin-main-frame-navigation-popup.sub.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html> +<head> +</head> +<body> + <script> + document.location = "window-name-navigation.sub.html?hostname={{domains[www1]}}&shouldhavename=false&sendmessage=true"; + </script> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/support/window-name-after-same-origin-main-frame-navigation-1.sub.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/support/window-name-after-same-origin-main-frame-navigation-1.sub.html new file mode 100644 index 0000000000..4b7824b488 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/support/window-name-after-same-origin-main-frame-navigation-1.sub.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<script> + window.location = "window-name-navigation.sub.html?hostname={{host}}&shouldhavename=true&sendmessage=true"; +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/support/window-name-navigation.sub.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/support/window-name-navigation.sub.html new file mode 100644 index 0000000000..285469a148 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/support/window-name-navigation.sub.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> + <script> + var url = new URL(window.location.href); + url.hostname = "{{GET[hostname]}}"; + url.pathname = "/html/browsers/browsing-the-web/history-traversal/support/window-name-test.sub.html"; + url.search = "shouldhavename={{GET[shouldhavename]}}&sendmessage={{GET[sendmessage]}}"; + window.name = "test"; + window.location = url.href; + </script> +</html> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/support/window-name-test.sub.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/support/window-name-test.sub.html new file mode 100644 index 0000000000..460db2e979 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/support/window-name-test.sub.html @@ -0,0 +1,23 @@ +<script> + function process_test_result(passed, test_name) { + if ({{GET[sendmessage]}}) { + if (window.opener) { + window.opener.postMessage(passed, "*"); + } else { + parent.postMessage(passed, "*"); + } + } else { + let host = window.opener || parent; + host.test(function(t) { + host.assert_equals(passed, true); + }, test_name); + host.done(); + } + } + + if ({{GET[shouldhavename]}}) { + process_test_result(window.name == "test", "Test that window name is present"); + } else { + process_test_result(window.name == "", "Test that window name is not present"); + } +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/unset_context_name.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/unset_context_name.html new file mode 100644 index 0000000000..285f6d7428 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/unset_context_name.html @@ -0,0 +1,32 @@ +<!doctype html> +<title>window.name is reset after navigating to a different origin</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script> + +async function pollResultAndCheck(t, id, expected) { + const stashURL = new URL("resources/unset_context_name_stash.py", location); + stashURL.searchParams.set('id', id); + + let res = "NONE"; + while (res == "NONE") { + await new Promise(resolve => { t.step_timeout(resolve, 100); }); + + const response = await fetch(stashURL); + res = await response.text(); + } + if (res !== expected) { + assert_unreached('Stash result does not equal expected result.') + } +} + +promise_test(async t => { + const id = token(); + + window.open(`resources/unset_context_name-1.sub.html?set=${id}|navigate|report=${id}|close`, "_blank", "noopener"); + await pollResultAndCheck(t, id, ""); +}, "Window.name is reset after navigating to a different origin"); + +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/window-name-after-cross-origin-aux-frame-navigation.sub.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/window-name-after-cross-origin-aux-frame-navigation.sub.html new file mode 100644 index 0000000000..6bc64c6373 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/window-name-after-cross-origin-aux-frame-navigation.sub.html @@ -0,0 +1,17 @@ +<!DOCTYPE HTML> +<html> +<head> + <!-- window.name should equal "test" after a cross-origin auxiliary frame navigation. --> + <script src='/resources/testharness.js'></script> + <script src='/resources/testharnessreport.js'></script> +</head> +<body> + <script> + t = async_test("Test that the window name is correct"); + window.addEventListener("message", t.step_func_done(function(e) { + assert_equals(e.data, true); + })); + window.open("support/window-name-navigation.sub.html?hostname={{domains[www1]}}&shouldhavename=true&sendmessage=true"); + </script> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/window-name-after-cross-origin-main-frame-navigation.sub.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/window-name-after-cross-origin-main-frame-navigation.sub.html new file mode 100644 index 0000000000..fb0bb1883f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/window-name-after-cross-origin-main-frame-navigation.sub.html @@ -0,0 +1,24 @@ +<!DOCTYPE HTML> +<html> +<head> + <script src='/resources/testharness.js'></script> + <script src='/resources/testharnessreport.js'></script> +</head> +<body> + <button id="button" onclick="popup();">open popup</button> + <script> + function popup() { + window.popupWin = window.open('support/window-name-after-cross-origin-main-frame-navigation-popup.sub.html', '_blank'); + } + async_test(t => { + t.add_cleanup(() => { + popupWin.close(); + }) + document.getElementById('button').click(); + onmessage = t.step_func(e => { + assert_true(e.data); + }); + }, 'window.name should equal "" after a cross-origin main frame navigation'); + </script> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/window-name-after-cross-origin-sub-frame-navigation.sub.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/window-name-after-cross-origin-sub-frame-navigation.sub.html new file mode 100644 index 0000000000..a309be6d80 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/window-name-after-cross-origin-sub-frame-navigation.sub.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML> +<html> +<head> + <!-- window.name should equal "test" after a cross-origin sub frame navigation. --> + <script src='/resources/testharness.js'></script> + <script src='/resources/testharnessreport.js'></script> +</head> +<body> + <script> + t = async_test("Test that the window name is correct"); + window.addEventListener("message", t.step_func_done(function(e) { + assert_equals(e.data, true); + })); + </script> + <iframe src="support/window-name-navigation.sub.html?hostname={{domains[www1]}}&shouldhavename=true&sendmessage=true"; + </iframe> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/window-name-after-same-origin-aux-frame-navigation.sub.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/window-name-after-same-origin-aux-frame-navigation.sub.html new file mode 100644 index 0000000000..8e0a95d8c0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/window-name-after-same-origin-aux-frame-navigation.sub.html @@ -0,0 +1,17 @@ +<!DOCTYPE HTML> +<html> +<head> + <!-- window.name should equal "test" after a same-origin auxiliary frame navigation. --> + <script src='/resources/testharness.js'></script> + <script src='/resources/testharnessreport.js'></script> +</head> +<body> + <script> + t = async_test("Test that the window name is correct"); + window.addEventListener("message", t.step_func_done(function(e) { + assert_equals(e.data, true); + })); + window.open("support/window-name-navigation.sub.html?hostname={{host}}&shouldhavename=true&sendmessage=true"); + </script> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/window-name-after-same-origin-main-frame-navigation.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/window-name-after-same-origin-main-frame-navigation.html new file mode 100644 index 0000000000..ef11d9971a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/window-name-after-same-origin-main-frame-navigation.html @@ -0,0 +1,12 @@ +<!DOCTYPE HTML> +<title>window.name after a same-origin main frame navigation</title> +<script src='/resources/testharness.js'></script> +<script src='/resources/testharnessreport.js'></script> +<body> + <script> + var win; + async_test(function(t) { + win = window.open("support/window-name-after-same-origin-main-frame-navigation-1.sub.html") + addEventListener("message", t.step_func_done(e => assert_true(e.data))); + }).add_cleanup(() => {if (win) {win.close()}}); + </script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/window-name-after-same-origin-sub-frame-navigation.sub.html b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/window-name-after-same-origin-sub-frame-navigation.sub.html new file mode 100644 index 0000000000..48a6e247b0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/window-name-after-same-origin-sub-frame-navigation.sub.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML> +<html> +<head> + <!-- window.name should equal "test" after a same-origin sub frame navigation. --> + <script src='/resources/testharness.js'></script> + <script src='/resources/testharnessreport.js'></script> +</head> +<body> + <script> + t = async_test("Test that the window name is correct"); + window.addEventListener("message", t.step_func_done(function(e) { + assert_equals(e.data, true); + })); + </script> + <iframe src="support/window-name-navigation.sub.html?hostname={{host}}&shouldhavename=true&sendmessage=true"; + </iframe> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/003-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/003-1.html new file mode 100644 index 0000000000..4d2229eb51 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/003-1.html @@ -0,0 +1,7 @@ +<!doctype html> +<script> +onload = function() { + parent.postMessage("003-1", "*"); + setTimeout(function() {location = "003-2.html";}, 100); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/003-2.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/003-2.html new file mode 100644 index 0000000000..827a069479 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/003-2.html @@ -0,0 +1,9 @@ +<!doctype html> +003-2 +<script> +onload = function() { + parent.postMessage("003-2", "*") + setTimeout(function() {history.go(-1)}) +} +onunload = function() {location = "003-3.html"} +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/003-3.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/003-3.html new file mode 100644 index 0000000000..8b26c896fd --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/003-3.html @@ -0,0 +1,4 @@ +<!doctype html> +<script> +parent.postMessage("003-3", "*"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/003.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/003.html new file mode 100644 index 0000000000..f437150960 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/003.html @@ -0,0 +1,22 @@ +<!doctype html> +<title>Navigation from unload whilst traversing history</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<iframe src="003-1.html"></iframe> +<script> +var t = async_test(); + +var pages = []; +var iframe = document.getElementsByTagName("iframe")[0]; + + +onmessage = t.step_func(function(e) { + pages.push(e.data); + if(pages.length == 3) { + assert_array_equals(pages, ["003-1", "003-2", "003-1"]); + t.done(); + iframe.parentNode.removeChild(iframe); + } +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/004-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/004-1.html new file mode 100644 index 0000000000..02f916fd9c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/004-1.html @@ -0,0 +1,7 @@ +<!doctype html> +<script> +onload = function() { + parent.postMessage("004-1", "*"); + setTimeout(function() {location = location.href.replace("http://", "http://www.").replace("004-1.html", "004-2.html");}, 100); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/004-2.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/004-2.html new file mode 100644 index 0000000000..f2ef83ee1a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/004-2.html @@ -0,0 +1,9 @@ +<!doctype html> +003-2 +<script> +onload = function() { + parent.postMessage("004-2", "*") + setTimeout(function() {history.go(-1)}) +} +onunload = function() {location = "004-3.html"} +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/004-3.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/004-3.html new file mode 100644 index 0000000000..c98711ae98 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/004-3.html @@ -0,0 +1,4 @@ +<!doctype html> +<script> +parent.postMessage("004-3", "*"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/004.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/004.html new file mode 100644 index 0000000000..dddde4918b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/004.html @@ -0,0 +1,22 @@ +<!doctype html> +<title>Navigation from unload whilst traversing cross-origin history</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<iframe src="004-1.html"></iframe> +<script> +var t = async_test(); + +var pages = []; +var iframe = document.getElementsByTagName("iframe")[0]; + + +onmessage = t.step_func(function(e) { + pages.push(e.data); + if(pages.length == 3) { + assert_array_equals(pages, ["004-1", "004-2", "004-1"]); + t.done(); + iframe.parentNode.removeChild(iframe); + } +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/005.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/005.html new file mode 100644 index 0000000000..a87e6e9b3d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/005.html @@ -0,0 +1,16 @@ +<!doctype html> +<title>Link with onclick navigation and href navigation </title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<iframe id="test" name="test"></iframe> +<a target="test" onclick="document.getElementById('test').contentWindow.location='resources/click.html'" href="resources/href.html">Test</a> +<script> +var t = async_test(); +t.step(function() {document.links[0].click()}); +onmessage = t.step_func( + function(e) { + assert_equals(e.data, "href"); + t.done(); + }); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/006.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/006.html new file mode 100644 index 0000000000..17b3123faa --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/006.html @@ -0,0 +1,17 @@ +<!doctype html> +<title>Link with onclick form submit and href navigation </title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<iframe name="test"></iframe> +<form target="test" action="resources/click.html"></form> +<a target="test" onclick="document.forms[0].submit()" href="resources/href.html">Test</a> +<script> +var t = async_test(); +t.step(function() {document.links[0].click()}); +onmessage = t.step_func( + function(e) { + assert_equals(e.data, "href"); + t.done(); + }); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/007.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/007.html new file mode 100644 index 0000000000..dbb23ee644 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/007.html @@ -0,0 +1,16 @@ +<!doctype html> +<title>Link with onclick javascript url and href navigation </title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<iframe id="test" name="test"></iframe> +<a target="test" onclick="document.getElementById('test').contentWindow.location = 'javascript:\'abc<script>parent.postMessage("click", "*")</script>\'';" href="resources/href.html">Test</a> +<script> +var t = async_test(); +t.step(function() {document.getElementsByTagName("a")[0].click()}); +onmessage = t.step_func( + function(e) { + assert_equals(e.data, "href"); + t.done(); + }); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/008.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/008.html new file mode 100644 index 0000000000..3201d0679d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/008.html @@ -0,0 +1,17 @@ +<!doctype html> +<title>Link with onclick form submit to javascript url and href navigation </title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<iframe id="test" name="test"></iframe> +<form target="test" action="javascript:'<script>parent.postMessage("click", "*")</script>'"></form> +<a target="test" onclick="document.forms[0].submit()" href="resources/href.html">Test</a> +<script> +var t = async_test(); +t.step(function() {document.getElementsByTagName("a")[0].click()}); +onmessage = t.step_func( + function(e) { + assert_equals(e.data, "href"); + t.done(); + }); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/009.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/009.html new file mode 100644 index 0000000000..d963bed2bf --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/009.html @@ -0,0 +1,22 @@ +<!doctype html> +<title>Link with onclick form submit to javascript url with document.write and href navigation </title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<iframe id="test" name="test"></iframe> +<form target="test" action="javascript:(function() {document.write('<script>parent.postMessage("write", "*")</script>'); return '<script>parent.postMessage("click", "*")</script>'})()"></form> +<a target="test" onclick="document.forms[0].submit()" href="resources/href.html">Test</a> +<script> +var t = async_test(); +var events = []; +t.step(function() { + document.getElementsByTagName("a")[0].click()}); +onmessage = t.step_func( + function(e) { + events.push(e.data); + if (events.length === 2) { + assert_array_equals(events, ["write", "href"]); + t.done(); + } + }); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/010.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/010.html new file mode 100644 index 0000000000..606ad82f48 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/010.html @@ -0,0 +1,17 @@ +<!doctype html> +<title>Link with onclick form submit to javascript url with delayed document.write and href navigation </title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<iframe id="test" name="test"></iframe> +<form target="test" action="javascript:(function() {var x = new XMLHttpRequest(); x.open('GET', 'resources/blank.html?pipe=trickle(d2)', false); x.send(); document.write('<script>parent.postMessage("write", "*")</script>'); return '<script>parent.postMessage("click", "*")</script>'})()"></form> +<a target="test" onclick="document.forms[0].submit()" href="resources/href.html">Test</a> +<script> +var t = async_test(); +onload = t.step_func(function() {document.getElementsByTagName("a")[0].click()}); +onmessage = t.step_func( + function(e) { + assert_equals(e.data, "href"); + t.done(); + }); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/011.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/011.html new file mode 100644 index 0000000000..4d54267968 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/011.html @@ -0,0 +1,21 @@ +<!doctype html> +<title>Link with onclick navigation to javascript url with document.write and href navigation </title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<iframe id="test" name="test"></iframe> +<a target="test" onclick="javascript:(function() {document.write('<script>parent.postMessage("write", "*")</script>'); return '<script>parent.postMessage("click", "*")</script>'})()" href="resources/href.html">Test</a> +<script> +var t = async_test(); +var events = []; +t.step(function() { + document.getElementsByTagName("a")[0].click()}); +onmessage = t.step_func( + function(e) { + events.push(e.data); + if (events.length === 2) { + assert_array_equals(events, ["write", "href"]); + t.done(); + } + }); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/012.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/012.html new file mode 100644 index 0000000000..3795975d70 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/012.html @@ -0,0 +1,20 @@ +<!doctype html> +<title>Link with onclick navigation to javascript url with delayed document.write and href navigation </title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<iframe id="test" name="test"></iframe> +<!-- XXX: What is this test trying to do? It's navigating the subframe, but + doing a write() to _this_ document, and the "javascript:" in there is + completely a red herring: it's a label, not a protocol. There is no + javascript url involved here, unlike what the title claims! --> +<a target="test" onclick="javascript:(function() {var x = new XMLHttpRequest(); x.open('GET', 'blank.html?pipe=trickle(d2)', false); x.send(); document.write('write<script>parent.postMessage("write", "*")</script>'); return '<script>parent.postMessage("click", "*")</script>'})()" href="href.html">Test</a> +<script> +var t = async_test(); +t.step(function() {document.getElementsByTagName("a")[0].click()}); +onmessage = t.step_func( + function(e) { + assert_equals(e.data, "href"); + t.done(); + }); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/013.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/013.html new file mode 100644 index 0000000000..36a4521843 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/013.html @@ -0,0 +1,20 @@ +<!doctype html> +<title>Link with onclick navigation to javascript url with delayed document.write and href navigation </title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<iframe id="test" name="test"></iframe> +<a target="test" href="javascript:parent.events.push('javascript');">Test</a> +<script> +var t = async_test(); +var events = []; +t.step(function() { + document.getElementsByTagName("a")[0].click(); + events.push('after script'); +}); +onload = t.step_func(function() { + // javascript: executions are async. + assert_array_equals(events, ['after script', 'javascript']); + t.done(); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/014.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/014.html new file mode 100644 index 0000000000..b27ca992bd --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/014.html @@ -0,0 +1,21 @@ +<!doctype html> +<title> Link with javascript onclick form submission script order </title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<iframe id="test" name="test"></iframe> +<form target="test" action="javascript:parent.events.push('submit');"></form> +<a target="test" onclick="document.forms[0].submit()">Test</a> +<script> +var t = async_test(); +var events = []; +t.step(function() { + document.getElementsByTagName("a")[0].click(); + events.push('after script'); +}); +onload = t.step_func(function() { + // javascript: executions are async. + assert_array_equals(events, ['after script', 'submit']); + t.done(); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/015.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/015.html new file mode 100644 index 0000000000..696aaec2c8 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/015.html @@ -0,0 +1,20 @@ +<!doctype html> +<title> Link with javascript onclick and href script order </title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<iframe id="test" name="test"></iframe> +<a target="test" onclick="parent.events.push('click');" href="javascript:parent.events.push('href')">Test</a> +<script> +var t = async_test(); +var events = []; +t.step(function() { + document.getElementsByTagName("a")[0].click(); + events.push('after script'); +}); +onload = t.step_func(function() { + // javascript: executions are async. + assert_array_equals(events, ['click', 'after script', 'href']); + t.done(); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/abort-document-load-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/abort-document-load-1.html new file mode 100644 index 0000000000..50a9a50fa0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/abort-document-load-1.html @@ -0,0 +1,32 @@ +<!doctype html> +<script> +parent.postMessage(document.readyState, "*"); +let f = document.createElement("iframe"); +f.onload = function() { + parent.postMessage("stop", "*"); + window.stop(); +}; +document.documentElement.appendChild(f); + +window.addEventListener("load", (event) => { + parent.postMessage("load", "*"); +}); +window.addEventListener("error", (event) => { + parent.postMessage("error", "*"); +}); +window.addEventListener("abort", (event) => { + parent.postMessage("abort", "*"); +}); +window.addEventListener("pageshow", (event) => { + parent.postMessage("pageshow", "*"); +}); +window.addEventListener("DOMContentLoaded", (event) => { + parent.postMessage("DOMContentLoaded", "*"); +}); +document.addEventListener("readystatechange", (event) => { + if (document.readyState === "complete") { + parent.postMessage("complete", "*"); + } +}); + +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/abort-document-load-2.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/abort-document-load-2.html new file mode 100644 index 0000000000..966b93c51a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/abort-document-load-2.html @@ -0,0 +1,32 @@ +<!doctype html> +<script> +parent.postMessage(document.readyState, "*"); + +window.addEventListener("load", (event) => { + parent.postMessage("load", "*"); +}); +window.addEventListener("error", (event) => { + parent.postMessage("error", "*"); +}); +window.addEventListener("abort", (event) => { + parent.postMessage("abort", "*"); +}); +window.addEventListener("pageshow", (event) => { + parent.postMessage("pageshow", "*"); +}); +window.addEventListener("DOMContentLoaded", (event) => { + parent.postMessage("DOMContentLoaded", "*"); +}); +document.addEventListener("readystatechange", (event) => { + if (document.readyState === "complete") { + parent.postMessage("complete", "*"); + } +}); + +window.setTimeout(function() { + parent.postMessage("stop", "*"); + window.stop(); +}, 100); + +</script> +<link rel="stylesheet" href="/common/slow.py"></link> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/abort-document-load.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/abort-document-load.html new file mode 100644 index 0000000000..4a4c3df4e7 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/abort-document-load.html @@ -0,0 +1,30 @@ +<!doctype html> +<title>Aborting a Document load</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://html.spec.whatwg.org/multipage/browsing-the-web.html#aborting-a-document-load"> +<div id="log"></div> +<script> +var events = []; +onmessage = function(e) { + events.push(e.data); +}; +async_test(test => { + test.step_timeout(() => { + const frame = document.querySelector('iframe'); + const child = frame.contentWindow; + assert_equals(child.document.readyState, 'complete', 'readyState is complete'); + assert_array_equals(events, ["loading", "DOMContentLoaded", "stop", "complete"], 'no load event was fired'); + events = []; + frame.src = "abort-document-load-2.html"; + + test.step_timeout(() => { + const child = frame.contentWindow; + assert_equals(child.document.readyState, 'complete', 'readyState is complete'); + assert_array_equals(events, ["loading", "DOMContentLoaded", "stop", "complete"], 'no load event was fired'); + test.done(); + }, 1000); + }, 1000); +}); +</script> +<iframe src="abort-document-load-1.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/about-srcdoc-navigation-blocked.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/about-srcdoc-navigation-blocked.window.js new file mode 100644 index 0000000000..659f7321c0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/about-srcdoc-navigation-blocked.window.js @@ -0,0 +1,45 @@ +// META: title=Navigation to about:srcdoc, not via srcdoc="", must be blocked +// META: script=../resources/helpers.js + +promise_test(async t => { + const iframe = await addSrcdocIframe(); + + iframe.contentWindow.location = "/common/blank.html"; + await waitForIframeLoad(iframe); + + iframe.contentWindow.location = "about:srcdoc"; + + // Fetching "about:srcdoc" should result in a network error, and navigating + // to a network error should produce an opaque-origin page. In particular, + // since the error page should end up being cross-origin to the parent + // frame, `contentDocument` should return `null`. + // + // If instead this results in a message because we re-loaded a srcdoc document + // from the contents of the srcdoc="" attribute, immediately fail. + await Promise.race([ + t.step_wait(() => iframe.contentDocument === null), + failOnMessage(iframe.contentWindow) + ]); +}, "Navigations to about:srcdoc via window.location must be blocked"); + +promise_test(async t => { + const iframe = await addSrcdocIframe(); + iframe.contentWindow.name = "test_frame"; + + iframe.contentWindow.location = "/common/blank.html"; + await waitForIframeLoad(iframe); + + window.open("about:srcdoc", "test_frame"); + + // Fetching "about:srcdoc" should result in a network error, and navigating + // to a network error should produce an opaque-origin page. In particular, + // since the error page should end up being cross-origin to the parent + // frame, `contentDocument` should return `null`. + // + // If instead this results in a message because we re-loaded a srcdoc document + // from the contents of the srcdoc="" attribute, immediately fail. + await Promise.race([ + t.step_wait(() => iframe.contentDocument === null), + failOnMessage(iframe.contentWindow) + ]); +}, "Navigations to about:srcdoc via window.open() must be blocked"); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/anchor-fragment-form-submit-longfragment.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/anchor-fragment-form-submit-longfragment.html new file mode 100644 index 0000000000..8b9802f589 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/anchor-fragment-form-submit-longfragment.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org"> +<link rel="help" href="https://html.spec.whatwg.org/C/#following-hyperlinks"> +<title>Anchor element with onclick form submission and href to fragment</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- When an anchor element has an onclick handler which submits a form, + the anchor's navigation should occur instead of the form's navigation. + However, if the anchor has an href which is just a fragment like "#", + then the form should be submitted. Many sites rely on this behavior. --> + +<iframe name="test"></iframe> +<form target="test" action="resources/form.html"></form> +<a id="anchor" target="test" onclick="document.forms[0].submit()" href="#fragment">Test</a> + +<script> +async_test(t => { + const anchor = document.getElementById('anchor'); + t.step(() => anchor.click()); + window.onmessage = t.step_func(event => { + if (typeof event.data === 'string' && event.data.includes('navigation')) { + assert_equals(event.data, 'form navigation'); + t.done(); + } + }); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/anchor-fragment-form-submit-withpath.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/anchor-fragment-form-submit-withpath.html new file mode 100644 index 0000000000..823174181e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/anchor-fragment-form-submit-withpath.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org"> +<link rel="help" href="https://html.spec.whatwg.org/C/#following-hyperlinks"> +<title>Anchor element with onclick form submission and href to fragment</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- When an anchor element has an onclick handler which submits a form, + the anchor's navigation should occur instead of the form's navigation. + However, if the anchor has an href which is just a fragment like "#", + then the form should be submitted. Many sites rely on this behavior. --> + +<iframe name="test"></iframe> +<form target="test" action="resources/form.html"></form> +<a id="anchor" target="test" onclick="document.forms[0].submit()">Test</a> + +<script> +async_test(t => { + const iframe = document.querySelector('iframe'); + iframe.onload = t.step_func(() => { + const anchor = document.getElementById('anchor'); + anchor.href = '/#'; + anchor.click(); + window.onmessage = t.step_func(event => { + if (typeof event.data === 'string' && event.data.includes('navigation')) { + assert_equals(event.data, 'form navigation'); + t.done(); + } + }); + }); + + iframe.src = '/'; +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/anchor-fragment-form-submit.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/anchor-fragment-form-submit.html new file mode 100644 index 0000000000..e0c0e6f82d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/anchor-fragment-form-submit.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org"> +<link rel="help" href="https://html.spec.whatwg.org/C/#following-hyperlinks"> +<title>Anchor element with onclick form submission and href to fragment</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- When an anchor element has an onclick handler which submits a form, + the anchor's navigation should occur instead of the form's navigation. + However, if the anchor has an href which is just a fragment like "#", + then the form should be submitted. Many sites rely on this behavior. --> + +<iframe name="test"></iframe> +<form target="test" action="resources/form.html"></form> +<a id="anchor" target="test" onclick="document.forms[0].submit()" href="#">Test</a> + +<script> +async_test(t => { + const anchor = document.getElementById('anchor'); + t.step(() => anchor.click()); + window.onmessage = t.step_func(event => { + if (typeof event.data === 'string' && event.data.includes('navigation')) { + assert_equals(event.data, 'form navigation'); + t.done(); + } + }); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/anchor-jsurl-form-submit.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/anchor-jsurl-form-submit.html new file mode 100644 index 0000000000..1bf5d3595a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/anchor-jsurl-form-submit.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org"> +<link rel="help" href="https://html.spec.whatwg.org/C/#following-hyperlinks"> +<title>Anchor element with onclick form submission and href to javascript: url</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- When an anchor element has an onclick handler which submits a form, + the anchor's navigation should occur instead of the form's navigation. + However, if the anchor has an href which returns undefined, then the form + should be submitted. --> + +<iframe name="test"></iframe> +<form target="test" action="resources/form.html"></form> +<a id="anchor" target="test" onclick="document.forms[0].submit()" href="javascript:void(0)">Test</a> + +<script> +async_test(t => { + const anchor = document.getElementById('anchor'); + t.step(() => anchor.click()); + window.onmessage = t.step_func(event => { + if (typeof event.data === 'string' && event.data.includes('navigation')) { + assert_equals(event.data, 'form navigation'); + t.done(); + } + }); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/child-navigates-parent-cross-origin.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/child-navigates-parent-cross-origin.window.js new file mode 100644 index 0000000000..c5bed0fd4c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/child-navigates-parent-cross-origin.window.js @@ -0,0 +1,90 @@ +// META: script=/common/get-host-info.sub.js +// META: script=resources/wait-for-messages.js + +function testNavigationFails(params) { + return async (t) => { + // Start waiting for messages before inserting the child frame, to avoid any + // race conditions. Note that this would be racy if we executed tests + // concurrently, thankfully `promise_test` executes sequentially. See also: + // https://github.com/web-platform-tests/rfcs/pull/75 + const messagesPromise = waitForMessages(1); + + // Execute the test in an iframe, so that the document executing the test + // is not navigated away mid-test in case of failure. + const child = document.createElement("iframe"); + document.body.appendChild(child); + t.add_cleanup(() => { document.body.removeChild(child); }); + + const url = new URL( + "resources/child-navigates-parent-cross-origin-inner.html", + window.location); + + // Load the grandchild iframe from a different origin. + url.host = get_host_info().REMOTE_HOST; + + for (const key in params || {}) { + url.searchParams.set(key, params[key]); + } + + const grandchild = child.contentDocument.createElement("iframe"); + grandchild.src = url; + child.contentDocument.body.appendChild(grandchild); + + const messages = await messagesPromise; + assert_array_equals(messages, ["error: SecurityError"]); + } +} + +promise_test( + testNavigationFails(), + "Child document attempts to navigate cross-origin parent via location"); + +promise_test( + testNavigationFails({ "property": "hash" }), + "Child document attempts to navigate cross-origin parent via "+ + "location.hash"); + +promise_test( + testNavigationFails({ "property": "host" }), + "Child document attempts to navigate cross-origin parent via "+ + "location.host"); + +promise_test( + testNavigationFails({ "property": "hostname" }), + "Child document attempts to navigate cross-origin parent via "+ + "location.hostname"); + +promise_test( + testNavigationFails({ "property": "href" }), + "Child document attempts to navigate cross-origin parent via "+ + "location.href"); + +promise_test( + testNavigationFails({ "property": "pathname" }), + "Child document attempts to navigate cross-origin parent via "+ + "location.pathname"); + +promise_test( + testNavigationFails({ "property": "protocol" }), + "Child document attempts to navigate cross-origin parent via "+ + "location.protocol"); + +promise_test( + testNavigationFails({ "property": "reload" }), + "Child document attempts to navigate cross-origin parent via "+ + "location.reload()"); + +promise_test( + testNavigationFails({ "property": "replace" }), + "Child document attempts to navigate cross-origin parent via "+ + "location.replace()"); + +promise_test( + testNavigationFails({ "property": "search" }), + "Child document attempts to navigate cross-origin parent via "+ + "location.search"); + +promise_test( + testNavigationFails({ "property": "xxxNonExistent" }), + "Child document attempts to navigate cross-origin parent via non-standard "+ + "location property"); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/child-navigates-parent-same-origin.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/child-navigates-parent-same-origin.window.js new file mode 100644 index 0000000000..a40c412029 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/child-navigates-parent-same-origin.window.js @@ -0,0 +1,24 @@ +// META: script=resources/wait-for-messages.js + +function testNavigation(url) { + return async (t) => { + // Start waiting for messages before inserting the child frame, to avoid any + // race conditions. + const messagesPromise = waitForMessages(3); + + const iframe = document.createElement("iframe"); + iframe.src = url; + document.body.appendChild(iframe); + + const messages = await messagesPromise; + assert_array_equals(messages, ["initial", "inner", "destination"]); + } +} + +promise_test( + testNavigation("resources/child-navigates-parent-location-initial.html"), + "Child document navigates same-origin parent via document.location"); + +promise_test( + testNavigation("resources/child-navigates-parent-submit-initial.html"), + "Child document navigates same-origin parent via form submission"); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/cross-origin-top-navigation-with-user-activation-in-parent.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/cross-origin-top-navigation-with-user-activation-in-parent.window.js new file mode 100644 index 0000000000..42b4b208ee --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/cross-origin-top-navigation-with-user-activation-in-parent.window.js @@ -0,0 +1,8 @@ +async_test(t => { + addEventListener('message', t.step_func_done(e => { + assert_equals(e.data, 'Denied'); + })); + const w = open("resources/page-with-top-navigating-iframe.html?parent_user_gesture=true"); + t.add_cleanup(() => {w.close()}); + +}, "Cross-origin top navigation is blocked without user activation, even if the parent has user activation"); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/cross-origin-top-navigation-without-user-activation.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/cross-origin-top-navigation-without-user-activation.window.js new file mode 100644 index 0000000000..57f0bce247 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/cross-origin-top-navigation-without-user-activation.window.js @@ -0,0 +1,8 @@ +async_test(t => { + addEventListener('message', t.step_func_done(e => { + assert_equals(e.data, 'Denied'); + })); + const w = open("resources/page-with-top-navigating-iframe.html"); + t.add_cleanup(() => {w.close()}); + +}, "Cross-origin top navigation is blocked without user activation"); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/empty-iframe-load-event.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/empty-iframe-load-event.html new file mode 100644 index 0000000000..b9108f9937 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/empty-iframe-load-event.html @@ -0,0 +1,39 @@ +<!doctype html> +<title>load event for empty iframe in relation to the event loop</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script> +setup({explicit_done:true}); +let ran = false; + +onload = function() { + let iframe = document.createElement("iframe"); + iframe.onload = function() { + test(function() { + assert_equals(ran, false, 'Expected onload to run first'); + }, "Check execution order on load handler"); + if (ran) { + done(); + } else { + ran = true; + } + }; + document.body.appendChild(iframe); + + // Nested timeout to accommodate Gecko, because the it seems + // the outer setTimeout takes its slot in the event queue right away + // but the load event task takes its slot only at the end of this script. + setTimeout(function() { + setTimeout(function() { + test(function() { + assert_equals(ran, true, 'Expected nested setTimeout to run second'); + }, "Check execution order from nested timeout"); + if (ran) { + done(); + } else { + ran = true; + } + }); + }); +}; +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/empty_fragment.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/empty_fragment.html new file mode 100644 index 0000000000..18a6f84c9f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/empty_fragment.html @@ -0,0 +1,20 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Navigating to the same URL with an empty fragment aborts the navigation</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe src="empty_fragment_iframe.html"></iframe> +<script> +// If the navigation were not aborted, we would expect multiple load events +// as the page continually reloads itself. +async_test(function(t) { + var count = 0; + var iframe = document.querySelector('iframe'); + iframe.onload = t.step_func(function() { + count++; + }); + window.child_succeeded = t.step_func_done(function() { + assert_equals(count, 1); + }); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/empty_fragment_iframe.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/empty_fragment_iframe.html new file mode 100644 index 0000000000..26b28a0d7d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/empty_fragment_iframe.html @@ -0,0 +1,11 @@ +<script> +var timeout; +onload = function() { + location.hash = ""; + timeout = setTimeout(function() { parent.child_succeeded() }, 2000); +}; + +onbeforeunload = function() { + clearTimeout(timeout); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/failure-check-sequence.https.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/failure-check-sequence.https.html new file mode 100644 index 0000000000..5d7aa2b9cb --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/failure-check-sequence.https.html @@ -0,0 +1,76 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>Sequence of the checks performed against a navigation response</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +</head> +<body> +<script> +'use strict'; +const collect = (win) => { + const report = new Promise((resolve) => { + if (!win.ReportingObserver) { + return; + } + + const observer = new win.ReportingObserver(resolve); + observer.observe(); + }).then((reports) => reports[0].type); + // Although CSP also makes use of ReportingObserver, monitoring this event + // allows the test to provide value to implementations that have not yet + // integrated CSP and Reporting (as of the time of this writing, Firefox and + // Safari). + const cspViolation = new Promise((resolve) => { + win.document.addEventListener('securitypolicyviolation', () => resolve('csp-violation')); + }); + const halfASecond = new Promise((resolve) => setTimeout(() => resolve(null), 500)); + + return Promise.race([report, cspViolation, halfASecond]); +}; + +const createWindow = (t, url) => { + const win = open(url); + t.add_cleanup(() => win.close()); + return new Promise((resolve) => win.onload = () => resolve(win)); +}; + +promise_test(async (t) => { + const win = await createWindow(t, '/common/blank.html?pipe=header(content-security-policy, frame-src none)'); + const iframe = win.document.createElement('iframe'); + iframe.src = '/common/blank.html?pipe=header(x-frame-options, deny)'; + win.document.body.appendChild(iframe); + + assert_equals(await collect(win), 'csp-violation'); +}, 'CSP check precedes X-Frame-Options check'); + +promise_test(async (t) => { + const win = await createWindow(t, '/common/blank.html?pipe=header(content-security-policy, frame-src none)|header(cross-origin-embedder-policy, require-corp)'); + const iframe = win.document.createElement('iframe'); + iframe.src = '/common/blank.html'; + win.document.body.appendChild(iframe); + + assert_equals(await collect(win), 'csp-violation'); +}, 'CSP check precedes COEP check - CSP header first'); + +promise_test(async (t) => { + const win = await createWindow(t, '/common/blank.html?pipe=header(cross-origin-embedder-policy, require-corp)|header(content-security-policy, frame-src none)'); + const iframe = win.document.createElement('iframe'); + iframe.src = '/common/blank.html'; + win.document.body.appendChild(iframe); + + assert_equals(await collect(win), 'csp-violation'); +}, 'CSP check precedes COEP check - COEP header first'); + +promise_test(async (t) => { + const win = await createWindow(t, '/common/blank.html?pipe=header(cross-origin-embedder-policy, require-corp)'); + const iframe = win.document.createElement('iframe'); + iframe.src = '/common/blank.html?pipe=header(x-frame-options, deny)'; + win.document.body.appendChild(iframe); + + assert_equals(await collect(win), 'coep'); +}, 'COEP check precedes X-Frame-Options check'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-nosrc.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-nosrc.html new file mode 100644 index 0000000000..b89d3bb54b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-nosrc.html @@ -0,0 +1,178 @@ +<!DOCTYPE html> +<meta charset="UTF-8"> +<title>Navigations on iframe without src attribute</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> +<body></body> +<script> +/* + When an iframe is created it will contain the initial empty document. If the + src and srcdoc attribute is not set, it will stay on the initial empty + document. + These tests verifies the behavior of navigations that happen on the initial + empty document in that situation. They should all be converted to do a + replacement. +*/ +"use strict"; +const url1 = "/common/blank.html?1"; +const url2 = "/common/blank.html?2"; + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src not set, which will stay on the initial empty + // document. + const iframe = insertIframe(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with no src must not change history.length"); + + // Navigate away from the initial empty document through iframe.src. This + // should do a replacement. + iframe.src = url1; + await waitForLoad(t, iframe, url1); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation on document loaded by iframe with no src"); + + // Navigate again using the same method, but this time it shouldn't do a + // replacement since it's no longer on the initial empty document. + iframe.src = url2; + await waitForLoad(t, iframe, url2); + assert_equals(history.length, startingHistoryLength + 1, + "history.length increases after normal navigation from non-initial empty document"); +}, "src"); + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src not set, which will stay on the initial empty + // document. + const iframe = insertIframe(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with no src must not change history.length"); + + // Navigate away from the initial empty document through setting + // location.href. This should do a replacement. + iframe.contentWindow.location.href = url1; + await waitForLoad(t, iframe, url1); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation on document loaded by iframe with no src"); + + // Navigate again using the same method, but this time it shouldn't do a + // replacement since it's no longer on the initial empty document. + iframe.contentWindow.location.href = url2; + await waitForLoad(t, iframe, url2); + assert_equals(history.length, startingHistoryLength + 1, + "history.length increases after normal navigation from non-initial empty document"); +}, "location.href"); + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src not set, which will stay on the initial empty + // document. + const iframe = insertIframe(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with no src must not change history.length"); + + // Navigate away from the initial empty document through location.assign(). + // This should do a replacement. + iframe.contentWindow.location.assign(url1); + await waitForLoad(t, iframe, url1); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation on document loaded by iframe with no src"); + + // Navigate again using the same method, but this time it shouldn't do a + // replacement since it's no longer on the initial empty document. + iframe.contentWindow.location.assign(url2); + await waitForLoad(t, iframe, url2); + assert_equals(history.length, startingHistoryLength + 1, + "history.length increases after normal navigation from non-initial empty document"); +}, "location.assign"); + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src not set, which will stay on the initial empty + // document. + const iframe = insertIframe(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with no src must not change history.length"); + + // Navigate away from the initial empty document through window.open(). + // This should do a replacement. + iframe.contentWindow.open(url1, "_self"); + await waitForLoad(t, iframe, url1); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation on document loaded by iframe with no src"); + + // Navigate again using the same method, but this time it shouldn't do a + // replacement since it's no longer on the initial empty document. + iframe.contentWindow.open(url2, "_self"); + await waitForLoad(t, iframe, url2); + assert_equals(history.length, startingHistoryLength + 1, + "history.length increases after normal navigation from non-initial empty document"); +}, "window.open"); + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src not set, which will stay on the initial empty + // document. + const iframe = insertIframe(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with no src must not change history.length"); + + // Navigate away from the initial empty document through clicking an <a> + // element. This should do a replacement. + const a1 = iframe.contentDocument.createElement("a"); + a1.href = url1; + iframe.contentDocument.body.appendChild(a1); + a1.click(); + await waitForLoad(t, iframe, url1); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation on document loaded by iframe with no src"); + + // Navigate again using the same method, but this time it shouldn't do a + // replacement since it's no longer on the initial empty document. + const a2 = iframe.contentDocument.createElement("a"); + a2.href = url2; + iframe.contentDocument.body.appendChild(a2); + a2.click(); + await waitForLoad(t, iframe, url2); + assert_equals(history.length, startingHistoryLength + 1, + "history.length increases after normal navigation from non-initial empty document"); +}, "link click"); + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src not set, which will stay on the initial empty + // document. + const iframe = insertIframe(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with no src must not change history.length"); + + // Navigate away from the initial empty document through form submission. + // This should do a replacement. + const form1 = iframe.contentDocument.createElement("form"); + form1.action = "/common/blank.html"; + iframe.contentDocument.body.appendChild(form1); + const input1 = iframe.contentDocument.createElement("input"); + input1.type = "hidden"; + input1.name = "1"; + form1.append(input1); + form1.submit(); + await waitForLoad(t, iframe, url1 + "="); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation on document loaded by iframe with no src"); + + // Navigate again using the same method, but this time it shouldn't do a + // replacement since it's no longer on the initial empty document. + const form2 = iframe.contentDocument.createElement("form"); + form2.action = "/common/blank.html"; + iframe.contentDocument.body.appendChild(form2); + const input2 = iframe.contentDocument.createElement("input"); + input2.type = "hidden"; + input2.name = "2"; + form2.append(input2); + form2.submit(); + await waitForLoad(t, iframe, url2 + "="); + assert_equals(history.length, startingHistoryLength + 1, + "history.length increases after normal navigation from non-initial empty document"); +}, "form submission"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-204-fragment.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-204-fragment.html new file mode 100644 index 0000000000..2a472f6a6d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-204-fragment.html @@ -0,0 +1,144 @@ +<!DOCTYPE html> +<meta charset="UTF-8"> +<title>Fragment navigations on iframe with src set to URL that doesn't load a document (HTTP 204)</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> +<body></body> +<script> +/* + When an iframe is created it will contain the initial empty document. If the + src is set to a URL that doesn't load a new document (e.g. it results in a + HTTP 204 response), it will stay on the initial empty document. After fragment + navigations happen on it, it should still stay on the initial empty document. + These tests verifies the behavior of navigations that happen on the initial + empty document in that situation. They should all be converted to do a + replacement. +*/ +"use strict"; +const url1 = "about:blank#1"; +const url2 = "/common/blank.html?2"; + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src set to URL that doesn't load a new document, so + // it will stay in the initial empty document. + const iframe = insertIframeWith204Src(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with src set to URL that doesn't load a new document must not change history.length"); + + // Do a fragment navigation within the initial empty document through iframe.src. + iframe.src = url1; + await new Promise(resolve => t.step_timeout(resolve, 100)); + assert_equals(iframe.contentWindow.location.href, url1); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after fragment navigation on initial empty document"); + + // Navigate away from the initial empty document through iframe.src. + iframe.src = url2; + await waitForLoad(t, iframe, url2); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation from initial empty document"); +}, "src"); + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src set to URL that doesn't load a new document, so + // it will stay in the initial empty document. + const iframe = insertIframeWith204Src(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with src set to URL that doesn't load a new document must not change history.length"); + + // Do a fragment navigation within the initial empty document through setting location.href. + // This should do a replacement. + iframe.contentWindow.location.href = url1; + await new Promise(resolve => t.step_timeout(resolve, 100)); + assert_equals(iframe.contentWindow.location.href, url1); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after fragment navigation on initial empty document"); + + // Navigate away from the initial empty document through setting location.href. + // This should do a replacement. + iframe.contentWindow.location.href = url2; + await waitForLoad(t, iframe, url2); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation from initial empty document"); +}, "location.href"); + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src set to URL that doesn't load a new document, so + // it will stay in the initial empty document. + const iframe = insertIframeWith204Src(t); + await new Promise(resolve => t.step_timeout(resolve, 100)); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with src set to URL that doesn't load a new document must not change history.length"); + + // Do a fragment navigation within the initial empty document through location.assign(). + // This should do a replacement. + iframe.contentWindow.location.assign(url1); + assert_equals(iframe.contentWindow.location.href, url1); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after fragment navigation on initial empty document"); + + // Navigate away from the initial empty document through location.assign(). + // This should do a replacement. + iframe.contentWindow.location.assign(url2); + await waitForLoad(t, iframe, url2); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation from initial empty document"); +}, "location.assign"); + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src set to URL that doesn't load a new document, so + // it will stay in the initial empty document. + const iframe = insertIframeWith204Src(t); + await new Promise(resolve => t.step_timeout(resolve, 100)); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with src set to URL that doesn't load a new document must not change history.length"); + + // Do a fragment navigation within the initial empty document through window.open(). + // This should do a replacement. + iframe.contentWindow.open(url1, "_self"); + assert_equals(iframe.contentWindow.location.href, url1); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after fragment navigation on initial empty document"); + + // Navigate away from the initial empty document through window.open(). + // This should do a replacement. + iframe.contentWindow.open(url2, "_self"); + await waitForLoad(t, iframe, url2); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation from initial empty document"); +}, "window.open"); + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src set to URL that doesn't load a new document, so it will stay in the initial empty document. + const iframe = insertIframeWith204Src(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with src set to URL that doesn't load a new document must not change history.length"); + + // Do a fragment navigation within the initial empty document through clicking an <a> element. + // This should do a replacement. + const a1 = iframe.contentDocument.createElement("a"); + a1.href = url1; + iframe.contentDocument.body.appendChild(a1); + a1.click(); + await new Promise(resolve => t.step_timeout(resolve, 100)); + assert_equals(iframe.contentWindow.location.href, url1); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after fragment navigation on initial empty document"); + + // Navigate away from the initial empty document through clicking an <a> element. + // This should do a replacement. + const a2 = iframe.contentDocument.createElement("a"); + a2.href = url2; + iframe.contentDocument.body.appendChild(a2); + a2.click(); + await waitForLoad(t, iframe, url2); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation from initial empty document"); +}, "link click"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-204-pushState-replaceState.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-204-pushState-replaceState.html new file mode 100644 index 0000000000..8a97fd36a4 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-204-pushState-replaceState.html @@ -0,0 +1,66 @@ +<!DOCTYPE html> +<meta charset="UTF-8"> +<title>pushState/replaceState on iframe with src set to URL that doesn't load a document (HTTP 204)</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> +<body></body> +<script> +/* + When an iframe is created it will contain the initial empty document. If the + src is set to a URL that doesn't load a new document (e.g. it results in a + HTTP 204 response), it will stay on the initial empty document. If + history.pushState() or history.replaceState() is called on it, it should + still stay on the initial empty document. + These tests verifies the behavior of navigations that happen on the initial + empty document in that situation. They should all be converted to do a + replacement. +*/ +"use strict"; +const crossDocumentURL = "/common/blank.html?2"; + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src set to URL that doesn't load a new document, so + // it will stay on the initial empty document. + const iframe = insertIframeWith204Src(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with src set to URL that doesn't load a new document must not change history.length"); + + // Do a history.pushState() to about:blank#foo. + let pushURL = "about:blank#foo"; + iframe.contentWindow.history.pushState({}, "title", pushURL); + assert_equals(iframe.contentWindow.location.href, pushURL); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after history.pushState() on the initial empty document"); + + // Navigate away from the initial empty document. This should do replacement. + iframe.src = crossDocumentURL; + await waitForLoad(t, iframe, crossDocumentURL); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation from initial empty document"); +}, "history.pushState"); + + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src set to URL that doesn't load a new document, so + // it will stay on the initial empty document. + const iframe = insertIframeWith204Src(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with src set to URL that doesn't load a new document must not change history.length"); + + // Do a history.replaceState() to about:blank#foo. + let replaceURL = "about:blank#foo"; + iframe.contentWindow.history.replaceState({}, "title", replaceURL); + assert_equals(iframe.contentWindow.location.href, replaceURL); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after history.replaceState() on the initial empty document"); + + // Navigate away from the initial empty document. This should do replacement. + iframe.src = crossDocumentURL; + await waitForLoad(t, iframe, crossDocumentURL); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation from initial empty document"); +}, "history.replaceState"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-204.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-204.html new file mode 100644 index 0000000000..f7bade68fb --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-204.html @@ -0,0 +1,178 @@ +<!DOCTYPE html> +<meta charset="UTF-8"> +<title>Navigations on iframe with src set to URL that doesn't load a new document</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> +<body></body> +<script> +/* + When an iframe is created it will contain the initial empty document. If the + src is set to a URL that doesn't load a new document (e.g. it results in a + HTTP 204 response), it will stay on the initial empty document. + These tests verify the behavior of navigations that happen on the initial + empty document in that situation. They should all be converted to do a + replacement. +*/ +"use strict"; +const url1 = "/common/blank.html?1"; +const url2 = "/common/blank.html?2"; + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src set to URL that doesn't load a new document, so + // it will stay on the initial empty document. + const iframe = insertIframeWith204Src(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with src set to URL that doesn't load a new document must not change history.length"); + + // Navigate away from the initial empty document through iframe.src. This + // should do a replacement. + iframe.src = url1; + await waitForLoad(t, iframe, url1); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation on document loaded by iframe with no src"); + + // Navigate again using the same method, but this time it shouldn't do a + // replacement since it's no longer on the initial empty document. + iframe.src = url2; + await waitForLoad(t, iframe, url2); + assert_equals(history.length, startingHistoryLength + 1, + "history.length increases after normal navigation from non-initial empty document"); +}, "Navigating to a different document with src"); + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src set to URL that doesn't load a new document, so + // it will stay on the initial empty document. + const iframe = insertIframeWith204Src(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with src set to URL that doesn't load a new document must not change history.length"); + + // Navigate away from the initial empty document through setting + // location.href. This should do a replacement. + iframe.contentWindow.location.href = url1; + await waitForLoad(t, iframe, url1); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation on document loaded by iframe with no src"); + + // Navigate again using the same method, but this time it shouldn't do a + // replacement since it's no longer on the initial empty document. + iframe.contentWindow.location.href = url2; + await waitForLoad(t, iframe, url2); + assert_equals(history.length, startingHistoryLength + 1, + "history.length increases after normal navigation from non-initial empty document"); +}, "Navigating to a different document with location.href"); + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src set to URL that doesn't load a new document, so + // it will stay on the initial empty document. + const iframe = insertIframeWith204Src(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with src set to URL that doesn't load a new document must not change history.length"); + + // Navigate away from the initial empty document through location.assign(). + // This should do a replacement. + iframe.contentWindow.location.assign(url1); + await waitForLoad(t, iframe, url1); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation on document loaded by iframe with no src"); + + // Navigate again using the same method, but this time it shouldn't do a + // replacement since it's no longer on the initial empty document. + iframe.contentWindow.location.assign(url2); + await waitForLoad(t, iframe, url2); + assert_equals(history.length, startingHistoryLength + 1, + "history.length increases after normal navigation from non-initial empty document"); +}, "Navigating to a different document with location.assign"); + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src set to URL that doesn't load a new document, so + // it will stay on the initial empty document. + const iframe = insertIframeWith204Src(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with src set to URL that doesn't load a new document must not change history.length"); + + // Navigate away from the initial empty document through window.open(). + // This should do a replacement. + iframe.contentWindow.open(url1, "_self"); + await waitForLoad(t, iframe, url1); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation on document loaded by iframe with no src"); + + // Navigate again using the same method, but this time it shouldn't do a + // replacement since it's no longer on the initial empty document. + iframe.contentWindow.open(url2, "_self"); + await waitForLoad(t, iframe, url2); + assert_equals(history.length, startingHistoryLength + 1, + "history.length increases after normal navigation from non-initial empty document"); +}, "Navigating to a different document with window.open"); + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src set to URL that doesn't load a new document, so + // it will stay on the initial empty document. + const iframe = insertIframeWith204Src(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with src set to URL that doesn't load a new document must not change history.length"); + + // Navigate away from the initial empty document through clicking an <a> + // element. This should do a replacement. + const a1 = iframe.contentDocument.createElement("a"); + a1.href = url1; + iframe.contentDocument.body.appendChild(a1); + a1.click(); + await waitForLoad(t, iframe, url1); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation on document loaded by iframe with no src"); + + // Navigate again using the same method, but this time it shouldn't do a + // replacement since it's no longer on the initial empty document. + const a2 = iframe.contentDocument.createElement("a"); + a2.href = url2; + iframe.contentDocument.body.appendChild(a2); + a2.click(); + await waitForLoad(t, iframe, url2); + assert_equals(history.length, startingHistoryLength + 1, + "history.length increases after normal navigation from non-initial empty document"); +}, "Navigating to a different document with link click"); + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src set to URL that doesn't load a new document, so + // it will stay on the initial empty document. + const iframe = insertIframeWith204Src(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with src set to URL that doesn't load a new document must not change history.length"); + + // Navigate away from the initial empty document through form submission. + // This should do a replacement. + const form1 = iframe.contentDocument.createElement("form"); + form1.action = "/common/blank.html"; + iframe.contentDocument.body.appendChild(form1); + const input1 = iframe.contentDocument.createElement("input"); + input1.type = "hidden"; + input1.name = "1"; + form1.append(input1); + form1.submit(); + await waitForLoad(t, iframe, url1 + "="); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation on document loaded by iframe with no src"); + + // Navigate again using the same method, but this time it shouldn't do a + // replacement since it's no longer on the initial empty document. + const form2 = iframe.contentDocument.createElement("form"); + form2.action = "/common/blank.html"; + iframe.contentDocument.body.appendChild(form2); + const input2 = iframe.contentDocument.createElement("input"); + input2.type = "hidden"; + input2.name = "2"; + form2.append(input2); + form2.submit(); + await waitForLoad(t, iframe, url2 + "="); + assert_equals(history.length, startingHistoryLength + 1, + "history.length increases after normal navigation from non-initial empty document"); +}, "Navigating to a different document with form submission"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-aboutblank-navigate-immediately.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-aboutblank-navigate-immediately.html new file mode 100644 index 0000000000..a75257d91c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-aboutblank-navigate-immediately.html @@ -0,0 +1,124 @@ +<!DOCTYPE html> +<meta charset="UTF-8"> +<title>Navigations immediately after appending iframe with src='about:blank'</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> +<body></body> +<script> +/* + When an iframe is created with src="about:blank", it will stay on the initial + empty document. These tests verify the behavior of navigations that happen + immediately after the iframe is created, which should result in replacement. +*/ +"use strict"; +const url1 = "/common/blank.html?1"; + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src set to about:blank. This would trigger a + // navigation to a non-initial about:blank document. + const iframe = insertIframeWithAboutBlankSrc(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with src='about:blank' must not change history.length"); + + // Trigger a navigation to url1 through the iframe's src attribute. + // The iframe should still be on the initial empty document, and the + // navigation should do replacement. + iframe.src = url1; + // Wait for the latest navigation to finish. + await waitForLoad(t, iframe, url1); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation on initial empty document"); +}, "Navigating to a different document with src"); + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src set to about:blank but navigate away from it immediately below. + const iframe = insertIframeWithAboutBlankSrc(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with src='about:blank' must not change history.length"); + + // Navigate away from the initial empty document through setting + // location.href. The iframe should still be on the initial empty document, + // and the navigation should do replacement. + iframe.contentWindow.location.href = url1; + await waitForLoad(t, iframe, url1); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation on initial empty document"); +}, "Navigating to a different document with location.href"); + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src set to about:blank but navigate away from it immediately below. + const iframe = insertIframeWithAboutBlankSrc(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with src='about:blank' must not change history.length"); + + // Navigate away from the initial empty document through location.assign(). + // The iframe should still be on the initial empty document, and the + // navigation should do replacement. + iframe.contentWindow.location.assign(url1); + await waitForLoad(t, iframe, url1); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation on initial empty document"); +}, "Navigating to a different document with location.assign"); + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src set to about:blank but navigate away from it immediately below. + const iframe = insertIframeWithAboutBlankSrc(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with src='about:blank' must not change history.length"); + + // Navigate away from the initial empty document through window.open(). + // The iframe should still be on the initial empty document, and the + // navigation should do replacement. + iframe.contentWindow.open(url1, "_self"); + await waitForLoad(t, iframe, url1); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation on initial empty document"); +}, "Navigating to a different document with window.open"); + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src set to about:blank but navigate away from it immediately below. + const iframe = insertIframeWithAboutBlankSrc(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with src='about:blank' must not change history.length"); + + // Navigate away from the initial empty document through clicking an <a> + // element. The iframe should still be on the initial empty document, and the + // navigation should do replacement. + const a = iframe.contentDocument.createElement("a"); + a.href = url1; + iframe.contentDocument.body.appendChild(a); + a.click(); + await waitForLoad(t, iframe, url1); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation on initial empty document"); +}, "Navigating to a different document with link click"); + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src set to about:blank but navigate away from it immediately below. + const iframe = insertIframeWithAboutBlankSrc(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with src='about:blank' must not change history.length"); + + // Navigate away from the initial empty document through form submission. + // The iframe should still be on the initial empty document, and the + // navigation should do replacement. + const form = iframe.contentDocument.createElement("form"); + form.action = "/common/blank.html"; + iframe.contentDocument.body.appendChild(form); + const input = iframe.contentDocument.createElement("input"); + input.type = "hidden"; + input.name = "1"; + form.append(input); + form.submit(); + await waitForLoad(t, iframe, url1 + "="); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation on initial empty document"); +}, "Navigating to a different document with form submission"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-aboutblank-wait-for-load.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-aboutblank-wait-for-load.html new file mode 100644 index 0000000000..b7066ce521 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-aboutblank-wait-for-load.html @@ -0,0 +1,134 @@ +<!DOCTYPE html> +<meta charset="UTF-8"> +<title>Navigations after iframe with src='about:blank' finished loading</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> +<body></body> +<script> + /* + When an iframe is created with src="about:blank", it will stay on the initial + empty document. These tests verify the behavior of navigations that happen + immediately after the load event is fired on the iframe element, which + should result in replacement. + */ +"use strict"; +const url1 = "/common/blank.html?1"; + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src set to about:blank, and wait for it to finish + // loading. This would trigger and commit a navigation to a non-initial + // about:blank document. + const iframe = await insertIframeWithAboutBlankSrcWaitForLoad(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with src='about:blank' must not change history.length"); + + // Trigger a navigation to url1 through the iframe's src attribute. + // The iframe should still be on the initial empty document, and the + // navigation should do replacement. + iframe.src = url1; + // Wait for the latest navigation to finish. + await waitForLoad(t, iframe, url1); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation on initial empty document"); +}, "Navigating to a different document with src"); + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src set to about:blank, and wait for it to finish + // loading. This would trigger and commit a navigation to a non-initial + // about:blank document. + const iframe = await insertIframeWithAboutBlankSrcWaitForLoad(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with src='about:blank' must not change history.length"); + + // Navigate away from the initial empty document through setting + // location.href. The iframe should still be on the initial empty document, + // and the navigation should do replacement. + iframe.contentWindow.location.href = url1; + await waitForLoad(t, iframe, url1); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation on initial empty document"); + }, "Navigating to a different document with location.href"); + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src set to about:blank, and wait for it to finish + // loading. This would trigger and commit a navigation to a non-initial + // about:blank document. + const iframe = await insertIframeWithAboutBlankSrcWaitForLoad(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with src='about:blank' must not change history.length"); + + // Navigate away from the initial empty document through setting + // location.href. The iframe should still be on the initial empty document, + // and the navigation should do replacement. + iframe.contentWindow.location.href = url1; + await waitForLoad(t, iframe, url1); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation on initial empty document"); +}, "Navigating to a different document with location.assign"); + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src set to about:blank, and wait for it to finish + // loading. This would trigger and commit a navigation to a non-initial + // about:blank document. + const iframe = await insertIframeWithAboutBlankSrcWaitForLoad(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with src='about:blank' must not change history.length"); + + // Navigate away from the initial empty document through window.open(). + // The iframe should still be on the initial empty document, and the + // navigation should do replacement. + iframe.contentWindow.open(url1, "_self"); + await waitForLoad(t, iframe, url1); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation on initial empty document"); +}, "Navigating to a different document with window.open"); + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src set to about:blank, and wait for it to finish + // loading. This would trigger and commit a navigation to a non-initial + // about:blank document. + const iframe = await insertIframeWithAboutBlankSrcWaitForLoad(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with src='about:blank' must not change history.length"); + + // Navigate away from the initial empty document through clicking an <a> + // element. The iframe should still be on the initial empty document, and the + // navigation should do replacement. + const a = iframe.contentDocument.createElement("a"); + a.href = url1; + iframe.contentDocument.body.appendChild(a); + a.click(); + await waitForLoad(t, iframe, url1); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation on initial empty document"); +}, "Navigating to a different document with link click"); + +promise_test(async t => { + const startingHistoryLength = history.length; + // Create an iframe with src set to about:blank which will commit an about:blank document that is not the initial empty document, and wait for it to load. + const iframe = await insertIframeWithAboutBlankSrcWaitForLoad(t); + assert_equals(history.length, startingHistoryLength, + "Inserting iframe with src='about:blank' must not change history.length"); + + // Navigate away from the initial empty document through form submission. + // The iframe should still be on the initial empty document, and the + // navigation should do replacement. + const form = iframe.contentDocument.createElement("form"); + form.action = "/common/blank.html"; + iframe.contentDocument.body.appendChild(form); + const input = iframe.contentDocument.createElement("input"); + input.type = "hidden"; + input.name = "1"; + form.append(input); + form.submit(); + await waitForLoad(t, iframe, url1 + "="); + assert_equals(history.length, startingHistoryLength, + "history.length must not change after normal navigation on initial empty document"); +}, "Navigating to a different document with form submission"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/initial-content-replacement.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/initial-content-replacement.html new file mode 100644 index 0000000000..44f890df6c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/initial-content-replacement.html @@ -0,0 +1,86 @@ +<!DOCTYPE html> +<meta charset="UTF-8"> +<title> + Content synchronously added to iframe/opened window's document after creation + won't get replaced asynchronously when staying on the initial empty document +</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> +<body></body> +<script> +"use strict"; + +// Asserts the document on |w| stays the same after waiting 100ms. +function assertDocumentStaysTheSame(t, w) { + const initialDocument = w.document; + initialDocument.body.innerHTML = "foo"; + return new Promise((resolve) => { + t.step_timeout(() => { + assert_equals(w.document.body.innerHTML, "foo"); + assert_equals(w.document, initialDocument); + resolve(); + }, 100); + }); +} + +promise_test(async t => { + const iframe = document.createElement("iframe"); + document.body.appendChild(iframe); + await assertDocumentStaysTheSame(t, iframe.contentWindow); +}, "Content synchronously added to <iframe> with no src won't get replaced"); + +promise_test(async t => { + const iframe = document.createElement("iframe"); + iframe.src = ""; + document.body.appendChild(iframe); + await assertDocumentStaysTheSame(t, iframe.contentWindow); +}, "Content synchronously added to <iframe> with src='' won't get replaced"); + +promise_test(async t => { + const iframe = document.createElement("iframe"); + iframe.src = "about:blank"; + document.body.appendChild(iframe); + await assertDocumentStaysTheSame(t, iframe.contentWindow); +}, "Content synchronously added to <iframe> with src='about:blank' won't get replaced"); + +promise_test(async t => { + const iframe = document.createElement("iframe"); + iframe.src = "about:blank#foo"; + document.body.appendChild(iframe); + await assertDocumentStaysTheSame(t, iframe.contentWindow); +}, "Content synchronously added to <iframe> with src='about:blank#foo' won't get replaced"); + +promise_test(async t => { + const iframe = document.createElement("iframe"); + iframe.src = "about:blank?foo"; + document.body.appendChild(iframe); + await assertDocumentStaysTheSame(t, iframe.contentWindow); +}, "Content synchronously added to <iframe> with src='about:blank?foo' won't get replaced"); + +promise_test(async t => { + const w = window.open(); + await assertDocumentStaysTheSame(t, w); +}, "Content synchronously added to window.open()-ed document won't get replaced"); + +promise_test(async t => { + const w = window.open(""); + await assertDocumentStaysTheSame(t, w); +}, "Content synchronously added to window.open('')-ed document won't get replaced"); + +promise_test(async t => { + const w = window.open("about:blank"); + await assertDocumentStaysTheSame(t, w); +}, "Content synchronously added to window.open('about:blank')-ed document won't get replaced"); + +promise_test(async t => { + const w = window.open("about:blank#foo"); + await assertDocumentStaysTheSame(t, w); +}, "Content synchronously added to window.open('about:blank#foo')-ed document won't get replaced"); + +promise_test(async t => { + const w = window.open("about:blank?foo"); + await assertDocumentStaysTheSame(t, w); +}, "Content synchronously added to window.open('about:blank?foo')-ed document won't get replaced"); + +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-event-iframe-element.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-event-iframe-element.html new file mode 100644 index 0000000000..0d19770cc1 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-event-iframe-element.html @@ -0,0 +1,64 @@ +<!DOCTYPE html> +<meta charset="UTF-8"> +<title>"load" event fires on the iframe element when loading the initial empty document</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> +<body></body> +<script> +"use strict"; + +promise_test(async t => { + const iframe = document.createElement("iframe"); + let loadCount = 0; + iframe.addEventListener("load", () => { + loadCount++; + }); + document.body.appendChild(iframe); + assert_equals(loadCount, 1); +}, "load event fires synchronously on <iframe> element created with no src"); + +promise_test(async t => { + const iframe = document.createElement("iframe"); + iframe.src = ""; + let loadCount = 0; + iframe.addEventListener("load", () => { + loadCount++; + }); + document.body.appendChild(iframe); + assert_equals(loadCount, 1); +}, "load event fires synchronously on <iframe> element created with src=''"); + +promise_test(async t => { + const iframe = document.createElement("iframe"); + iframe.src = "about:blank"; + let loadCount = 0; + iframe.addEventListener("load", () => { + loadCount++; + }); + document.body.appendChild(iframe); + assert_equals(loadCount, 1); +}, "load event fires synchronously on <iframe> element created with src='about:blank'"); + +promise_test(async t => { + const iframe = document.createElement("iframe"); + iframe.src = "about:blank#foo"; + let loadCount = 0; + iframe.addEventListener("load", () => { + loadCount++; + }); + document.body.appendChild(iframe); + assert_equals(loadCount, 1); +}, "load event fires synchronously on <iframe> element created with src='about:blank#foo'"); + +promise_test(async t => { + const iframe = document.createElement("iframe"); + iframe.src = "about:blank?foo"; + let loadCount = 0; + iframe.addEventListener("load", () => { + loadCount++; + }); + document.body.appendChild(iframe); + assert_equals(loadCount, 1); +}, "load event fires synchronously on <iframe> element created with src='about:blank?foo'"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-pageshow-events-iframe-contentWindow.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-pageshow-events-iframe-contentWindow.html new file mode 100644 index 0000000000..4aea4aac81 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-pageshow-events-iframe-contentWindow.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<meta charset="UTF-8"> +<title>"load" & "pageshow" events do not fire on contentWindow of iframe that stays on the initial empty document</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> +<body></body> +<script> +"use strict"; + +promise_test(async t => { + const iframe = document.createElement("iframe"); + document.body.appendChild(iframe); + return assertNoLoadAndPageshowEvent(t, iframe.contentWindow); +}, "load & pageshow event do not fire on contentWindow of <iframe> element created with no src"); + +promise_test(async t => { + const iframe = document.createElement("iframe"); + iframe.src = ""; + document.body.appendChild(iframe); + return assertNoLoadAndPageshowEvent(t, iframe.contentWindow); +}, "load & pageshow events do not fire on contentWindow of <iframe> element created with src=''"); + +promise_test(async t => { + const iframe = document.createElement("iframe"); + iframe.src = "about:blank"; + document.body.appendChild(iframe); + return assertNoLoadAndPageshowEvent(t, iframe.contentWindow); +}, "load & pageshow events do not fire on contentWindow of <iframe> element created with src='about:blank'"); + +promise_test(async t => { + const iframe = document.createElement("iframe"); + iframe.src = "about:blank#foo"; + document.body.appendChild(iframe); + return assertNoLoadAndPageshowEvent(t, iframe.contentWindow); +}, "load & pageshow events do not fire on contentWindow of <iframe> element created with src='about:blank#foo'"); + +promise_test(async t => { + const iframe = document.createElement("iframe"); + iframe.src = "about:blank?foo"; + document.body.appendChild(iframe); + return assertNoLoadAndPageshowEvent(t, iframe.contentWindow); +}, "load & pageshow events do not fire on contentWindow of <iframe> element created with src='about:blank?foo'"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-pageshow-events-window-open.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-pageshow-events-window-open.html new file mode 100644 index 0000000000..9703502f7f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-pageshow-events-window-open.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<meta charset="UTF-8"> +<title>"load" and "pageshow" events don't fire on window.open() that stays on the initial empty document</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> +<body></body> +<script> +"use strict"; + +promise_test(async t => { + const w = window.open(); + return assertNoLoadAndPageshowEvent(t, w) +}, "load event does not fire on window.open()"); + +promise_test(async t => { + const w = window.open(""); + return assertNoLoadAndPageshowEvent(t, w) +}, "load event does not fire on window.open('')"); + +promise_test(async t => { + const w = window.open("about:blank"); + return assertNoLoadAndPageshowEvent(t, w) +}, "load event does not fire on window.open('about:blank')"); + +promise_test(async t => { + const w = window.open("about:blank#foo"); + return assertNoLoadAndPageshowEvent(t, w) +}, "load event does not fire on window.open('about:blank#foo')"); + +promise_test(async t => { + const w = window.open("about:blank?foo"); + return assertNoLoadAndPageshowEvent(t, w) +}, "load event does not fire on window.open('about:blank?foo')"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/resources/code-injector.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/resources/code-injector.html new file mode 100644 index 0000000000..631b95f9ed --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/resources/code-injector.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Subframe</title> + +<script> +"use strict"; +{{GET[code]}} +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/resources/helpers.js b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/resources/helpers.js new file mode 100644 index 0000000000..8d9473a949 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/resources/helpers.js @@ -0,0 +1,121 @@ +// Returns a promise that asserts the "load" and "pageshow" events are not +// fired on |target|. +function assertNoLoadAndPageshowEvent(t, target) { + target.addEventListener("load", t.unreached_func("load should not be fired")); + target.addEventListener("pageshow", t.unreached_func("pageshow should not be fired")); + return new Promise(resolve => { + // Wait 50ms to ensure events fired after asynchronous navigations are + // also captured. + setTimeout(resolve, 50); + }); +} + +const url204 = "/common/blank.html?pipe=status(204)"; +const postMessageToOpenerOnLoad = ` + window.onload = () => { + window.opener.postMessage("loaded", "*") + } + `; + +// -- Start of helpers for iframe initial empty document tests. + +// Creates an iframe with an unset src and appends it to the document. +window.insertIframe = (t) => { + const iframe = document.createElement("iframe"); + t.add_cleanup(() => iframe.remove()); + document.body.append(iframe); + return iframe; +}; + +// Creates an iframe with src set to a URL that doesn't commit a new document +// (results in a HTTP 204 response) and appends it to the document. +window.insertIframeWith204Src = (t) => { + const iframe = document.createElement("iframe"); + iframe.src = url204; + t.add_cleanup(() => iframe.remove()); + document.body.append(iframe); + return iframe; +}; + +// Creates an iframe with src="about:blank" and appends it to the document. +window.insertIframeWithAboutBlankSrc = (t) => { + const iframe = document.createElement("iframe"); + t.add_cleanup(() => iframe.remove()); + iframe.src = "about:blank"; + document.body.append(iframe); + return iframe; +}; + +// Creates an iframe with src="about:blank", appends it to the document, and +// waits for the non-initial about:blank document finished loading. +window.insertIframeWithAboutBlankSrcWaitForLoad = async (t) => { + const iframe = insertIframeWithAboutBlankSrc(t); + const aboutBlankLoad = new Promise(resolve => { + // In some browsers, the non-initial about:blank navigation commits + // asynchronously, while in other browsers, it would commit synchronously. + // This means we can't wait for the "load" event as it might have already + // ran. Instead, just wait for 100ms before resolving, as the non-initial + // about:blank navigation will most likely take less than 100 ms to commit. + t.step_timeout(resolve, 100); + }); + await aboutBlankLoad; + return iframe; +}; + +// Waits for the "load" event for |urlRelativeToThisDocument| to run on +// |iframe|. +window.waitForLoad = (t, iframe, urlRelativeToThisDocument) => { + return new Promise(resolve => { + iframe.addEventListener("load", t.step_func(() => { + assert_equals(iframe.contentWindow.location.href, (new URL(urlRelativeToThisDocument, location.href)).href); + + // Wait a bit longer to ensure all history stuff has settled, e.g. the document is "completely loaded" + // (which happens from a queued task). + setTimeout(resolve, 0); + }), { once: true }); + }); +}; + +// -- End of helpers for iframe initial empty document tests. + +// -- Start of helpers for opened windows' initial empty document tests. + +// window.open() to a URL that doesn't load a new document (results in a HTTP +// 204 response). This should create a new window that stays on the initial +// empty document. +window.windowOpen204 = (t) => { + const openedWindow = window.open(url204); + t.add_cleanup(() => openedWindow.close()); + return openedWindow; +}; + +// window.open() (no URL set). This should create a new window that stays on +// the initial empty document as it won't trigger a non-initial about:blank +// navigation. +window.windowOpenNoURL = (t) => { + const openedWindow = window.open(); + t.add_cleanup(() => openedWindow.close()); + return openedWindow; +}; + +// window.open("about:blank"). This should create a new window that stays on +// the initial empty document as it won't trigger a non-initial about:blank +// navigation. +window.windowOpenAboutBlank = (t) => { + const openedWindow = window.open("about:blank"); + t.add_cleanup(() => openedWindow.close()); + return openedWindow; +}; + +// Waits for a postMessage with data set to |message| is received on the current +// window. +window.waitForMessage = (t, message) => { + return new Promise(resolve => { + window.addEventListener("message", t.step_func((event) => { + if (event.data == message) + resolve(); + })); + }); +}; + +// -- End of helpers for opened windows' initial empty document tests. diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/window-open-204-fragment.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/window-open-204-fragment.html new file mode 100644 index 0000000000..bb47cd3820 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/window-open-204-fragment.html @@ -0,0 +1,63 @@ +<!DOCTYPE html> +<meta charset="UTF-8"> +<title>Fragment navigation on initial empty document created through window.open(url-with-204-response)</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> +<body></body> +<script> +/* + When a new window is opened through window.open() it will contain the initial + empty document. If the URL parameter is set to the URL that doesn't load a + new document (e.g. it results in a HTTP 204 response), it will stay on the + initial empty document. If fragment navigations happen, it will still stay on + the initial empty document. + These tests verify the behavior of navigations that happen on the initial + empty document in that situation. They should all be converted to do a + replacement. +*/ +"use strict"; +const url1 = "about:blank#foo"; +const url2 = "resources/code-injector.html?2&pipe=sub(none)&code=" + + encodeURIComponent(postMessageToOpenerOnLoad); + +promise_test(async t => { + // Open a new window with a URL that doesn't load a new document, so it will stay in the initial empty document. + const openedWindow = windowOpen204(t); + + // Do a fragment navigation within the initial empty document through setting location.href. + // This should do a replacement. + openedWindow.location.href = url1; + await new Promise(resolve => t.step_timeout(resolve, 100)); + assert_equals(openedWindow.location.hash, "#foo"); + assert_equals(openedWindow.history.length, 1, + "history.length should not increase after fragment navigation on initial empty document"); + + // Navigate away from the initial empty document through setting location.href. + // This should do a replacement. + openedWindow.location.href = url2; + await waitForMessage(t, "loaded"); + assert_equals(openedWindow.history.length, 1, + "history.length should not increase after normal navigation away from initial empty document"); +}, "location.href"); + +promise_test(async t => { + // Open a new window with a URL that doesn't load a new document, so it will stay in the initial empty document. + const openedWindow = windowOpen204(t); + + // Do a fragment navigation within the initial empty document through location.assign(). + // This should do a replacement. + openedWindow.location.assign(url1); + await new Promise(resolve => t.step_timeout(resolve, 100)); + assert_equals(openedWindow.location.hash, "#foo"); + assert_equals(openedWindow.history.length, 1, + "history.length should not increase after fragment navigation on initial empty document"); + + // Navigate away from the initial empty document through location.assign(). + // This should do a replacement. + openedWindow.location.assign(url2); + await waitForMessage(t, "loaded"); + assert_equals(openedWindow.history.length, 1, + "history.length should not increase after normal navigation away from initial empty document"); +}, "location.assign"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/window-open-204-pushState-replaceState.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/window-open-204-pushState-replaceState.html new file mode 100644 index 0000000000..b5382b189f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/window-open-204-pushState-replaceState.html @@ -0,0 +1,61 @@ +<!DOCTYPE html> +<meta charset="UTF-8"> +<title>Fragment navigation on initial empty document created through window.open(url-with-204-response)</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> +<body></body> +<script> +/* + When a new window is opened through window.open() it will contain the initial + empty document. If the URL parameter is set to the URL that doesn't load a + new document (e.g. it results in a HTTP 204 response), it will stay on the + initial empty document. If history.pushState() or history.replaceState() is + called on it, it should still stay on the initial empty document. + These tests verify the behavior of navigations that happen on the initial + empty document in that situation. They should all be converted to do a + replacement. +*/ +"use strict"; +const url1 = "about:blank#foo"; +const url2 = "resources/code-injector.html?2&pipe=sub(none)&code=" + + encodeURIComponent(postMessageToOpenerOnLoad); + +promise_test(async t => { + // Open a new window with a URL that doesn't load a new document, so it will stay in the initial empty document. + const openedWindow = windowOpen204(t); + + // Do a history.pushState() to about:blank#foo. + let pushURL = "about:blank#foo"; + openedWindow.history.pushState({}, "title", pushURL); + assert_equals(openedWindow.location.href, pushURL); + assert_equals(history.length, 1, + "history.length must not change after history.pushState() on the initial empty document"); + + // Navigate away from the initial empty document through setting location.href. + // This should do a replacement. + openedWindow.location.href = url2; + await waitForMessage(t, "loaded"); + assert_equals(openedWindow.history.length, 1, + "history.length should not increase after normal navigation away from initial empty document"); +}, "history.pushState"); + +promise_test(async t => { + // Open a new window with a URL that doesn't load a new document, so it will stay in the initial empty document. + const openedWindow = windowOpen204(t); + + // Do a history.pushState() to about:blank#foo. + let replaceURL = "about:blank#foo"; + openedWindow.history.replaceState({}, "title", replaceURL); + assert_equals(openedWindow.location.href, replaceURL); + assert_equals(history.length, 1, + "history.length must not change after history.replaceState() on the initial empty document"); + + // Navigate away from the initial empty document through location.assign(). + // This should do a replacement. + openedWindow.location.assign(url2); + await waitForMessage(t, "loaded"); + assert_equals(openedWindow.history.length, 1, + "history.length should not increase after normal navigation away from initial empty document"); +}, "history.replaceState"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/window-open-204.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/window-open-204.html new file mode 100644 index 0000000000..6461b3c8fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/window-open-204.html @@ -0,0 +1,81 @@ +<!DOCTYPE html> +<meta charset="UTF-8"> +<title>Navigations on window.open() with URL that doesn't load a new document</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> +<body></body> +<script> +/* + When a new window is opened through window.open() it will contain the initial + empty document. If the URL parameter is set to the URL that doesn't load a + new document (e.g. it results in a HTTP 204 response), it will stay on the + initial empty document. + These tests verify the behavior of navigations that happen on the initial + empty document in that situation. They should all be converted to do a + replacement. +*/ +"use strict"; +const url1 = "resources/code-injector.html?1&pipe=sub(none)&code=" + + encodeURIComponent(postMessageToOpenerOnLoad); +const url2 = "resources/code-injector.html?2&pipe=sub(none)&code=" + + encodeURIComponent(postMessageToOpenerOnLoad); + +promise_test(async t => { + // Open a new window with a URL that doesn't load a new document, so it will stay in the initial empty document. + const openedWindow = windowOpen204(t); + + // Navigate away from the initial empty document through setting + // location.href. This should do a replacement. + openedWindow.location.href = url1; + await waitForMessage(t, "loaded"); + assert_equals(openedWindow.history.length, 1, + "history.length should not increase after normal navigation away from initial empty document"); + + // Navigate again using the same method, but this time it shouldn't do a + // replacement since it's no longer on the initial empty document. + openedWindow.location.href = url2; + await waitForMessage(t, "loaded"); + assert_equals(openedWindow.history.length, 2, + "history.length should increase after normal navigation away from non-initial empty document"); +}, "location.href"); + +promise_test(async t => { + // Open a new window with a URL that doesn't load a new document, so it will stay in the initial empty document. + const openedWindow = windowOpen204(t); + + // Navigate away from the initial empty document through location.assign(). + // This should do a replacement. + openedWindow.location.assign(url1); + await waitForMessage(t, "loaded"); + assert_equals(openedWindow.history.length, 1, + "history.length should not increase after normal navigation away from initial empty document"); + + // Navigate again using the same method, but this time it shouldn't do a + // replacement since it's no longer on the initial empty document. + openedWindow.location.assign(url2); + await waitForMessage(t, "loaded"); + assert_equals(openedWindow.history.length, 2, + "history.length should increase after normal navigation away from non-initial empty document"); +}, "location.assign"); +/* +promise_test(async t => { + // Open a new window with a URL that doesn't load a new document, so it will stay in the initial empty document. + const openedWindow = windowOpen204(t); + + // Navigate away from the initial empty document through setting + // window.open(). This should do a replacement. + openedWindow.open(url1, "_self"); + await waitForMessage(t, "loaded"); + assert_equals(openedWindow.history.length, 1, + "history.length should increase after normal navigation away from non-initial empty document"); + + // Navigate again using the same method, but this time it shouldn't do a + // replacement since it's no longer on the initial empty document. + openedWindow.open(url2, "_self"); + await waitForMessage(t, "loaded"); + assert_equals(openedWindow.history.length, 2, + "history.length should increase after normal navigation away from non-initial empty document"); +}, "window.open"); +*/ +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/window-open-aboutblank.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/window-open-aboutblank.html new file mode 100644 index 0000000000..f3033d6a21 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/window-open-aboutblank.html @@ -0,0 +1,81 @@ +<!DOCTYPE html> +<meta charset="UTF-8"> +<title>Navigations on window.open(about:blank) after waiting for it to load</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> +<body></body> +<script> +/* + When a new window is opened through window.open() it will contain the initial + empty document. If the URL parameter is set to about:blank, it will stay on + the initial empty document (unlike iframes with src="about:blank", which will + start a navigation to a non-initial about:blank document). + These tests verify the behavior of navigations that happen on the initial + empty document in that situation. They should all be converted to do a + replacement. +*/ +"use strict"; +const url1 = "resources/code-injector.html?1&pipe=sub(none)&code=" + + encodeURIComponent(postMessageToOpenerOnLoad); +const url2 = "resources/code-injector.html?2&pipe=sub(none)&code=" + + encodeURIComponent(postMessageToOpenerOnLoad); + +promise_test(async t => { + // Open a window with URL about:blank, which will commit the + // initial empty document and stay on it. + const openedWindow = windowOpenAboutBlank(t); + + // Unlike iframe with src="about:blank", window.open("about:blank") won't + // trigger a navigation to a non-initial about:blank document, so it should + // stay on the initial empty document. To verify, wait for 100ms before + // continuing. + await new Promise((resolve) => t.step_timeout(resolve, 100)); + + // Navigate away from the initial empty document through location.href. + // This should do a replacement. + openedWindow.location.href = url1; + await waitForMessage(t, "loaded"); + assert_equals(openedWindow.history.length, 1, + "history.length should not increase after normal navigation away from initial empty document"); +}, "location.href"); + +promise_test(async t => { + // Open a window with URL about:blank, which will commit the + // initial empty document and stay on it. + const openedWindow = windowOpenAboutBlank(t); + + // Unlike iframe with src="about:blank", window.open("about:blank") won't + // trigger a navigation to a non-initial about:blank document, so it should + // stay on the initial empty document. To verify, wait for 100ms before + // continuing. + await new Promise((resolve) => t.step_timeout(resolve, 100)); + + // Navigate away from the initial empty document through location.assign(). + // This should do a replacement. + openedWindow.location.assign(url1); + await waitForMessage(t, "loaded"); + assert_equals(openedWindow.history.length, 1, + "history.length should not increase after normal navigation away from initial empty document"); +}, "location.assign"); +/* +promise_test(async t => { + // Open a window with URL about:blank, which will commit the + // initial empty document and stay on it. + const openedWindow = windowOpenAboutBlank(t); + + // Unlike iframe with src="about:blank", window.open("about:blank") won't + // trigger a navigation to a non-initial about:blank document, so it should + // stay on the initial empty document. To verify, wait for 100ms before + // continuing. + await new Promise((resolve) => t.step_timeout(resolve, 100)); + + // Navigate away from the initial empty document through window.open(). + // This should do a replacement. + openedWindow.open(url1, "_self"); + await waitForMessage(t, "loaded"); + assert_equals(openedWindow.history.length, 1, + "history.length should not increase after normal navigation away from initial empty document"); +}, "window.open"); +*/ +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/window-open-history-length.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/window-open-history-length.html new file mode 100644 index 0000000000..ab89bc4098 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/window-open-history-length.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<meta charset="UTF-8"> +<title>history.length value on window.open()-ed window</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> +<body></body> +<script> +/* + When a new window is opened through window.open() it will contain the initial + empty document, and the history.length value should be 1. +*/ + +promise_test(async t => { + const openedWindow = windowOpenNoURL(t); + assert_equals(openedWindow.history.length, 1, + "history.length should start at 1 for newly opened window"); +}, "Starting history.length for window.open()"); + +promise_test(async t => { + const openedWindow = windowOpenAboutBlank(t); + assert_equals(openedWindow.history.length, 1, + "history.length should start at 1 for newly opened window"); +}, "Starting history.length for window.open(about:blank)"); + +promise_test(async t => { + const openedWindow = windowOpen204(t); + assert_equals(openedWindow.history.length, 1, + "history.length should start at 1 for newly opened window"); +}, "Starting history.length for window.open(url-with-204-response)"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/window-open-nourl.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/window-open-nourl.html new file mode 100644 index 0000000000..801f77ad48 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/window-open-nourl.html @@ -0,0 +1,78 @@ +<!DOCTYPE html> +<meta charset="UTF-8"> +<title>Navigations on window.open() to URL that doesn't load a new document</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> +<body></body> +<script> +/* + When a new window is opened through window.open() it will contain the initial + empty document. If the URL parameter is not set, it will stay on the initial + empty document. + These tests verify the behavior of navigations that happen on the initial + empty document in that situation. They should all be converted to do a + replacement. +*/ +"use strict"; +const url1 = "resources/code-injector.html?1&pipe=sub(none)&code=" + encodeURIComponent(postMessageToOpenerOnLoad); +const url2 = "resources/code-injector.html?2&pipe=sub(none)&code=" + encodeURIComponent(postMessageToOpenerOnLoad); + +promise_test(async t => { + // Open a new window with no URL, which will stay in the initial empty document until the navigation below. + const openedWindow = windowOpenNoURL(t); + + // Navigate away from the initial empty document through setting + // location.href. This should do a replacement. + openedWindow.location.href = url1; + await waitForMessage(t, "loaded"); + assert_equals(openedWindow.history.length, 1, + "history.length should not increase after normal navigation away from initial empty document"); + + // Navigate again using the same method, but this time it shouldn't do a + // replacement since it's no longer on the initial empty document. + openedWindow.location.href = url2; + await waitForMessage(t, "loaded"); + assert_equals(openedWindow.history.length, 2, + "history.length should increase after normal navigation away from non-initial empty document"); +}, "location.href"); + +promise_test(async t => { + // Open a new window with no URL, which will stay in the initial empty document until the navigation below. + const openedWindow = windowOpenNoURL(t); + + // Navigate away from the initial empty document through location.assign(). + // This should do a replacement. + openedWindow.location.assign(url1); + await waitForMessage(t, "loaded"); + assert_equals(openedWindow.history.length, 1, + "history.length should not increase after normal navigation away from initial empty document"); + + // Navigate again using the same method, but this time it shouldn't do a + // replacement since it's no longer on the initial empty document. + openedWindow.location.assign(url2); + await waitForMessage(t, "loaded"); + assert_equals(openedWindow.history.length, 2, + "history.length should increase after normal navigation away from non-initial empty document"); +}, "location.assign"); +/* +promise_test(async t => { + // Open a new window with no URL, which will stay in the initial empty document until the navigation below. + const openedWindow = windowOpenNoURL(t); + + // Navigate away from the initial empty document through setting + // window.open(). This should do a replacement. + openedWindow.open(url1, "_self"); + await waitForMessage(t, "loaded"); + assert_equals(openedWindow.history.length, 1, + "history.length should not increase after normal navigation away from initial empty document"); + + // Navigate again using the same method, but this time it shouldn't do a + // replacement since it's no longer on the initial empty document. + openedWindow.open(url2, "_self"); + await waitForMessage(t, "loaded"); + assert_equals(openedWindow.history.length, 2, + "history.length should increase after normal navigation away from non-initial empty document"); +}, "window.open"); +*/ +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-abort/javascript-url-abort-return-value-string.tentative.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-abort/javascript-url-abort-return-value-string.tentative.html new file mode 100644 index 0000000000..f626a79ae6 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-abort/javascript-url-abort-return-value-string.tentative.html @@ -0,0 +1,28 @@ +<!doctype html> +<title>Aborting fetch for javascript:string navigation</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#navigate"> +<link rel="help" href="https://github.com/whatwg/html/issues/2590"> +<div id="log"></div> +<iframe src="support/iframe-and-links.html"></iframe> +<script> +async_test(test => { + onload = () => { + const child = document.querySelector('iframe').contentWindow; + child.document.querySelector("#slowLink").click(); + // The step below is in a timeout. The framed page can't communicate back mid-parse because that + // would involve running script, which makes that navigation "mature", and we need to do this + // before it matures. + test.step_timeout(() => { + child.document.querySelector("#javascriptStringLink").click(); + child.document.querySelector("iframe").onload = test.step_func_done(() => { + assert_false(child.childLoaded, 'child.childLoaded'); + }); + }, 100); + }; + window.javascriptStringDocLoaded = test.step_func(() => { + assert_unreached("javascript: URL doc replaced the document, should be targeted to child iframe."); + }); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-abort/javascript-url-abort-return-value-undefined.tentative.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-abort/javascript-url-abort-return-value-undefined.tentative.html new file mode 100644 index 0000000000..80a0d27a7d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-abort/javascript-url-abort-return-value-undefined.tentative.html @@ -0,0 +1,25 @@ +<!doctype html> +<title>Not aborting fetch for javascript:undefined navigation</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#navigate"> +<link rel="help" href="https://github.com/whatwg/html/issues/2590"> +<div id="log"></div> +<iframe src="support/iframe-and-links.html"></iframe> +<script> +async_test(test => { + onload = () => { + const child = document.querySelector('iframe').contentWindow; + child.document.querySelector("#slowLink").click(); + // The step below is in a timeout. The framed page can't communicate back mid-parse because that + // would involve running script, which makes that navigation "mature", and we need to do this + // before it matures. + test.step_timeout(() => { + child.document.querySelector("#javascriptUndefinedLink").click(); + child.document.querySelector("iframe").onload = test.step_func_done(() => { + assert_true(child.childLoaded, 'child.childLoaded'); + }); + }, 100); + }; +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-abort/support/iframe-and-links.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-abort/support/iframe-and-links.html new file mode 100644 index 0000000000..545b0988ba --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-abort/support/iframe-and-links.html @@ -0,0 +1,18 @@ +<!doctype html> + +<iframe name="iframe"></iframe> + +<!-- slow link's response is delayed by 1 second --> +<!-- https://wptserve.readthedocs.io/en/latest/pipes.html#trickle --> +<a target="iframe" href="set-child-loaded.html?pipe=trickle(d1)" id="slowLink">slow link</a> +<a target="iframe" href="javascript:'javascript:string <script> parent.javascriptStringDocLoaded(); </script>'" id="javascriptStringLink">javascript:string link</a> +<a target="iframe" href="javascript:undefined" id="javascriptUndefinedLink">javascript:undefined link</a> + +<script> +// set-child-loaded.html (the slow link) sets this to true. +window.childLoaded = false; + +// Do nothing when the javascript:string doc has loaded, if it's correctly targeted to the above iframe. +// However, if it replaces this document, it needs to fail the test (handled in the parent). +function javascriptStringDocLoaded() {} +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-abort/support/set-child-loaded.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-abort/support/set-child-loaded.html new file mode 100644 index 0000000000..a4b34ad6e9 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-abort/support/set-child-loaded.html @@ -0,0 +1,5 @@ +<!doctype html> +set-child-loaded.html +<script> +parent.childLoaded = true; +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-global-scope.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-global-scope.html new file mode 100644 index 0000000000..d3dd38ebed --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-global-scope.html @@ -0,0 +1,16 @@ +<!doctype html> +<meta charset=utf-8> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> + +<a id="javascript-link" href="javascript:changeStatus()">link</a> + +<script> +function changeStatus() { + t.done(); +} + +var t = async_test(function(t) { + document.querySelector("#javascript-link").click(); +}, "javascript: scheme urls should be executed in current global scope"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-load-as-html.xhtml b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-load-as-html.xhtml new file mode 100644 index 0000000000..b23bef5917 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-load-as-html.xhtml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="windows-1250"?> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="../resources/helpers.js"></script> + <meta charset="windows-1250"/> + <title>javascript: URL navigation to a string must create a HTML document using the correct properties</title> +</head> +<body> + <!-- + This document is XHTML and windows-1250 so that we can test the resulting javascript: URL document is not. + The same for the window we open. + --> + <script><![CDATA[ + promise_test(async (t) => { + const w = await openWindow("resources/xhtml-and-non-utf-8.xhtml", t); + + w.location.href = `javascript:'a string<script> + opener.postMessage({ + compatMode: document.compatMode, + contentType: document.contentType, + characterSet: document.characterSet, + doctypeIsNull: document.doctype === null + }, "*"); + <` + `/script>'`; + + const results = await waitForMessage(w); + + assert_equals(results.compatMode, "BackCompat"); + assert_equals(results.contentType, "text/html"); + assert_equals(results.characterSet, "UTF-8"); + assert_equals(results.doctypeIsNull, true); + }); + ]]></script> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-no-beforeunload.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-no-beforeunload.window.js new file mode 100644 index 0000000000..47e8f11797 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-no-beforeunload.window.js @@ -0,0 +1,80 @@ +// META: script=../resources/helpers.js + +for (const stringCompletion of [false, true]) { + const testNameSuffix = stringCompletion ? ": string completion" : ": undefined completion"; + + testNoBeforeunload( + { testRunnerWindow: "top", stringCompletion }, + async (t, urlToSet) => { + const iframe = await addIframe(); + iframe.contentWindow.location.href = urlToSet; + + return iframe.contentWindow; + }, + `Navigating an iframe via location.href to a javascript: URL must not fire beforeunload${testNameSuffix}` + ); + + testNoBeforeunload( + { testRunnerWindow: "top", stringCompletion }, + async (t, urlToSet) => { + const iframe = await addIframe(); + iframe.src = urlToSet; + + return iframe.contentWindow; + }, + `Navigating an iframe via src="" to a javascript: URL after insertion must not fire beforeunload${testNameSuffix}` + ); + + testNoBeforeunload( + { testRunnerWindow: "opener", stringCompletion }, + async (t, urlToSet) => { + const w = await openWindow("/common/blank.html", t); + w.location.href = urlToSet; + + return w; + }, + `Navigating an opened window via location.href to a javascript: URL must not fire beforeunload${testNameSuffix}` + ); + + + testNoBeforeunload( + { testRunnerWindow: "opener", stringCompletion }, + async (t, urlToSet) => { + const w = await openWindow("../resources/has-iframe.html", t); + w.frames[0].onbeforeunload = t.unreached_func("beforeunload must not fire on the iframe"); + w.location.href = urlToSet; + + return w; + }, + `Navigating an opened window with an iframe via location.href to a javascript: URL must not fire beforeunload on the iframe${testNameSuffix}` + ); +} + +function testNoBeforeunload({ testRunnerWindow, stringCompletion }, setupAndNavigateFunc, description) { + promise_test(async t => { + t.add_cleanup(() => { + delete window.resolveTestPromise; + }); + + const ranPromise = new Promise(resolve => { + window.resolveTestPromise = resolve; + }); + + const urlToSet = makeURL({ testRunnerWindow, stringCompletion }); + const w = await setupAndNavigateFunc(t, urlToSet); + w.onbeforeunload = t.unreached_func("beforeunload must not fire"); + + await ranPromise; + if (stringCompletion) { + await waitForMessage(w); + } + }, description); +} + +function makeURL({ testRunnerWindow, stringCompletion }) { + const completion = stringCompletion ? + `"a string<script>window.${testRunnerWindow}.postMessage('ready', '*');</script>";` : + `undefined;`; + + return `javascript:window.${testRunnerWindow}.resolveTestPromise();${completion};`; +} diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-query-fragment-components.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-query-fragment-components.html new file mode 100644 index 0000000000..eced9646e5 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-query-fragment-components.html @@ -0,0 +1,28 @@ +<!doctype html> +<title> javascript url with query and fragment components </title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +var a = null; +var b = null; +var c = null; +</script> + +<iframe id="a" src='javascript:"nope" ? "yep" : "what";'></iframe> +<iframe id="b" src='javascript:"wrong"; // # %0a "ok";'></iframe> +<iframe id="c" src='javascript:"%252525 ? %252525 # %252525"'></iframe> + +<script> +var t = async_test("iframes with javascript src"); +function check(id, expected) { + assert_equals( + document.getElementById(id).contentDocument.body.textContent, + expected); +} +onload = t.step_func(function() { + check("a", "yep"); + check("b", "ok"); + check("c", "%2525 ? %2525 # %2525"); + t.done(); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-referrer.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-referrer.window.js new file mode 100644 index 0000000000..1f11429c9e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-referrer.window.js @@ -0,0 +1,38 @@ +// META: script=../resources/helpers.js +// META: title=javascript: URL navigation to a string must create a document whose referrer is the navigation initiator + +const originalURL = location.href; + +const testCases = [ + ["unsafe-url", location.href], + ["origin", self.origin + "/"], + ["no-referrer", ""] +]; + +for (const [referrerPolicyForStartingWindowCreation, expectedReferrer] of testCases) { + promise_test(async (t) => { + const meta = document.createElement("meta"); + meta.name = "referrer"; + meta.content = referrerPolicyForStartingWindowCreation; + t.add_cleanup(() => meta.remove()); + document.head.append(meta); + + const w = await openWindow("/common/blank.html", t); + const originalReferrer = w.document.referrer; + assert_equals(originalReferrer, expectedReferrer, + "Sanity check: opened window's referrer is set correctly"); + + // Mess with the current document's URL so that the initiator URL is different. Then, if that + // shows up as the javascript: URL document's referrer, we know the navigation initiator's URL is + // being used as the referrer, which is incorrect. + history.replaceState(undefined, "", "/incorrect-referrer.html"); + t.add_cleanup(() => history.replaceState(undefined, "", originalURL)); + + w.location.href = `javascript:'a string<script>opener.postMessage(document.referrer, "*");</script>'`; + + const referrer = await waitForMessage(w); + + assert_equals(referrer, originalReferrer, + "javascript: URL-created document's referrer equals the previous document's referrer"); + }, `${referrerPolicyForStartingWindowCreation} referrer policy used to create the starting page`); +} diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-return-value-handling-dynamic.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-return-value-handling-dynamic.html new file mode 100644 index 0000000000..3c08d29674 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-return-value-handling-dynamic.html @@ -0,0 +1,58 @@ +<!doctype html> +<meta charset=windows-1252> <!-- intentionally not UTF-8 to test that the javascript: frames are forced to UTF-8 --> +<title>Test javascript URL string return values in direct and indirect (target) frame contexts.</title> +<!-- Waiting on https://github.com/whatwg/html/pull/6781 to be non-tentative. --> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<div id=log></div> +<script> +const testCases = [ + [[0x41]], + [[0x80,0xFF]], + [[0x80,0xFF,0x100]], + [[0xD83D,0xDE0D]], + [[0xDE0D,0x41], [0xFFFD,0x41]] +]; + +function formatCharCodes(charCodes) { + return charCodes.map(code => code.toString(16).toUpperCase().padStart(4, '0')).join(" "); +} + +for (const [input, expected = input] of testCases) { + const javascriptURL = `javascript:String.fromCharCode(${input})`; + const output = String.fromCharCode(...expected); + + async_test(t => { + const frame = document.createElement("iframe"); + t.add_cleanup(() => frame.remove()); + frame.src = javascriptURL; + + t.step_timeout(() => { + assert_equals(frame.contentDocument.body.textContent, output); + assert_equals(frame.contentDocument.charset, "UTF-8"); + t.done(); + }, 200); + + document.body.appendChild(frame); + }, `${formatCharCodes(input)} set in src=""`); + + async_test(t => { + const frame = document.createElement("iframe"); + const href = document.createElement("a"); + t.add_cleanup(() => { frame.remove(); href.remove(); }); + frame.name = "hi" + input; + href.target = "hi" + input; + href.href = javascriptURL; + + t.step_timeout(() => { + assert_equals(frame.contentDocument.body.textContent, output); + assert_equals(frame.contentDocument.charset, "UTF-8"); + t.done(); + }, 200) + + document.body.appendChild(frame); + document.body.appendChild(href); + href.click(); + }, `${formatCharCodes(input)} set in href="" targeting a frame and clicked`); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-return-value-handling.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-return-value-handling.html new file mode 100644 index 0000000000..621a8cbaec --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-return-value-handling.html @@ -0,0 +1,36 @@ +<!doctype html> +<meta charset=utf-8> +<title>Test that javascript: evaluation only performs a navigation to the + result when the result is a string value.</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<iframe src="javascript:'1'"></iframe> +<iframe src="javascript:1"></iframe> +<iframe src="javascript:({ toString: function() { return '1'; } })"></iframe> +<iframe src="javascript:undefined"></iframe> +<iframe src="javascript:null"></iframe> +<iframe src="javascript:true"></iframe> +<iframe src="javascript:new String('1')"></iframe> +<script> + var t = async_test(); + onload = t.step_func_done(function() { + assert_equals(frames[0].document.documentElement.textContent, + "1", "string return should cause navigation"); + // The rest of the test is disabled for now, until + // https://github.com/whatwg/html/issues/1895 gets sorted out +/* + assert_equals(frames[1].document.documentElement.textContent, + "", "number return should not cause navigation"); + assert_equals(frames[2].document.documentElement.textContent, + "", "object return should not cause navigation"); + assert_equals(frames[3].document.documentElement.textContent, + "", "undefined return should not cause navigation"); + assert_equals(frames[4].document.documentElement.textContent, + "", "null return should not cause navigation"); + assert_equals(frames[5].document.documentElement.textContent, + "", "null return should not cause navigation"); + assert_equals(frames[6].document.documentElement.textContent, + "", "String object return should not cause navigation"); +*/ + }); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-security-check-failure.sub.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-security-check-failure.sub.html new file mode 100644 index 0000000000..a153ad3e48 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-security-check-failure.sub.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>javascript: URL security check</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<body> +<script> +"use strict"; + +const cases = [ + ["cross-origin", "http://{{hosts[][www]}}:{{ports[http][0]}}/common/blank.html"], + ["cross-origin-domain but same-origin", "/html/browsers/windows/resources/document-domain-setter.html"] +]; + +for (const [description, url] of cases) { + promise_test(async t => { + const iframe = await insertIframe(t, url); + + const unreached = t.unreached_func("message event fired"); + t.add_cleanup(() => window.removeEventListener("message", unreached)); + window.addEventListener("message", unreached); + + iframe.src = `javascript:parent.postMessage("boo", "*")`; + + // If no message was received after this time, the test passes. + await new Promise(r => t.step_timeout(r, 50)); + }, `${description}, setting src`); + + promise_test(async t => { + const iframe = await insertIframe(t, url); + + const unreached = t.unreached_func("message event fired"); + t.add_cleanup(() => window.removeEventListener("message", unreached)); + window.addEventListener("message", unreached); + + iframe.contentWindow.location.href = `javascript:parent.postMessage("boo", "*")`; + + // If no message was received after this time, the test passes. + await new Promise(r => t.step_timeout(r, 50)); + }, `${description}, setting location.href`); +} + +function insertIframe(t, url) { + return new Promise((resolve, reject) => { + const iframe = document.createElement("iframe"); + iframe.src = url; + iframe.onload = () => resolve(iframe); + iframe.onerror = () => reject(new Error("Failed to load the outer iframe")); + + t.add_cleanup(() => iframe.remove()); + + document.body.append(iframe); + }); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-security-check-multi-globals.sub.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-security-check-multi-globals.sub.html new file mode 100644 index 0000000000..4b9d3b7afa --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-security-check-multi-globals.sub.html @@ -0,0 +1,66 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Multi-globals: which one is the initiator for the javascript: URL security check?</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<body> +<script> +"use strict"; +document.domain = "{{hosts[][]}}"; + +// These tests would fail if a different pair of origins were compared (see, e.g., the discussion in +// https://github.com/whatwg/html/issues/6514). + +promise_test(async t => { + const iframe = await insertIframe(t); + const innerIframe = iframe.contentDocument.querySelector("iframe"); + + // - incumbentNavigationOrigin = this page's origin, http://{{hosts[][]}}:{{ports[http][0]}} + // - iframe's current origin is this origin, http://{{hosts[][]}}:{{ports[http][0]}}. + // javascript:'s security check uses incumbentNavigationOrigin vs. the iframe's current origin + // so the check will pass and the result will get written. + innerIframe.src = "javascript:'test'"; + + await waitForLoad(innerIframe, "Failed to load the javascript: URL"); + + assert_equals(innerIframe.contentDocument.body.textContent, "test"); +}, "Using iframeEl.src"); + +promise_test(async t => { + const iframe = await insertIframe(t); + const innerIframe = iframe.contentDocument.querySelector("iframe"); + + // Here, https://html.spec.whatwg.org/#location-object-navigate sets the source browsing context to the + // incumbent settings object's browsing context. So incumbentNavigationOrigin = this page's origin, + // http://{{hosts[][]}}:{{ports[http][0]}}. + // + // So again, the check will pass. + + iframe.contentWindow.frames[0].location.href = "javascript:'test'"; + + await waitForLoad(innerIframe, "Failed to load the javascript: URL"); + + assert_equals(innerIframe.contentDocument.body.textContent, "test"); +}, "Using location.href"); + +function insertIframe(t) { + return new Promise((resolve, reject) => { + const iframe = document.createElement("iframe"); + iframe.src = "http://{{hosts[][www]}}:{{ports[http][0]}}/html/browsers/browsing-the-web/navigating-across-documents/resources/multi-globals-subframe-1.sub.html"; + iframe.onload = () => resolve(iframe); + iframe.onerror = () => reject(new Error("Failed to load the outer iframe")); + + t.add_cleanup(() => iframe.remove()); + + document.body.append(iframe); + }); +} + +function waitForLoad(iframe, errorMessage = "Failed to load iframe") { + return new Promise((resolve, reject) => { + iframe.onload = () => resolve(iframe); + iframe.onerror = () => reject(new Error(errorMessage)); + }); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-security-check-same-origin-domain.sub.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-security-check-same-origin-domain.sub.html new file mode 100644 index 0000000000..a14a13cfd6 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-security-check-same-origin-domain.sub.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>javascript: URL security check for same-origin-domain but not same-origin</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<iframe src="http://{{hosts[][www]}}:{{ports[http][0]}}/html/browsers/browsing-the-web/navigating-across-documents/resources/document-domain-set-to-site.sub.html"></iframe> +<script> +"use strict"; +document.domain = "{{host}}"; + +setup({ explicit_done: true }); + +window.onload = () => { + async_test(t => { + assert_equals(frames[0].document.body.textContent, "", "before"); + + window.onmessage = t.step_func_done(() => { + assert_equals(frames[0].document.body.textContent, "new", "after"); + }); + + frames[0].location.href = "javascript:parent.postMessage('done', '*'); 'new';"; + }); + done(); +}; +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-task-queuing.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-task-queuing.html new file mode 100644 index 0000000000..1bb05bfb19 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-task-queuing.html @@ -0,0 +1,58 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>javascript: URL task queuing</title> +<link rel="help" href="https://github.com/whatwg/html/issues/3730"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<body> +<script> +"use strict"; + +testIsAsync(() => { + const iframe = document.createElement("iframe"); + document.body.append(iframe); + iframe.contentWindow.location.href = "javascript:window.top.javascriptURLRan = true; window.top.resolveTestPromise();"; +}, `Navigating an iframe via location.href to a javascript: URL must queue a task`); + +testIsAsync(() => { + const iframe = document.createElement("iframe"); + iframe.src = "javascript:window.top.javascriptURLRan = true; window.top.resolveTestPromise();"; + document.body.append(iframe); +}, `Navigating an iframe via src="" to a javascript: URL before insertion must queue a task`); + +testIsAsync(() => { + const iframe = document.createElement("iframe"); + document.body.append(iframe); + iframe.src = "javascript:window.top.javascriptURLRan = true; window.top.resolveTestPromise();"; +}, `Navigating an iframe via src="" to a javascript: URL after insertion must queue a task`); + +testIsAsync(() => { + const w = window.open(); + w.location.href = "javascript:window.opener.javascriptURLRan = true; window.opener.resolveTestPromise();"; +}, `Navigating an opened window via location.href to a javascript: URL must queue a task`); + +testIsAsync(() => { + window.open("javascript:window.opener.javascriptURLRan = true; window.opener.resolveTestPromise();"); +}, `Navigating an opened window as part of creation to a javascript: URL must queue a task`); + +function testIsAsync(setupFunc, description) { + promise_test(async t => { + t.add_cleanup(() => { + delete window.resolveTestPromise; + delete window.javascriptURLRan; + }); + + const ranPromise = new Promise(resolve => { + window.resolveTestPromise = resolve; + }); + + setupFunc(); + + assert_equals(window.javascriptURLRan, undefined, "Must not run sync"); + + // Ensure that we do actually run the code, though. + await ranPromise; + }, description); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/context-for-location-assign.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/context-for-location-assign.html new file mode 100644 index 0000000000..cb2984d409 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/context-for-location-assign.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/context-helper.js"></script> +<script> +window.scriptToRun = 'relevantWindow.location.assign("target.html");'; + +async_test(t => { + window.addEventListener("message", t.step_func_done(function(e) { + // Base URL used for parsing a relative URL to `target.html` + // should be the base URL of the entry settings object in + // https://html.spec.whatwg.org/C/#dom-location-assign + assert_equals( + e.data.location, + new URL('target.html', entryUrl).href, + 'Base URL should use the entry settings object'); + + // `document.referrer` should reflect the source browsing context, + // which is the incumbent in + // https://html.spec.whatwg.org/C/#location-object-navigate + assert_equals( + e.data.referrer, incumbentUrl, + 'Referrer should use the incumbent settings object'); + })); +}, 'Fetch client and URL resolution for location.assign()'); +</script> +<iframe id="entry" src="entry/entry.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/context-for-location-href.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/context-for-location-href.html new file mode 100644 index 0000000000..02ff214ed5 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/context-for-location-href.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/context-helper.js"></script> +<script> +window.scriptToRun = 'relevantWindow.location.href = "target.html";'; + +async_test(t => { + window.addEventListener("message", t.step_func_done(function(e) { + // Base URL used for parsing a relative URL to `target.html` + // should be the base URL of the entry settings object in + // https://html.spec.whatwg.org/C/#dom-location-href + assert_equals( + e.data.location, + new URL('target.html', entryUrl).href, + 'Base URL should use the entry settings object'); + + // `document.referrer` should reflect the source browsing context, + // which is the incumbent in + // https://html.spec.whatwg.org/C/#location-object-navigate + assert_equals( + e.data.referrer, incumbentUrl, + 'Referrer should use the incumbent settings object'); + })); +}, 'Fetch client and URL resolution for location.href setter'); +</script> +<iframe id="entry" src="entry/entry.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/context-for-location.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/context-for-location.html new file mode 100644 index 0000000000..fae17dd2ac --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/context-for-location.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/context-helper.js"></script> +<script> +window.scriptToRun = 'relevantWindow.location = "target.html";'; + +async_test(t => { + window.addEventListener("message", t.step_func_done(function(e) { + // Base URL used for parsing a relative URL to `target.html` + // should be the base URL of the entry settings object in + // https://html.spec.whatwg.org/C/#dom-location-assign + assert_equals( + e.data.location, + new URL('target.html', entryUrl).href, + 'Base URL should use the entry settings object'); + + // `document.referrer` should reflect the source browsing context, + // which is the incumbent in + // https://html.spec.whatwg.org/C/#location-object-navigate + assert_equals( + e.data.referrer, incumbentUrl, + 'Referrer should use the incumbent settings object'); + })); +}, 'Fetch client and URL resolution for location setter'); +</script> +<iframe id="entry" src="entry/entry.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/context-for-window-open.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/context-for-window-open.html new file mode 100644 index 0000000000..0a391ef28e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/context-for-window-open.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/context-helper.js"></script> +<script> +window.scriptToRun = + 'relevantWindow.open("target.html", "target");'; + +async_test(t => { + window.addEventListener("message", t.step_func_done(function(e) { + // Base URL used for parsing a relative URL to `target.html` + // should be the base URL of the entry settings object in + // https://html.spec.whatwg.org/C/#window-open-steps + assert_equals( + e.data.location, + new URL('target.html', entryUrl).href, + 'Base URL should use the entry settings object'); + + // `document.referrer` should reflect the source browsing context, + // which is the entry in + // https://html.spec.whatwg.org/C/#window-open-steps + assert_equals( + e.data.referrer, entryUrl, + 'Referrer should use the entry settings object'); + })); +}, 'Fetch client and URL resolution for window.open()'); +</script> +<iframe id="entry" src="entry/entry.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/entry/entry.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/entry/entry.html new file mode 100644 index 0000000000..82fecfdd38 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/entry/entry.html @@ -0,0 +1,4 @@ +<body onload="top.go()"> +<iframe id="incumbent" src="../incumbent/empty.html"></iframe> +<iframe id="relevant" src="../relevant/empty.html"></iframe> +</body> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/entry/target.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/entry/target.html new file mode 100644 index 0000000000..5ceaeaf07e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/entry/target.html @@ -0,0 +1,2 @@ +<!DOCTYPE html> +<script src="../resources/target.js"></script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/incumbent/empty.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/incumbent/empty.html new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/incumbent/empty.html diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/incumbent/target.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/incumbent/target.html new file mode 100644 index 0000000000..5ceaeaf07e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/incumbent/target.html @@ -0,0 +1,2 @@ +<!DOCTYPE html> +<script src="../resources/target.js"></script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/relevant/empty.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/relevant/empty.html new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/relevant/empty.html diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/relevant/target.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/relevant/target.html new file mode 100644 index 0000000000..5ceaeaf07e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/relevant/target.html @@ -0,0 +1,2 @@ +<!DOCTYPE html> +<script src="../resources/target.js"></script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/resources/context-helper.js b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/resources/context-helper.js new file mode 100644 index 0000000000..dda338b4cc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/resources/context-helper.js @@ -0,0 +1,34 @@ +// Usage: in the top-level Window, include: +// +// <script src="resources/context-helper.js"></script> +// <script> +// window.scriptToRun = '...'; +// </script> +// <iframe id="entry" src="entry/entry.html"></iframe> +// +// Then `scriptToRun` is evaluated, with: +// - The entry Realm is that of entry/entry.html +// - The incumbent Realm is that of incumbent/empty.html +// - The relevant Realm of `relevantWindow`, `relevantWindow.location` etc. is +// that of relevant/empty.html + +window.scriptToRun = ''; + +const entryUrl = new URL('entry/entry.html', location).href; +const incumbentUrl = new URL('incumbent/empty.html', location).href; +const relevantUrl = new URL('relevant/empty.html', location).href; + +function go() { + const entry = document.querySelector('#entry'); + const incumbent = entry.contentDocument.querySelector('#incumbent'); + const incumbentScript = incumbent.contentDocument.createElement('script'); + incumbentScript.textContent = ` + function go() { + const relevantWindow = + parent.document.querySelector('#relevant').contentWindow; + ${window.scriptToRun} + } + `; + incumbent.contentDocument.head.appendChild(incumbentScript); + incumbent.contentWindow.go(); +} diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/resources/target.js b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/resources/target.js new file mode 100644 index 0000000000..e3a507b0d8 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/multiple-globals/resources/target.js @@ -0,0 +1,11 @@ +window.onload = function() { + let testWindow; + if (opener) { + testWindow = opener.top; + } else { + testWindow = top; + } + testWindow.postMessage( + {location: location.href, referrer: document.referrer}, + "*"); +} diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigate-cross-origin-iframe-to-same-url-with-fragment-fire-load-event.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigate-cross-origin-iframe-to-same-url-with-fragment-fire-load-event.html new file mode 100644 index 0000000000..f74bbfd7d3 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigate-cross-origin-iframe-to-same-url-with-fragment-fire-load-event.html @@ -0,0 +1,31 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<body> +<script> +async_test(t => { + const crossOriginUrl = new URL(get_host_info().HTTPS_REMOTE_ORIGIN); + crossOriginUrl.pathname = "/common/blank.html"; + const i = document.createElement("iframe"); + i.src = crossOriginUrl; + document.body.appendChild(i); + + let wasLoadEventFired = false; + i.onload = t.step_func(() => { + // Though iframe is cross-origin and changing hash leads soft reload, the + // load event should be fired to protect sensitive information. + // See: https://crbug.com/1248444 + crossOriginUrl.hash = "#foo"; + i.onload = () => { + assert_false(wasLoadEventFired) + wasLoadEventFired = true; + // Wait for a while to ensure other onload events are never fired. + t.step_timeout(() => t.done(), 100); + }; + i.src = crossOriginUrl; + }); + +}, "Changing the URL hash of a cross-origin iframe should fire a load event"); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigate-cross-origin-iframe-to-same-url-with-fragment.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigate-cross-origin-iframe-to-same-url-with-fragment.html new file mode 100644 index 0000000000..ed228ad59b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigate-cross-origin-iframe-to-same-url-with-fragment.html @@ -0,0 +1,27 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<body> +<script> +async_test(t => { + let starting_history_length = history.length; + let cross_origin_url = new URL(get_host_info().HTTPS_REMOTE_ORIGIN); + cross_origin_url.pathname = "/common/blank.html"; + cross_origin_url.hash = "#foo"; + let i = document.createElement("iframe"); + i.src = cross_origin_url; + document.body.appendChild(i); + + window.onload = () => t.step_timeout(() => { + assert_equals(starting_history_length, history.length); + i.src = cross_origin_url; + // Give the navigation time to happen - no events will fire. + t.step_timeout(() => { + assert_equals(starting_history_length + 1, history.length); + t.done(); + }, 100); + }, 0); +}, "Navigating a cross-origin iframe to its current url should not replace"); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigate-cross-origin-iframe-to-same-url.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigate-cross-origin-iframe-to-same-url.html new file mode 100644 index 0000000000..9996d58914 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigate-cross-origin-iframe-to-same-url.html @@ -0,0 +1,22 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<body> +<script> +async_test(t => { + let starting_history_length = history.length; + let cross_origin_url = new URL(get_host_info().HTTPS_REMOTE_ORIGIN); + cross_origin_url.pathname = "/common/blank.html"; + let i = document.createElement("iframe"); + i.src = cross_origin_url; + document.body.appendChild(i); + + window.onload = () => t.step_timeout(() => { + assert_equals(starting_history_length, history.length); + i.onload = t.step_func_done(() => assert_equals(starting_history_length + 1, history.length)); + i.src = cross_origin_url; + }, 0); +}, "Navigating a cross-origin iframe to its current url should not replace"); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigate-to-unparseable-url.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigate-to-unparseable-url.html new file mode 100644 index 0000000000..f4e4e36f37 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigate-to-unparseable-url.html @@ -0,0 +1,58 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>location.href unparseable URL throws a SyntaxError DOMException</title> +<link rel="help" href="https://html.spec.whatwg.org/#the-location-interface:dom-location-href-2"> +<link rel="help" href="https://html.spec.whatwg.org/#following-hyperlinks-2"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<body> +<script> +const kUnparseableURL = self.origin + ":notaport/common/blank.html"; + +promise_test(async t => { + const win = window.open("/common/blank.html"); + t.add_cleanup(() => { + win.close(); + }); + + await new Promise(resolve => { + win.onload = resolve; + }); + + assert_throws_dom("SyntaxError", win.DOMException, () => { + win.location.href = kUnparseableURL; + }, "location.href setter throws a SyntaxError DOMException"); +}, "location.href setter throws a SyntaxError DOMException for unparseable " + + "URLs"); + +promise_test(async t => { + const win = window.open("/common/blank.html"); + t.add_cleanup(() => { + win.close(); + }); + + await new Promise(resolve => { + win.onload = resolve; + }); + + // If the newly-opened window tries to navigate, fail the test. + const failPromise = new Promise((resolve, reject) => { + win.onunload = () => + reject(new Error("Navigation was attempted to unparseable URL")); + }); + + // A promise to wait on to confirm the newly-opened window did not navigate. + const successPromise = new Promise(resolve => { + t.step_timeout(resolve, 2000); + }); + + const a = win.document.createElement('a'); + a.href = kUnparseableURL; + win.document.body.append(a); + a.click(); + + return Promise.race([successPromise, failPromise]); +}, "<a> tag navigate fails for unparseable URLs"); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-cross-origin.sub.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-cross-origin.sub.window.js new file mode 100644 index 0000000000..f23e2c440b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-cross-origin.sub.window.js @@ -0,0 +1,15 @@ +// META: title=Cross-origin navigation started from unload handler must be ignored +// META: script=../resources/helpers.js + +promise_test(async () => { + const iframe = await addIframe(); + + iframe.contentWindow.addEventListener("unload", () => { + iframe.contentWindow.location.href = "//{{hosts[][www]}}/common/blank.html?fail"; + }); + + iframe.src = "/common/blank.html?pass"; + + await waitForIframeLoad(iframe); + assert_equals(iframe.contentWindow.location.search, "?pass"); +}); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-data-url.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-data-url.window.js new file mode 100644 index 0000000000..cf39b01107 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-data-url.window.js @@ -0,0 +1,15 @@ +// META: title=data: URL navigation started from unload handler must be ignored +// META: script=../resources/helpers.js + +promise_test(async () => { + const iframe = await addIframe(); + + iframe.contentWindow.addEventListener("unload", () => { + iframe.contentWindow.location.href = + `data:text/html,unload<script>parent.postMessage('fail', '*');</script>`; + }); + + iframe.src = + `data:text/html,load<script>parent.postMessage('pass', '*')</script>`; + assert_equals(await waitForMessage(iframe.contentWindow), "pass"); +}); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-form-submit-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-form-submit-1.html new file mode 100644 index 0000000000..e06def9b20 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-form-submit-1.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> + +<h1>navigation-unload-form-submit-1.html</h1> + +<script> +let isSubmit = false; + +window.unload = function () { + window.location = 'is-Submit' + isSubmit; +} + +function setIsSubmit() { + isSubmit = true; +} +</script> + +<form onsubmit="setIsSubmit" action="navigation-unload-form-submit-2.html"> + <input type="submit"> +</form> + +<script> +parent.finishedLoading(); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-form-submit-2.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-form-submit-2.html new file mode 100644 index 0000000000..43cd3c1b33 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-form-submit-2.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> + +<h1>navigation-unload-form-submit-2.html</h1> + +<script> +parent.finishedLoading(); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-form-submit.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-form-submit.html new file mode 100644 index 0000000000..029170d46a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-form-submit.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://html.spec.whatwg.org/multipage/browsers.html#navigating-across-documents"> +<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"> + +<iframe id="i" src="navigation-unload-form-submit-1.html"></iframe> + +<!-- derived from https://bugzilla.mozilla.org/show_bug.cgi?id=247660#c0 --> + +<script> +var test = async_test('Tests that navigation during an unload caused by a form submit does nothing'); +window.onload = test.step_func(function() { + var i = document.querySelector('#i'); + + window.finishedLoading = test.step_func_done(function () { + assert_equals(i.contentWindow.location.pathname.split('/').pop(), 'navigation-unload-form-submit-2.html'); + assert_equals(i.contentWindow.location.hash, ''); + }); + + i.contentWindow.document.querySelector('input[type="submit"]').click(); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-javascript-url.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-javascript-url.window.js new file mode 100644 index 0000000000..abbcb888a0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-javascript-url.window.js @@ -0,0 +1,15 @@ +// META: title=javascript: URL navigation started from unload handler must be ignored +// META: script=../resources/helpers.js + +promise_test(async () => { + const iframe = await addIframe(); + + iframe.contentWindow.addEventListener("unload", () => { + iframe.contentWindow.location.href = + `javascript:"unload<script>parent.postMessage('fail', '*');</script>"`; + }); + + iframe.src = + `javascript:"load<script>parent.postMessage('pass', '*')</script>"`; + assert_equals(await waitForMessage(iframe.contentWindow), "pass"); +}); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-same-origin-fragment-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-same-origin-fragment-1.html new file mode 100644 index 0000000000..3b5d725fee --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-same-origin-fragment-1.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> + +<h1>navigation-unload-same-origin-fragment-1.html</h1> + +<script> +if (parent.finishedLoading) { + parent.finishedLoading(); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-same-origin-fragment-2.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-same-origin-fragment-2.html new file mode 100644 index 0000000000..2fb7f50b43 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-same-origin-fragment-2.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> + +<h1>navigation-unload-same-origin-fragment-2.html</h1> + +<script> +if (parent.finishedLoading) { + parent.finishedLoading(); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-same-origin-fragment.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-same-origin-fragment.html new file mode 100644 index 0000000000..c4dceb9e04 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-same-origin-fragment.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://html.spec.whatwg.org/multipage/browsers.html#navigating-across-documents"> +<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"> + +<iframe id="i" src="navigation-unload-same-origin-fragment-1.html"></iframe> + +<!-- a timeout indicates that setting i.contentWindow.location.hash (a second navigation) aborted the first navigation, + and so it stayed on a.html and finishedLoading was never called --> + +<script> +var test = async_test('Tests that a fragment navigation in the unload handler will not block the initial navigation'); +window.onload = test.step_func(function() { + var i = document.querySelector('#i'); + + i.contentWindow.onunload = test.step_func(function() { + i.contentWindow.location.hash = '#fragment'; + }); + + window.finishedLoading = test.step_func_done(function () { + assert_equals(i.contentWindow.location.pathname.split('/').pop(), 'navigation-unload-same-origin-fragment-2.html'); + assert_equals(i.contentWindow.location.hash, ''); + }); + + i.contentWindow.location.href = 'navigation-unload-same-origin-fragment-2.html'; +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-same-origin.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-same-origin.window.js new file mode 100644 index 0000000000..826af4b75b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-same-origin.window.js @@ -0,0 +1,15 @@ +// META: title=Same-origin navigation started from unload handler must be ignored +// META: script=../resources/helpers.js + +promise_test(async () => { + const iframe = await addIframe(); + + iframe.contentWindow.addEventListener("unload", () => { + iframe.contentWindow.location.href = "/common/blank.html?fail"; + }); + + iframe.src = "/common/blank.html?pass"; + + await waitForIframeLoad(iframe); + assert_equals(iframe.contentWindow.location.search, "?pass"); +}); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/plugin-document.historical.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/plugin-document.historical.html new file mode 100644 index 0000000000..547917b795 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/plugin-document.historical.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Same-origin PDFs must not create accessible Document objects</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- https://github.com/whatwg/html/pull/6947 --> + +<iframe src="resources/portable-document-format-sample-valid.pdf"></iframe> + +<script> +setup({ explicit_done: true }); + +window.onload = () => { + test(() => { + assert_throws_dom("SecurityError", () => { + document.querySelector("iframe").contentWindow.document; + }); + }); + done(); +}; +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/redirect-to-about.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/redirect-to-about.window.js new file mode 100644 index 0000000000..5480911895 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/redirect-to-about.window.js @@ -0,0 +1,34 @@ +"use strict"; + +["about:blank", "about:srcdoc", "about:nonstandard"].forEach(aboutURL => { + promise_test(async t => { + const iframe = document.createElement("iframe"); + iframe.src = `resources/redirect.py?location=${aboutURL}`; + document.body.append(iframe); + + // Unfortunately Firefox does not fire a load event for network errors yet, but there is no + // other way I can see to test this. (Also applicable below.) + await new Promise(r => iframe.onload = r); + + // Must throw since error pages are opaque origin. + assert_throws_dom("SecurityError", () => { + iframe.contentWindow.document; + }); + }, `An iframe with src set to a redirect to ${aboutURL}`); + + promise_test(async t => { + const iframe = document.createElement("iframe"); + iframe.src = "/common/blank.html"; + document.body.append(iframe); + + await new Promise(r => iframe.onload = r); + + iframe.contentWindow.location.href = `resources/redirect.py?location=${aboutURL}`; + await new Promise(r => iframe.onload = r); + + // Must throw since error pages are opaque origin. + assert_throws_dom("SecurityError", () => { + iframe.contentWindow.document; + }); + }, `An iframe that is navigated to a redirect to ${aboutURL}`); +}); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/redirect-to-data.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/redirect-to-data.html new file mode 100644 index 0000000000..f9e8021ddf --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/redirect-to-data.html @@ -0,0 +1,75 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Redirecting to data: URLs is disallowed</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<body> +<script> +"use strict"; + +promise_test(async (t) => { + window.onmessage = t.unreached_func("must not be messaged"); + t.add_cleanup(() => { window.onmessage = null; }); + + const iframe = document.createElement("iframe"); + iframe.src = `resources/redirect.py?location=data:text/html,FAIL<script>parent.postMessage('FAIL', '*')</${'script'}>`; + document.body.append(iframe); + + await new Promise(r => iframe.onload = r); + + // Must throw since error pages are opaque origin. + assert_throws_dom("SecurityError", () => { + iframe.contentWindow.document; + }); + + // Test passes if after 100 ms we haven't gotten the message. + await new Promise(r => t.step_timeout(r, 100)); +}, "Loading an iframe with src=redirecting URL"); + +promise_test(async (t) => { + window.onmessage = t.unreached_func("must not be messaged"); + t.add_cleanup(() => { window.onmessage = null; }); + + const iframe = document.createElement("iframe"); + iframe.src = "/common/blank.html"; + document.body.append(iframe); + + await new Promise(r => iframe.onload = r); + + iframe.contentWindow.location.href = `resources/redirect.py?location=data:text/html,FAIL<script>parent.postMessage('FAIL', '*')</${'script'}>`; + await new Promise(r => iframe.onload = r); + + // Must throw since error pages are opaque origin. + assert_throws_dom("SecurityError", () => { + iframe.contentWindow.document; + }); + + // Test passes if after 100 ms we haven't gotten the message. + await new Promise(r => t.step_timeout(r, 100)); +}, "Navigating an iframe to a redirecting URL"); + +promise_test(async (t) => { + window.onmessage = t.unreached_func("must not be messaged"); + t.add_cleanup(() => { window.onmessage = null; }); + + const w = window.open(`resources/redirect.py?location=data:text/html,FAIL<script>parent.postMessage('FAIL', '*')</${'script'}>`); + + // Test passes if after 100 ms we haven't gotten the message. + await new Promise(r => t.step_timeout(r, 100)); +}, "Loading a popup directly to the redirecting URL"); + +promise_test(async (t) => { + const w = window.open(`resources/message-opener.html`); + await new Promise(r => window.onmessage = r); + + window.onmessage = t.unreached_func("must not be messaged"); + t.add_cleanup(() => { window.onmessage = null; }); + + w.location.href = `resources/redirect.py?location=data:text/html,FAIL<script>parent.postMessage('FAIL', '*')</${'script'}>`; + + // Test passes if after 100 ms we haven't gotten the message. + await new Promise(r => t.step_timeout(r, 100)); +}, "Loading a popup that eventually goes to the redirecting URL"); + +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/redirect-to-unparseable-url.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/redirect-to-unparseable-url.html new file mode 100644 index 0000000000..b025f34478 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/redirect-to-unparseable-url.html @@ -0,0 +1,71 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Session history interaction with redirects to unparseable URLs</title> +<link rel="help" href="https://html.spec.whatwg.org/#create-navigation-params-by-fetching"> +<link rel="help" href="https://html.spec.whatwg.org/#read-ua-inline"> +<script src="/common/utils.js"></script> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<body> +<script> +const kUnparseableURL = self.origin + ":notaport/common/blank.html"; + +promise_test(async t => { + const iframe = document.createElement('iframe'); + t.add_cleanup(() => { + iframe.remove(); + }); + + function getIframeLoadPromise() { + return new Promise(resolve => { + iframe.addEventListener('load', () => { + // Wait for the iframe to load + one task so that its navigations are + // not done in "replace" mode. + t.step_timeout(resolve, 0); + }, {once: true}); + }); + } + + document.body.append(iframe); + + assert_equals(history.length, 1, "Precondition: history.length is 1"); + + const first_load_promise = getIframeLoadPromise(); + iframe.src = '/common/blank.html'; + await first_load_promise; + + // This navigation will fail, because it redirects to an unparseable URL. + const error_load_promise = getIframeLoadPromise(); + const error_url = new URL('resources/no-cache-single-redirect.py', location.href); + error_url.searchParams.append('uuid', token()); + error_url.searchParams.append('location', kUnparseableURL); + iframe.src = error_url; + await error_load_promise; + + assert_equals(history.length, 2, + "history.length is 2 after two iframe navigations beyond the initial " + + "about:blank Document, the first of which 'replaced' the initial " + + "about:blank Document"); + + // Per https://html.spec.whatwg.org/#read-ua-inline, error Documents have + // opaque origins, so the `contentDocument` shouldn't be accessible. + assert_equals(iframe.contentDocument, null, + "Cannot reach iframe.contentDocument for error Documents"); + + const back_load_promise = getIframeLoadPromise(); + history.back(); + await back_load_promise; + + const forward_load_promise = getIframeLoadPromise(); + history.forward(); + await forward_load_promise; + + assert_not_equals(iframe.contentDocument, null, "iframe.contentDocument is accessible"); + assert_equals(iframe.contentDocument.body.innerText, "No redirect", + "Traversal to history entry whose URL was once associated with an " + + "error Document correctly requests the same URL again"); +}, "Navigating to a url (A) that redirects to an unparseable URL (B), saves " + + "the URL (A) in the history entry, for later traversal"); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/refresh/README.md b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/refresh/README.md new file mode 100644 index 0000000000..52548db656 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/refresh/README.md @@ -0,0 +1 @@ +See `/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/parsing.html` for more detailed parsing tests (shared with `<meta http-equiv=refresh>`). diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/refresh/navigate.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/refresh/navigate.window.js new file mode 100644 index 0000000000..7d5a0fe21d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/refresh/navigate.window.js @@ -0,0 +1,23 @@ +async_test(t => { + const frame = document.createElement("iframe"); + frame.src = "resources/refresh.py" + frame.onload = t.step_func(() => { + // Could be better by verifying that resources/refresh.py loads too + if(frame.contentWindow.location.href === (new URL("resources/refreshed.txt?\u0080\u00FF", self.location)).href) { // Make sure bytes got mapped to code points of the same value + t.done(); + } + }); + document.body.appendChild(frame) +}, "When navigating the Refresh header needs to be followed"); + +async_test(t => { + const frame = document.createElement("iframe"); + frame.src = "resources/multiple.asis" + frame.onload = t.step_func(() => { + // Could be better by verifying that resources/refresh.py loads too + if(frame.contentWindow.location.href === (new URL("resources/refreshed.txt", self.location)).href) { + t.done(); + } + }); + document.body.appendChild(frame) +}, "When there's both a Refresh header and <meta> the Refresh header wins") diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/refresh/resources/multiple.asis b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/refresh/resources/multiple.asis new file mode 100644 index 0000000000..3026d8297b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/refresh/resources/multiple.asis @@ -0,0 +1,6 @@ +HTTP/1.1 200 OK +Refresh: 0,./refreshed.txt +Content-Type:text/html + +I don't understand. +<meta http-equiv=refresh content=1;./> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/refresh/resources/refresh.py b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/refresh/resources/refresh.py new file mode 100644 index 0000000000..ecdd24f268 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/refresh/resources/refresh.py @@ -0,0 +1,4 @@ +def main(request, response): + response.headers.set(b"Content-Type", b"text/plain") + response.headers.set(b"Refresh", b"0;./refreshed.txt?\x80\xFF") # Test byte to Unicode conversion + response.content = u"Not refreshed.\n" diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/refresh/resources/refreshed.txt b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/refresh/resources/refreshed.txt new file mode 100644 index 0000000000..5df065b456 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/refresh/resources/refreshed.txt @@ -0,0 +1 @@ +Have another. diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/refresh/subresource.any.js b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/refresh/subresource.any.js new file mode 100644 index 0000000000..930dd34ad5 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/refresh/subresource.any.js @@ -0,0 +1,6 @@ +promise_test(() => { + return fetch("resources/refresh.py").then(response => { + assert_equals(response.headers.get("refresh"), "0;./refreshed.txt?\u0080\u00FF"); // Make sure bytes got mapped to code points of the same value + assert_equals(response.url, (new URL("resources/refresh.py", self.location)).href); + }); +}, "Refresh does not affect subresources."); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-click-during-load.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-click-during-load.html new file mode 100644 index 0000000000..e035b1f517 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-click-during-load.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + window.onload = () => { + const a = document.createElement("a"); + a.href = "/common/blank.html?thereplacement"; + document.body.append(a); + a.click(); + }; + `; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement"; + const iframe = insertIframe(t, startURL); + + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoadAllowingIntermediateLoads(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength + 1, "history.length must change after waiting for the load"); +}, "aElement.click() during the load event must NOT replace"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-click-during-pageshow.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-click-during-pageshow.html new file mode 100644 index 0000000000..006ce531e0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-click-during-pageshow.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + window.onpageshow = () => { + const a = document.createElement("a"); + a.href = "/common/blank.html?thereplacement"; + document.body.append(a); + a.click(); + }; + `; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement"; + const iframe = insertIframe(t, startURL); + + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoadAllowingIntermediateLoads(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength + 1, "history.length must change after waiting for the load"); +}, "aElement.click() during the pageshow event must NOT replace"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-click.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-click.html new file mode 100644 index 0000000000..be7d1a9849 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-click.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + const a = document.createElement("a"); + a.href = "/common/blank.html?thereplacement"; + document.currentScript.before(a); + a.click(); + `; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement"; + const iframe = insertIframe(t, startURL); + + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoad(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength + 1, "history.length must change after waiting for the load"); +}, "aElement.click() before the load event must NOT replace"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-user-click-during-load.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-user-click-during-load.html new file mode 100644 index 0000000000..811c828331 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-user-click-during-load.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + window.onload = () => { + const a = document.createElement("a"); + a.href = "/common/blank.html?thereplacement"; + a.id = "the-anchor"; + a.textContent = "needs to have content to be clickable"; + document.body.append(a); + parent.test_driver.click(a); + }; + `; + + const startURL = "resources/slow-code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement"; + const iframe = insertIframe(t, startURL); + + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoadAllowingIntermediateLoads(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength + 1, "history.length must change after waiting for the load"); +}, "User click on <a> during the load event must NOT replace"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-user-click-during-pageshow.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-user-click-during-pageshow.html new file mode 100644 index 0000000000..6621b081e1 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-user-click-during-pageshow.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + window.onpageshow = () => { + const a = document.createElement("a"); + a.href = "/common/blank.html?thereplacement"; + a.id = "the-anchor"; + a.textContent = "needs to have content to be clickable"; + document.body.append(a); + parent.test_driver.click(a); + }; + `; + + const startURL = "resources/slow-code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement"; + const iframe = insertIframe(t, startURL); + + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoadAllowingIntermediateLoads(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength + 1, "history.length must change after waiting for the load"); +}, "User click on <a> during the pageshow event must NOT replace"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-user-click.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-user-click.html new file mode 100644 index 0000000000..c9034f3573 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-user-click.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + const a = document.createElement("a"); + a.href = "/common/blank.html?thereplacement"; + a.id = "the-anchor"; + a.textContent = "needs to have content to be clickable"; + document.currentScript.before(a); + parent.test_driver.click(a); + `; + + const startURL = "resources/slow-code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement"; + const iframe = insertIframe(t, startURL); + + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoad(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength + 1, "history.length must change after waiting for the load"); +}, "User click on <a> before the load event must NOT replace"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-requestsubmit-during-load.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-requestsubmit-during-load.html new file mode 100644 index 0000000000..0dee49edb3 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-requestsubmit-during-load.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + window.onload = () => { + const form = document.createElement("form"); + form.action = "/common/blank.html"; + + const input = document.createElement("input"); + input.type = "hidden"; + input.name = "thereplacement"; + form.append(input); + + document.body.append(form); + form.requestSubmit(); + }; + `; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement="; + const iframe = insertIframe(t, startURL); + + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoadAllowingIntermediateLoads(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength, "history.length must not change after waiting for the replacement"); + + await checkSentinelIframe(t, sentinelIframe); + assert_equals(history.length, startingHistoryLength, "history.length must not change after checking the sentinel iframe"); +}, "Replace during the load event, triggered by formElement.submit()"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-requestsubmit-during-pageshow.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-requestsubmit-during-pageshow.html new file mode 100644 index 0000000000..0cf0838496 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-requestsubmit-during-pageshow.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + window.onpageshow = () => { + const form = document.createElement("form"); + form.action = "/common/blank.html"; + + const input = document.createElement("input"); + input.type = "hidden"; + input.name = "thereplacement"; + form.append(input); + + document.body.append(form); + form.requestSubmit(); + }; + `; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement="; + const iframe = insertIframe(t, startURL); + + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoadAllowingIntermediateLoads(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength, "history.length must not change after waiting for the replacement"); + + await checkSentinelIframe(t, sentinelIframe); + assert_equals(history.length, startingHistoryLength, "history.length must not change after checking the sentinel iframe"); +}, "Replace during the pageshow event, triggered by formElement.submit()"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-requestsubmit.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-requestsubmit.html new file mode 100644 index 0000000000..80beb3718b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-requestsubmit.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + const form = document.createElement("form"); + form.action = "/common/blank.html"; + + const input = document.createElement("input"); + input.type = "hidden"; + input.name = "thereplacement"; + form.append(input); + + document.currentScript.before(form); + form.requestSubmit(); + `; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement="; + const iframe = insertIframe(t, startURL); + + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoad(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength, "history.length must not change after waiting for the replacement"); + + await checkSentinelIframe(t, sentinelIframe); + assert_equals(history.length, startingHistoryLength, "history.length must not change after checking the sentinel iframe"); +}, "Replace before load, triggered by formElement.requestSubmit()"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit-button-click-during-load.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit-button-click-during-load.html new file mode 100644 index 0000000000..1b3a163176 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit-button-click-during-load.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + window.onload = () => { + const form = document.createElement("form"); + form.action = "/common/blank.html"; + + const input = document.createElement("input"); + input.type = "hidden"; + input.name = "thereplacement"; + form.append(input); + + const button = document.createElement("button"); + button.type = "submit"; + form.append(button); + + document.body.append(form); + button.click(); + }; + `; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement="; + const iframe = insertIframe(t, startURL); + + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoadAllowingIntermediateLoads(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength, "history.length must not change after waiting for the replacement"); + + await checkSentinelIframe(t, sentinelIframe); + assert_equals(history.length, startingHistoryLength, "history.length must not change after checking the sentinel iframe"); +}, "Replace during load, triggered by submitButton.click()"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit-button-click-during-pageshow.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit-button-click-during-pageshow.html new file mode 100644 index 0000000000..c0022fd0f2 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit-button-click-during-pageshow.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + window.onpageshow = () => { + const form = document.createElement("form"); + form.action = "/common/blank.html"; + + const input = document.createElement("input"); + input.type = "hidden"; + input.name = "thereplacement"; + form.append(input); + + const button = document.createElement("button"); + button.type = "submit"; + form.append(button); + + document.body.append(form); + button.click(); + }; + `; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement="; + const iframe = insertIframe(t, startURL); + + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoadAllowingIntermediateLoads(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength, "history.length must not change after waiting for the replacement"); + + await checkSentinelIframe(t, sentinelIframe); + assert_equals(history.length, startingHistoryLength, "history.length must not change after checking the sentinel iframe"); +}, "Replace during pageshow, triggered by submitButton.click()"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit-button-click.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit-button-click.html new file mode 100644 index 0000000000..873c49b5be --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit-button-click.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + const form = document.createElement("form"); + form.action = "/common/blank.html"; + + const input = document.createElement("input"); + input.type = "hidden"; + input.name = "thereplacement"; + form.append(input); + + const button = document.createElement("button"); + button.type = "submit"; + form.append(button); + + document.currentScript.before(form); + button.click(); + `; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement="; + const iframe = insertIframe(t, startURL); + + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoad(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength, "history.length must not change after waiting for the replacement"); + + await checkSentinelIframe(t, sentinelIframe); + assert_equals(history.length, startingHistoryLength, "history.length must not change after checking the sentinel iframe"); +}, "Replace before load, triggered by submitButton.click()"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit-cross-frame-crossorigin.sub.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit-cross-frame-crossorigin.sub.html new file mode 100644 index 0000000000..a2ea20bf7f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit-cross-frame-crossorigin.sub.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<form target="the-frame"> + <input type="hidden" name="pushed"> +</form> + +<script> +"use strict"; +promise_test(async t => { + const startingHistoryLength = history.length; + + const form = document.querySelector("form"); + const frameEndingURL = changeURLHost( + absoluteURL("resources/slow-message-source-with-history-and-location.html?pushed="), + "{{hosts[][www]}}" + ); + form.action = frameEndingURL; + + const frameStartingCode = ` + parent.postMessage({ historyLength: history.length, locationHref: location.href }, "*"); + `; + + const frameStartingURL = codeInjectorURL(frameStartingCode); + const frame = insertIframe(t, frameStartingURL, "the-frame"); + t.add_cleanup(() => frame.remove()); // helps avoid waiting for the slow load to finish the tests + assert_equals(history.length, startingHistoryLength, "Inserting frame must not change history.length"); + + const frameBeforeLoadedMessage = await waitForMessage(); + assert_equals(frameBeforeLoadedMessage.historyLength, startingHistoryLength, "frame's starting history.length"); + assert_equals(frameBeforeLoadedMessage.locationHref, frame.src, "frame's starting location.href"); + + form.submit(); + + const frameAfterFormSubmitMessage = await waitForMessage(); + assert_equals(frameAfterFormSubmitMessage.historyLength, startingHistoryLength + 1, "frame's after-submit history.length"); + assert_equals(frameAfterFormSubmitMessage.locationHref, frameEndingURL, "frame's after-submit location.href"); +}, "No replace before load, triggered by cross-iframe formElement.submit() [iframe is cross-origin]"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit-cross-frame.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit-cross-frame.html new file mode 100644 index 0000000000..d647e6ad06 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit-cross-frame.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<form target="the-frame"> + <input type="hidden" name="pushed"> +</form> + +<script> +"use strict"; +promise_test(async t => { + const startingHistoryLength = history.length; + + const form = document.querySelector("form"); + const frameEndingURL = absoluteURL("resources/slow-message-source-with-history-and-location.html?pushed="); + form.action = frameEndingURL; + + const frameStartingCode = ` + window.onload = () => { window.onloadFired = true; }; + parent.postMessage({ historyLength: history.length, locationHref: location.href }, "*"); + parent.document.querySelector("form").submit(); + `; + + const frameStartingURL = codeInjectorURL(frameStartingCode); + const frame = insertIframe(t, frameStartingURL, "the-frame"); + t.add_cleanup(() => frame.remove()); // helps avoid waiting for the slow load to finish the tests + assert_equals(history.length, startingHistoryLength, "Inserting frame must not change history.length"); + + const frameBeforeLoadedMessage = await waitForMessage(); + assert_equals(frameBeforeLoadedMessage.historyLength, startingHistoryLength, "frame's starting history.length"); + assert_equals(frameBeforeLoadedMessage.locationHref, frame.src, "frame's starting location.href"); + assert_equals(frame.contentWindow.onloadFired, undefined, "frame's onload not fired yet"); + + const frameAfterFormSubmitMessage = await waitForMessage(); + assert_equals(frameAfterFormSubmitMessage.historyLength, startingHistoryLength + 1, "frame's after-submit history.length"); + assert_equals(frameAfterFormSubmitMessage.locationHref, frameEndingURL, "frame's after-submit location.href"); +}, "No replace before load, triggered by cross-iframe formElement.submit()"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit-during-load.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit-during-load.html new file mode 100644 index 0000000000..2a60f44fc6 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit-during-load.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + window.onload = () => { + const form = document.createElement("form"); + form.action = "/common/blank.html"; + + const input = document.createElement("input"); + input.type = "hidden"; + input.name = "thereplacement"; + form.append(input); + + document.body.append(form); + form.submit(); + }; + `; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement="; + const iframe = insertIframe(t, startURL); + + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoadAllowingIntermediateLoads(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength, "history.length must not change after waiting for the replacement"); + + await checkSentinelIframe(t, sentinelIframe); + assert_equals(history.length, startingHistoryLength, "history.length must not change after checking the sentinel iframe"); +}, "Replace during the load event, triggered by formElement.submit()"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit-during-pageshow.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit-during-pageshow.html new file mode 100644 index 0000000000..886bf469a2 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit-during-pageshow.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + window.onpageshow = () => { + const form = document.createElement("form"); + form.action = "/common/blank.html"; + + const input = document.createElement("input"); + input.type = "hidden"; + input.name = "thereplacement"; + form.append(input); + + document.body.append(form); + form.submit(); + }; + `; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement="; + const iframe = insertIframe(t, startURL); + + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoadAllowingIntermediateLoads(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength, "history.length must not change after waiting for the replacement"); + + await checkSentinelIframe(t, sentinelIframe); + assert_equals(history.length, startingHistoryLength, "history.length must not change after checking the sentinel iframe"); +}, "Replace during the pageshow event, triggered by formElement.submit()"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit-popup-crossorigin.sub.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit-popup-crossorigin.sub.html new file mode 100644 index 0000000000..d54cc81477 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit-popup-crossorigin.sub.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<form target="the-window"> + <input type="hidden" name="pushed"> +</form> + +<script> +"use strict"; +promise_test(async t => { + const form = document.querySelector("form"); + const wEndingURL = changeURLHost( + absoluteURL("resources/slow-message-source-with-history-and-location.html?pushed="), + "{{hosts[][www]}}" + ); + form.action = wEndingURL; + + const wStartingCode = ` + opener.postMessage({ historyLength: history.length, locationHref: location.href }, "*"); + `; + + const wStartingURL = codeInjectorURL(wStartingCode); + const w = window.open(wStartingURL, "the-window"); + t.add_cleanup(() => w.close()); + + const wBeforeLoadedMessage = await waitForMessage(); + assert_equals(wBeforeLoadedMessage.historyLength, 1, "window's starting history.length"); + assert_equals(wBeforeLoadedMessage.locationHref, wStartingURL, "window's starting location.href"); + + form.submit(); + + const wAfterFormSubmitMessage = await waitForMessage(); + assert_equals(wAfterFormSubmitMessage.historyLength, 2, "window's after-submit history.length"); + assert_equals(wAfterFormSubmitMessage.locationHref, wEndingURL, "window's after-submit location.href"); +}, "No replace before load, triggered by formElement.submit() in the opener window, after the opener has loaded [window is cross-origin]"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit-popup.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit-popup.html new file mode 100644 index 0000000000..b27d54675d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit-popup.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<form target="the-window"> + <input type="hidden" name="pushed"> +</form> + +<script> +"use strict"; +promise_test(async t => { + const form = document.querySelector("form"); + const wEndingURL = absoluteURL("resources/slow-message-source-with-history-and-location.html?pushed="); + form.action = wEndingURL; + + const wStartingCode = ` + window.onload = () => { window.onloadFired = true; }; + opener.postMessage({ historyLength: history.length, locationHref: location.href }, "*"); + opener.document.querySelector("form").submit(); + `; + + const wStartingURL = codeInjectorURL(wStartingCode); + const w = window.open(wStartingURL, "the-window"); + t.add_cleanup(() => w.close()); + + const wBeforeLoadedMessage = await waitForMessage(); + assert_equals(wBeforeLoadedMessage.historyLength, 1, "window's starting history.length"); + assert_equals(wBeforeLoadedMessage.locationHref, wStartingURL, "window's starting location.href"); + assert_equals(w.onloadFired, undefined, "window's onload not fired yet"); + + const wAfterFormSubmitMessage = await waitForMessage(); + assert_equals(wAfterFormSubmitMessage.historyLength, 2, "window's after-submit history.length"); + assert_equals(wAfterFormSubmitMessage.locationHref, wEndingURL, "window's after-submit location.href"); +}, "No replace before load, triggered by formElement.submit() in the opener window, after the opener has loaded"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit.html new file mode 100644 index 0000000000..eace0b0a69 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + const form = document.createElement("form"); + form.action = "/common/blank.html"; + + const input = document.createElement("input"); + input.type = "hidden"; + input.name = "thereplacement"; + form.append(input); + + document.currentScript.before(form); + form.submit(); + `; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement="; + const iframe = insertIframe(t, startURL); + + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoad(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength, "history.length must not change after waiting for the replacement"); + + await checkSentinelIframe(t, sentinelIframe); + assert_equals(history.length, startingHistoryLength, "history.length must not change after checking the sentinel iframe"); +}, "Replace before load, triggered by same-document formElement.submit()"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/history-pushstate-during-load.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/history-pushstate-during-load.html new file mode 100644 index 0000000000..233d80d8d7 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/history-pushstate-during-load.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + window.onload = () => { + history.pushState(null, null, "/common/blank.html?thereplacement"); + }; + `; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement"; + const iframe = insertIframe(t, startURL); + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoadAllowingIntermediateLoads(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength + 1, "history.length must change after waiting for the replacement"); +}, "history.pushState() during the load event must NOT replace"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/history-pushstate-during-pageshow.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/history-pushstate-during-pageshow.html new file mode 100644 index 0000000000..a4e83baf1d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/history-pushstate-during-pageshow.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + window.onpageshow = () => { + history.pushState(null, null, "/common/blank.html?thereplacement"); + parent.postMessage("done", "*"); + }; + `; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement"; + const iframe = insertIframe(t, startURL); + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + assert_equals(await waitForMessage(), "done"); + assert_equals(history.length, startingHistoryLength + 1, "history.length must change after waiting for the replacement"); +}, "history.pushState() during the pageshow event must NOT replace"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/history-pushstate.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/history-pushstate.html new file mode 100644 index 0000000000..2faef7c319 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/history-pushstate.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent("history.pushState(null, null, `/common/blank.html?thereplacement`);"); + const afterReplacementURL = "/common/blank.html?thereplacement"; + const iframe = insertIframe(t, startURL); + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoad(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength + 1, "history.length must change after waiting for the replacement"); +}, "history.pushState() before the load event must NOT replace"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/iframe-src-during-load.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/iframe-src-during-load.html new file mode 100644 index 0000000000..83f26b2aa9 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/iframe-src-during-load.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + window.onload = () => { + parent.document.querySelectorAll("iframe")[1].src = "/common/blank.html?thereplacement"; + }; + `; + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement"; + const iframe = insertIframe(t, startURL); + + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoadAllowingIntermediateLoads(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength, "history.length must not change after waiting for the replacement"); + + await checkSentinelIframe(t, sentinelIframe); + assert_equals(history.length, startingHistoryLength, "history.length must not change after checking the sentinel iframe"); +}, "Replace during the load event, triggered by setting iframeElement.src"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/iframe-src-during-pageshow.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/iframe-src-during-pageshow.html new file mode 100644 index 0000000000..61345cf7f9 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/iframe-src-during-pageshow.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + window.onpageshow = () => { + parent.document.querySelectorAll("iframe")[1].src = "/common/blank.html?thereplacement"; + }; + `; + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement"; + const iframe = insertIframe(t, startURL); + + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoadAllowingIntermediateLoads(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength, "history.length must not change after waiting for the replacement"); + + await checkSentinelIframe(t, sentinelIframe); + assert_equals(history.length, startingHistoryLength, "history.length must not change after checking the sentinel iframe"); +}, "Replace during the pageshow event, triggered by setting iframeElement.src"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/iframe-src.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/iframe-src.html new file mode 100644 index 0000000000..47407c106e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/iframe-src.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + window.onload = () => { window.onloadFired = true; }; + `; + const startURL = "resources/slow-code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement"; + const iframe = insertIframe(t, startURL); + + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + const absoluteStartURL = (new URL(startURL, location.href)).href; + while (true) { + if (iframe.contentWindow.location.href === absoluteStartURL) { + break; + } + await new Promise(r => setTimeout(r, 0)); + } + + assert_equals(iframe.contentWindow.location.href, (new URL(startURL, location.href)).href, "Iframe must be navigated away from the initial about:blank document"); + assert_equals(iframe.contentWindow.onloadFired, undefined, "onload must not yet have fired"); + + iframe.src = afterReplacementURL; + + await checkSentinelIframe(t, sentinelIframe); + assert_equals(history.length, startingHistoryLength, "history.length must not change after checking the sentinel iframe"); +}, "Replace before load, triggered by setting iframeElement.src"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-assign-during-load.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-assign-during-load.html new file mode 100644 index 0000000000..2b4ac49533 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-assign-during-load.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + window.onload = () => { + location.assign("/common/blank.html?thereplacement"); + }; + `; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement"; + const iframe = insertIframe(t, startURL); + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoadAllowingIntermediateLoads(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength, "history.length must not change after waiting for the replacement"); + + await checkSentinelIframe(t, sentinelIframe); + assert_equals(history.length, startingHistoryLength, "history.length must not change after checking the sentinel iframe"); +}, "Replace during the load event, triggered by location.assign()"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-assign-during-pageshow.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-assign-during-pageshow.html new file mode 100644 index 0000000000..0f84cbe246 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-assign-during-pageshow.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + window.onpageshow = () => { + location.assign("/common/blank.html?thereplacement"); + }; + `; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement"; + const iframe = insertIframe(t, startURL); + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoadAllowingIntermediateLoads(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength, "history.length must not change after waiting for the replacement"); + + await checkSentinelIframe(t, sentinelIframe); + assert_equals(history.length, startingHistoryLength, "history.length must not change after checking the sentinel iframe"); +}, "Replace during the pageshow event, triggered by location.assign()"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-assign-user-click.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-assign-user-click.html new file mode 100644 index 0000000000..785ec024e0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-assign-user-click.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + const button = document.createElement("button"); + button.id = "the-button"; + button.textContent = "needs to have content to be clickable"; + button.onclick = () => { location.assign("/common/blank.html?thereplacement"); }; + document.currentScript.before(button); + parent.test_driver.click(button); + `; + + const startURL = "resources/slow-code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement"; + const iframe = insertIframe(t, startURL); + + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoad(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength + 1, "history.length must change after waiting for the load"); +}, "NO replace before load, triggered by location.assign()"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-assign.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-assign.html new file mode 100644 index 0000000000..53ea96ffea --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-assign.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent("location.assign(`/common/blank.html?thereplacement`);"); + const afterReplacementURL = "/common/blank.html?thereplacement"; + const iframe = insertIframe(t, startURL); + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoad(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength, "history.length must not change after waiting for the replacement"); + + await checkSentinelIframe(t, sentinelIframe); + assert_equals(history.length, startingHistoryLength, "history.length must not change after checking the sentinel iframe"); +}, "Replace before load, triggered by location.assign()"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-during-load.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-during-load.html new file mode 100644 index 0000000000..e4c8ca80a1 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-during-load.html @@ -0,0 +1,76 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Replace during the load event, triggered by location setters</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +"use strict"; + +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + window.onload = () => { + location.href = "/common/blank.html?thereplacement"; + }; + `; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement"; + const iframe = insertIframe(t, startURL); + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoadAllowingIntermediateLoads(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength, "history.length must not change after waiting for the replacement"); + + await checkSentinelIframe(t, sentinelIframe); + assert_equals(history.length, startingHistoryLength, "history.length must not change after checking the sentinel iframe"); +}, "href"); + +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + window.onload = () => { + location.search = "thereplacement"; + }; + `; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "resources/code-injector.html?thereplacement"; + const iframe = insertIframe(t, startURL); + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoadAllowingIntermediateLoads(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength, "history.length must not change after waiting for the replacement"); + + await checkSentinelIframe(t, sentinelIframe); +}, "search"); + +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + window.onload = () => { + location.hash = "thereplacement"; + }; + `; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = startURL + "#thereplacement"; + const iframe = insertIframe(t, startURL); + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoadAllowingIntermediateLoads(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength, "history.length must not change after waiting for the replacement"); + + await checkSentinelIframe(t, sentinelIframe); + assert_equals(history.length, startingHistoryLength, "history.length must not change after checking the sentinel iframe"); +}, "hash"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-during-pageshow.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-during-pageshow.html new file mode 100644 index 0000000000..9997783ff5 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-during-pageshow.html @@ -0,0 +1,77 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Replace during the pageshow event, triggered by location setters</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +"use strict"; + +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + window.onpageshow = () => { + location.href = "/common/blank.html?thereplacement"; + }; + `; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement"; + const iframe = insertIframe(t, startURL); + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoadAllowingIntermediateLoads(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength, "history.length must not change after waiting for the replacement"); + + await checkSentinelIframe(t, sentinelIframe); + assert_equals(history.length, startingHistoryLength, "history.length must not change after checking the sentinel iframe"); +}, "href"); + +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + window.onpageshow = () => { + location.search = "thereplacement"; + }; + `; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "resources/code-injector.html?thereplacement"; + const iframe = insertIframe(t, startURL); + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoadAllowingIntermediateLoads(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength, "history.length must not change after waiting for the replacement"); + + await checkSentinelIframe(t, sentinelIframe); +}, "search"); + +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + window.onpageshow = () => { + location.hash = "thereplacement"; + parent.postMessage("done", "*"); + }; + `; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = startURL + "#thereplacement"; + const iframe = insertIframe(t, startURL); + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + assert_equals(await waitForMessage(), "done"); + assert_equals(history.length, startingHistoryLength, "history.length must not change after waiting for the replacement"); + + await checkSentinelIframe(t, sentinelIframe); + assert_equals(history.length, startingHistoryLength, "history.length must not change after checking the sentinel iframe"); +}, "hash"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-user-click.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-user-click.html new file mode 100644 index 0000000000..9276327f1a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-user-click.html @@ -0,0 +1,82 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>No replace before load, triggered by location setters called as part of user-initiated clicks</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> +<script> +"use strict"; + +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + const button = document.createElement("button"); + button.id = "the-button"; + button.textContent = "needs to have content to be clickable"; + button.onclick = () => { location.href = "/common/blank.html?thereplacement"; }; + document.currentScript.before(button); + parent.test_driver.click(button); + `; + + const startURL = "resources/slow-code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement"; + const iframe = insertIframe(t, startURL); + + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoad(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength + 1, "history.length must change after waiting for the load"); +}, "href"); + +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + const button = document.createElement("button"); + button.id = "the-button"; + button.textContent = "needs to have content to be clickable"; + button.onclick = () => { location.search = "thereplacement"; }; + document.currentScript.before(button); + parent.test_driver.click(button); + `; + + const startURL = "resources/slow-code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "resources/slow-code-injector.html?thereplacement"; + const iframe = insertIframe(t, startURL); + + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoad(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength + 1, "history.length must change after waiting for the load"); +}, "search"); + +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + const button = document.createElement("button"); + button.id = "the-button"; + button.textContent = "needs to have content to be clickable"; + button.onclick = () => { location.hash = "thereplacement"; }; + document.currentScript.before(button); + parent.test_driver.click(button); + `; + + const startURL = "resources/slow-code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = startURL + "#thereplacement"; + const iframe = insertIframe(t, startURL); + + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoad(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength + 1, "history.length must change after waiting for the load"); +}, "hash"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-user-mouseup.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-user-mouseup.html new file mode 100644 index 0000000000..9068e5a116 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-user-mouseup.html @@ -0,0 +1,89 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>No replace before load, triggered by location setters called as part of user-initiated mouseups</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<!-- + We test this separate from click because the spec as of + https://html.spec.whatwg.org/commit-snapshots/4ba46b025ec806ded7b4911bf8f9dd7bf9ff365e/#location-object-setter-navigate + referenced click handlers specifically, instead of using the general user activation concept which + includes other events like mouseup. +--> + +<body> +<script> +"use strict"; + +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + const button = document.createElement("button"); + button.id = "the-button"; + button.textContent = "needs to have content to be clickable"; + button.onmouseup = () => { location.href = "/common/blank.html?thereplacement"; }; + document.currentScript.before(button); + parent.test_driver.click(button); + `; + + const startURL = "resources/slow-code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement"; + const iframe = insertIframe(t, startURL); + + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoad(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength + 1, "history.length must change after waiting for the load"); +}, "href"); + +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + const button = document.createElement("button"); + button.id = "the-button"; + button.textContent = "needs to have content to be clickable"; + button.onmouseup = () => { location.search = "thereplacement"; }; + document.currentScript.before(button); + parent.test_driver.click(button); + `; + + const startURL = "resources/slow-code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "resources/slow-code-injector.html?thereplacement"; + const iframe = insertIframe(t, startURL); + + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoad(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength + 1, "history.length must change after waiting for the load"); +}, "search"); + +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + const button = document.createElement("button"); + button.id = "the-button"; + button.textContent = "needs to have content to be clickable"; + button.onmouseup = () => { location.hash = "thereplacement"; }; + document.currentScript.before(button); + parent.test_driver.click(button); + `; + + const startURL = "resources/slow-code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = startURL + "#thereplacement"; + const iframe = insertIframe(t, startURL); + + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoad(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength + 1, "history.length must change after waiting for the load"); +}, "hash"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter.html new file mode 100644 index 0000000000..b8049f084b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter.html @@ -0,0 +1,58 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Replace before load, triggered by location setters</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +"use strict"; + +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent("location.href = `/common/blank.html?thereplacement`;"); + const afterReplacementURL = "/common/blank.html?thereplacement"; + const iframe = insertIframe(t, startURL); + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoad(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength, "history.length must not change after waiting for the replacement"); + + await checkSentinelIframe(t, sentinelIframe); + assert_equals(history.length, startingHistoryLength, "history.length must not change after checking the sentinel iframe"); +}, "href"); + +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent("location.search = `thereplacement`;"); + const afterReplacementURL = "resources/code-injector.html?thereplacement"; + const iframe = insertIframe(t, startURL); + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoad(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength, "history.length must not change after waiting for the replacement"); + + await checkSentinelIframe(t, sentinelIframe); +}, "search"); + +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent("location.hash = `thereplacement`;"); + const afterReplacementURL = startURL + "#thereplacement"; + const iframe = insertIframe(t, startURL); + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoad(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength, "history.length must not change after waiting for the replacement"); + + await checkSentinelIframe(t, sentinelIframe); + assert_equals(history.length, startingHistoryLength, "history.length must not change after checking the sentinel iframe"); +}, "hash"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/resources/code-injector.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/resources/code-injector.html new file mode 100644 index 0000000000..73dacf0d76 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/resources/code-injector.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Subframe</title> + +<body> +<script> +"use strict"; +{{GET[code]}} +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/resources/helpers.js b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/resources/helpers.js new file mode 100644 index 0000000000..58102aa925 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/resources/helpers.js @@ -0,0 +1,89 @@ +window.waitForLoad = (t, iframe, urlRelativeToThisDocument) => { + return new Promise(resolve => { + iframe.addEventListener("load", t.step_func(() => { + assert_equals(iframe.contentWindow.location.href, (new URL(urlRelativeToThisDocument, location.href)).href); + + // Wait a bit longer to ensure all history stuff has settled, e.g. the document is "completely loaded" + // (which happens from a queued task). + setTimeout(resolve, 0); + }), { once: true }); + }); +}; + +window.waitForLoadAllowingIntermediateLoads = (t, iframe, urlRelativeToThisDocument) => { + return new Promise(resolve => { + const handler = t.step_func(() => { + if (iframe.contentWindow.location.href === (new URL(urlRelativeToThisDocument, location.href)).href) { + // Wait a bit longer to ensure all history stuff has settled, e.g. the document is "completely loaded" + // (which happens from a queued task). + setTimeout(resolve, 0); + iframe.removeEventListener("load", handler); + } + }); + + iframe.addEventListener("load", handler); + }); +}; + +window.waitForMessage = () => { + return new Promise(resolve => { + window.addEventListener("message", e => { + resolve(e.data); + }, { once: true }); + }); +}; + +window.setupSentinelIframe = async (t) => { + // If this iframe gets navigated by history.back(), then the iframe under test did not, so we did a replace. + const sentinelIframe = document.createElement("iframe"); + sentinelIframe.src = "/common/blank.html?sentinelstart"; + document.body.append(sentinelIframe); + t.add_cleanup(() => sentinelIframe.remove()); + + await waitForLoad(t, sentinelIframe, "/common/blank.html?sentinelstart"); + + sentinelIframe.src = "/common/blank.html?sentinelend"; + await waitForLoad(t, sentinelIframe, "/common/blank.html?sentinelend"); + + return sentinelIframe; +}; + +window.checkSentinelIframe = async (t, sentinelIframe) => { + // Go back. Since iframe should have done a replace, this should move sentinelIframe back, not iframe. + history.back(); + await waitForLoad(t, sentinelIframe, "/common/blank.html?sentinelstart"); +}; + +window.insertIframe = (t, url, name) => { + const iframe = document.createElement("iframe"); + iframe.src = url; + + // In at least Chromium, window name targeting for form submission doesn't work if the name is set + // after the iframe is inserted into the DOM. So we can't just have callers do this themselves. + if (name) { + iframe.name = name; + } + + document.body.append(iframe); + + // Intentionally not including the following: + // t.add_cleanup(() => iframe.remove()); + // Doing so breaks some of the testdriver.js tests with "cannot find window" errors. + return iframe; +}; + +// TODO(domenic): clean up other tests in the parent directory to use this. +window.absoluteURL = relativeURL => { + return (new URL(relativeURL, location.href)).href; +}; + +// TODO(domenic): clean up other tests in the parent directory to use this. +window.codeInjectorURL = code => { + return absoluteURL("resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code)); +}; + +window.changeURLHost = (url, newHost) => { + const urlObj = new URL(url); + urlObj.host = newHost; + return urlObj.href; +}; diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/resources/message-opener.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/resources/message-opener.html new file mode 100644 index 0000000000..b6f9d31358 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/resources/message-opener.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Popup</title> + +<script> +"use strict"; +opener.postMessage("ready", "*"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/resources/slow-code-injector.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/resources/slow-code-injector.html new file mode 100644 index 0000000000..b7e99dcbfa --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/resources/slow-code-injector.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Subframe</title> + +<body> +<script> +"use strict"; +{{GET[code]}} +</script> + +<!-- + This is necessary in cases involving user interaction because those happen async through the + webdriver infrastructure. Without this, the load event might happen before the click ever goes + through. +--> +<img src="/common/slow.py"> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/resources/slow-message-source-with-history-and-location.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/resources/slow-message-source-with-history-and-location.html new file mode 100644 index 0000000000..da279ebf82 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/resources/slow-message-source-with-history-and-location.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Subframe or popup</title> + +<script> +"use strict"; +(window.opener || window.parent).postMessage( + { historyLength: history.length, locationHref: location.href }, + "*" +); +</script> + +<!-- + This delays the load event, hopefully long enough that we can do whatever before-load action we're aiming for. +--> +<img src="/common/slow.py"> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/window-open-popup-during-load.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/window-open-popup-during-load.html new file mode 100644 index 0000000000..5308c01c39 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/window-open-popup-during-load.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + // Generating a new one instead of hard-coding makes running tests manually a bit easier. + const windowName = token(); + + const code = ` + window.onload = () => opener.navigateMe(); + opener.postMessage("arrived at start URL", "*"); + `; + + const startURL = "resources/slow-code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const absoluteStartURL = (new URL(startURL, location.href)).href; + + const afterReplacementURL = "resources/message-opener.html"; + const absoluteAfterReplacementURL = (new URL(afterReplacementURL, location.href)).href; + + window.navigateMe = () => { + window.open(absoluteAfterReplacementURL, windowName); + }; + + // First message sent is ignored; we only check it after navigating back. + const w = window.open(startURL, windowName); + t.add_cleanup(() => w.close()); + + // Wait to get past any initial about:blank + while (true) { + if (w.location.href === absoluteStartURL) { + break; + } + await new Promise(r => t.step_timeout(r, 0)); + } + + assert_equals(w.onloadFired, undefined, "onload must not yet have fired"); + assert_equals(w.history.length, 1, "history.length for the opened window must start at 1"); + + await new Promise(r => { + window.addEventListener("message", t.step_func(e => { + if (e.data === "ready") { + resolve(); + } + })); + }); + + assert_equals(w.history.length, 2, "history.length must increase"); + assert_equals(w.location.href, absoluteAfterReplacementURL); + + const promise = new Promise(resolve => { + window.addEventListener("message", t.step_func(e => { + assert_equals(e.data, "arrived at start URL"); + resolve(); + })); + }); + + w.history.back(); + + await promise; + assert_equals(w.location.href, absoluteStartURL, "1 second after attempting to go back, it indeed went back"); +}, "No replace before load, triggered by window.open() on a non-_self window"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/window-open-popup-during-pageshow.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/window-open-popup-during-pageshow.html new file mode 100644 index 0000000000..065d933225 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/window-open-popup-during-pageshow.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + // Generating a new one instead of hard-coding makes running tests manually a bit easier. + const windowName = token(); + + const code = ` + window.onpageshow = () => opener.navigateMe(); + opener.postMessage("arrived at start URL", "*"); + `; + + const startURL = "resources/slow-code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const absoluteStartURL = (new URL(startURL, location.href)).href; + + const afterReplacementURL = "resources/message-opener.html"; + const absoluteAfterReplacementURL = (new URL(afterReplacementURL, location.href)).href; + + window.navigateMe = () => { + window.open(absoluteAfterReplacementURL, windowName); + }; + + // First message sent is ignored; we only check it after navigating back. + const w = window.open(startURL, windowName); + t.add_cleanup(() => w.close()); + + // Wait to get past any initial about:blank + while (true) { + if (w.location.href === absoluteStartURL) { + break; + } + await new Promise(r => t.step_timeout(r, 0)); + } + + assert_equals(w.onloadFired, undefined, "onload must not yet have fired"); + assert_equals(w.history.length, 1, "history.length for the opened window must start at 1"); + + await new Promise(r => { + window.addEventListener("message", t.step_func(e => { + if (e.data === "ready") { + resolve(); + } + })); + }); + + assert_equals(w.history.length, 2, "history.length must increase"); + assert_equals(w.location.href, absoluteAfterReplacementURL); + + const promise = new Promise(resolve => { + window.addEventListener("message", t.step_func(e => { + assert_equals(e.data, "arrived at start URL"); + resolve(); + })); + }); + + w.history.back(); + + await promise; + assert_equals(w.location.href, absoluteStartURL, "1 second after attempting to go back, it indeed went back"); +}, "No replace before load, triggered by window.open() on a non-_self window"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/window-open-popup.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/window-open-popup.html new file mode 100644 index 0000000000..7b3e05f8f6 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/window-open-popup.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + // Generating a new one instead of hard-coding makes running manual tests a bit easier. + const windowName = token(); + + const code = ` + window.onload = () => { window.onloadFired = true; }; + `; + + const startURL = "resources/slow-code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const absoluteStartURL = (new URL(startURL, location.href)).href; + + const afterReplacementURL = "resources/message-opener.html"; + const absoluteAfterReplacementURL = (new URL(afterReplacementURL, location.href)).href; + + const w = window.open(startURL, windowName); + t.add_cleanup(() => w.close()); + + // Wait to get past any initial about:blank + while (true) { + if (w.location.href === absoluteStartURL) { + break; + } + await new Promise(r => setTimeout(r, 0)); + } + + assert_equals(w.onloadFired, undefined, "onload must not yet have fired"); + assert_equals(w.history.length, 1, "history.length for the opened window must start at 1"); + + window.open(afterReplacementURL, windowName); + await new Promise(r => { window.onmessage = r; }); + + assert_equals(w.history.length, 2, "history.length must increase"); + assert_equals(w.location.href, absoluteAfterReplacementURL); + + w.history.back(); + + await new Promise(r => t.step_timeout(r, 1000)); + assert_equals(w.location.href, absoluteStartURL, "1 second after attempting to go back, it indeed went back"); +}, "No replace before load, triggered by window.open() on a non-_self window"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/window-open-self-during-load.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/window-open-self-during-load.html new file mode 100644 index 0000000000..255601ba8d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/window-open-self-during-load.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + window.onload = () => { + window.open("/common/blank.html?thereplacement", "_self"); + }; + `; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement"; + const iframe = insertIframe(t, startURL); + + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoadAllowingIntermediateLoads(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength + 1, "history.length must increase"); +}, "No replace during load, triggered by window.open(_self) on an iframe"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/window-open-self-during-pageshow.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/window-open-self-during-pageshow.html new file mode 100644 index 0000000000..a8327cfac3 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/window-open-self-during-pageshow.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const code = ` + window.onpageshow = () => { + window.open("/common/blank.html?thereplacement", "_self"); + }; + `; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent(code); + const afterReplacementURL = "/common/blank.html?thereplacement"; + const iframe = insertIframe(t, startURL); + + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoadAllowingIntermediateLoads(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength + 1, "history.length must increase"); +}, "No replace during pageshow, triggered by window.open(_self) on an iframe"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/window-open-self.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/window-open-self.html new file mode 100644 index 0000000000..10e8f38001 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/window-open-self.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +"use strict"; +promise_test(async t => { + const sentinelIframe = await setupSentinelIframe(t); + const startingHistoryLength = history.length; + + const startURL = "resources/code-injector.html?pipe=sub(none)&code=" + encodeURIComponent("window.open(`/common/blank.html?thereplacement`, `_self`);"); + const afterReplacementURL = "/common/blank.html?thereplacement"; + const iframe = insertIframe(t, startURL); + + assert_equals(history.length, startingHistoryLength, "Inserting the under-test iframe must not change history.length"); + + await waitForLoad(t, iframe, afterReplacementURL); + assert_equals(history.length, startingHistoryLength + 1, "history.length must increase"); +}, "No replace before load, triggered by window.open(_self) on an iframe"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/blank.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/blank.html new file mode 100644 index 0000000000..c50eddd41f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/blank.html @@ -0,0 +1 @@ +<!doctype html> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/child-navigates-parent-cross-origin-inner.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/child-navigates-parent-cross-origin-inner.html new file mode 100644 index 0000000000..72b92c8061 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/child-navigates-parent-cross-origin-inner.html @@ -0,0 +1,20 @@ +<!doctype html> +<script> +const params = new URL(window.location).searchParams; +const property = params.get("property"); + +try { + if (property === null) { + parent.location = "foo"; + } else if (property === "reload") { + parent.location.reload(); + } else if (property === "replace") { + parent.location.replace("foo"); + } else { + parent.location[property] = "foo"; + } + parent.parent.postMessage("success", "*"); +} catch (e) { + parent.parent.postMessage(`error: ${e.name}`, "*"); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/child-navigates-parent-destination.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/child-navigates-parent-destination.html new file mode 100644 index 0000000000..bb8ba4e698 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/child-navigates-parent-destination.html @@ -0,0 +1,4 @@ +<!doctype html> +<script> +parent.postMessage("destination", "*"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/child-navigates-parent-location-initial.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/child-navigates-parent-location-initial.html new file mode 100644 index 0000000000..a4a1713a27 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/child-navigates-parent-location-initial.html @@ -0,0 +1,3 @@ +<!doctype html> +<script>parent.postMessage("initial", "*")</script> +<iframe src="child-navigates-parent-location-inner.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/child-navigates-parent-location-inner.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/child-navigates-parent-location-inner.html new file mode 100644 index 0000000000..4d7d24730f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/child-navigates-parent-location-inner.html @@ -0,0 +1,5 @@ +<!doctype html> +<script> +parent.parent.postMessage("inner", "*"); +parent.location = "child-navigates-parent-destination.html" +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/child-navigates-parent-submit-initial.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/child-navigates-parent-submit-initial.html new file mode 100644 index 0000000000..943708e0cd --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/child-navigates-parent-submit-initial.html @@ -0,0 +1,3 @@ +<!doctype html> +<script>parent.postMessage("initial", "*")</script> +<iframe src="child-navigates-parent-submit-inner.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/child-navigates-parent-submit-inner.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/child-navigates-parent-submit-inner.html new file mode 100644 index 0000000000..0b49e13d65 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/child-navigates-parent-submit-inner.html @@ -0,0 +1,6 @@ +<!doctype html> +<form action="child-navigates-parent-destination.html" target="_parent"></form> +<script> +parent.parent.postMessage("inner", "*"); +document.forms[0].submit(); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/click.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/click.html new file mode 100644 index 0000000000..8cb03b74d5 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/click.html @@ -0,0 +1,4 @@ +<!doctype html> +<script> +parent.postMessage("click", "*"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/document-domain-set-to-site.sub.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/document-domain-set-to-site.sub.html new file mode 100644 index 0000000000..3c4355c452 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/document-domain-set-to-site.sub.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<meta charset="utf-8"> + +<script> +"use strict"; +document.domain = "{{host}}"; +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/form.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/form.html new file mode 100644 index 0000000000..6523a82b39 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/form.html @@ -0,0 +1,5 @@ +<!DOCTYPE html> +<script> +parent.postMessage("form navigation", "*"); +</script> +form navigation diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/href.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/href.html new file mode 100644 index 0000000000..eccadadf41 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/href.html @@ -0,0 +1,5 @@ +<!doctype html> +<script> +parent.postMessage("href", "*"); +</script> +href diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/message-opener.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/message-opener.html new file mode 100644 index 0000000000..b6f9d31358 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/message-opener.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Popup</title> + +<script> +"use strict"; +opener.postMessage("ready", "*"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/message-parent.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/message-parent.html new file mode 100644 index 0000000000..3656358f2d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/message-parent.html @@ -0,0 +1,3 @@ +<script> + window.parent.postMessage("ready", "*"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/multi-globals-subframe-1.sub.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/multi-globals-subframe-1.sub.html new file mode 100644 index 0000000000..a87b2fd2be --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/multi-globals-subframe-1.sub.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Multi-globals test outer subframe</title> + +<script> +"use strict"; +document.domain = "{{hosts[][]}}"; +</script> + +<iframe src="http://{{hosts[][]}}:{{ports[http][0]}}/html/browsers/browsing-the-web/navigating-across-documents/resources/multi-globals-subframe-2.sub.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/multi-globals-subframe-2.sub.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/multi-globals-subframe-2.sub.html new file mode 100644 index 0000000000..593c428a67 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/multi-globals-subframe-2.sub.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Multi-globals test inner subframe</title> + +<script> +"use strict"; +document.domain = "{{hosts[][]}}"; +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/no-cache-single-redirect.py b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/no-cache-single-redirect.py new file mode 100644 index 0000000000..9d3aff817d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/no-cache-single-redirect.py @@ -0,0 +1,21 @@ +# This handler receives requests identified by UUIDs that have mandatory +# `location` query parameters. Every other request for the same URL will result +# in a redirect to the URL described by `location`. When we don't redirect, we +# simply return the HTML document "No redirect". +def main(request, response): + response.headers.set(b"Cache-Control", b"no-store") + + uuid = request.GET.first(b"uuid") + value = request.server.stash.take(uuid) + + if value is None: + response.status = 302 + location = request.GET.first(b"location") + response.headers.set(b"Location", location) + # Ensure that the next time this uuid is request, we don't redirect. + request.server.stash.put(uuid, "sentinel value") + else: + # If we're in this branch, then `value` is not none, but the stash now + # has `None` associated with `uuid`, which means on the next request for + # this `uuid` we'll end up in the above branch instead. + return ([(b"Content-Type", b"text/html")], b"No redirect") diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/page-that-post-message-to-opener.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/page-that-post-message-to-opener.html new file mode 100644 index 0000000000..65a0c82149 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/page-that-post-message-to-opener.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Page that postMessage to its opener</title> +<script> + opener.postMessage('Allowed', '*'); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/page-with-top-navigating-iframe.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/page-with-top-navigating-iframe.html new file mode 100644 index 0000000000..568e44296f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/page-with-top-navigating-iframe.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src=/common/get-host-info.sub.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> +<title>Page that embeds an iframe that navigates its top</title> +<script> +function addIframe() { + const iframe = document.createElement('iframe'); + const path = new URL("top-navigating-page.html", window.location).pathname; + iframe.src = get_host_info().HTTP_NOTSAMESITE_ORIGIN + path; + document.body.appendChild(iframe); +} + +addEventListener('load', () => { + const urlParams = new URLSearchParams(location.search); + const parentUserGesture = urlParams.get('parent_user_gesture') === 'true'; + if (parentUserGesture) + test_driver.bless("Giving parent frame user activation").then(addIframe); + else + addIframe(); +}); +</script> + diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/portable-document-format-sample-valid.pdf b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/portable-document-format-sample-valid.pdf Binary files differnew file mode 100644 index 0000000000..d008b1f23a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/portable-document-format-sample-valid.pdf diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/portable-document-format-sample-valid.pdf.headers b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/portable-document-format-sample-valid.pdf.headers new file mode 100644 index 0000000000..5a8e57e482 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/portable-document-format-sample-valid.pdf.headers @@ -0,0 +1 @@ +Content-Type: application/pdf diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/redirect.py b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/redirect.py new file mode 100644 index 0000000000..3c78c256b0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/redirect.py @@ -0,0 +1,4 @@ +def main(request, response): + location = request.GET.first(b"location") + response.status = 302 + response.headers.set(b"Location", location) diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/top-navigating-page.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/top-navigating-page.html new file mode 100644 index 0000000000..557f408fba --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/top-navigating-page.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Page that navigates its top</title> +<script src=/common/get-host-info.sub.js></script> +<script> + +let path = new URL("page-that-post-message-to-opener.html", window.location).pathname; +let fullUrl = get_host_info().HTTP_NOTSAMESITE_ORIGIN + path; +try { + top.location = fullUrl; +} catch { + top.opener.postMessage('Denied', '*'); +} + +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/wait-for-messages.js b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/wait-for-messages.js new file mode 100644 index 0000000000..62ddec49f1 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/wait-for-messages.js @@ -0,0 +1,15 @@ +// Asynchronous function that waits for the given number of messages to be +// received by `window`, then returns those messages. +function waitForMessages(numMessages) { + return new Promise((resolve) => { + const messages = []; + + window.addEventListener("message", function handler(evt) { + messages.push(evt.data); + if (messages.length == numMessages) { + window.removeEventListener("message", handler); + resolve(messages); + } + }); + }); +} diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/xhtml-and-non-utf-8.xhtml b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/xhtml-and-non-utf-8.xhtml new file mode 100644 index 0000000000..3aacf33f1c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/resources/xhtml-and-non-utf-8.xhtml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="windows-1250"?> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <meta charset="windows-1250"/> + <title>A test document used when you need something very non-default</title> +</head> +<body> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/source/navigate-child-function-parent-then-fragment.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/source/navigate-child-function-parent-then-fragment.html new file mode 100644 index 0000000000..d01ef4c77a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/source/navigate-child-function-parent-then-fragment.html @@ -0,0 +1,36 @@ +<!doctype html> +<meta charset=utf-8> +<title> + Set location from a parent, then do a fragment navigation from within the + frame. +</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe></iframe> +<script> + promise_test(async test => { + // Wait for the DOM to be ready before inserting an <iframe> into it. + await new Promise(resolve => { onload = resolve }); + // Insert an <iframe> and wait for a dummy document to be loaded into it. + let iframe = document.createElement("iframe"); + iframe.src = "support/dummy.html"; + let iframe_loaded = new Promise(resolve => { iframe.onload = resolve }); + document.body.appendChild(iframe); + await iframe_loaded; + // The referrer is the main frame's URL since it initiated the iframe + // creation. + assert_equals(iframe.contentDocument.referrer, document.URL); + // Do a fragment navigation from the frame, which will fire the + // 'hashchange' function. + let hash_changed = new Promise(resolve => { + iframe.contentWindow.onhashchange = resolve + }); + let navigateScript = iframe.contentDocument.createElement("script"); + navigateScript.innerHTML = "location.href = '#foo'"; + iframe.contentDocument.body.appendChild(navigateScript); + await hash_changed; + // The referrer stays the same, even when the last navigation was + // initiated by the iframe (instead of the main frame document). + assert_equals(iframe.contentDocument.referrer, document.URL); + }); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/source/navigate-child-function-parent.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/source/navigate-child-function-parent.html new file mode 100644 index 0000000000..2efc3a6b4a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/source/navigate-child-function-parent.html @@ -0,0 +1,18 @@ +<!doctype html> +<meta charset=utf-8> +<title>Set location from a parent</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id=log></div> +<iframe></iframe> +<script> + setup({ single_test: true }); + onload = function() { + var fr = document.querySelector("iframe") + fr.contentWindow.location = "support/dummy.html" + fr.onload = function() { + assert_equals(fr.contentDocument.referrer, document.URL) + done() + } + } +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/source/navigate-child-function-src.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/source/navigate-child-function-src.html new file mode 100644 index 0000000000..e21260cd35 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/source/navigate-child-function-src.html @@ -0,0 +1,18 @@ +<!doctype html> +<meta charset=utf-8> +<title>Set src from a function called from a parent</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id=log></div> +<iframe src="support/set-parent-src.html"></iframe> +<script> +async_test(function() { + onload = this.step_func(function() { + var fr = document.querySelector("iframe") + fr.contentWindow.go() + fr.onload = this.step_func_done(function() { + assert_equals(fr.contentDocument.referrer, document.URL) + }) + }) +}) +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/source/navigate-child-function.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/source/navigate-child-function.html new file mode 100644 index 0000000000..c6fa765b89 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/source/navigate-child-function.html @@ -0,0 +1,19 @@ +<!doctype html> +<meta charset=utf-8> +<title>Set location from a function called from a parent</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id=log></div> +<iframe src="support/location-set.html"></iframe> +<script> +async_test(function() { + onload = this.step_func(function() { + var fr = document.querySelector("iframe") + var url = fr.contentDocument.URL + fr.contentWindow.go() + fr.onload = this.step_func_done(function() { + assert_equals(fr.contentDocument.referrer, url) + }) + }) +}) +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/source/navigate-child-src-about-blank.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/source/navigate-child-src-about-blank.html new file mode 100644 index 0000000000..479019d847 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/source/navigate-child-src-about-blank.html @@ -0,0 +1,20 @@ +<!doctype html> +<meta charset=utf-8> +<title>Set the src attribute to about:blank and check referrer</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id=log></div> +<iframe></iframe> +<script> + setup({ + single_test: true + }); + onload = function() { + var fr = document.querySelector("iframe") + fr.src = "about:blank" + fr.onload = function() { + assert_equals(fr.contentDocument.referrer, document.location.origin + '/') + done() + } + } +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/source/support/dummy.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/source/support/dummy.html new file mode 100644 index 0000000000..0638657093 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/source/support/dummy.html @@ -0,0 +1,3 @@ +<!doctype html> +<meta charset=utf-8> +<p>Hello.
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/source/support/location-set.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/source/support/location-set.html new file mode 100644 index 0000000000..ad733afac3 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/source/support/location-set.html @@ -0,0 +1,8 @@ +<!doctype html> +<meta charset=utf-8> +<script> + function go() { + location.href = "support/dummy.html" + } +</script> +<p>Hello. Go.
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/source/support/set-parent-src.html b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/source/support/set-parent-src.html new file mode 100644 index 0000000000..9d45be8c8d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/source/support/set-parent-src.html @@ -0,0 +1,8 @@ +<!doctype html> +<meta charset=utf-8> +<script> + function go() { + frameElement.src = "support/dummy.html" + } +</script> +<p>Hello. Go.
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/top-level-data-url.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/top-level-data-url.window.js new file mode 100644 index 0000000000..ca321f106a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/navigating-across-documents/top-level-data-url.window.js @@ -0,0 +1,20 @@ +// META: timeout=long + +const dataURL = `data:text/html,...`; +const encodedDataURL = encodeURIComponent(dataURL); + +[dataURL, `resources/redirect.py?location=${encodedDataURL}`].forEach(url => { + [undefined, "opener", "noopener", "noreferrer"].forEach(opener => { + async_test(t => { + const popup = window.open(url, "", opener); + t.step_timeout(() => { + if (opener === "noopener" || opener == "noreferrer") { + assert_equals(popup, null); + } else { + assert_true(popup.closed); + } + t.done(); + }, 1500); + }, `Navigating a popup using window.open("${url}", "", "${opener}")`); + }); +}); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/README.md b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/README.md new file mode 100644 index 0000000000..cc313a155a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/README.md @@ -0,0 +1,11 @@ +# Overlapping navigation and traversal tests + +These tests follow the behavior outlined in the +[session history rewrite](https://github.com/whatwg/html/pull/6315). + +<https://github.com/whatwg/html/issues/6927> discusses these results. + +We are not yet 100% sure on this behavior, especially for overlapping +traversal cases where the spec is complex and some of the tests don't +seem to match any browser. Please feel free to discuss on the spec +issue. diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/anchor-fragment-history-back-on-click.html b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/anchor-fragment-history-back-on-click.html new file mode 100644 index 0000000000..a081bec514 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/anchor-fragment-history-back-on-click.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + location.hash = "#1"; + assert_equals(location.hash, "#1"); + location.hash = "#2"; + assert_equals(location.hash, "#2"); + + let anchor = document.createElement("a"); + anchor.href = "#3"; + anchor.onclick = () => { + history.back(); + }; + + let navigations = []; + let navigationsPromise = new Promise(resolve => { + onpopstate = () => { + navigations.push(location.hash); + if (navigations.length === 2) { + resolve(); + } + } + }); + + anchor.click(); + await navigationsPromise; + + // We were on #2 when history.back() was called so we should be on #1 now. + assert_equals(location.hash, "#1"); + + // While the history navigation back to "#1" was pending, we should have navigated to "#3". + assert_array_equals(navigations, ["#3", "#1"]); +}, "Anchor with a fragment href and a click handler that navigates back"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-nav-cross-document-nav.html b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-nav-cross-document-nav.html new file mode 100644 index 0000000000..99d9a8fbb1 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-nav-cross-document-nav.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Cross-document navigation after a cross-document navigation</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- + According to the spec, the navigate algorithm synchronously cancels ongoing + non-mature navigations. +--> + +<body> +<script type="module"> +import { createIframe, waitForLoad, waitForPotentialNetworkLoads } from "./resources/helpers.mjs"; + +promise_test(async t => { + const iframe = await createIframe(t); + + iframe.contentWindow.location.search = "?1"; + iframe.contentWindow.location.search = "?2"; + assert_equals(iframe.contentWindow.location.search, ""); + + await waitForLoad(iframe); + assert_equals(iframe.contentWindow.location.search, "?2"); + + iframe.onload = t.unreached_func("second load event"); + await waitForPotentialNetworkLoads(t); + assert_equals(iframe.contentWindow.location.search, "?2"); +}, "cross-document navigation then cross-document navigation"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-nav-cross-document-traversal.html b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-nav-cross-document-traversal.html new file mode 100644 index 0000000000..341f66a996 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-nav-cross-document-traversal.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Cross-document traversal during cross-document navigation</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- + According to the spec, "apply the history step" will set the ongoing + navigation to "traversal", canceling any non-mature navigations. +--> + +<body> +<script type="module"> +import { createIframe, waitForLoad, delay } from "./resources/helpers.mjs"; + +promise_test(async t => { + const iframe = await createIframe(t); + + // Setup + // Extra delay()s are necessary because if we navigate "inside" the load + // handler (i.e. in a promise reaction for the load handler) then it will + // be a replace navigation. + iframe.contentWindow.location.search = "?1"; + await waitForLoad(iframe); + await delay(t, 0); + iframe.contentWindow.location.search = "?2"; + await waitForLoad(iframe); + await delay(t, 0); + + iframe.contentWindow.location.search = "?3"; + iframe.contentWindow.history.back(); + + assert_equals(iframe.contentWindow.location.search, "?2", "must not go back synchronously"); + + await waitForLoad(iframe); + assert_equals(iframe.contentWindow.location.search, "?1", "must go back one step eventually"); +}, "cross-document navigations are stopped by cross-document back()"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-nav-same-document-nav.html b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-nav-same-document-nav.html new file mode 100644 index 0000000000..99525cb3ed --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-nav-same-document-nav.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Cross-document navigation after a same-document navigation</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- + According to the spec, the "URL and history update steps" (used by + pushState()) and the fragment navigation steps, do *not* modify the ongoing + navigation, i.e. do not cancel any navigations. +--> + +<body> +<script type="module"> +import { createIframe, waitForLoad } from "./resources/helpers.mjs"; + +promise_test(async t => { + const iframe = await createIframe(t); + + iframe.contentWindow.location.search = "?1"; + iframe.contentWindow.location.hash = "#2"; + + assert_equals(iframe.contentWindow.location.search, ""); + assert_equals(iframe.contentWindow.location.hash, "#2"); + + await waitForLoad(iframe); + assert_equals(iframe.contentWindow.location.search, "?1"); + assert_equals(iframe.contentWindow.location.hash, ""); +}, "cross-document navigation then fragment navigation"); + +promise_test(async t => { + const iframe = await createIframe(t); + + iframe.contentWindow.location.search = "?1"; + iframe.contentWindow.history.pushState(null, "", "/2"); + + assert_equals(iframe.contentWindow.location.search, ""); + assert_equals(iframe.contentWindow.location.pathname, "/2"); + + await waitForLoad(iframe); + assert_equals(iframe.contentWindow.location.search, "?1"); + assert_equals(iframe.contentWindow.location.pathname, "/common/blank.html"); +}, "cross-document navigation then pushState()"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-nav-same-document-traversal.html b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-nav-same-document-traversal.html new file mode 100644 index 0000000000..2ff91be7e1 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-nav-same-document-traversal.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Same-document traversal during cross-document navigation</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- + According to the spec, "apply the history step" will set the ongoing + navigation to "traversal", canceling any navigation that is still processing + in parallel and hasn't yet reached "apply the history step". +--> + +<body> +<script type="module"> +import { createIframe, delay } from "./resources/helpers.mjs"; + +promise_test(async t => { + const iframe = await createIframe(t); + + // Setup + iframe.contentWindow.location.hash = "#1"; + await delay(t, 0); + iframe.contentWindow.location.hash = "#2"; + await delay(t, 0); + + iframe.contentWindow.location.search = "?1"; + iframe.contentWindow.onload = t.unreached_func("load event fired"); + + iframe.contentWindow.history.back(); + + assert_equals(iframe.contentWindow.location.search, "", "must not go back synchronously (search)"); + assert_equals(iframe.contentWindow.location.hash, "#2", "must not go back synchronously (hash)"); + + // Does go back eventually, and only one step + await t.step_wait(() => iframe.contentWindow.location.hash === "#1" && iframe.contentWindow.location.search === ""); +}, "cross-document navigations are stopped by same-document back()"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-nav-stop.html b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-nav-stop.html new file mode 100644 index 0000000000..0803d6c8d1 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-nav-stop.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Stop during cross-document navigations</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<body> +<script type="module"> +import { createIframe, waitForPotentialNetworkLoads } from "./resources/helpers.mjs"; + +promise_test(async t => { + const iframe = await createIframe(t); + + iframe.contentWindow.location.search = "?1"; + iframe.contentWindow.onload = t.unreached_func("load event fired"); + iframe.contentWindow.stop(); + + await waitForPotentialNetworkLoads(t); + assert_equals(iframe.contentWindow.location.search, ""); +}, "cross-document navigations are stopped by stop()"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-traversal-cross-document-nav.html b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-traversal-cross-document-nav.html new file mode 100644 index 0000000000..5141259d08 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-traversal-cross-document-nav.html @@ -0,0 +1,77 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Cross-document navigations during cross-document traversals</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- + According to the spec, if ongoing navigation is "traversal", the navigation + fails and nothing happens. +--> + +<body> +<script type="module"> +import { createIframe, waitForLoad, delay, waitForPotentialNetworkLoads } from "./resources/helpers.mjs"; + +promise_test(async t => { + const iframe = await createIframe(t); + const slowURL = (new URL("resources/slow.py", location.href)).href; + + // Setup + // Extra delay()s are necessary because if we navigate "inside" the load + // handler (i.e. in a promise reaction for the load handler) then it will + // be a replace navigation. + iframe.contentWindow.location.href = slowURL; + await waitForLoad(iframe); + await delay(t, 0); + iframe.contentWindow.location.href = "/common/blank.html?2"; + await waitForLoad(iframe); + await delay(t, 0); + + iframe.contentWindow.history.back(); + + assert_equals(iframe.contentWindow.location.search, "?2", "must not go back synchronously"); + + iframe.contentWindow.location.href = "/common/blank.html?3"; + assert_equals(iframe.contentWindow.location.search, "?2", "must not navigate synchronously"); + + // We end up at slow.py and never at /common/blank.html?3 + await waitForLoad(iframe); + assert_equals(iframe.contentWindow.location.href, slowURL, "first load after the nav"); + + await waitForPotentialNetworkLoads(t); + assert_equals(iframe.contentWindow.location.href, slowURL, "must stay on slow.py"); +}, "slow cross-document traversal and then fast cross-document navigation: traversal wins and nav is ignored"); + +promise_test(async t => { + const iframe = await createIframe(t); + const slowURL = (new URL("resources/slow.py", location.href)).href; + + // Setup + // Extra delay()s are necessary because if we navigate "inside" the load + // handler (i.e. in a promise reaction for the load handler) then it will + // be a replace navigation. + iframe.contentWindow.location.search = "?1"; + await waitForLoad(iframe); + await delay(t, 0); + iframe.contentWindow.location.search = "?2"; + await waitForLoad(iframe); + await delay(t, 0); + + iframe.contentWindow.history.back(); + + assert_equals(iframe.contentWindow.location.search, "?2", "must not go back synchronously"); + + iframe.contentWindow.location.href = slowURL; + assert_equals(iframe.contentWindow.location.search, "?2", "must not navigate synchronously"); + + // We end up at ?1 and never at slowURL + await waitForLoad(iframe); + assert_equals(iframe.contentWindow.location.search, "?1", "first load after the nav"); + + // The long timeout is because slow.py would take 2 seconds, if it did load. + await delay(t, 3000); + assert_equals(iframe.contentWindow.location.search, "?1", "must stay on ?1"); +}, "fast cross-document traversal and then slow cross-document navigation: traversal wins and nav is ignored"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-traversal-cross-document-traversal.html b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-traversal-cross-document-traversal.html new file mode 100644 index 0000000000..97907df23d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-traversal-cross-document-traversal.html @@ -0,0 +1,160 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Cross-document traversals during cross-document traversals</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- + In the spec, all traversals are queued, and that includes computing what + "back" and "forward" mean, based on the "current session history step". The + "current session history step" is updated at the end of "apply the history + step", at which point the queued steps in "traverse history by a delta" get to + run and compute what is back/forward. So the basic structure is: + + - back(), back(): go back once, then again. + - back(), forward(): go back once, then go forward. + + However, note that these observable effects (e.g., actually loading an + intermediate document) are done via queued tasks. Those tasks will end up not + running, once we switch the active document due to the second traversal. So + the end observable result looks like: + + - back(), back(): go back -2. + - back(), forward(): go nowhere. +--> + +<body> +<script type="module"> +import { createIframe, waitForLoad, delay, waitForPotentialNetworkLoads } from "./resources/helpers.mjs"; + +promise_test(async t => { + const iframe = await createIframe(t); + + // Setup + // Extra delay()s are necessary because if we navigate "inside" the load + // handler (i.e. in a promise reaction for the load handler) then it will + // be a replace navigation. + iframe.contentWindow.location.search = "?1"; + await waitForLoad(iframe); + await delay(t, 0); + iframe.contentWindow.location.search = "?2"; + await waitForLoad(iframe); + await delay(t, 0); + iframe.contentWindow.location.search = "?3"; + await waitForLoad(iframe); + await delay(t, 0); + iframe.contentWindow.history.back(); + await waitForLoad(iframe); + await delay(t, 0); + assert_equals(iframe.contentWindow.location.search, "?2", "we made our way to ?2 for setup"); + + iframe.contentWindow.history.back(); + assert_equals(iframe.contentWindow.location.search, "?2", "must not go back synchronously"); + + iframe.contentWindow.history.forward(); + assert_equals(iframe.contentWindow.location.search, "?2", "must not go forward synchronously"); + + iframe.onload = t.unreached_func("second load event"); + + await waitForPotentialNetworkLoads(t); + assert_equals(iframe.contentWindow.location.search, "?2", "must stay on ?2"); +}, "cross-document traversals in opposite directions: the result is going nowhere"); + +promise_test(async t => { + const iframe = await createIframe(t); + + // Setup + // Extra delay()s are necessary because if we navigate "inside" the load + // handler (i.e. in a promise reaction for the load handler) then it will + // be a replace navigation. + iframe.contentWindow.location.search = "?1"; + await waitForLoad(iframe); + await delay(t, 0); + iframe.contentWindow.location.search = "?2"; + await waitForLoad(iframe); + await delay(t, 0); + + iframe.contentWindow.history.back(); + assert_equals(iframe.contentWindow.location.search, "?2", "must not go back synchronously"); + + iframe.contentWindow.history.forward(); + assert_equals(iframe.contentWindow.location.search, "?2", "must not go forward synchronously"); + + iframe.onload = t.unreached_func("second load event"); + + await waitForPotentialNetworkLoads(t); + assert_equals(iframe.contentWindow.location.search, "?2", "must stay on ?2"); +}, "cross-document traversals in opposite directions, second traversal invalid at queuing time but valid at the time it is run: the result is going nowhere"); + +promise_test(async t => { + const iframe = await createIframe(t); + + // Setup + // Extra delay()s are necessary because if we navigate "inside" the load + // handler (i.e. in a promise reaction for the load handler) then it will + // be a replace navigation. + iframe.contentWindow.location.search = "?1"; + await waitForLoad(iframe); + await delay(t, 0); + iframe.contentWindow.location.search = "?2"; + await waitForLoad(iframe); + await delay(t, 0); + iframe.contentWindow.location.search = "?3"; + await waitForLoad(iframe); + await delay(t, 0); + + iframe.contentWindow.history.back(); + assert_equals(iframe.contentWindow.location.search, "?3", "must not go back synchronously (1)"); + + iframe.contentWindow.history.back(); + assert_equals(iframe.contentWindow.location.search, "?3", "must not go back synchronously (2)"); + + await waitForLoad(iframe); + assert_equals(iframe.contentWindow.location.search, "?1", "first load event must be going back"); + + iframe.onload = t.unreached_func("second load event"); + + await waitForPotentialNetworkLoads(t); + assert_equals(iframe.contentWindow.location.search, "?1", "must stay on ?1"); +}, "cross-document traversals in the same (back) direction: the result is going -2 with only one load event"); + +promise_test(async t => { + const iframe = await createIframe(t); + + // Setup + // Extra delay()s are necessary because if we navigate "inside" the load + // handler (i.e. in a promise reaction for the load handler) then it will + // be a replace navigation. + iframe.contentWindow.location.search = "?1"; + await waitForLoad(iframe); + await delay(t, 0); + iframe.contentWindow.location.search = "?2"; + await waitForLoad(iframe); + await delay(t, 0); + iframe.contentWindow.location.search = "?3"; + await waitForLoad(iframe); + await delay(t, 0); + iframe.contentWindow.history.back(); + await waitForLoad(iframe); + await delay(t, 0); + assert_equals(iframe.contentWindow.location.search, "?2", "we made our way to ?2 for setup"); + iframe.contentWindow.history.back(); + await waitForLoad(iframe); + await delay(t, 0); + assert_equals(iframe.contentWindow.location.search, "?1", "we made our way to ?1 for setup"); + + iframe.contentWindow.history.forward(); + assert_equals(iframe.contentWindow.location.search, "?1", "must not go forward synchronously (1)"); + + iframe.contentWindow.history.forward(); + assert_equals(iframe.contentWindow.location.search, "?1", "must not go forward synchronously (2)"); + + await waitForLoad(iframe); + assert_equals(iframe.contentWindow.location.search, "?3", "first load event must be going forward"); + + iframe.onload = t.unreached_func("second load event"); + + await waitForPotentialNetworkLoads(t); + assert_equals(iframe.contentWindow.location.search, "?3", "must stay on ?3"); +}, "cross-document traversals in the same (forward) direction: the result is going +2 with only one load event"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-traversal-same-document-nav.html b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-traversal-same-document-nav.html new file mode 100644 index 0000000000..df6258f9b3 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-traversal-same-document-nav.html @@ -0,0 +1,66 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Same-document navigations during cross-document traversals</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- + The spec explicitly covers this case, with a Jake diagram: + https://whatpr.org/html/6315/browsing-the-web.html#example-sync-navigation-steps-queue-jumping-basic +--> + +<body> +<script type="module"> +import { createIframe, waitForLoad, delay } from "./resources/helpers.mjs"; + +promise_test(async t => { + const iframe = await createIframe(t); + + // Setup + // Extra delay()s are necessary because if we navigate "inside" the load + // handler (i.e. in a promise reaction for the load handler) then it will + // be a replace navigation. + iframe.contentWindow.location.search = "?1"; + await waitForLoad(iframe); + await delay(t, 0); + iframe.contentWindow.location.search = "?2"; + await waitForLoad(iframe); + await delay(t, 0); + + iframe.contentWindow.history.back(); + + assert_equals(iframe.contentWindow.location.search, "?2", "must not go back synchronously"); + + iframe.contentWindow.location.hash = "#3"; + assert_equals(iframe.contentWindow.location.search, "?2"); + assert_equals(iframe.contentWindow.location.hash, "#3"); + + // Eventually ends up on ?1 + await t.step_wait(() => iframe.contentWindow.location.search === "?1" && iframe.contentWindow.location.hash === ""); +}, "same-document traversals + fragment navigations"); + +promise_test(async t => { + const iframe = await createIframe(t); + + // Setup + // Extra delay()s are necessary because if we navigate "inside" the load + // handler (i.e. in a promise reaction for the load handler) then it will + // be a replace navigation. + iframe.contentWindow.location.search = "?1"; + await waitForLoad(iframe); + await delay(t, 0); + iframe.contentWindow.location.search = "?2"; + await waitForLoad(iframe); + await delay(t, 0); + + iframe.contentWindow.history.back(); + + assert_equals(iframe.contentWindow.location.search, "?2", "must not go back synchronously"); + + iframe.contentWindow.history.pushState(null, "", "?3"); + assert_equals(iframe.contentWindow.location.search, "?3"); + + // Eventually ends up on ?1 + await t.step_wait(() => iframe.contentWindow.location.search === "?1"); +}, "same-document traversals + pushState()"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-traversal-same-document-traversal.html b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-traversal-same-document-traversal.html new file mode 100644 index 0000000000..3c37c46b64 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-traversal-same-document-traversal.html @@ -0,0 +1,94 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Same-document traversals during cross-document traversals</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- + This case is not significantly different from + cross-document-traversal-cross-document-traversal.html. +--> + +<body> +<script type="module"> +import { createIframe, waitForLoad, waitForHashchange, delay, waitForPotentialNetworkLoads } from "./resources/helpers.mjs"; + +promise_test(async t => { + const iframe = await createIframe(t); + + // Setup + // Extra delay()s are necessary because if we navigate "inside" the load + // handler (i.e. in a promise reaction for the load handler) then it will + // be a replace navigation. + iframe.contentWindow.location.search = "?1"; + await waitForLoad(iframe); + await delay(t, 0); + iframe.contentWindow.location.hash = "#2"; + await waitForHashchange(iframe.contentWindow); + iframe.contentWindow.location.search = "?3"; + await waitForLoad(iframe); + await delay(t, 0); + + iframe.contentWindow.history.back(); + assert_equals(iframe.contentWindow.location.search, "?3", "must not go back synchronously 1 (search)"); + assert_equals(iframe.contentWindow.location.hash, "#2", "must not go back synchronously 1 (hash)"); + + iframe.contentWindow.history.back(); + assert_equals(iframe.contentWindow.location.search, "?3", "must not go back synchronously 2 (search)"); + assert_equals(iframe.contentWindow.location.hash, "#2", "must not go back synchronously 2 (hash)"); + + await waitForLoad(iframe); + assert_equals(iframe.contentWindow.location.search, "?1", "first load event must be going back (search)"); + assert_equals(iframe.contentWindow.location.hash, "", "first load event must be going back (hash)"); + + iframe.contentWindow.onhashchange = t.unreached_func("hashchange event"); + iframe.onload = t.unreached_func("second load event"); + + await waitForPotentialNetworkLoads(t); + assert_equals(iframe.contentWindow.location.search, "?1", "must stay on ?1 (search)"); + assert_equals(iframe.contentWindow.location.hash, "", "must stay on ?1 (hash)"); +}, "traversals in the same (back) direction: coalesced"); + +promise_test(async t => { + const iframe = await createIframe(t); + + // Setup + // Extra delay()s are necessary because if we navigate "inside" the load + // handler (i.e. in a promise reaction for the load handler) then it will + // be a replace navigation. + iframe.contentWindow.location.search = "?1"; + await waitForLoad(iframe); + await delay(t, 0); + iframe.contentWindow.location.search = "?2"; + await waitForLoad(iframe); + await delay(t, 0); + iframe.contentWindow.location.hash = "#3"; + await waitForHashchange(iframe.contentWindow); + iframe.contentWindow.history.back(); + await waitForHashchange(iframe.contentWindow); + assert_equals(iframe.contentWindow.location.hash, "", "we made our way to ?2 for setup"); + iframe.contentWindow.history.back(); + await waitForLoad(iframe); + await delay(t, 0); + assert_equals(iframe.contentWindow.location.search, "?1", "we made our way to ?1 for setup"); + + iframe.contentWindow.history.forward(); + assert_equals(iframe.contentWindow.location.search, "?1", "must not go forward synchronously 1 (search)"); + assert_equals(iframe.contentWindow.location.hash, "", "must not go forward synchronously 1 (hash)"); + + iframe.contentWindow.history.forward(); + assert_equals(iframe.contentWindow.location.search, "?1", "must not go forward synchronously 2 (search)"); + assert_equals(iframe.contentWindow.location.hash, "", "must not go forward synchronously 2 (hash)"); + + await waitForLoad(iframe); + assert_equals(iframe.contentWindow.location.search, "?2", "first load event must be going forward (search)"); + assert_equals(iframe.contentWindow.location.hash, "#3", "first load event must be going forward (hash)"); + + iframe.contentWindow.onhashchange = t.unreached_func("hashchange event"); + iframe.onload = t.unreached_func("second load event"); + + await waitForPotentialNetworkLoads(t); + assert_equals(iframe.contentWindow.location.search, "?2", "must stay on ?2"); + assert_equals(iframe.contentWindow.location.hash, "#3", "must stay on ?2"); +}, "traversals in the same (forward) direction: coalesced"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-traversal-stop.html b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-traversal-stop.html new file mode 100644 index 0000000000..6202eb9226 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-traversal-stop.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Stop during cross-document traversals</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- + The spec says that stop() must not stop traverals. + + (Note: the spec also says the UI "stop" button must not stop traversals, but + that does not match browsers. See https://github.com/whatwg/html/issues/6905. + But that is not what's under test here.) +--> + +<body> +<script type="module"> +import { createIframe, waitForLoad, delay } from "./resources/helpers.mjs"; + +promise_test(async t => { + const iframe = await createIframe(t); + + // Setup + // Extra delay()s are necessary because if we navigate "inside" the load + // handler (i.e. in a promise reaction for the load handler) then it will + // be a replace navigation. + iframe.contentWindow.location.search = "?1"; + await waitForLoad(iframe); + await delay(t, 0); + iframe.contentWindow.location.search = "?2"; + await waitForLoad(iframe); + await delay(t, 0); + + iframe.contentWindow.history.back(); + + assert_equals(iframe.contentWindow.location.search, "?2", "must not go back synchronously"); + + window.stop(); + + await waitForLoad(iframe); + assert_equals(iframe.contentWindow.location.search, "?1", "must go back eventually"); +}, "cross-document traversals are not stopped by stop()"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/forward-to-pruned-entry.html b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/forward-to-pruned-entry.html new file mode 100644 index 0000000000..8e1c349e21 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/forward-to-pruned-entry.html @@ -0,0 +1,24 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + location.hash = "#1"; + location.hash = "#2"; + history.go(-2); + await new Promise(r => window.onpopstate = r); + + // Traverse forward then immediately do a same-document push. This will + // truncate the back forward list. + history.forward(); + location.hash = "#clobber"; + + // history.forward() should be aborted. + window.onpopstate = t.unreached_func("history.forward() should have been cancelled"); + await new Promise(r => t.step_timeout(r, 20)); + assert_equals(location.hash, "#clobber"); +}, "If forward pruning clobbers the target of a traverse, abort"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/nav-cancelation-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/nav-cancelation-1.html new file mode 100644 index 0000000000..b52fa04977 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/nav-cancelation-1.html @@ -0,0 +1,62 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent main frame cancels a same-origin child whose navigation is pending</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- + This test asserts that a parent canceling a same-origin child's cross-origin + navigation does not result in load events firing synchronously in the parent +--> + +<body> + +<iframe src=resources/slow.py></iframe> + +<script> +promise_test(async t => { + let window_load_fired = false; + let iframe_load_fired = false; + const iframe = document.querySelector('iframe'); + + const window_load_promise = new Promise(resolve => { + window.onload = () => { + window_load_fired = true; + resolve(); + } + }); + + const iframe_onload_promise = new Promise(resolve => { + iframe.onload = () => { + iframe_load_fired = true; + resolve(); + } + }); + + // While the child navigation is in-flight, cancel it and record when the + // parent `load` event fires. + window.frames[0].location.href = "resources/slow.py?different"; + + // Synchronously after cancelation, no load events should have been fired. + assert_false(window_load_fired, + "Parent's load event does not synchronously fire after cancelation"); + assert_false(iframe_load_fired, + "<iframe> load event does not synchronously fire after cancelation"); + + // Load events did not fire in a microtask after cancelation. + await Promise.resolve(); + assert_false(window_load_fired, + "Parent's load event does not fire in the microtask after cancelation"); + assert_false(iframe_load_fired, + "<iframe> load event does not fire in the microtask after cancelation"); + + // Canceling the navigation should unblock the parent's load event, but the + // new iframe navigation should still be pending, and the iframe load event + // shouldn't fire until *that one* is complete. + await window_load_promise; + assert_true(window_load_fired, + "Parent's load event fires asynchronously after child navigation cancelation"); + assert_false(iframe_load_fired, + "<iframe> load event does not fire until subsequent navigation is complete"); +}, "parent cancels a pending navigation in a same-origin child"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/nav-cancelation-2.sub.html b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/nav-cancelation-2.sub.html new file mode 100644 index 0000000000..c081513b7c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/nav-cancelation-2.sub.html @@ -0,0 +1,178 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Grandparent main frame cancels a navigation in a cross-origin grandchild</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- + This test asserts that an ancestor canceling a cross-origin descendant's + ongoing navigation does not result in load events firing in the ancestor + synchronously. + + The reason this test uses a grandparent/grandchild pair to represent the + ancestor/descendant, instead of a parent/child pair, is because if a child + frame is blocking its parent window's load event, that means the child frame + navigation is being made from the initial about:blank Document to some + resource, and the initial about:blank child is synchronously scriptable from + the parent since they share the same window agent. This test is trying to + capture the scenario where the descendant document (that owns the ongoing + navigation) is hosted/scheduled on a different agent than the ancestor + document that cancels the descendant's ongoing navigation. The only way to do + this is to have a grandparent frame load a cross-origin child, whose document + itself loads a child frame that has a very slow ongoing navigation. That way + the grandparent can reach the grandchild via `window.frames[0].frames[0]`, + which is a proxy to the document living in a different agent. +--> + +<body> + +<iframe src="http://{{domains[www1]}}:{{ports[http][0]}}/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/resources/nav-cancelation-2-helper.html"></iframe> + +<script> +promise_test(async t => { + let window_load_fired = false; + let iframe_load_fired = false; + let grandchild_iframe_load_fired = false; + const iframe = document.querySelector('iframe'); + + const window_load_promise = new Promise(resolve => { + window.onload = () => { + window_load_fired = true; + resolve(); + } + }); + + const iframe_onload_promise = new Promise(resolve => { + iframe.onload = () => { + iframe_load_fired = true; + resolve(); + } + }); + + // Let the grandchild frame get registered in window.frames. + await new Promise((resolve, reject) => { + window.addEventListener('message', e => { + if (e.data != "grandchild frame created") { + reject(Error(`Expected 'grandchild frame created', but got ${e.data}`)); + } + + resolve(); + }, {once: true}); + }); + + // Set up a message handler to listen to the grandchild's iframe element's + // load event being fired. We should never get this message, and we assert + // this below. If we ever get this message, that means one of two things: + // 1.) The grandparent (this document)'s load event was blocked on the + // completion of its grandchild's subsequent navigation (after + // cancelation) + // 2.) After the grandchild's navigation was canceled, its <iframe>'s load + // event was fired before its subsequent navigation completed + // Both of these are wrong. + addEventListener('message', e => { + assert_equals(e.data, "grandchild frame loaded", + `Expected 'grandchild frame loaded', but got ${e.data}`); + grandchild_iframe_load_fired = true; + }); + + // While the grandchild navigation is in-flight, cancel it and record when the + // our `load` event fires. The second navigation is a slow resource so that + // the speed of the network doesn't cause the grandchild load event to fire + // early and confuse the grandparent when running the assertions below. We're + // trying to clearly separate out when the grandparent load event fires vs + // when the grandchild load event fires. + window.frames[0].frames[0].location.href = "resources/slow.py?different"; + + // Synchronously after cancelation, no load events should have been fired. + assert_false(window_load_fired, + "Grandparent's load event does not synchronously fire after grandchild " + + "navigation cancelation"); + assert_false(iframe_load_fired, + "<iframe> load event does not synchronously fire after grandchild " + + "navigation cancelation"); + assert_false(grandchild_iframe_load_fired, + "Grandchild <iframe>'s load event does not synchronously fire upon " + + "navigation cancelation"); + + // Load events did not fire in a microtask after cancelation. + await Promise.resolve(); + assert_false(window_load_fired, + "Grandparent's load event does not fire in the microtask after " + + "navigation canceled"); + assert_false(iframe_load_fired, + "<iframe> load event does not fire in the microtask after navigation " + + "canceled"); + assert_false(grandchild_iframe_load_fired, + "Grandchild <iframe> load event does not fire in the microtask after " + + "navigation canceled"); + + // Canceling the navigation should however, asynchronously unblock, in this + // order: + // 1.) Our child window's load event, captured by our `iframe`'s load event + // 2.) Our window load event + // On the other hand, the grandchild navigation should still be ongoing, so + // inside our child's document, the nested <iframe> representing our + // grandchild should not have had its load event fired yet. + await iframe_onload_promise; + assert_true(iframe_load_fired); + assert_false(window_load_fired, + "Grandparent's load event does not fire before its child iframe's load " + + "event"); + assert_false(grandchild_iframe_load_fired, + "Grandchild <iframe>'s load event does not fire before its parent's load " + + "event and grandparent's load event"); + + // We want to assert that the grandparent is not (incorrectly) blocked on its + // grandchild's second navigation from completing. One sign that it was + // incorrectly blocked on its grandchild's second navigation is if the + // grandparent receives a message (saying that the grandchild <iframe> + // element's load event fired) before the grandparent's load event fires. + // + // This indicates a weird state where the grandparent's immediate child fired + // its load event in response to navigation cancelation (see the assertions + // above), but the grandparent itself is still blocked on the grandchild + // loading. If this is the case, the the postMessage() (that sets + // `grandchild_iframe_load_fired = true`) is received by the grandparent just + // before the grandparent's load event is unblocked and fired. Therefore we + // can detect this situation by checking `grandchild_iframe_load_fired`. + await window_load_promise; + assert_true(iframe_load_fired); + assert_true(window_load_fired, + "Grandparent's load event fires asynchronously after grandchild " + + "navigation cancelation"); + assert_false(grandchild_iframe_load_fired, + "Grandchild <iframe> load event doesn't fire before grandparent's " + + "load event"); + + // Verify that the grandchild <iframe>'s load event does not fire within one + // task of the grandchild's load event from being fired. This is to further + // verify that the grandparent's load event is not tied to its grandchild's + // second navigation. + // + // If for example, the grandparent's load event *is* blocked on the + // grandchild's second navigation from finishing, it is still possible for the + // grandparent's load event to fire. For example, Chromium has a bug where if + // both are true: + // 1.) The grandparent frame is in the same process as the grandchild frame + // 2.) The grandparent frame's load event is blocked on its grandchild's + // second navigation + // + // ...then the following will happen: + // 1.) The grandchild's load event will fire, triggering a postMessage() to + // the grandparent frame. This queues a task to run the grandparent's + // message handler. + // 2.) The grandparent's load event will *immediately* fire, and the + // postMessage() will fire a single task later since it is queued. + // + // Therefore, we assert that `grandchild_iframe_load_fired` is not true up to + // a single task after the grandparent's load event fires. + await new Promise(resolve => { + t.step_timeout(resolve, 0); + }); + + assert_false(grandchild_iframe_load_fired, + "Grandchild <iframe>'s load event does not fire at least one task " + + "after the grandparent's window load event fires. It should only fire " + + "when its subsequent navigation is complete"); +}, "grandparent cancels a pending navigation in a cross-origin grandchild"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/resources/helpers.mjs b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/resources/helpers.mjs new file mode 100644 index 0000000000..7938497920 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/resources/helpers.mjs @@ -0,0 +1,52 @@ +export function createIframe(t) { + return new Promise((resolve, reject) => { + const iframe = document.createElement("iframe"); + iframe.onload = () => resolve(iframe); + iframe.onerror = () => reject(new Error("Could not load iframe")); + iframe.src = "/common/blank.html"; + + t.add_cleanup(() => iframe.remove()); + document.body.append(iframe); + }); +} + +export function delay(t, ms) { + return new Promise(resolve => t.step_timeout(resolve, ms)); +} + +export function waitForLoad(obj) { + return new Promise(resolve => { + obj.addEventListener("load", resolve, { once: true }); + }); +} + +export function waitForHashchange(obj) { + return new Promise(resolve => { + obj.addEventListener("hashchange", resolve, { once: true }); + }); +} + +export function waitForPopstate(obj) { + return new Promise(resolve => { + obj.addEventListener("popstate", resolve, { once: true }); + }); +} + +// This is used when we want to end the test by asserting some load doesn't +// happen, but we're not sure how long to wait. We could just wait a long-ish +// time (e.g. a second), but that makes the tests slow. Instead, assume that +// network loads take roughly the same time. Then, you can use this function to +// wait a small multiple of the duration of a separate iframe load; this should +// be long enough to catch any problems. +export async function waitForPotentialNetworkLoads(t) { + const before = performance.now(); + + // Sometimes we're doing something, like a traversal, which cancels our first + // attempt at iframe loading. In that case we bail out after 100 ms and try + // again. (Better ideas welcome...) + await Promise.race([createIframe(t), delay(t, 100)]); + await createIframe(t); + + const after = performance.now(); + await delay(t, after - before); +} diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/resources/nav-cancelation-2-helper.html b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/resources/nav-cancelation-2-helper.html new file mode 100644 index 0000000000..a0b4acda2e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/resources/nav-cancelation-2-helper.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Page with child frame that navigates slowly</title> + +<!-- + This file is used by `../nav-cancelation-2.sub.html`. The iframe below is its + grandchild iframe, and whenever its load event fires we report this up to our + parent Document. +--> +<iframe src="slow.py"></iframe> + +<script> + window.parent.postMessage("grandchild frame created", "*"); + const iframe = document.querySelector('iframe'); + iframe.onload = e => { + window.parent.postMessage("grandchild frame loaded", "*"); + }; +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/resources/slow.py b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/resources/slow.py new file mode 100644 index 0000000000..5ee32a60ba --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/resources/slow.py @@ -0,0 +1,7 @@ +# Like /common/slow.py except with text/html content-type so that it won't +# trigger strange parts of the <iframe> navigate algorithm. +import time + +def main(request, response): + time.sleep(2) + return 200, [["Content-Type", "text/html"]], b'' diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-nav-cross-document-nav.html b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-nav-cross-document-nav.html new file mode 100644 index 0000000000..8082e9bbe0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-nav-cross-document-nav.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Cross-document navigation after a same-document navigation</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- + These tests are kind of silly since it's hard to imagine any other result: + same-document navigations are always synchronous so of course the + same-document navigation will succeed, followed by the cross-document one. + + Nevertheless they're nice as a basis from which to write corresponding app + history tests, where the consequences aren't as obvious. +--> + +<body> +<script type="module"> +import { createIframe, waitForLoad } from "./resources/helpers.mjs"; + +promise_test(async t => { + const iframe = await createIframe(t); + + iframe.contentWindow.location.hash = "#1"; + assert_equals(iframe.contentWindow.location.hash, "#1"); + + iframe.contentWindow.location.search = "?2"; + await waitForLoad(iframe); + assert_equals(iframe.contentWindow.location.search, "?2"); +}, "fragment navigation then cross-document navigation"); + +promise_test(async t => { + const iframe = await createIframe(t); + + iframe.contentWindow.history.pushState(null, "", "?1"); + assert_equals(iframe.contentWindow.location.search, "?1"); + + iframe.contentWindow.location.search = "?2"; + await waitForLoad(iframe); + assert_equals(iframe.contentWindow.location.search, "?2"); +}, "pushState() then cross-document navigation"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-nav-cross-document-traversal.html b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-nav-cross-document-traversal.html new file mode 100644 index 0000000000..fc6f92e819 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-nav-cross-document-traversal.html @@ -0,0 +1,70 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Traversal after a same-document navigations</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- + These tests are kind of silly since it's hard to imagine any other result: + same-document navigations are always synchronous so of course back() won't + cancel them. + + Nevertheless they're nice as a basis from which to write corresponding app + history tests, where the consequences aren't as obvious. +--> + +<body> +<script type="module"> +import { createIframe, waitForLoad, delay } from "./resources/helpers.mjs"; + +promise_test(async t => { + const iframe = await createIframe(t); + + // Setup + // Extra delay()s are necessary because if we navigate "inside" the load + // handler (i.e. in a promise reaction for the load handler) then it will + // be a replace navigation. + iframe.contentWindow.location.search = "?1"; + await waitForLoad(iframe); + await delay(t, 0); + iframe.contentWindow.location.search = "?2"; + await waitForLoad(iframe); + await delay(t, 0); + + iframe.contentWindow.location.hash = "#3"; + iframe.contentWindow.history.go(-2); + + assert_equals(iframe.contentWindow.location.search, "?2", "must not go back synchronously (search)"); + assert_equals(iframe.contentWindow.location.hash, "#3", "must not go back synchronously (hash)"); + + await waitForLoad(iframe); + assert_equals(iframe.contentWindow.location.search, "?1", "must go back eventually (search)"); + assert_equals(iframe.contentWindow.location.hash, "", "must go back eventually (hash)"); +}, "fragment navigation then go(-2)"); + +promise_test(async t => { + const iframe = await createIframe(t); + + // Setup + // Extra delay()s are necessary because if we navigate "inside" the load + // handler (i.e. in a promise reaction for the load handler) then it will + // be a replace navigation. + iframe.contentWindow.location.search = "?1"; + await waitForLoad(iframe); + await delay(t, 0); + iframe.contentWindow.location.search = "?2"; + await waitForLoad(iframe); + await delay(t, 0); + + iframe.contentWindow.history.pushState(null, "", "/3"); + iframe.contentWindow.history.go(-2); + + assert_equals(iframe.contentWindow.location.search, "", "must not go back synchronously (search)"); + assert_equals(iframe.contentWindow.location.pathname, "/3", "must not go back synchronously (pathname)"); + + await waitForLoad(iframe); + assert_equals(iframe.contentWindow.location.search, "?1", "must go back eventually (search)"); + assert_equals(iframe.contentWindow.location.pathname, "/common/blank.html", "must go back eventually (pathname)"); + +}, "pushState then go(-2)"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-nav-same-document-nav.html b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-nav-same-document-nav.html new file mode 100644 index 0000000000..2d8961d6e4 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-nav-same-document-nav.html @@ -0,0 +1,61 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Same-document navigation after a same-document navigation</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- + These tests are kind of silly since it's hard to imagine any other result: + same-document navigations are always synchronous so of course two in a row + will succeed. + + Nevertheless they're nice as a basis from which to write corresponding app + history tests, where the consequences aren't as obvious. +--> + +<body> +<script type="module"> +import { createIframe } from "./resources/helpers.mjs"; + +promise_test(async t => { + const iframe = await createIframe(t); + + iframe.contentWindow.location.hash = "#1"; + assert_equals(iframe.contentWindow.location.hash, "#1"); + + iframe.contentWindow.location.hash = "#2"; + assert_equals(iframe.contentWindow.location.hash, "#2"); +}, "fragment navigation then fragment navigation"); + +promise_test(async t => { + const iframe = await createIframe(t); + + iframe.contentWindow.history.pushState(null, "", "?1"); + assert_equals(iframe.contentWindow.location.search, "?1"); + + iframe.contentWindow.history.pushState(null, "", "?2"); + assert_equals(iframe.contentWindow.location.search, "?2"); +}, "pushState() then pushState()"); + +promise_test(async t => { + const iframe = await createIframe(t); + + iframe.contentWindow.history.pushState(null, "", "?1"); + assert_equals(iframe.contentWindow.location.search, "?1"); + + iframe.contentWindow.location.hash = "#2"; + assert_equals(iframe.contentWindow.location.search, "?1"); + assert_equals(iframe.contentWindow.location.hash, "#2"); +}, "pushState() then fragment navigation"); + +promise_test(async t => { + const iframe = await createIframe(t); + + iframe.contentWindow.location.hash = "#1"; + assert_equals(iframe.contentWindow.location.hash, "#1"); + + iframe.contentWindow.history.pushState(null, "", "?2"); + assert_equals(iframe.contentWindow.location.search, "?2"); + assert_equals(iframe.contentWindow.location.hash, ""); +}, "fragment navigation then pushState()"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-nav-same-document-traversal.html b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-nav-same-document-traversal.html new file mode 100644 index 0000000000..a112143837 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-nav-same-document-traversal.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Same-document traversal after a same-document navigations</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- + These tests are kind of silly since it's hard to imagine any other result: + same-document navigations are always synchronous so of course back() won't + cancel them. + + Nevertheless they're nice as a basis from which to write corresponding app + history tests, where the consequences aren't as obvious. +--> + +<body> +<script type="module"> +import { createIframe, delay } from "./resources/helpers.mjs"; + +promise_test(async t => { + const iframe = await createIframe(t); + + // Setup + iframe.contentWindow.location.hash = "#1"; + await delay(t, 0); + iframe.contentWindow.location.hash = "#2"; + await delay(t, 0); + + iframe.contentWindow.location.hash = "#3"; + iframe.contentWindow.history.back(); + + assert_equals(iframe.contentWindow.location.hash, "#3", "must not go back synchronously"); + + // Does go back eventually, and only one step + await t.step_wait(() => iframe.contentWindow.location.hash === "#2"); +}, "fragment navigation then back()"); + +promise_test(async t => { + const iframe = await createIframe(t); + + // Setup + iframe.contentWindow.history.pushState(null, "", "?1"); + await delay(t, 0); + iframe.contentWindow.history.pushState(null, "", "?2"); + await delay(t, 0); + + iframe.contentWindow.history.pushState(null, "", "?3"); + iframe.contentWindow.history.back(); + + assert_equals(iframe.contentWindow.location.search, "?3", "must not go back synchronously"); + + // Does go back eventually, and only one step + await t.step_wait(() => iframe.contentWindow.location.search === "?2"); +}, "pushState then back()"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-nav-stop.html b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-nav-stop.html new file mode 100644 index 0000000000..a9036209a5 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-nav-stop.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Stop after a same-document navigations</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<body> +<script type="module"> +import { createIframe } from "./resources/helpers.mjs"; + +promise_test(async t => { + const iframe = await createIframe(t); + + iframe.contentWindow.location.hash = "#1"; + iframe.contentWindow.stop(); + + assert_equals(iframe.contentWindow.location.hash, "#1"); +}, "fragment navigations are not stopped by stop()"); + +promise_test(async t => { + const iframe = await createIframe(t); + + iframe.contentWindow.history.pushState(null, "", "?1"); + iframe.contentWindow.stop(); + + assert_equals(iframe.contentWindow.location.search, "?1"); +}, "pushState() navigations are not stopped by stop()"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-traversal-cross-document-nav.html b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-traversal-cross-document-nav.html new file mode 100644 index 0000000000..37960c3c54 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-traversal-cross-document-nav.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Cross-document navigations during same-document traversals</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- + The spec says that navigations are ignored if there is an ongoing traversal. +--> + +<body> +<script type="module"> +import { createIframe, delay, waitForPotentialNetworkLoads } from "./resources/helpers.mjs"; + +promise_test(async t => { + const iframe = await createIframe(t); + + // Setup + iframe.contentWindow.location.hash = "#1"; + await delay(t, 0); + iframe.contentWindow.location.hash = "#2"; + await delay(t, 0); + + iframe.contentWindow.history.back(); + + assert_equals(iframe.contentWindow.location.hash, "#2", "must not go back synchronously"); + + iframe.contentWindow.location.search = "?1"; + assert_equals(iframe.contentWindow.location.search, "", "must not navigate synchronously (search)"); + assert_equals(iframe.contentWindow.location.hash, "#2", "must not navigate synchronously (hash)"); + + // Eventually ends up on #1. + await t.step_wait(() => iframe.contentWindow.location.hash === "#1", "traversal"); + + // Never loads a different document. + iframe.onload = t.unreached_func("load event"); + await waitForPotentialNetworkLoads(t); + assert_equals(iframe.contentWindow.location.search, "", "must stay on #2 (search)"); + assert_equals(iframe.contentWindow.location.hash, "#2", "must stay on #2 (hash)"); +}, "same-document traversals are not canceled by cross-document navigations"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-traversal-cross-document-traversal.html b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-traversal-cross-document-traversal.html new file mode 100644 index 0000000000..a48f4d484f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-traversal-cross-document-traversal.html @@ -0,0 +1,88 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Cross-document traversals during same-document traversals</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- + Compare this to cross-document-traversal-cross-document-traversal.html. Since + there are no network loads for the first traversal here, it does observably go + through. So we end up with both traversals before observable in sequence. +--> + +<body> +<script type="module"> +import { createIframe, waitForLoad, waitForHashchange, delay } from "./resources/helpers.mjs"; + +promise_test(async t => { + const iframe = await createIframe(t); + + // Setup + // Extra delay()s are necessary because if we navigate "inside" the load + // handler (i.e. in a promise reaction for the load handler) then it will + // be a replace navigation. + iframe.contentWindow.location.search = "?1"; + await waitForLoad(iframe); + await delay(t, 0); + iframe.contentWindow.location.search = "?2"; + await waitForLoad(iframe); + await delay(t, 0); + iframe.contentWindow.location.hash = "#3"; + await waitForHashchange(iframe.contentWindow); + + iframe.contentWindow.history.back(); + assert_equals(iframe.contentWindow.location.search, "?2", "must not go back synchronously 1 (search)"); + assert_equals(iframe.contentWindow.location.hash, "#3", "must not go back synchronously 1 (hash)"); + + iframe.contentWindow.history.back(); + assert_equals(iframe.contentWindow.location.search, "?2", "must not go back synchronously 1 (search)"); + assert_equals(iframe.contentWindow.location.hash, "#3", "must not go back synchronously 1 (hash)"); + + await waitForHashchange(iframe.contentWindow); + assert_equals(iframe.contentWindow.location.search, "?2", "first hashchange event must be going back (search)"); + assert_equals(iframe.contentWindow.location.hash, "", "first hashchange event must be going back (hash)"); + + await waitForLoad(iframe); + assert_equals(iframe.contentWindow.location.search, "?1", "first load event must be going back (search)"); + assert_equals(iframe.contentWindow.location.hash, "", "first load event must be going back (hash)"); +}, "traversals in the same (back) direction: queued up"); + +promise_test(async t => { + const iframe = await createIframe(t); + + // Setup + // Extra delay()s are necessary because if we navigate "inside" the load + // handler (i.e. in a promise reaction for the load handler) then it will + // be a replace navigation. + iframe.contentWindow.location.search = "?1"; + await waitForLoad(iframe); + await delay(t, 0); + iframe.contentWindow.location.hash = "#2"; + await waitForHashchange(iframe.contentWindow); + iframe.contentWindow.location.search = "?3"; + await waitForLoad(iframe); + await delay(t, 0); + iframe.contentWindow.history.back(); + await waitForLoad(iframe); + iframe.contentWindow.history.back(); + await waitForHashchange(iframe.contentWindow); + assert_equals(iframe.contentWindow.location.search, "?1", "we made our way to ?1 for setup (search)"); + assert_equals(iframe.contentWindow.location.hash, "", "we made our way to ?1 for setup (search)"); + + iframe.contentWindow.history.forward(); + assert_equals(iframe.contentWindow.location.search, "?1", "must not go forward synchronously 1 (search)"); + assert_equals(iframe.contentWindow.location.hash, "", "must not go forward synchronously 1 (hash)"); + + iframe.contentWindow.history.forward(); + assert_equals(iframe.contentWindow.location.search, "?1", "must not go forward synchronously 2 (search)"); + assert_equals(iframe.contentWindow.location.hash, "", "must not go forward synchronously 2 (hash)"); + + await waitForHashchange(iframe.contentWindow); + assert_equals(iframe.contentWindow.location.search, "?1", "first hashchange event must be going forward (search)"); + assert_equals(iframe.contentWindow.location.hash, "#2", "first hashchange event must be going forward (hash)"); + + await waitForLoad(iframe); + assert_equals(iframe.contentWindow.location.search, "?3", "first load event must be going forward (search)"); + assert_equals(iframe.contentWindow.location.hash, "#2", "first load event must be going forward (hash)"); +}, "traversals in the same (forward) direction: queued up"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-traversal-same-document-nav.html b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-traversal-same-document-nav.html new file mode 100644 index 0000000000..5094651ab5 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-traversal-same-document-nav.html @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Same-document navigations during same-document traversals</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- + Per spec, same-document navigations ignore the "ongoing navigation" flag and + just happen synchronously, then queue onto the session history traversal queue + to update the source of truth. However, the traversal was queued first, so it + will ignore that update when calculating its endpoint. +--> + +<body> +<script type="module"> +import { createIframe, delay } from "./resources/helpers.mjs"; + +promise_test(async t => { + const iframe = await createIframe(t); + + // Setup + iframe.contentWindow.location.hash = "#1"; + await delay(t, 0); + iframe.contentWindow.location.hash = "#2"; + await delay(t, 0); + + iframe.contentWindow.history.back(); + + assert_equals(iframe.contentWindow.location.hash, "#2", "must not go back synchronously"); + + iframe.contentWindow.location.hash = "#3"; + assert_equals(iframe.contentWindow.location.hash, "#3"); + + // Eventually ends up on #1 + await t.step_wait(() => iframe.contentWindow.location.hash === "#1"); +}, "same-document traversals are not canceled by fragment navigations and calculate their endpoint based on the original placement"); + +promise_test(async t => { + const iframe = await createIframe(t); + + // Setup + iframe.contentWindow.history.pushState(null, "", "/1"); + await delay(t, 0); + iframe.contentWindow.history.pushState(null, "", "/2"); + await delay(t, 0); + + iframe.contentWindow.history.back(); + + assert_equals(iframe.contentWindow.location.pathname, "/2", "must not go back synchronously"); + + iframe.contentWindow.history.pushState(null, "", "/3"); + assert_equals(iframe.contentWindow.location.pathname, "/3"); + + // Eventually ends up on /1 + await t.step_wait(() => iframe.contentWindow.location.pathname === "/1"); +}, "same-document traversals are not canceled by pushState() and calculate their endpoint based on the original placement"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-traversal-same-document-traversal-hashchange.html b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-traversal-same-document-traversal-hashchange.html new file mode 100644 index 0000000000..df5ea5caab --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-traversal-same-document-traversal-hashchange.html @@ -0,0 +1,148 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Same-document traversals during same-document traversals (using fragment navigations)</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- + Compare this to cross-document-traversal-cross-document-traversal.html. Since + there are no network loads or document unloads to cancel tasks, both + traversals should observably go through. Target step calculation for the + second traversal should take place after the first traversal is finished. So + we end up with both traversals observable in sequence. +--> + +<body> +<script type="module"> +import { createIframe, delay, waitForHashchange } from "./resources/helpers.mjs"; + +promise_test(async t => { + const iframe = await createIframe(t); + const baseURL = iframe.contentWindow.location.href; + + // Setup + iframe.contentWindow.location.hash = "#1"; + await waitForHashchange(iframe.contentWindow); + iframe.contentWindow.location.hash = "#2"; + await waitForHashchange(iframe.contentWindow); + iframe.contentWindow.location.hash = "#3"; + await waitForHashchange(iframe.contentWindow); + iframe.contentWindow.history.back(); + await waitForHashchange(iframe.contentWindow); + assert_equals(iframe.contentWindow.location.hash, "#2", "we made our way to #2 for setup"); + + iframe.contentWindow.history.back(); + assert_equals(iframe.contentWindow.location.hash, "#2", "must not go back synchronously"); + + iframe.contentWindow.history.forward(); + assert_equals(iframe.contentWindow.location.hash, "#2", "must not go forward synchronously"); + + const event1 = await waitForHashchange(iframe.contentWindow); + assert_equals(event1.oldURL, baseURL + "#2", "oldURL 1"); + assert_equals(event1.newURL, baseURL + "#1", "newURL 1"); + // Cannot test iframe.contentWindow.location.hash since the second history + // traversal task is racing with the fire an event task, so we don't know + // which will happen first. + + const event2 = await waitForHashchange(iframe.contentWindow); + assert_equals(event2.oldURL, baseURL + "#1", "oldURL 2"); + assert_equals(event2.newURL, baseURL + "#2", "newURL 2"); + assert_equals(iframe.contentWindow.location.hash, "#2"); +}, "same-document traversals in opposite directions: queued up"); + +promise_test(async t => { + const iframe = await createIframe(t); + const baseURL = iframe.contentWindow.location.href; + + // Setup + iframe.contentWindow.location.hash = "#1"; + await waitForHashchange(iframe.contentWindow); + iframe.contentWindow.location.hash = "#2"; + await waitForHashchange(iframe.contentWindow); + + iframe.contentWindow.history.back(); + assert_equals(iframe.contentWindow.location.hash, "#2", "must not go back synchronously"); + + iframe.contentWindow.history.forward(); + assert_equals(iframe.contentWindow.location.hash, "#2", "must not go forward synchronously"); + + const event1 = await waitForHashchange(iframe.contentWindow); + assert_equals(event1.oldURL, baseURL + "#2", "oldURL 1"); + assert_equals(event1.newURL, baseURL + "#1", "newURL 1"); + // Cannot test iframe.contentWindow.location.hash since the second history + // traversal task is racing with the fire an event task, so we don't know + // which will happen first. + + const event2 = await waitForHashchange(iframe.contentWindow); + assert_equals(event2.oldURL, baseURL + "#1", "oldURL 2"); + assert_equals(event2.newURL, baseURL + "#2", "newURL 2"); + assert_equals(iframe.contentWindow.location.hash, "#2"); +}, "same-document traversals in opposite directions, second traversal invalid at queuing time: queued up"); + +promise_test(async t => { + const iframe = await createIframe(t); + const baseURL = iframe.contentWindow.location.href; + + // Setup + iframe.contentWindow.location.hash = "#1"; + await waitForHashchange(iframe.contentWindow); + iframe.contentWindow.location.hash = "#2"; + await waitForHashchange(iframe.contentWindow); + iframe.contentWindow.location.hash = "#3"; + await waitForHashchange(iframe.contentWindow); + + iframe.contentWindow.history.back(); + assert_equals(iframe.contentWindow.location.hash, "#3", "must not go back synchronously (1)"); + + iframe.contentWindow.history.back(); + assert_equals(iframe.contentWindow.location.hash, "#3", "must not go back synchronously (2)"); + + const event1 = await waitForHashchange(iframe.contentWindow); + assert_equals(event1.oldURL, baseURL + "#3", "oldURL 1"); + assert_equals(event1.newURL, baseURL + "#2", "newURL 1"); + // Cannot test iframe.contentWindow.location.hash since the second history + // traversal task is racing with the fire an event task, so we don't know + // which will happen first. + + const event2 = await waitForHashchange(iframe.contentWindow); + assert_equals(event2.oldURL, baseURL + "#2", "oldURL 2"); + assert_equals(event2.newURL, baseURL + "#1", "newURL 2"); + assert_equals(iframe.contentWindow.location.hash, "#1"); +}, "same-document traversals in the same (back) direction: queue up"); + +promise_test(async t => { + const iframe = await createIframe(t); + const baseURL = iframe.contentWindow.location.href; + + // Setup + iframe.contentWindow.location.hash = "#1"; + await waitForHashchange(iframe.contentWindow); + iframe.contentWindow.location.hash = "#2"; + await waitForHashchange(iframe.contentWindow); + iframe.contentWindow.location.hash = "#3"; + await waitForHashchange(iframe.contentWindow); + iframe.contentWindow.history.back(); + await waitForHashchange(iframe.contentWindow); + iframe.contentWindow.history.back(); + await waitForHashchange(iframe.contentWindow); + assert_equals(iframe.contentWindow.location.hash, "#1", "we made our way to #1 for setup"); + + iframe.contentWindow.history.forward(); + assert_equals(iframe.contentWindow.location.hash, "#1", "must not go forward synchronously (1)"); + + iframe.contentWindow.history.forward(); + assert_equals(iframe.contentWindow.location.hash, "#1", "must not go forward synchronously (2)"); + + const event1 = await waitForHashchange(iframe.contentWindow); + assert_equals(event1.oldURL, baseURL + "#1", "oldURL 1"); + assert_equals(event1.newURL, baseURL + "#2", "newURL 1"); + // Cannot test iframe.contentWindow.location.hash since the second history + // traversal task is racing with the fire an event task, so we don't know + // which will happen first. + + const event2 = await waitForHashchange(iframe.contentWindow); + assert_equals(event2.oldURL, baseURL + "#2", "oldURL 2"); + assert_equals(event2.newURL, baseURL + "#3", "newURL 2"); + assert_equals(iframe.contentWindow.location.hash, "#3"); +}, "same-document traversals in the same (forward) direction: queue up"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-traversal-same-document-traversal-pushstate.html b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-traversal-same-document-traversal-pushstate.html new file mode 100644 index 0000000000..47c7d6e3dc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-traversal-same-document-traversal-pushstate.html @@ -0,0 +1,137 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Same-document traversals during same-document traversals (using pushState())</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- + Compare this to cross-document-traversal-cross-document-traversal.html. Since + there are no network loads or document unloads to cancel tasks, both + traversals should observably go through. Target step calculation for the + second traversal should take place after the first traversal is finished. So + we end up with both traversals observable in sequence. +--> + +<body> +<script type="module"> +import { createIframe, delay, waitForPopstate } from "./resources/helpers.mjs"; + +promise_test(async t => { + const iframe = await createIframe(t); + + // Setup + iframe.contentWindow.history.pushState(1, "", "/1"); + assert_equals(iframe.contentWindow.location.pathname, "/1", "setup /1"); + iframe.contentWindow.history.pushState(2, "", "/2"); + assert_equals(iframe.contentWindow.location.pathname, "/2", "setup /2"); + iframe.contentWindow.history.pushState(3, "", "/3"); + assert_equals(iframe.contentWindow.location.pathname, "/3", "setup /3"); + iframe.contentWindow.history.back(); + await waitForPopstate(iframe.contentWindow); + assert_equals(iframe.contentWindow.location.pathname, "/2", "we made our way to /2 for setup"); + + iframe.contentWindow.history.back(); + assert_equals(iframe.contentWindow.location.pathname, "/2", "must not go back synchronously"); + + iframe.contentWindow.history.forward(); + assert_equals(iframe.contentWindow.location.pathname, "/2", "must not go forward synchronously"); + + const event1 = await waitForPopstate(iframe.contentWindow); + assert_equals(event1.state, 1, "state 1"); + // Cannot test iframe.contentWindow.location.pathname since the second history + // traversal task is racing with the fire an event task, so we don't know + // which will happen first. + + const event2 = await waitForPopstate(iframe.contentWindow); + assert_equals(event2.state, 2, "state 2"); + assert_equals(iframe.contentWindow.location.pathname, "/2"); +}, "same-document traversals in opposite directions: queued up"); + +promise_test(async t => { + const iframe = await createIframe(t); + + // Setup + iframe.contentWindow.history.pushState(1, "", "/1"); + assert_equals(iframe.contentWindow.location.pathname, "/1", "setup /1"); + iframe.contentWindow.history.pushState(2, "", "/2"); + assert_equals(iframe.contentWindow.location.pathname, "/2", "we made our way to /2 for setup"); + + iframe.contentWindow.history.back(); + assert_equals(iframe.contentWindow.location.pathname, "/2", "must not go back synchronously"); + + iframe.contentWindow.history.forward(); + assert_equals(iframe.contentWindow.location.pathname, "/2", "must not go forward synchronously"); + + const event1 = await waitForPopstate(iframe.contentWindow); + assert_equals(event1.state, 1, "state 1"); + // Cannot test iframe.contentWindow.location.pathname since the second history + // traversal task is racing with the fire an event task, so we don't know + // which will happen first. + + const event2 = await waitForPopstate(iframe.contentWindow); + assert_equals(event2.state, 2, "state 2"); + assert_equals(iframe.contentWindow.location.pathname, "/2"); +}, "same-document traversals in opposite directions, second traversal invalid at queuing time: queued up"); + +promise_test(async t => { + const iframe = await createIframe(t); + + // Setup + iframe.contentWindow.history.pushState(1, "", "/1"); + assert_equals(iframe.contentWindow.location.pathname, "/1", "setup /1"); + iframe.contentWindow.history.pushState(2, "", "/2"); + assert_equals(iframe.contentWindow.location.pathname, "/2", "setup /2"); + iframe.contentWindow.history.pushState(3, "", "/3"); + assert_equals(iframe.contentWindow.location.pathname, "/3", "we made our way to /3 for setup"); + + iframe.contentWindow.history.back(); + assert_equals(iframe.contentWindow.location.pathname, "/3", "must not go back synchronously (1)"); + + iframe.contentWindow.history.back(); + assert_equals(iframe.contentWindow.location.pathname, "/3", "must not go back synchronously (2)"); + + const event1 = await waitForPopstate(iframe.contentWindow); + assert_equals(event1.state, 2, "state 1"); + // Cannot test iframe.contentWindow.location.pathname since the second history + // traversal task is racing with the fire an event task, so we don't know + // which will happen first. + + const event2 = await waitForPopstate(iframe.contentWindow); + assert_equals(event2.state, 1, "state 2"); + assert_equals(iframe.contentWindow.location.pathname, "/1"); +}, "same-document traversals in the same (back) direction: queue up"); + +promise_test(async t => { + const iframe = await createIframe(t); + + // Setup + iframe.contentWindow.history.pushState(1, "", "/1"); + assert_equals(iframe.contentWindow.location.pathname, "/1", "setup /1"); + iframe.contentWindow.history.pushState(2, "", "/2"); + assert_equals(iframe.contentWindow.location.pathname, "/2", "setup /2"); + iframe.contentWindow.history.pushState(3, "", "/3"); + assert_equals(iframe.contentWindow.location.pathname, "/3", "setup /3"); + iframe.contentWindow.history.back(); + await waitForPopstate(iframe.contentWindow); + assert_equals(iframe.contentWindow.location.pathname, "/2", "setup /2 again"); + iframe.contentWindow.history.back(); + await waitForPopstate(iframe.contentWindow); + assert_equals(iframe.contentWindow.location.pathname, "/1", "we made our way to /1 for setup"); + + iframe.contentWindow.history.forward(); + assert_equals(iframe.contentWindow.location.pathname, "/1", "must not go forward synchronously (1)"); + + iframe.contentWindow.history.forward(); + assert_equals(iframe.contentWindow.location.pathname, "/1", "must not go forward synchronously (2)"); + + const event1 = await waitForPopstate(iframe.contentWindow); + assert_equals(event1.state, 2, "state 1"); + // Cannot test iframe.contentWindow.location.pathname since the second history + // traversal task is racing with the fire an event task, so we don't know + // which will happen first. + + const event2 = await waitForPopstate(iframe.contentWindow); + assert_equals(event2.state, 3, "state 2"); + assert_equals(iframe.contentWindow.location.pathname, "/3"); +}, "same-document traversals in the same (forward) direction: queue up"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-traversal-stop.html b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-traversal-stop.html new file mode 100644 index 0000000000..2f0570380a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-traversal-stop.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Stop during same-document traversals</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- + The spec says that stop() must not stop traverals. + + (Note: the spec also says the UI "stop" button must not stop traversals, but + that does not match browsers. See https://github.com/whatwg/html/issues/6905. + But that is not what's under test here.) +--> + +<body> +<script type="module"> +import { createIframe, delay } from "./resources/helpers.mjs"; + +promise_test(async t => { + const iframe = await createIframe(t); + + // Setup + iframe.contentWindow.location.hash = "#1"; + await delay(t, 0); + iframe.contentWindow.location.hash = "#2"; + await delay(t, 0); + + iframe.contentWindow.history.back(); + + assert_equals(iframe.contentWindow.location.hash, "#2", "must not go back synchronously"); + + window.stop(); + + // Does go back eventually + await t.step_wait(() => iframe.contentWindow.location.hash === "#1"); +}, "same-document traversals are not stopped by stop()"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/read-media/cross-origin-video.html b/testing/web-platform/tests/html/browsers/browsing-the-web/read-media/cross-origin-video.html new file mode 100644 index 0000000000..b99658facb --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/read-media/cross-origin-video.html @@ -0,0 +1,32 @@ +<!doctype html> +<title>Test cross origin load of media document in parts</title> +<link rel="motivation" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1781759"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src="/common/get-host-info.sub.js"></script> +<body></body> +<script> +promise_test(async () => { + const frame = document.createElement('iframe'); + const dir = location.pathname.replace(/\/[^\/]*$/, '/'); + frame.src = + // remote origin intermediate document + get_host_info().HTTP_NOTSAMESITE_ORIGIN + dir + // iframe-document.sub.html has an iframe with src=childsrc. + + 'resources/iframe-document.sub.html?childsrc=' + // same origin video document, so that we can play(). + + get_host_info().ORIGIN + // 'PartialContent' ensures that the entire video resource does not load + // in one fetch. + + '/service-workers/service-worker/resources/fetch-access-control.py?' + + 'VIDEO%26PartialContent'; + + document.body.appendChild(frame); + await new Promise(resolve => frame.onload = resolve); + + const inner = frame.contentWindow.frames[0]; + const video = inner.document.body.childNodes[0]; + video.muted = true; // to allow playback + return video.play(); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/read-media/pageload-image-in-popup.html b/testing/web-platform/tests/html/browsers/browsing-the-web/read-media/pageload-image-in-popup.html new file mode 100644 index 0000000000..e9284824f4 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/read-media/pageload-image-in-popup.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media documents: image</title> + <link rel="author" title="Takayoshi Kochi" href="mailto:kochi@chromium.org"> + <link rel="author" title="Michael Ventnor" href="mailto:mventnor@mozilla.com"> + <link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com"> + <link rel="help" href="https://html.spec.whatwg.org/multipage/#read-media"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + +<script> + var t = async_test("The document for a standalone media file should have one child in the body."); + + var imgwin = window.open('/images/blue.png'); + imgwin.onload = t.step_func_done(function() { + assert_equals(imgwin.opener, window); + assert_equals(imgwin.document.contentType, "image/png"); + var imgwinChildren = imgwin.document.body.childNodes; + assert_equals(imgwinChildren.length, 1, "Body of image document has 1 child"); + assert_equals(imgwinChildren[0].nodeName, "IMG", "Only child of body must be an <img> element"); + assert_equals(imgwinChildren[0].namespaceURI, "http://www.w3.org/1999/xhtml", + "Only child of body must be an HTML element"); + imgwin.close(); + }); +</script> +</head> +<body> + <div id="log"></div> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/read-media/pageload-image.html b/testing/web-platform/tests/html/browsers/browsing-the-web/read-media/pageload-image.html new file mode 100644 index 0000000000..67c97bafdd --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/read-media/pageload-image.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media documents: image</title> + <link rel="author" title="Michael Ventnor" href="mailto:mventnor@mozilla.com"> + <link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com"> + <link rel="help" href="https://html.spec.whatwg.org/multipage/#read-media"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + +<script> + var t = async_test("The document for a standalone media file should have one child in the body."); + + function frameLoaded() { + var testframe = document.getElementById('testframe'); + assert_equals(testframe.contentDocument.contentType, "image/png"); + assert_equals(testframe.contentDocument.compatMode, "CSS1Compat", "Media documents should be in standards mode"); + var testframeChildren = testframe.contentDocument.body.childNodes; + assert_equals(testframeChildren.length, 1, "Body of image document has 1 child"); + assert_equals(testframeChildren[0].nodeName, "IMG", "Only child of body must be an <img> element"); + assert_equals(testframeChildren[0].namespaceURI, "http://www.w3.org/1999/xhtml", + "Only child of body must be an HTML element"); + t.done(); + } +</script> +</head> +<body> + <div id="log"></div> + <iframe id="testframe" onload="t.step(frameLoaded)" src="/images/blue.png"></iframe> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/read-media/pageload-video.html b/testing/web-platform/tests/html/browsers/browsing-the-web/read-media/pageload-video.html new file mode 100644 index 0000000000..9bce1f0c36 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/read-media/pageload-video.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<meta charset="utf-8"> +<title>Media documents: video</title> +<link rel="author" title="Michael Ventnor" href="mailto:mventnor@mozilla.com"> +<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#read-media"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/media.js"></script> +<div id="log"></div> +<script> +async_test(function() { + var testframe = document.createElement('iframe'); + var url = getVideoURI("/media/A4"); + var contentType = getMediaContentType(url); + testframe.onload = this.step_func_done(function() { + assert_equals(testframe.contentDocument.contentType, contentType); + assert_equals(testframe.contentDocument.compatMode, "CSS1Compat", "Media documents should be in standards mode"); + var testframeChildren = testframe.contentDocument.body.childNodes; + assert_equals(testframeChildren.length, 1, "Body of image document has 1 child"); + assert_equals(testframeChildren[0].nodeName, "VIDEO", "Only child of body must be an <video> element"); + assert_equals(testframeChildren[0].namespaceURI, "http://www.w3.org/1999/xhtml", + "Only child of body must be an HTML element"); + }); + testframe.src = url; + document.body.appendChild(testframe); +}, "The document for a standalone media file should have one child in the body."); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/read-media/resources/iframe-document.sub.html b/testing/web-platform/tests/html/browsers/browsing-the-web/read-media/resources/iframe-document.sub.html new file mode 100644 index 0000000000..69523efa7a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/read-media/resources/iframe-document.sub.html @@ -0,0 +1,5 @@ +<!doctype html> +<html> +<iframe src="{{GET[childsrc]}}"> +</iframe> +</html> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/read-text/load-text-plain.html b/testing/web-platform/tests/html/browsers/browsing-the-web/read-text/load-text-plain.html new file mode 100644 index 0000000000..ad75631533 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/read-text/load-text-plain.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<title>Page load processing model for text files</title> +<link rel="author" title="Ms2ger" href="ms2ger@gmail.com"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#read-text"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test("Checking document metadata for text file"); +var tD = async_test("Checking DOM for text file"); +var tC = async_test("Checking contents for text file"); +var iframe = document.body.appendChild(document.createElement("iframe")); +iframe.onload = function(e) { + var doc = iframe.contentDocument; + t.step(function() { + assert_equals(doc.compatMode, "CSS1Compat"); + assert_equals(doc.contentType, "text/plain"); + assert_equals(doc.doctype, null); + t.done(); + }) + tD.step(function() { + assert_equals(doc.childNodes.length, 1, "Document should have 1 child") + assert_equals(doc.documentElement.tagName, "HTML"); + assert_equals(doc.documentElement.childNodes.length, 2, + "Root element should have 2 children") + assert_equals(doc.documentElement.firstChild.tagName, "HEAD"); + assert_equals(doc.documentElement.lastChild.tagName, "BODY"); + assert_equals(doc.documentElement.lastChild.childNodes.length, 1, + "Body element should have 1 child") + assert_equals(doc.documentElement.lastChild.firstChild.tagName, "PRE"); + tD.done(); + }) + tC.step(function() { + assert_equals(doc.documentElement.lastChild.firstChild.firstChild.data, + "This is a sample text/plain document.\n\nThis is not an HTML document.\n\n"); + tC.done(); + }) +}; +iframe.src = "../../../../common/text-plain.txt"; +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addHTML.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addHTML.window.js new file mode 100644 index 0000000000..0c093cc462 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addHTML.window.js @@ -0,0 +1,20 @@ +// META: title=RemoteContextWrapper addHtml +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js +// META: script=./resources/test-helper.js + +'use strict'; + +// This tests that arguments passed to the constructor are respected. +promise_test(async t => { + const rcHelper = new RemoteContextHelper(); + + const main = await rcHelper.addWindow(); + await assertSimplestScriptRuns(main); + + await main.addHTML('<div id=div-id>div-content</div>'); + await assertFunctionRuns( + main, () => document.getElementById('div-id').textContent, 'div-content'); +}); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addIframe.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addIframe.window.js new file mode 100644 index 0000000000..c1630e4680 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addIframe.window.js @@ -0,0 +1,40 @@ +// META: title=RemoteContextWrapper addIframe +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js +// META: script=./resources/test-helper.js + +'use strict'; + +// This tests that arguments passed to the constructor are respected. +promise_test(async t => { + // Precondition: Test was loaded from the HTTP_ORIGIN. + assert_equals( + location.origin, get_host_info()['HTTP_ORIGIN'], + 'test window was loaded on HTTP_ORIGIN'); + + const rcHelper = new RemoteContextHelper(); + + const main = await rcHelper.addWindow(); + + const headerName = 'x-wpt-test-header'; + const headerValue = 'test-escaping()'; + const iframe = await main.addIframe( + /*extraConfig=*/ { + origin: 'HTTP_REMOTE_ORIGIN', + scripts: ['/common/get-host-info.sub.js', './resources/test-script.js'], + headers: [[headerName, headerValue]], + }, + /*attributes=*/ {id: 'test-id'}, + ); + + await assertSimplestScriptRuns(iframe); + await assertFunctionRuns(iframe, () => testFunction(), 'testFunction exists'); + await assertOriginIsAsExpected(iframe, get_host_info()['HTTP_REMOTE_ORIGIN']); + await assertHeaderIsAsExpected(iframe, headerName, headerValue); + + assert_equals( + await main.executeScript(() => document.getElementById('test-id').id), + 'test-id', 'verify id'); +}); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addScripts.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addScripts.window.js new file mode 100644 index 0000000000..01cf06c65d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addScripts.window.js @@ -0,0 +1,19 @@ +// META: title=RemoteContextWrapper addScripts +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js +// META: script=./resources/test-helper.js + +'use strict'; + +// This tests that arguments passed to the constructor are respected. +promise_test(async t => { + const rcHelper = new RemoteContextHelper(); + + const main = await rcHelper.addWindow(); + await assertSimplestScriptRuns(main); + + await main.addScripts(['./resources/test-script.js']); + await assertFunctionRuns(main, () => testFunction(), 'testFunction exists'); +}); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addWindow-defaults.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addWindow-defaults.window.js new file mode 100644 index 0000000000..34fa9cd39a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addWindow-defaults.window.js @@ -0,0 +1,15 @@ +// META: title=RemoteContextHelper with defaults +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js +// META: script=./resources/test-helper.js + +'use strict'; + +promise_test(async t => { + const rcHelper = new RemoteContextHelper(); + const main = await rcHelper.addWindow(); + await assertSimplestScriptRuns(main); + await assertOriginIsAsExpected(main, location.origin); +}); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addWindow-extra-config.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addWindow-extra-config.window.js new file mode 100644 index 0000000000..112d2d726e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addWindow-extra-config.window.js @@ -0,0 +1,36 @@ +// META: title=RemoteContextHelper addWindow with extra config +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js +// META: script=./resources/test-helper.js + +'use strict'; + +// This tests that arguments passed to the constructor are respected. +promise_test(async t => { + const header1Name = 'x-wpt-test-header1'; + const header1Value = 'test-escaping1()'; + const rcHelper = new RemoteContextHelper({ + origin: 'HTTP_REMOTE_ORIGIN', + scripts: ['/common/get-host-info.sub.js', './resources/test-script.js'], + headers: [[header1Name, header1Value]], + }); + + const header2Name = 'x-wpt-test-header2'; + const header2Value = 'test-escaping2()'; + const main = await rcHelper.addWindow( + { + origin: location.origin, + scripts: [new URL('./resources/test-script2.js', location).toString()], + headers: [[header2Name, header2Value]], + }, + ); + + await assertSimplestScriptRuns(main); + await assertFunctionRuns(main, () => testFunction(), 'testFunction exists'); + await assertFunctionRuns(main, () => testFunction2(), 'testFunction2 exists'); + await assertOriginIsAsExpected(main, location.origin); + await assertHeaderIsAsExpected(main, header1Name, header1Value); + await assertHeaderIsAsExpected(main, header2Name, header2Value); +}); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addWindow-features.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addWindow-features.window.js new file mode 100644 index 0000000000..329c55e626 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addWindow-features.window.js @@ -0,0 +1,23 @@ +// META: title=RemoteContextHelper addWindow features +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js +// META: script=./resources/test-helper.js + +'use strict'; + +promise_test(async t => { + const rcHelper = new RemoteContextHelper(); + { + const main = await rcHelper.addWindow(); + await assertSimplestScriptRuns(main); + await assertWindowHasOpenerEquals(main, true); + } + { + const main = await rcHelper.addWindow( + /*extraConfig=*/ null, /*options=*/ {features: 'noopener'}); + await assertSimplestScriptRuns(main); + await assertWindowHasOpenerEquals(main, false); + } +}); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addWindow-invalid-origin.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addWindow-invalid-origin.window.js new file mode 100644 index 0000000000..58aee312cc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addWindow-invalid-origin.window.js @@ -0,0 +1,21 @@ +// META: title=RemoteContextHelper addWindow with extra config +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js +// META: script=./resources/test-helper.js + +'use strict'; + +// This tests that arguments passed to the constructor are respected. +promise_test(async t => { + const header1Name = 'x-wpt-test-header1'; + const header1Value = 'test-escaping1()'; + const rcHelper = new RemoteContextHelper({ + origin: 'INVALID', + }); + + promise_rejects_js( + t, RangeError, rcHelper.addWindow(), + 'Exception should be thrown for invalid origin.'); +}); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addWindow-startOn.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addWindow-startOn.window.js new file mode 100644 index 0000000000..0f57a87638 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addWindow-startOn.window.js @@ -0,0 +1,19 @@ +// META: title=RemoteContextHelper addWindow target +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js +// META: script=./resources/test-helper.js + +'use strict'; + +promise_test(async t => { + const rcHelper = new RemoteContextHelper(); + const main = await rcHelper.addWindow({startOn: 'pageshow'}); + await assertSimplestScriptRuns(main); + await assert_equals( + await main.executeScript(() => { + return executorStartEvent.type; + }), + 'pageshow'); +}); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addWindow-target.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addWindow-target.window.js new file mode 100644 index 0000000000..3f742048b4 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addWindow-target.window.js @@ -0,0 +1,17 @@ +// META: title=RemoteContextHelper addWindow target +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js +// META: script=./resources/test-helper.js + +'use strict'; + +promise_test(async t => { + const rcHelper = new RemoteContextHelper(); + const name = 'a name'; + const main = await rcHelper.addWindow( + /*extraConfig=*/ null, /*options=*/ {target: name}); + await assertSimplestScriptRuns(main); + await assertWindowNameEquals(main, name); +}); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addWorker.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addWorker.window.js new file mode 100644 index 0000000000..d4ba8b0312 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/addWorker.window.js @@ -0,0 +1,29 @@ +// META: title=RemoteContextWrapper addWorker +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js +// META: script=./resources/test-helper.js + +'use strict'; + +// This tests that arguments passed to the constructor are respected. +promise_test(async t => { + const rcHelper = new RemoteContextHelper(); + + const main = await rcHelper.addWindow(); + + const headerName = 'x-wpt-test-header'; + const headerValue = 'test-escaping()'; + const worker = await main.addWorker( + { + scripts: ['/common/get-host-info.sub.js', './resources/test-script.js'], + headers: [[headerName, headerValue]], + }, + ); + + await assertSimplestScriptRuns(worker); + await assertFunctionRuns(worker, () => testFunction(), 'testFunction exists'); + await assertOriginIsAsExpected(worker, location.origin); + await assertHeaderIsAsExpected(worker, headerName, headerValue); +}); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/constructor.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/constructor.window.js new file mode 100644 index 0000000000..dcbb3dabbc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/constructor.window.js @@ -0,0 +1,38 @@ +// META: title=RemoteContextHelper constructor +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js +// META: script=./resources/test-helper.js + +'use strict'; + +// This tests that arguments passed to the constructor are respected. +promise_test(async t => { + // Precondition: Test was loaded from the HTTP_ORIGIN. + assert_equals( + location.origin, get_host_info()['HTTP_ORIGIN'], + 'test window was loaded on HTTP_ORIGIN'); + + const headerName = 'x-wpt-test-header'; + const headerValue = 'test-escaping()'; + const rcHelper = new RemoteContextHelper({ + origin: 'HTTP_REMOTE_ORIGIN', + scripts: [ + '/common/get-host-info.sub.js', + './resources/test-script.js', + ], + headers: [[headerName, headerValue]], + }); + + + const main = await rcHelper.addWindow(); + + await assertSimplestScriptRuns(main); + await assertFunctionRuns(main, () => testFunction(), 'testFunction exists'); + + // Verify that the origin is different. + await assertOriginIsAsExpected(main, get_host_info()['HTTP_REMOTE_ORIGIN']); + + await assertHeaderIsAsExpected(main, headerName, headerValue); +}); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/createContext-bad-executorCreator.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/createContext-bad-executorCreator.window.js new file mode 100644 index 0000000000..bf24581173 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/createContext-bad-executorCreator.window.js @@ -0,0 +1,20 @@ +// META: title=RemoteContextHelper createContext with throwing/rejecting executorCreators. +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js + +'use strict'; + +promise_test(async t => { + const rcHelper = new RemoteContextHelper(); + + const err = new Error('something bad!'); + promise_rejects_exactly( + t, err, rcHelper.createContext({ executorCreator() { throw err; } }), + 'Sync exception must be rethrown'); + + promise_rejects_exactly( + t, err, rcHelper.createContext({ executorCreator() { return Promise.reject(err); } }), + 'Async rejection must be rethrown'); +}); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/navigateToNew.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/navigateToNew.window.js new file mode 100644 index 0000000000..f7dd3f8325 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/navigateToNew.window.js @@ -0,0 +1,37 @@ +// META: title=RemoteContextWrapper navigateToNew +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js +// META: script=./resources/test-helper.js + +'use strict'; + +promise_test(async t => { + // Precondition: Test was loaded from the HTTP_ORIGIN. + assert_equals( + location.origin, get_host_info()['HTTP_ORIGIN'], + 'test window was loaded on HTTP_ORIGIN'); + + const rcHelper = new RemoteContextHelper(); + + const main = await rcHelper.addWindow(); + + const headerName = 'x-wpt-test-header'; + const headerValue = 'test-escaping()'; + const newMain = await main.navigateToNew( + { + origin: 'HTTP_REMOTE_ORIGIN', + scripts: ['/common/get-host-info.sub.js', './resources/test-script.js'], + headers: [[headerName, headerValue]], + }, + ); + + await assertSimplestScriptRuns(newMain); + await assertFunctionRuns( + newMain, () => testFunction(), 'testFunction exists'); + + const remoteOrigin = get_host_info()['HTTP_REMOTE_ORIGIN']; + await assertOriginIsAsExpected(newMain, remoteOrigin); + await assertHeaderIsAsExpected(newMain, headerName, headerValue); +}); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/navigation-bfcache.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/navigation-bfcache.window.js new file mode 100644 index 0000000000..1fa90a9064 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/navigation-bfcache.window.js @@ -0,0 +1,35 @@ +// META: title=RemoteContextHelper navigation using BFCache +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js +// META: script=./resources/test-helper.js + +'use strict'; + +promise_test(async t => { + const rcHelper = new RemoteContextHelper(); + + // Open a window with noopener so that BFCache will work. + const rc1 = await rcHelper.addWindow( + /*config=*/ null, /*options=*/ {features: 'noopener'}); + + // Add a pageshow listener to stash the event. + await rc1.executeScript(() => { + window.addEventListener('pageshow', (event) => { + window.pageshowEvent = event; + }); + }); + + // Navigate away. + const rc2 = await rc1.navigateToNew(); + await assertSimplestScriptRuns(rc2); + + // Navigate back. + await rc2.historyBack(); + + // Verify that the document was BFCached. + assert_true(await rc1.executeScript(() => { + return window.pageshowEvent.persisted; + })); +}); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/navigation-helpers.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/navigation-helpers.window.js new file mode 100644 index 0000000000..079e20661e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/navigation-helpers.window.js @@ -0,0 +1,28 @@ +// META: title=RemoteContextHelper navigation helpers +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js +// META: script=./resources/test-helper.js +// META: timeout=long + +'use strict'; + +promise_test(async t => { + const rcHelper = new RemoteContextHelper(); + const rc1 = await rcHelper.addWindow(); + await assertSimplestScriptRuns(rc1); + + const rc2 = await rc1.navigateToNew(); + await assertSimplestScriptRuns(rc2); + + await rc2.historyBack(); + await assertSimplestScriptRuns(rc1); + + await rc1.historyForward(); + await assertSimplestScriptRuns(rc2); + + const rc3 = await rc2.navigateToNew(); + await rc3.historyGo(-2); + await assertSimplestScriptRuns(rc1); +}); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/navigation-same-document.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/navigation-same-document.window.js new file mode 100644 index 0000000000..6f637e2b90 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/navigation-same-document.window.js @@ -0,0 +1,39 @@ +// META: title=RemoteContextHelper navigation using BFCache +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js +// META: script=./resources/test-helper.js + +'use strict'; + +async function assertLocationIs(remoteContextWrapper, expectedLocation) { + assert_equals( + await remoteContextWrapper.executeScript(() => { + return location.toString(); + }), + expectedLocation, 'verify location'); +} + +promise_test(async t => { + const rcHelper = new RemoteContextHelper(); + + const rc = await rcHelper.addWindow(); + + const oldLocation = await rc.executeScript(() => { + return location.toString(); + }); + const newLocation = oldLocation + '#fragment'; + + // Navigate to same document. + await rc.navigateTo(newLocation); + + // Verify that the window navigated. + await assertLocationIs(rc, newLocation); + + // Navigate back. + await rc.historyBack(oldLocation); + + // Verify that the window navigated back and the executor is running. + await assertLocationIs(rc, oldLocation); +}); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/resources/test-helper.js b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/resources/test-helper.js new file mode 100644 index 0000000000..71cd87e553 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/resources/test-helper.js @@ -0,0 +1,48 @@ +async function assertSimplestScriptRuns(remoteContextWrapper) { + assert_equals( + await remoteContextWrapper.executeScript(() => { + return 1; + }), + 1, 'simplest script runs'); +} + +async function assertFunctionRuns( + remoteContextWrapper, functionToRun, expectedReturn) { + assert_equals( + await remoteContextWrapper.executeScript(functionToRun), expectedReturn, + 'function runs'); +} + +async function assertOriginIsAsExpected(remoteContextWrapper, expectedOrigin) { + assert_equals( + await remoteContextWrapper.executeScript(() => { + return location.origin; + }), + expectedOrigin, 'verify origin'); +} + +async function assertWindowNameEquals(remoteContextWrapper, expectedName) { + assert_equals( + await remoteContextWrapper.executeScript(() => { + return window.name; + }), + expectedName, 'verify name'); +} + +async function assertWindowHasOpenerEquals(remoteContextWrapper, hasParent) { + assert_equals( + await remoteContextWrapper.executeScript(() => { + return !!window.opener; + }), + hasParent, 'verify opener'); +} + +async function assertHeaderIsAsExpected( + remoteContextWrapper, headerName, headerValue) { + assert_equals( + headerValue, + await remoteContextWrapper.executeScript(async (headerName) => { + const res = await fetch(location); + return res.headers.get(headerName); + }, [headerName]), 'header is set'); +}
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/resources/test-script.js b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/resources/test-script.js new file mode 100644 index 0000000000..d1c02cab29 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/resources/test-script.js @@ -0,0 +1,3 @@ +function testFunction() { + return 'testFunction exists'; +} diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/resources/test-script2.js b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/resources/test-script2.js new file mode 100644 index 0000000000..f9e72c442b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper-tests/resources/test-script2.js @@ -0,0 +1,3 @@ +function testFunction2() { + return 'testFunction2 exists'; +} diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper/resources/executor-common.js b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper/resources/executor-common.js new file mode 100644 index 0000000000..8df42de28c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper/resources/executor-common.js @@ -0,0 +1,17 @@ +// Functions available by default in the executor. + +'use strict'; + +let executor; + +// Expects addScript to be present (window or worker version). +function addScripts(urls) { + return Promise.all(urls.map(addScript)); +} + +function startExecutor() { + const params = new URLSearchParams(location.search); + addScripts(params.getAll('script')); + const uuid = params.get('uuid'); + executor = new Executor(uuid); +} diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper/resources/executor-window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper/resources/executor-window.js new file mode 100644 index 0000000000..bc9ff6faf1 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper/resources/executor-window.js @@ -0,0 +1,70 @@ +// Functions available by default in the executor. + +'use strict'; + +let executorStartEvent = null; + +function requestExecutor() { + const params = new URLSearchParams(location.search); + const startOn = params.get('startOn'); + + if (startOn) { + addEventListener(startOn, (e) => { + executorStartEvent = e; + startExecutor(); + }); + } else { + startExecutor(); + } +} + +function addScript(url) { + const script = document.createElement('script'); + script.src = url; + const promise = new Promise((resolve, reject) => { + script.onload = () => resolve(url); + script.onerror = (e) => reject(e); + }); + document.body.appendChild(script); + return promise; +} + +/** + * Suspends the executor and executes the function in `fnString` when it has + * suspended. Installs a pageshow handler to resume the executor if the + * document is BFCached. Installs a hashchange handler to detect when the + * navigation did not change documents. + * + * This returns nothing because fn is invoke after waiting for the document to + * be suspended. If we were to return a promise, the executor could not suspend + * until that promise resolved but the promise cannot resolve until the executor + * is suspended. This could be avoided by adding support + * directly in the dispatcher for tasks suspend immediately after execution. + * + * @param {string} fnString A stringified function to be executed. + * @param {any[]} args The arguments to pass to the function. + */ +function executeScriptToNavigate(fnString, args) { + // Only one of these listeners should run. + const controller = new AbortController(); + window.addEventListener('pageshow', (event) => { + controller.abort(); + executor.resume(); + }, {signal: controller.signal, once: true}); + window.addEventListener('hashchange', (event) => { + controller.abort(); + const oldURLObject = new URL(event.oldURL); + const newURLObject = new URL(event.newURL); + oldURLObject.hash = ''; + newURLObject.hash = ''; + // If only the hash-fragment changed then the navigation was + // same-document and we should resume the executor. + if (oldURLObject.toString() == newURLObject.toString()) { + executor.resume(); + } + }, {signal: controller.signal, once: true}); + + executor.suspend(() => { + eval(fnString).apply(null, args); + }); +} diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper/resources/executor-worker.js b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper/resources/executor-worker.js new file mode 100644 index 0000000000..49ce6bd52a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper/resources/executor-worker.js @@ -0,0 +1,9 @@ +'use strict'; + +importScripts('/common/dispatcher/dispatcher.js', './executor-common.js'); + +function addScript(url) { + importScripts(url); +} + +startExecutor(); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper/resources/executor.sub.html b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper/resources/executor.sub.html new file mode 100644 index 0000000000..38f4429d91 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper/resources/executor.sub.html @@ -0,0 +1,16 @@ +<!DOCTYPE HTML> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="./executor-common.js"></script> +<script src="./executor-window.js"></script> + +<body> + <script> + window.requestHeaders = { + 'sec-fetch-mode': '{{header_or_default(sec-fetch-mode, absent)}}', + 'sec-fetch-site': '{{header_or_default(sec-fetch-site, absent)}}', + 'sec-fetch-dest': '{{header_or_default(sec-fetch-dest, absent)}}', + 'referer': '{{header_or_default(referer, absent)}}', + }; + requestExecutor(); + </script> +</body> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js new file mode 100644 index 0000000000..6978cef832 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js @@ -0,0 +1,533 @@ +'use strict'; + +// Requires: +// - /common/dispatcher/dispatcher.js +// - /common/utils.js +// - /common/get-host-info.sub.js if automagic conversion of origin names to +// URLs is used. + +/** + * This provides a more friendly interface to remote contexts in dispatches.js. + * The goal is to make it easy to write multi-window/-frame/-worker tests where + * the logic is entirely in 1 test file and there is no need to check in any + * other file (although it is often helpful to check in files of JS helper + * functions that are shared across remote context). + * + * So for example, to test that history traversal works, we create a new window, + * navigate it to a new document, go back and then go forward. + * + * @example + * promise_test(async t => { + * const rcHelper = new RemoteContextHelper(); + * const rc1 = await rcHelper.addWindow(); + * const rc2 = await rc1.navigateToNew(); + * assert_equals(await rc2.executeScript(() => 'here'), 'here', 'rc2 is live'); + * rc2.historyBack(); + * assert_equals(await rc1.executeScript(() => 'here'), 'here', 'rc1 is live'); + * rc1.historyForward(); + * assert_equals(await rc2.executeScript(() => 'here'), 'here', 'rc2 is live'); + * }); + * + * Note on the correspondence between remote contexts and + * `RemoteContextWrapper`s. A remote context is entirely determined by its URL. + * So navigating away from one and then back again will result in a remote + * context that can be controlled by the same `RemoteContextWrapper` instance + * before and after navigation. Messages sent to a remote context while it is + * destroyed or in BFCache will be queued and processed if that that URL is + * navigated back to. + * + * Navigation: + * This framework does not keep track of the history of the frame tree and so it + * is up to the test script to keep track of what remote contexts are currently + * active and to keep references to the corresponding `RemoteContextWrapper`s. + * + * Any action that leads to navigation in the remote context must be executed + * using + * @see RemoteContextWrapper.navigate. + */ + +{ + const RESOURCES_PATH = + '/html/browsers/browsing-the-web/remote-context-helper/resources'; + const WINDOW_EXECUTOR_PATH = `${RESOURCES_PATH}/executor.sub.html`; + const WORKER_EXECUTOR_PATH = `${RESOURCES_PATH}/executor-worker.js`; + + /** + * Turns a string into an origin. If `origin` is null this will return the + * current document's origin. If `origin` contains not '/', this will attempt + * to use it as an index in `get_host_info()`. Otherwise returns the input + * origin. + * @private + * @param {string|null} origin The input origin. + * @return {string|null} The output origin. + * @throws {RangeError} is `origin` cannot be found in + * `get_host_info()`. + */ + function finalizeOrigin(origin) { + if (!origin) { + return location.origin; + } + if (!origin.includes('/')) { + const origins = get_host_info(); + if (origin in origins) { + return origins[origin]; + } else { + throw new RangeError( + `${origin} is not a key in the get_host_info() object`); + } + } + return origin; + } + + /** + * @private + * @param {string} url + * @returns {string} Absolute url using `location` as the base. + */ + function makeAbsolute(url) { + return new URL(url, location).toString(); + } + + /** + * Represents a configuration for a remote context executor. + */ + class RemoteContextConfig { + /** + * @param {Object} [options] + * @param {string} [options.origin] A URL or a key in `get_host_info()`. + * @see finalizeOrigin for how origins are handled. + * @param {string[]} [options.scripts] A list of script URLs. The current + * document will be used as the base for relative URLs. + * @param {[string, string][]} [options.headers] A list of pairs of name + * and value. The executor will be served with these headers set. + * @param {string} [options.startOn] If supplied, the executor will start + * when this event occurs, e.g. "pageshow", + * (@see window.addEventListener). This only makes sense for + * window-based executors, not worker-based. + */ + constructor( + {origin, scripts = [], headers = [], startOn} = {}) { + this.origin = origin; + this.scripts = scripts; + this.headers = headers; + this.startOn = startOn; + } + + /** + * If `config` is not already a `RemoteContextConfig`, one is constructed + * using `config`. + * @private + * @param {object} [config] + * @returns + */ + static ensure(config) { + if (!config) { + return DEFAULT_CONTEXT_CONFIG; + } + return new RemoteContextConfig(config); + } + + /** + * Merges `this` with another `RemoteContextConfig` and to give a new + * `RemoteContextConfig`. `origin` is replaced by the other if present, + * `headers` and `scripts` are concatenated with `this`'s coming first. + * @param {RemoteContextConfig} extraConfig + * @returns {RemoteContextConfig} + */ + merged(extraConfig) { + let origin = this.origin; + if (extraConfig.origin) { + origin = extraConfig.origin; + } + let startOn = this.startOn; + if (extraConfig.startOn) { + startOn = extraConfig.startOn; + } + const headers = this.headers.concat(extraConfig.headers); + const scripts = this.scripts.concat(extraConfig.scripts); + return new RemoteContextConfig({ + origin, + headers, + scripts, + startOn, + }); + } + } + + /** + * The default `RemoteContextConfig` to use if none is supplied. It has no + * origin, headers or scripts. + * @constant {RemoteContextConfig} + */ + const DEFAULT_CONTEXT_CONFIG = new RemoteContextConfig(); + + /** + * This class represents a configuration for creating remote contexts. This is + * the entry-point + * for creating remote contexts, providing @see addWindow . + */ + class RemoteContextHelper { + /** + * @param {RemoteContextConfig|object} config The configuration + * for this remote context. + */ + constructor(config) { + this.config = RemoteContextConfig.ensure(config); + } + + /** + * Creates a new remote context and returns a `RemoteContextWrapper` giving + * access to it. + * @private + * @param {Object} options + * @param {(url: string) => Promise<undefined>} options.executorCreator A + * function that takes a URL and causes the browser to navigate some + * window to that URL, e.g. via an iframe or a new window. + * @param {RemoteContextConfig|object} [options.extraConfig] If supplied, + * extra configuration for this remote context to be merged with + * `this`'s existing config. If it's not a `RemoteContextConfig`, it + * will be used to construct a new one. + * @returns {Promise<RemoteContextWrapper>} + */ + async createContext({ + executorCreator, + extraConfig, + isWorker = false, + }) { + const config = + this.config.merged(RemoteContextConfig.ensure(extraConfig)); + + const origin = finalizeOrigin(config.origin); + const url = new URL( + isWorker ? WORKER_EXECUTOR_PATH : WINDOW_EXECUTOR_PATH, origin); + + // UUID is needed for executor. + const uuid = token(); + url.searchParams.append('uuid', uuid); + + if (config.headers) { + addHeaders(url, config.headers); + } + for (const script of config.scripts) { + url.searchParams.append('script', makeAbsolute(script)); + } + + if (config.startOn) { + url.searchParams.append('startOn', config.startOn); + } + + await executorCreator(url.href); + return new RemoteContextWrapper(new RemoteContext(uuid), this, url.href); + } + + /** + * Creates a window with a remote context. @see createContext for + * @param {RemoteContextConfig|object} [extraConfig] Will be + * merged with `this`'s config. + * @param {Object} [options] + * @param {string} [options.target] Passed to `window.open` as the + * 2nd argument + * @param {string} [options.features] Passed to `window.open` as the + * 3rd argument + * @returns {Promise<RemoteContextWrapper>} + */ + addWindow(extraConfig, options) { + return this.createContext({ + executorCreator: windowExecutorCreator(options), + extraConfig, + }); + } + + async createContextWithUrl(extraConfig) { + let saveUrl; + let wrapper = await this.createContext({ + executorCreator: (url) => {saveUrl = url}, + extraConfig, + }); + return [wrapper, saveUrl]; + } + } + // Export this class. + self.RemoteContextHelper = RemoteContextHelper; + + /** + * Attaches header to the URL. See + * https://web-platform-tests.org/writing-tests/server-pipes.html#headers + * @param {string} url the URL to which headers should be attached. + * @param {[[string, string]]} headers a list of pairs of head-name, + * header-value. + */ + function addHeaders(url, headers) { + function escape(s) { + return s.replace('(', '\\(').replace(')', '\\)'); + } + const formattedHeaders = headers.map((header) => { + return `header(${escape(header[0])}, ${escape(header[1])})`; + }); + url.searchParams.append('pipe', formattedHeaders.join('|')); + } + + function windowExecutorCreator({target = '_blank', features} = {}) { + return url => { + window.open(url, target, features); + }; + } + + function elementExecutorCreator( + remoteContextWrapper, elementName, attributes) { + return url => { + return remoteContextWrapper.executeScript((url, elementName, attributes) => { + const el = document.createElement(elementName); + for (const attribute in attributes) { + el.setAttribute(attribute, attributes[attribute]); + } + el.src = url; + document.body.appendChild(el); + }, [url, elementName, attributes]); + }; + } + + function workerExecutorCreator() { + return url => { + new Worker(url); + }; + } + + function navigateExecutorCreator(remoteContextWrapper) { + return url => { + return remoteContextWrapper.navigate((url) => { + window.location = url; + }, [url]); + }; + } + + /** + * This class represents a remote context running an executor (a + * window/frame/worker that can receive commands). It is the interface for + * scripts to control remote contexts. + * + * Instances are returned when new remote contexts are created (e.g. + * `addFrame` or `navigateToNew`). + */ + class RemoteContextWrapper { + /** + * This should only be constructed by `RemoteContextHelper`. + * @private + */ + constructor(context, helper) { + this.context = context; + this.helper = helper; + } + + /** + * Executes a script in the remote context. + * @param {function} fn The script to execute. + * @param {any[]} args An array of arguments to pass to the script. + * @returns {Promise<any>} The return value of the script (after + * being serialized and deserialized). + */ + async executeScript(fn, args) { + return this.context.execute_script(fn, args); + } + + /** + * Adds a string of HTML to the executor's document. + * @param {string} html + * @returns {Promise<undefined>} + */ + async addHTML(html) { + return this.executeScript((htmlSource) => { + document.body.insertAdjacentHTML('beforebegin', htmlSource); + }, [html]); + } + + /** + * Adds scripts to the executor's document. + * @param {string[]} urls A list of URLs. URLs are relative to the current + * document. + * @returns {Promise<undefined>} + */ + async addScripts(urls) { + if (!urls) { + return []; + } + return this.executeScript(urls => { + return addScripts(urls); + }, [urls.map(makeAbsolute)]); + } + + /** + * Adds an iframe to the current document. + * @param {RemoteContextConfig} [extraConfig] + * @param {[string, string][]} [attributes] A list of pairs of strings + * of attribute name and value these will be set on the iframe element + * when added to the document. + * @returns {Promise<RemoteContextWrapper>} The remote context. + */ + addIframe(extraConfig, attributes = {}) { + return this.helper.createContext({ + executorCreator: elementExecutorCreator(this, 'iframe', attributes), + extraConfig, + }); + } + + /** + * Adds a dedicated worker to the current document. + * @param {RemoteContextConfig} [extraConfig] + * @returns {Promise<RemoteContextWrapper>} The remote context. + */ + addWorker(extraConfig) { + return this.helper.createContext({ + executorCreator: workerExecutorCreator(), + extraConfig, + isWorker: true, + }); + } + + /** + * Executes a script in the remote context that will perform a navigation. + * To do this safely, we must suspend the executor and wait for that to + * complete before executing. This ensures that all outstanding requests are + * completed and no more can start. It also ensures that the executor will + * restart if the page goes into BFCache or it was a same-document + * navigation. It does not return a value. + * + * NOTE: We cannot monitor whether and what navigations are happening. The + * logic has been made as robust as possible but is not fool-proof. + * + * Foolproof rule: + * - The script must perform exactly one navigation. + * - If that navigation is a same-document history traversal, you must + * `await` the result of `waitUntilLocationIs`. (Same-document non-traversal + * navigations do not need this extra step.) + * + * More complex rules: + * - The script must perform a navigation. If it performs no navigation, + * the remote context will be left in the suspended state. + * - If the script performs a direct same-document navigation, it is not + * necessary to use this function but it will work as long as it is the only + * navigation performed. + * - If the script performs a same-document history navigation, you must + * `await` the result of `waitUntilLocationIs`. + * + * @param {function} fn The script to execute. + * @param {any[]} args An array of arguments to pass to the script. + * @returns {Promise<undefined>} + */ + navigate(fn, args) { + return this.executeScript((fnText, args) => { + executeScriptToNavigate(fnText, args); + }, [fn.toString(), args]); + } + + /** + * Navigates to the given URL, by executing a script in the remote + * context that will perform navigation with the `location.href` + * setter. + * + * Be aware that performing a cross-document navigation using this + * method will cause this `RemoteContextWrapper` to become dormant, + * since the remote context it points to is no longer active and + * able to receive messages. You also won't be able to reliably + * tell when the navigation finishes; the returned promise will + * fulfill when the script finishes running, not when the navigation + * is done. As such, this is most useful for testing things like + * unload behavior (where it doesn't matter) or prerendering (where + * there is already a `RemoteContextWrapper` for the destination). + * For other cases, using `navigateToNew()` will likely be better. + * + * @param {string|URL} url The URL to navigate to. + * @returns {Promise<undefined>} + */ + navigateTo(url) { + return this.navigate(url => { + location.href = url; + }, [url.toString()]); + } + + /** + * Navigates the context to a new document running an executor. + * @param {RemoteContextConfig} [extraConfig] + * @returns {Promise<RemoteContextWrapper>} The remote context. + */ + async navigateToNew(extraConfig) { + return this.helper.createContext({ + executorCreator: navigateExecutorCreator(this), + extraConfig, + }); + } + + ////////////////////////////////////// + // Navigation Helpers. + // + // It is up to the test script to know which remote context will be + // navigated to and which `RemoteContextWrapper` should be used after + // navigation. + // + // NOTE: For a same-document history navigation, the caller use `await` a + // call to `waitUntilLocationIs` in order to know that the navigation has + // completed. For convenience the method below can return the promise to + // wait on, if passed the expected location. + + async waitUntilLocationIs(expectedLocation) { + return this.executeScript(async (expectedLocation) => { + if (location.href === expectedLocation) { + return; + } + + // Wait until the location updates to the expected one. + await new Promise(resolve => { + const listener = addEventListener('hashchange', (event) => { + if (event.newURL === expectedLocation) { + removeEventListener(listener); + resolve(); + } + }); + }); + }, [expectedLocation]); + } + + /** + * Performs a history traversal. + * @param {integer} n How many steps to traverse. @see history.go + * @param {string} [expectedLocation] If supplied will be passed to @see waitUntilLocationIs. + * @returns {Promise<undefined>} + */ + async historyGo(n, expectedLocation) { + await this.navigate((n) => { + history.go(n); + }, [n]); + if (expectedLocation) { + await this.waitUntilLocationIs(expectedLocation); + } + } + + /** + * Performs a history traversal back. + * @param {string} [expectedLocation] If supplied will be passed to @see waitUntilLocationIs. + * @returns {Promise<undefined>} + */ + async historyBack(expectedLocation) { + await this.navigate(() => { + history.back(); + }); + if (expectedLocation) { + await this.waitUntilLocationIs(expectedLocation); + } + } + + /** + * Performs a history traversal back. + * @param {string} [expectedLocation] If supplied will be passed to @see waitUntilLocationIs. + * @returns {Promise<undefined>} + */ + async historyForward(expectedLocation) { + await this.navigate(() => { + history.forward(); + }); + if (expectedLocation) { + await this.waitUntilLocationIs(expectedLocation); + } + } + } +} diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/resources/has-iframe.html b/testing/web-platform/tests/html/browsers/browsing-the-web/resources/has-iframe.html new file mode 100644 index 0000000000..e6dfba1011 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/resources/has-iframe.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<iframe src="/common/blank.html"></iframe> +<script> +window.onload = () => { + window.opener.postMessage("top ready", "*"); +}; +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/resources/helpers.js b/testing/web-platform/tests/html/browsers/browsing-the-web/resources/helpers.js new file mode 100644 index 0000000000..b19addfadf --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/resources/helpers.js @@ -0,0 +1,84 @@ +// This file contains general helpers for navigation/history tests. The goal is +// to make tests more imperative and ordered, instead of requiring lots of +// nested callbacks and jumping back and forth. However, +// html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js +// might be even better at that, so prefer that when you can. +// +// TODO(domenic): consider unifying with +// overlapping-navigations-and-traversals/resources/helpers.mjs. + +window.openWindow = (url, t) => { + const w = window.open(url); + t?.add_cleanup(() => w.close()); + + return new Promise(resolve => { + w.addEventListener("load", () => resolve(w), { once: true }); + }); +}; + +window.addIframe = (url = "/common/blank.html", doc = document) => { + const iframe = doc.createElement("iframe"); + iframe.src = url; + doc.body.append(iframe); + + return new Promise(resolve => { + iframe.addEventListener("load", () => resolve(iframe), { once: true }); + }); +}; + +window.addSrcdocIframe = async () => { + const iframe = document.createElement("iframe"); + iframe.srcdoc = `<script>window.parent.postMessage("srcdoc ready", "*")</scr` + `ipt>`; + document.body.append(iframe); + + assert_equals(await waitForMessage(iframe.contentWindow), "srcdoc ready"); + + return iframe; +}; + +window.waitToAvoidReplace = t => { + return new Promise(resolve => t.step_timeout(resolve, 0)); +}; + +window.waitForIframeLoad = iframe => { + return new Promise(resolve => { + iframe.addEventListener("load", () => resolve(), { once: true }); + }); +}; + +window.waitForMessage = expectedSource => { + return new Promise(resolve => { + window.addEventListener("message", ({ source, data }) => { + if (source === expectedSource) { + resolve(data); + } + }); + }); +}; + +window.waitForHashchange = w => { + return new Promise(resolve => { + w.addEventListener("hashchange", () => resolve(), { once: true }); + }); +}; + +window.srcdocThatPostsParentOpener = text => { + return ` + <p>${text}</p> + <script> + window.onload = () => { + window.top.opener.postMessage('ready', '*'); + }; + <\/script> + `; +}; + +window.failOnMessage = expectedSource => { + return new Promise((_, reject) => { + window.addEventListener("message", ({ source, data }) => { + if (source === expectedSource) { + reject(new Error(`Received message "${data}" but expected to receive no message`)); + } + }); + }); +}; diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/resources/post-top-opener-on-load.html b/testing/web-platform/tests/html/browsers/browsing-the-web/resources/post-top-opener-on-load.html new file mode 100644 index 0000000000..47cb6c6150 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/resources/post-top-opener-on-load.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<script> +window.onload = () => { + window.top.opener.postMessage("ready", "*"); +}; +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/001.html b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/001.html new file mode 100644 index 0000000000..32599bbc50 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/001.html @@ -0,0 +1,16 @@ +<!doctype html> +<!-- this tests the spec as it hopefully will be once bug https://www.w3.org/Bugs/Public/show_bug.cgi?id=17155 is fixed --> +<title>Fragment Navigation: Updating document address</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +test(function() { + assert_equals(location.hash, "", "Page must be loaded with no hash") + var original_location = location.href; + location.hash = "test"; + assert_equals(location.hash, "#test"); + assert_equals(location.href, original_location + "#test"); + location.hash = "" +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/002.html b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/002.html new file mode 100644 index 0000000000..92bfd63415 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/002.html @@ -0,0 +1,21 @@ +<!doctype html> +<!-- this tests the spec as it hopefully will be once bug https://www.w3.org/Bugs/Public/show_bug.cgi?id=17155 is fixed --> +<title>Fragment Navigation: Updating document address twice</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +test(function() { + assert_equals(location.hash, "", "Page must be loaded with no hash") + var original_location = location.href; + location.hash = "test"; + assert_equals(location.hash, "#test"); + assert_equals(location.href, original_location + "#test"); + + location.hash = "test1"; + assert_equals(location.hash, "#test1"); + assert_equals(location.href, original_location + "#test1"); + + location.hash = ""; +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/003.html b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/003.html new file mode 100644 index 0000000000..86d85b9ca6 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/003.html @@ -0,0 +1,25 @@ +<!doctype html> +<!-- this tests the spec as it hopefully will be once bug https://www.w3.org/Bugs/Public/show_bug.cgi?id=17155 is fixed --> +<title>Fragment Navigation: Updating scroll position</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div id="log"></div> +<div id="test">scroll 1</div> +<div style="height:10000px">Filler</div> +<div id="test1">scroll 2</div> +<script> +test(function() { + assert_equals(document.scrollingElement.scrollTop, 0); + location.hash = "test"; + + var scroll1 = document.scrollingElement.scrollTop; + assert_true(scroll1 > 0); + + location.hash = "test1"; + var scroll2 = document.scrollingElement.scrollTop; + assert_true(scroll2 > scroll1); + + location.hash = "" +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/004.html b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/004.html new file mode 100644 index 0000000000..c365c6fd29 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/004.html @@ -0,0 +1,23 @@ +<!doctype html> +<!-- this tests the spec as it hopefully will be once bug https://www.w3.org/Bugs/Public/show_bug.cgi?id=17155 is fixed --> +<title>Fragment Navigation: hashchange event</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div id="log"></div> +<script> +var t = async_test(); +t.step(function() { + assert_equals(location.hash, "", "Page must be loaded with no hash"); + location.hash = "test"; + + addEventListener("hashchange", + t.step_func(function(e) { + assert_equals(e.target, window); + assert_equals(e.type, "hashchange"); + assert_false(e.bubbles, "bubbles"); + assert_false(e.cancelable, "cancelable"); + t.done(); + }), true) +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/005.html b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/005.html new file mode 100644 index 0000000000..f0761a64f6 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/005.html @@ -0,0 +1,23 @@ +<!doctype html> +<!-- this tests the spec as it hopefully will be once bug https://www.w3.org/Bugs/Public/show_bug.cgi?id=17155 is fixed --> +<title>Fragment Navigation: hashchange event</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div id="log"></div> +<script> +var t = async_test(); +t.step(function() { + var original_url = location.href; + assert_equals(location.hash, "", "Page must be loaded with no hash"); + location.hash = "test"; + + addEventListener("hashchange", + t.step_func(function(e) { + assert_equals(e.oldURL, original_url, "oldURL property"); + assert_equals(e.newURL, location.href, "newURL property"); + location.hash = ""; + t.done(); + }), true); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/006.html b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/006.html new file mode 100644 index 0000000000..a65b9eb4a2 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/006.html @@ -0,0 +1,34 @@ +<!doctype html> +<!-- this tests the spec as it hopefully will be once bug https://www.w3.org/Bugs/Public/show_bug.cgi?id=17155 is fixed --> +<title>Fragment Navigation: hashchange event multiple changes old/newURL</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div id="log"></div> +<script> +var t = async_test(); +t.step(function() { + var original_url = location.href; + assert_equals(location.hash, "", "Page must be loaded with no hash"); + location.hash = "test"; + + var count = 0; + var mid_url = location.href; + + addEventListener("hashchange", + t.step_func(function(e) { + if (count === 0) { + assert_equals(e.oldURL, original_url, "oldURL property first update"); + assert_equals(e.newURL, mid_url, "newURL property first update"); + count = 1; + } else if (count === 1) { + assert_equals(e.oldURL, mid_url, "oldURL property second update"); + assert_equals(e.newURL, location.href, "newURL property second update"); + location.hash = ""; + t.done(); + } + }), true); + + location.hash = "test1"; +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/007.html b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/007.html new file mode 100644 index 0000000000..0b6fe813ba --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/007.html @@ -0,0 +1,37 @@ +<!doctype html> +<!-- this tests the spec as it hopefully will be once bug https://www.w3.org/Bugs/Public/show_bug.cgi?id=17155 is fixed --> +<title>Fragment Navigation: hashchange event multiple changes old/newURL</title> +<meta name=timeout content=long> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div id="log"></div> +<script> +var t = async_test(); +t.step(function() { + var original_url = location.href; + assert_equals(location.hash, "", "Page must be loaded with no hash"); + + var count = 0; + + location.hash = "test"; + + hashes = []; + + addEventListener("hashchange", + t.step_func(function(e) { + if (count < 100) { + location.hash = "test" + count++; + hashes.push(location.hash); + } else if (count === 100) { + expected = []; + for (var i=0; i<100; i++) { + expected.push("#test" + i); + } + assert_array_equals(hashes, expected); + location.hash = ""; + t.done(); + } + }), true); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/forward-triggers-hashchange.html b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/forward-triggers-hashchange.html new file mode 100644 index 0000000000..45f7e38ba3 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/forward-triggers-hashchange.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Navigating forward after replace() should still trigger hashchange</title> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#history-traversal"> +<link rel="author" href="mailto:d@domenic.me" title="Domenic Denicola"> + +<!-- While writing ./replacement-enabled.html, a bug was discovered in Firefox where it does not +fire hashchange when using history.forward(), at least under certain conditions. So, this test +exercises that specifically. --> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="navigate-helpers.js"></script> + +<body> + +<script> +"use strict"; +let resolve, iframe; +promise_test(() => { + iframe = document.createElement("iframe"); + iframe.src = "/common/blank.html"; + iframe.addEventListener("load", runTest); + document.body.appendChild(iframe); + + return new Promise(r => resolve = r); +}); + +function runTest() { + iframe.removeEventListener("load", runTest); + const frameWindow = iframe.contentWindow; + + resolve((async () => { + await navigateAndWaitForChange(frameWindow, w => w.location.href = "/common/blank.html#apple"); + await navigateAndWaitForChange(frameWindow, w => w.location.href = "/common/blank.html#banana"); + await navigateAndWaitForChange(frameWindow, w => w.location.href = "/common/blank.html#cat"); + + await navigateAndWaitForChange(frameWindow, w => w.history.back()); + await navigateAndWaitForChange(frameWindow, + w => w.location.replace("/common/blank.html#zebra")); + await navigateAndWaitForChange(frameWindow, w => w.history.forward()); + })()); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/fragment-and-encoding-2.html b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/fragment-and-encoding-2.html new file mode 100644 index 0000000000..cd2502ab1c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/fragment-and-encoding-2.html @@ -0,0 +1,41 @@ +<!doctype html> +<meta charset=windows-1252> +<title>Fragment navigation: encoding</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<div style=height:10000px></div> +<div id=�></div> +<div id=�></div> +<script> +function goToTop() { + location.hash = "top"; + assert_equals(self.scrollY, 0, "#top"); +} + +test(() => { + assert_equals(location.hash, "", "Page must be loaded with no hash"); + + location.hash = "%C2"; + assert_equals(location.hash, "#%C2"); + assert_greater_than(self.scrollY, 1000, "#%C2"); +}, "Invalid percent-encoded UTF-8 byte should decode as U+FFFD"); + +test(() => { + goToTop(); + + location.hash = "%EF%BB%BF%C2"; + assert_equals(location.hash, "#%EF%BB%BF%C2"); + assert_greater_than(self.scrollY, 1000, "#%EF%BB%BF%C2"); +}, "Percent-encoded UTF-8 BOM followed by invalid UTF-8 byte should decode as U+FEFF U+FFFD"); + +test(() => { + goToTop(); + + location.hash = "%EF%BF%BD"; + assert_equals(location.hash, "#%EF%BF%BD"); + assert_greater_than(self.scrollY, 1000, "#%EF%BF%BD"); + + goToTop(); +}, "Percent-encoded UTF-8 byte sequence for U+FFFD should decode as U+FFFD"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/fragment-and-encoding.html b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/fragment-and-encoding.html new file mode 100644 index 0000000000..21fbd4618d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/fragment-and-encoding.html @@ -0,0 +1,50 @@ +<!doctype html> +<meta charset=windows-1252> +<title>Fragment navigation: encoding</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<div style=height:10000px></div> +<div id=ÿ></div> +<div id=></div> +<div id=♡ÿ><div> +<script> +function goToTop() { + location.hash = "top"; + assert_equals(self.scrollY, 0, "#top"); +} + +test(() => { + assert_equals(location.hash, "", "Page must be loaded with no hash"); + + location.hash = "\u00FF"; + assert_equals(location.hash, "#%C3%BF"); + assert_greater_than(self.scrollY, 1000, "#%C3%BF"); +}, "U+00FF should find U+00FF"); + +test(() => { + goToTop(); + + location.hash = "%EF%BB%BF"; + assert_greater_than(self.scrollY, 1000, "#%EF%BB%BF"); +}, "Percent-encoded UTF-8 BOM should find U+FEFF as BOM is not stripped when decoding"); + +test(() => { + goToTop(); + + location.hash = "%FF"; + assert_equals(self.scrollY, 0, "#%FF"); +}, "%FF should not find U+00FF as decoding it gives U+FFFD"); + +test(() => { + goToTop(); + + // U+2661 in UTF-8 + %FF. + // Chrome had an issue that the following fragment was decoded as U+2661 U+00FF. + // https://github.com/whatwg/html/pull/3111 + location.hash = "%E2%99%A1%FF"; + assert_equals(self.scrollY, 0, "%E2%99%A1%FF"); + + goToTop(); +}, "Valid UTF-8 + invalid UTF-8 should not be matched to the utf8-decoded former + the isomorphic-decoded latter"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/navigate-helpers.js b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/navigate-helpers.js new file mode 100644 index 0000000000..7a9adeb3d2 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/navigate-helpers.js @@ -0,0 +1,23 @@ +"use strict"; + +// Usage examples: +// navigateAndWaitForChange(frameWindow, w => w.location.href = "..."); +// navigateAndWaitForChange(frameWindow, w => w.history.back()); +// navigateAndWaitForChange(frameWindow, w => w.history.back(), { assumeSuccessAfter: 100 }); + +window.navigateAndWaitForChange = (w, navigationAction, { assumeSuccessAfter } = {}) => { + return new Promise(resolve => { + w.addEventListener("hashchange", listener); + + function listener() { + w.removeEventListener("hashchange", listener); + resolve(); + } + + if (assumeSuccessAfter !== undefined) { + step_timeout(resolve, assumeSuccessAfter); + } + + navigationAction(w); + }); +}; diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/replacement-enabled.html b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/replacement-enabled.html new file mode 100644 index 0000000000..b22fbed80f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/replacement-enabled.html @@ -0,0 +1,69 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Navigating to a fragment should not clear forward history</title> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#scroll-to-fragid"> +<link rel="help" href="https://github.com/whatwg/html/issues/2796"> +<link rel="help" href="https://github.com/whatwg/html/pull/2869"> +<link rel="author" href="mailto:d@domenic.me" title="Domenic Denicola"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="navigate-helpers.js"></script> + +<body> + +<script> +"use strict"; +let resolve, iframe; +promise_test(() => { + iframe = document.createElement("iframe"); + iframe.src = "/common/blank.html"; + iframe.addEventListener("load", runTest); + document.body.appendChild(iframe); + + return new Promise(r => resolve = r); +}); + +function runTest() { + iframe.removeEventListener("load", runTest); + const frameWindow = iframe.contentWindow; + + resolve((async () => { + await navigateAndWaitForChange(frameWindow, w => w.location.href = "/common/blank.html#apple"); + await navigateAndWaitForChange(frameWindow, w => w.location.href = "/common/blank.html#banana"); + await navigateAndWaitForChange(frameWindow, w => w.location.href = "/common/blank.html#cat"); + + assert_equals(frameWindow.location.hash, "#cat"); + + // Might not be 4 (= 3 for iframe + 1 initial) due to cross-browser differences or if people are + // running this test in a window that has previously been places. The important thing for this + // test is the delta from this value. + const afterThreeNavigations = frameWindow.history.length; + + await navigateAndWaitForChange(frameWindow, w => w.history.back()); + + assert_equals(frameWindow.location.hash, "#banana"); + assert_equals(frameWindow.history.length, afterThreeNavigations, + "back() must not change the history length"); + + await navigateAndWaitForChange(frameWindow, + w => w.location.replace("/common/blank.html#zebra")); + + assert_equals(frameWindow.location.hash, "#zebra"); + assert_equals(frameWindow.history.length, afterThreeNavigations, + "replace() must not change the history length"); + + // As of the time of this writing (2017-08-14), Firefox is not firing hashchange on forward, so + // we automatically assume navigation succeeded after 100 ms. A sibling test will test this + // particular Firefox bug. + await navigateAndWaitForChange(frameWindow, w => w.history.forward(), + { assumeSuccessAfter: 500 }); + + assert_equals(frameWindow.location.hash, "#cat"); + assert_equals(frameWindow.history.length, afterThreeNavigations, + "forward() must not change the history length"); + + })()); +} + +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/scroll-frag-non-utf8-encoded-document.html b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/scroll-frag-non-utf8-encoded-document.html new file mode 100644 index 0000000000..7d4e994f0a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/scroll-frag-non-utf8-encoded-document.html @@ -0,0 +1,21 @@ +<!doctype html> +<title>Fragment Navigation: fragment id should not be found in non UTF8 document</title> +<meta name=timeout content=long> +<meta http-equiv="Content-Type" content="text/html; charset=gbk"/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div></div> +<div id="塯" style="position:absolute; top:100px;"></div> +<div style="height:200vh;"></div> +<script> +async_test(test => { + assert_equals(document.characterSet, "GBK", "Document should be GBK encoded"); + assert_equals(location.hash, "", "Page must be loaded with no hash"); + location.hash = '%89g'; + test.step_timeout(() => { + assert_equals( document.scrollingElement.scrollTop, 0 ); + test.done(); + }, 1); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/scroll-frag-percent-encoded.html b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/scroll-frag-percent-encoded.html new file mode 100644 index 0000000000..aa179425c5 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/scroll-frag-percent-encoded.html @@ -0,0 +1,62 @@ +<!doctype html> +<title>Fragment Navigation: fragment id should be percent-decoded</title> +<meta name=timeout content=long> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div></div> +<div id="has two spaces" style="position:absolute; top:100px;"></div> +<div id="escape%20collision" style="position:absolute; top:200px;"></div> +<div id="%20has%20two%20spaces" style="position:absolute; top:300px;"></div> +<div id="escape collision" style="position:absolute; top:400px;"></div> +<div id="do%20not%20go%20here" style="position:absolute; top:400px;"></div> +<div style="height:200em;"></div> +<script> +var steps = [{ + fragid:'has%20two%20spaces', + handler: function(){ + assert_equals( document.scrollingElement.scrollTop, 100 ); + } + },{ + fragid:'escape%20collision', + handler: function(){ + assert_equals( document.scrollingElement.scrollTop, 200 ); + document.getElementById("%20has%20two%20spaces").setAttribute("id", "has%20two%20spaces"); + } + },{ + fragid:'has%20two%20spaces', + handler: function(){ + assert_equals( document.scrollingElement.scrollTop, 300 ); + } + },{ + fragid:'do%20not%20go%20here', + handler: function(){ + // don't move + assert_equals( document.scrollingElement.scrollTop, 400 ); + } + }]; + +function runNextStep(){ + if( steps.length > 0 ) { + var step = steps.shift(); + var listener = t.step_func( function(){ + step.handler(); + runNextStep(); + }); + scrollToFragmentThenDo( step.fragid, listener ); + } else { + t.done(); + } +} + +function scrollToFragmentThenDo( fragid, then ){ + location.hash = fragid; + setTimeout( then, 1 ); +} + +var t = async_test(); +t.step( function(){ + assert_equals(location.hash, "", "Page must be loaded with no hash"); + runNextStep(); +}) +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/scroll-position-vertical-lr.html b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/scroll-position-vertical-lr.html new file mode 100644 index 0000000000..57d99440e1 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/scroll-position-vertical-lr.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html style="writing-mode: vertical-lr;"> +<head> +<meta charset="UTF-8"> +<title>Fragment Navigation: Scroll to block start position in vertical-lr writing mode</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +</head> +<body> +<div id="test" style="position: absolute; top: 5px; left: 7px; margin: 5px 7px; border-style: solid; border-width: 5px 7px; padding: 5px 7px; height: 5px; width: 7px;"></div> +<div style="width: 200vw;"></div> +<script> +async_test(function (t) { + on_event(window, 'load', function () { + t.step(function () { + window.scrollTo(0, 0); + location.hash = 'test'; + assert_equals(window.scrollX, 14, 'Scroll to the left border edge of #test'); + }); + t.done(); + }); +}, ''); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/scroll-position-vertical-rl.html b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/scroll-position-vertical-rl.html new file mode 100644 index 0000000000..60a902199a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/scroll-position-vertical-rl.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html style="writing-mode: vertical-rl;"> +<head> +<meta charset="UTF-8"> +<title>Fragment Navigation: Scroll to block start position in vertical-rl writing mode</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +</head> +<body> +<div id="test" style="position: absolute; top: 5px; right: 7px; margin: 5px 7px; border-style: solid; border-width: 5px 7px; padding: 5px 7px; height: 5px; width: 7px;"></div> +<div style="width: 200vw;"></div> +<script> +async_test(function (t) { + on_event(window, 'load', function () { + t.step(function () { + window.scrollTo(0, 0); + location.hash = 'test'; + assert_equals(window.scrollX, -14, 'Scroll to the right border edge of #test'); + }); + t.done(); + }); +}, ''); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/scroll-position.html b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/scroll-position.html new file mode 100644 index 0000000000..c38d2de83b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/scroll-position.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="UTF-8"> +<title>Fragment Navigation: Scroll to block start position</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +</head> +<body> +<div id="test" style="position: absolute; top: 5px; left: 7px; margin: 5px 7px; border-style: solid; border-width: 5px 7px; padding: 5px 7px; height: 5px; width: 7px;"></div> +<div style="height: 200vh;"></div> +<script> +async_test(function (t) { + on_event(window, 'load', function () { + t.step(function () { + window.scrollTo(0, 0); + location.hash = 'test'; + assert_equals(window.scrollY, 10, 'Scroll to the top border edge of #test'); + }); + t.done(); + }); +}, ''); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/scroll-to-anchor-name.html b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/scroll-to-anchor-name.html new file mode 100644 index 0000000000..060aed11e2 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/scroll-to-anchor-name.html @@ -0,0 +1,59 @@ +<!doctype html> +<title>Fragment Navigation: scroll to anchor name is lower priority than equal id</title> +<meta name=timeout content=long> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div></div> +<a name="anchor1" style="position:absolute; top:200px;"></a> +<div id="id-equals-anchor" style="position:absolute; top:300px;"></div> +<a name="id-equals-anchor" style="position:absolute; top:400px;"></a> +<a name="§1" style="position:absolute; top:400px;"></a> +<div style="height:200em;"></div> +<script> +var steps = [{ + fragid:'anchor1', + handler: function(){ + assert_equals( scrollPosition(), 200 ); + } + },{ + fragid:'id-equals-anchor', + handler: function(){ + // id still takes precedence over anchor name + assert_equals( scrollPosition(), 300 ); + } + },{ + fragid:'§1', + handler: function(){ + assert_equals( scrollPosition(), 400 ); + } + }]; + +function scrollPosition(){ + return document.documentElement.scrollTop || document.body.scrollTop; +} + +function runNextStep(){ + if( steps.length > 0 ) { + var step = steps.shift(); + var listener = t.step_func( function(){ + step.handler(); + runNextStep(); + }); + scrollToFragmentThenDo( step.fragid, listener ); + } else { + t.done(); + } +} + +function scrollToFragmentThenDo( fragid, then ){ + location.hash = fragid; + setTimeout( then, 1 ); +} + +var t = async_test(); +t.step( function(){ + assert_equals(location.hash, "", "Page must be loaded with no hash"); + runNextStep(); +}) +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/scroll-to-id-top.html b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/scroll-to-id-top.html new file mode 100644 index 0000000000..601d40a2a1 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/scroll-to-id-top.html @@ -0,0 +1,51 @@ +<!doctype html> +<title>Fragment Navigation: TOP is a valid element id, which overrides navigating to top of the document</title> +<meta name=timeout content=long> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div></div> +<div id="Top" style="position:absolute; top:200px;"></div> +<div style="height:200em; position:relative;"></div> +<script> +var steps = [{ + fragid:'Top', + handler: function(){ + assert_equals( scrollPosition(), 200 ); + } + },{ + // scroling to top should work when fragid differs from id by case. + fragid:'top', + handler: function(){ + assert_equals( scrollPosition(), 0 ); + } + }]; + +function scrollPosition(){ + return document.documentElement.scrollTop || document.body.scrollTop; +} + +function runNextStep(){ + if( steps.length > 0 ) { + var step = steps.shift(); + var listener = t.step_func( function(){ + step.handler(); + runNextStep(); + }); + scrollToFragmentThenDo( step.fragid, listener ); + } else { + t.done(); + } +} + +function scrollToFragmentThenDo( fragid, then ){ + location.hash = fragid; + setTimeout( then, 1 ); +} + +var t = async_test(); +t.step( function(){ + assert_equals(location.hash, "", "Page must be loaded with no hash"); + runNextStep(); +}) +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/scroll-to-top.html b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/scroll-to-top.html new file mode 100644 index 0000000000..bf62e4cd55 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/scroll-to-fragid/scroll-to-top.html @@ -0,0 +1,56 @@ +<!doctype html> +<title>Fragment Navigation: When fragid is TOP scroll to the top of the document</title> +<meta name=timeout content=long> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div></div> +<div id="not-the-top"></div> +<div style="height:200em"></div> +<script> +var steps = [{ + fragid:'not-the-top', + handler: function(){ + assert_not_equals( document.scrollingElement.scrollTop, 0 ); + } + },{ + fragid:'top', + handler: function(){ + assert_equals( document.scrollingElement.scrollTop, 0 ); + } + },{ + fragid:'not-the-top', + handler: function(){ + assert_not_equals( document.scrollingElement.scrollTop, 0 ); + } + },{ + fragid:'TOP', + handler: function(){ + assert_equals( document.scrollingElement.scrollTop, 0 ); + } + }]; + +function runNextStep(){ + if( steps.length > 0 ) { + var step = steps.shift(); + var listener = t.step_func( function(){ + step.handler(); + runNextStep(); + }); + scrollToFragmentThenDo( step.fragid, listener ); + } else { + t.done(); + } +} + +function scrollToFragmentThenDo( fragid, then ){ + location.hash = fragid; + setTimeout( then, 1 ); +} + +var t = async_test(); +t.step( function(){ + assert_equals(location.hash, "", "Page must be loaded with no hash"); + runNextStep(); +}) +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/001.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/001.html new file mode 100644 index 0000000000..1ef88d3cc1 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/001.html @@ -0,0 +1,15 @@ +<!doctype html> +<title>document.open in unload</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test(); +var win; + +t.step(function() { + win = window.open("support/001-1.html"); +}); + +add_completion_callback(function() {win.close()}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/002.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/002.html new file mode 100644 index 0000000000..a4e0b243e2 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/002.html @@ -0,0 +1,15 @@ +<!doctype html> +<title>document.open in unload</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test(); +var win; + +t.step(function() { + win = window.open("support/002-1.html"); +}); + +add_completion_callback(function() {win.close()}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/003.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/003.html new file mode 100644 index 0000000000..d0a19e0ddc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/003.html @@ -0,0 +1,15 @@ +<!doctype html> +<title>document.open in beforeunload with link</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test(); +var win; + +t.step(function() { + win = window.open("support/003-1.html"); +}); + +add_completion_callback(function() {win.close()}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/004.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/004.html new file mode 100644 index 0000000000..fca926f652 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/004.html @@ -0,0 +1,15 @@ +<!doctype html> +<title>document.open in beforeunload with button</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test(); +var win; + +t.step(function() { + win = window.open("support/004-1.html"); +}); + +add_completion_callback(function() {win.close()}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/005.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/005.html new file mode 100644 index 0000000000..c215fb88e7 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/005.html @@ -0,0 +1,15 @@ +<!doctype html> +<title>document.open in pagehide in iframe</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test(); +var win; + +t.step(function() { + win = window.open("support/005-1.html"); +}); + +add_completion_callback(function() {win.close()}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/base.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/base.html new file mode 100644 index 0000000000..70c07cba4c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/base.html @@ -0,0 +1,14 @@ +<!doctype html> +Base +<script> +onpagehide = function() { + if(top.base_hide) { + top.base_hide(); + } +} +onpageshow = function() { +if (top.base_show) { + top.base_show(); +} +} +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-canceling-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-canceling-1.html new file mode 100644 index 0000000000..6ba1e65740 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-canceling-1.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Support page for beforeunload-canceling.html</title> + +<h1>If this goes away, it navigated</h1> + +<script> +"use strict"; + +window.runTest = (t, { valueToReturn, expectCancelation, setReturnValue, expectedReturnValue, cancel }) => { + window.onbeforeunload = t.step_func(e => { + if (cancel) { + e.preventDefault(); + } + + if (setReturnValue !== undefined) { + e.returnValue = setReturnValue; + } + + return valueToReturn; + }); + + const listener = t.step_func(e => { + top.assert_equals(e.defaultPrevented, expectCancelation, "canceled"); + top.assert_equals(e.returnValue, expectedReturnValue, "returnValue"); + window.onbeforeunload = null; + + t.done(); + }); + + window.addEventListener("beforeunload", listener); + + window.location.href = "about:blank"; +}; +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-canceling.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-canceling.html new file mode 100644 index 0000000000..29a685fa59 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-canceling.html @@ -0,0 +1,222 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>beforeunload return value cancelation behavior</title> +<link rel="help" href="https://html.spec.whatwg.org/multipage/webappapis.html#the-event-handler-processing-algorithm"> +<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script> +"use strict"; + +promise_test(t => { + let onbeforeunloadHappened = false; + window.onbeforeunload = t.step_func(() => { + onbeforeunloadHappened = true; + return "cancel me"; + }); + + const eventWatcher = new EventWatcher(t, window, "beforeunload"); + const promise = eventWatcher.wait_for("beforeunload").then(e => { + assert_true(onbeforeunloadHappened, "CustomEvent must be able to trigger the event handler"); + assert_false(e.defaultPrevented, "The event must not have been canceled"); + window.onbeforeunload = null; + }); + + window.dispatchEvent(new CustomEvent("beforeunload")); + + return promise; +}, "Returning a string must not cancel the event: CustomEvent, non-cancelable"); + +promise_test(t => { + let onbeforeunloadHappened = false; + window.onbeforeunload = t.step_func(() => { + onbeforeunloadHappened = true; + return "cancel me"; + }); + + const eventWatcher = new EventWatcher(t, window, "beforeunload"); + const promise = eventWatcher.wait_for("beforeunload").then(e => { + assert_true(onbeforeunloadHappened, "CustomEvent must be able to trigger the event handler"); + assert_false(e.defaultPrevented, "The event must not have been canceled"); + window.onbeforeunload = null; + t.done(); + }); + + window.dispatchEvent(new CustomEvent("beforeunload", { cancelable: true })); + + return promise; +}, "Returning a string must not cancel the event: CustomEvent, cancelable"); + +promise_test(t => { + let onbeforeunloadHappened = false; + window.onbeforeunload = t.step_func(() => { + onbeforeunloadHappened = true; + return false; + }); + + const eventWatcher = new EventWatcher(t, window, "beforeunload"); + const promise = eventWatcher.wait_for("beforeunload").then(e => { + assert_true(onbeforeunloadHappened, "CustomEvent must be able to trigger the event handler"); + assert_false(e.defaultPrevented, "The event must not have been canceled"); + window.onbeforeunload = null; + t.done(); + }); + + window.dispatchEvent(new CustomEvent("beforeunload", { cancelable: true })); + + return promise; +}, "Returning false must not cancel the event, because it's coerced to the DOMString \"false\" which does not cancel " + + "CustomEvents: CustomEvent, cancelable"); + +// This test can be removed if we update the DOM Standard to disallow createEvent("BeforeUnloadEvent"). Browser support +// is inconsistent. https://github.com/whatwg/dom/issues/362 +promise_test(t => { + const eventWatcher = new EventWatcher(t, window, "click"); + const promise = eventWatcher.wait_for("click").then(e => { + assert_false(e.defaultPrevented, "The event must not have been canceled"); + window.onbeforeunload = null; + t.done(); + }); + + const ev = document.createEvent("BeforeUnloadEvent"); + ev.initEvent("click", false, true); + window.dispatchEvent(ev); + + return promise; +}, "Returning a string must not cancel the event: BeforeUnloadEvent with type \"click\", cancelable"); + +const testCases = [ + { + valueToReturn: null, + expectCancelation: false, + expectedReturnValue: "" + }, + { + valueToReturn: undefined, + expectCancelation: false, + expectedReturnValue: "" + }, + { + valueToReturn: "", + expectCancelation: true, + expectedReturnValue: "" + }, + { + valueToReturn: false, + expectCancelation: true, + expectedReturnValue: "false" + }, + { + valueToReturn: true, + expectCancelation: true, + expectedReturnValue: "true" + }, + { + valueToReturn: 0, + expectCancelation: true, + expectedReturnValue: "0" + }, + { + valueToReturn: null, + expectCancelation: false, + setReturnValue: "foo", + expectedReturnValue: "foo" + }, + { + valueToReturn: undefined, + expectCancelation: false, + setReturnValue: "foo", + expectedReturnValue: "foo" + }, + { + valueToReturn: "", + expectCancelation: true, + setReturnValue: "foo", + expectedReturnValue: "foo" + }, + { + valueToReturn: false, + expectCancelation: true, + setReturnValue: "foo", + expectedReturnValue: "foo" + }, + { + valueToReturn: true, + expectCancelation: true, + setReturnValue: "foo", + expectedReturnValue: "foo" + }, + { + valueToReturn: 0, + expectCancelation: true, + setReturnValue: "foo", + expectedReturnValue: "foo" + }, + { + setReturnValue: "", + expectedReturnValue: "", + expectCancelation: false, + }, + { + expectCancelation: true, + expectedReturnValue: "", + cancel: true + }, + { + setReturnValue: "foo", + expectCancelation: true, + expectedReturnValue: "foo", + cancel: true + }, + { + valueToReturn: "foo", + expectedReturnValue: "foo", + expectCancelation: true, + cancel: true + }, + { + valueToReturn: "foo", + setReturnValue: "foo", + expectedReturnValue: "foo", + expectCancelation: true, + cancel: true + }, + { + valueToReturn: true, + setReturnValue: "", + expectedReturnValue: "true", + expectCancelation: true, + cancel: true + } +]; + +var testCaseIndex = 0; +function runNextTest() { + const testCase = testCases[testCaseIndex]; + + const labelAboutReturnValue = testCase.setReturnValue === undefined ? "" : + `; setting returnValue to ${testCase.setReturnValue}`; + + const labelAboutCancel = testCase.cancel === undefined ? "" : + "; calling preventDefault()"; + + const suffixLabels = labelAboutReturnValue + labelAboutCancel; + + async_test(t => { + const iframe = document.createElement("iframe"); + iframe.onload = t.step_func(() => { + iframe.contentWindow.runTest(t, testCase); + if (++testCaseIndex < testCases.length) + runNextTest(); + }); + + iframe.src = "beforeunload-canceling-1.html"; + document.body.appendChild(iframe); + }, `Returning ${testCase.valueToReturn} with a real iframe unloading${suffixLabels}`); +} + +runNextTest(); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-on-history-back-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-on-history-back-1.html new file mode 100644 index 0000000000..4403cfa8e9 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-on-history-back-1.html @@ -0,0 +1,5 @@ +<!doctype html> +001-1 +<script> +addEventListener("beforeunload", function() {top.t.step(function() {top.beforeunload_fired = true})}, false); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-on-history-back.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-on-history-back.html new file mode 100644 index 0000000000..5b0415c422 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-on-history-back.html @@ -0,0 +1,31 @@ +<!doctype html> +<title>beforeunload event fires on history navigation back</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +beforeunload_fired = false; +var t = async_test(); + +var base_count = 0; + +onload = function() {setTimeout(t.step_func(function() { + var iframe = document.getElementsByTagName("iframe")[0] + iframe.onload = t.step_func(function() { + iframe.onload = null; + history.go(-1); + }); + + iframe.src = "beforeunload-on-history-back-1.html"; +}), 100)}; + +base_show = t.step_func(function() { + base_count++; + if (base_count > 1) { + assert_true(beforeunload_fired); + t.done(); + } +}); + +</script> +<iframe src="base.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-on-navigation-of-parent-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-on-navigation-of-parent-1.html new file mode 100644 index 0000000000..4f239dad1e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-on-navigation-of-parent-1.html @@ -0,0 +1,2 @@ +<!doctype html> +<iframe src="beforeunload-on-navigation-of-parent-2.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-on-navigation-of-parent-2.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-on-navigation-of-parent-2.html new file mode 100644 index 0000000000..a34b182e70 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-on-navigation-of-parent-2.html @@ -0,0 +1,4 @@ +<!doctype html> +<script> +addEventListener("beforeunload", function() {parent.parent.beforeunload_fired=true}, false) +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-on-navigation-of-parent.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-on-navigation-of-parent.html new file mode 100644 index 0000000000..96d49567f3 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-on-navigation-of-parent.html @@ -0,0 +1,31 @@ +<!doctype html> +<title>beforeunload in iframe on navigation of parent</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +beforeunload_fired = false; +var t = async_test(); + +var base_count = 0; + +onload = function() {setTimeout(t.step_func(function() { + var iframe = document.getElementsByTagName("iframe")[0] + iframe.onload = t.step_func(function() { + iframe.onload = null; + history.go(-1); + }); + + iframe.src = "beforeunload-on-navigation-of-parent-1.html"; +}), 100)}; + +base_show = t.step_func(function() { + base_count++; + if (base_count > 1) { + assert_true(beforeunload_fired); + t.done(); + } +}); + +</script> +<iframe src="base.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-sticky-activation-iframe.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-sticky-activation-iframe.html new file mode 100644 index 0000000000..212a10c005 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-sticky-activation-iframe.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Beforeunload must be gated behind sticky activation: nested browsing context</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<p>If you happen to be running this test as a human, then be sure not to interact with any part of the page; that would invalidate the results! + +<script> +setup({ single_test: true }); + +const iframe = document.createElement('iframe'); +iframe.src = 'support/beforeunload-sticky-start.html'; + +window.onmessage = e => { + assert_equals(e.data, 'navigated successfully'); + + const desiredURL = (new URL('support/beforeunload-sticky-destination.html', location.href)).href; + assert_equals(iframe.contentWindow.location.href, desiredURL); + + done(); +}; + +document.body.append(iframe); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-sticky-activation-manual.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-sticky-activation-manual.html new file mode 100644 index 0000000000..55612bbfc4 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-sticky-activation-manual.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Beforeunload must be gated behind sticky activation: normal top-level browsing context</title> + +<p>This test is manual because we want to test non-popup, non-iframe situations. Sibling files contain automated tests for those situations. + +<p>In three seconds, this document will redirect itself to a new page. The test passes if the redirect succeeds. The test fails if a beforeunload dialog pops up asking for confirmation. + +<p>Be sure not to interact with any part of the page in the meantime. That would invalidate the results. + +<script> +window.onbeforeunload = e => e.preventDefault(); + +setTimeout(() => { + location.href = 'support/beforeunload-sticky-destination.html'; +}, 3000); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-sticky-activation-popup.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-sticky-activation-popup.html new file mode 100644 index 0000000000..23bf8a440d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-sticky-activation-popup.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Beforeunload must be gated behind sticky activation: auxiliary browsing context</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<p>If you happen to be running this test as a human, then be sure not to interact with any part of the page; that would invalidate the results! + +<script> +setup({ single_test: true }); + +const w = window.open('support/beforeunload-sticky-start.html'); + +window.onmessage = e => { + assert_equals(e.data, 'navigated successfully'); + + const desiredURL = (new URL('support/beforeunload-sticky-destination.html', location.href)).href; + assert_equals(w.location.href, desiredURL); + + w.close(); + + done(); +}; +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-synchronous.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-synchronous.html new file mode 100644 index 0000000000..6806eaf7a3 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/beforeunload-synchronous.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>beforeunload event is emitted synchronously</title> +<link rel="help" href="https://html.spec.whatwg.org/multipage/webappapis.html#the-event-handler-processing-algorithm"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +'use strict'; +// "navigate a browsing context" synchronously calls "prompt to unload", which +// synchronously calls "dispatch an event". + +async_test(function(t) { + var iframe = document.createElement('iframe'); + + iframe.onload = t.step_func(function() { + var callCount = 0; + + iframe.contentWindow.onbeforeunload = function() { + callCount += 1; + }; + + iframe.contentWindow.location.href = '/common/blank.html'; + + assert_equals(callCount, 1, 'invoked synchronously exactly once'); + + t.done(); + }); + + document.body.appendChild(iframe); +}); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/navigation-within-beforeunload-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/navigation-within-beforeunload-1.html new file mode 100644 index 0000000000..b96234fba2 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/navigation-within-beforeunload-1.html @@ -0,0 +1,10 @@ +<!doctype html> +004-1 +<script> +addEventListener("beforeunload", +function() { +if (top.counter++ < 999) { + location = "navigation-within-beforeunload-2.html?" + top.counter; +} +}, false); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/navigation-within-beforeunload-2.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/navigation-within-beforeunload-2.html new file mode 100644 index 0000000000..2dceaa6d6a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/navigation-within-beforeunload-2.html @@ -0,0 +1,4 @@ +<!doctype html> +<script> +document.write(location) +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/navigation-within-beforeunload.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/navigation-within-beforeunload.html new file mode 100644 index 0000000000..d6ecf5d52f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/navigation-within-beforeunload.html @@ -0,0 +1,29 @@ +<!doctype html> +<title>Triggering navigation from within beforeunload event</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +beforeunload_fired = false; +var t = async_test(); + +var base_count = 0; +var counter = 0; + +onload = function() {setTimeout(function() { + var iframe = document.getElementsByTagName("iframe")[0] + + iframe.onload = function() { + setTimeout(function() {iframe.contentWindow.location="navigation-within-beforeunload-2.html";}, 100); + // Step 4 of https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigating-across-documents + // doesn't seem to allow navigation within a beforeunload handler, + // so the counter should not go beyond 1. + iframe.onload = t.step_func(function() {assert_equals(counter, 1); t.done()}); + }; + + iframe.src = "navigation-within-beforeunload-1.html?" + Math.random(); + +}, 100)}; + +</script> +<iframe src="base.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/pagehide-on-history-forward-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/pagehide-on-history-forward-1.html new file mode 100644 index 0000000000..a60c20ed80 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/pagehide-on-history-forward-1.html @@ -0,0 +1,2 @@ +<!doctype html> +filler text diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/pagehide-on-history-forward.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/pagehide-on-history-forward.html new file mode 100644 index 0000000000..5e64b5ec66 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/pagehide-on-history-forward.html @@ -0,0 +1,19 @@ +<!doctype html> +<title>pagehide event fires on history navigation forward</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test(); + +onload = function() {setTimeout(t.step_func(function() { + var iframe = document.getElementsByTagName("iframe")[0] + + iframe.src = "pagehide-on-history-forward-1.html"; +}), 100)}; + +base_hide = t.step_func(function() { + t.done() +}); +</script> +<iframe src="base.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt-and-unload-script-closeable.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt-and-unload-script-closeable.html new file mode 100644 index 0000000000..b94789c40f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt-and-unload-script-closeable.html @@ -0,0 +1,23 @@ +<!doctype html> +<title>beforeunload and unload events fire after window.close() in script-closeable browsing context</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +beforeunload_fired = false; +var t = async_test(); + +onload = t.step_func(function() { + window.close(); +}); + +onbeforeunload = t.step_func(function() { + beforeunload_fired = true; +}); + +onunload = t.step_func(function() { + assert_true(beforeunload_fired); + t.done() +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt-and-unload-script-uncloseable-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt-and-unload-script-uncloseable-1.html new file mode 100644 index 0000000000..3a557ce34e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt-and-unload-script-uncloseable-1.html @@ -0,0 +1,10 @@ +<!doctype html> +script-uncloseable-1 +<script> +onbeforeunload = function() { + parent.beforeunload_fired = true; +}; +onunload = function() { + parent.unload_fired = true; +}; +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt-and-unload-script-uncloseable.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt-and-unload-script-uncloseable.html new file mode 100644 index 0000000000..f6a17d740b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt-and-unload-script-uncloseable.html @@ -0,0 +1,24 @@ +<!doctype html> +<title>beforeunload and unload events do not fire after window.close() in script-uncloseable browsing context</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var beforeunload_fired = false; +var unload_fired = false; +var t = async_test(); + +onload = t.step_func(function() { + var iframe = document.getElementsByTagName("iframe")[0] + iframe.onload = t.step_func(function() { + iframe.contentWindow.close() + t.step_timeout(function() { + assert_false(beforeunload_fired); + assert_false(unload_fired); + t.done(); + }, 1000); + }); + iframe.src = "prompt-and-unload-script-uncloseable-1.html"; +}); +</script> +<iframe></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/001-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/001-1.html new file mode 100644 index 0000000000..b68afc49ec --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/001-1.html @@ -0,0 +1,10 @@ +<script> +addEventListener("beforeunload", +function() { + parent.events.push("beforeunload"); +}, false); +parent.events.push("before src change"); + +location.href = "001-2.html&pipe=trickle(d2)"; +parent.events.push("after src change"); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/001-2.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/001-2.html new file mode 100644 index 0000000000..9da0f9395c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/001-2.html @@ -0,0 +1 @@ +001-2 diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/001.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/001.html new file mode 100644 index 0000000000..109dcc1393 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/001.html @@ -0,0 +1,14 @@ +<!doctype html> +<title>beforeunload event order</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test(); +events = []; +onload = t.step_func(function() { + assert_array_equals(events, ["before src change", "beforeunload", "after src change"]); + t.done(); +}) +</script> +<iframe src="001-1.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/002-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/002-1.html new file mode 100644 index 0000000000..c5f57375da --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/002-1.html @@ -0,0 +1,7 @@ +<script> +addEventListsner("beforeunload", parent.t.step_func( +function(e) { + parent.do_test(e); +}, false); +location.href = "001-2.html&pipe=trickle(d2)"; +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/002.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/002.html new file mode 100644 index 0000000000..d8f4fc60a9 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/002.html @@ -0,0 +1,20 @@ +<!doctype html> +<title>beforeunload event properties</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test(); + +function do_test(e) { + assert_equals(e.type, "beforeunload"); + assert_false(e.bubbles, "bubbles"); + assert_true(e.cancelable, "bubbles"); + assert_equals(e.returnValue, ""); +} + +onload = t.step_func(function() { + t.done(); +}) +</script> +<iframe src="001-1.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/003.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/003.html new file mode 100644 index 0000000000..5683f1b120 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/003.html @@ -0,0 +1,20 @@ +<!doctype html> +<title>beforeunload event in child frame for parent navigation</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test(); + +function do_test(e) { + assert_equals(e.type, "beforeunload"); + assert_false(e.bubbles, "bubbles"); + assert_true(e.cancelable, "bubbles"); + assert_equals(e.returnValue, ""); +} + +onload = t.step_func(function() { + t.done(); +}) +</script> +<iframe src="001-1.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/004-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/004-1.html new file mode 100644 index 0000000000..a3ca82f520 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/004-1.html @@ -0,0 +1,28 @@ +<!doctype html> +004-1 +<script> +var handleBeforeUnload = function() { + parent.beforeunload_fired = true; + removeListener(); + setTimeout(function() { + parent.timeout_fired = true; + }, 1000); +} + +var removeListener = function() { + assert_true(window.removeEventListener('beforeunload', handleBeforeUnload, false)); +} + +window.addEventListener('beforeunload', handleBeforeUnload, false); + +onload = function() { + if (!parent.loaded) { + parent.loaded = true; + location="004-2.html?" + Math.random(); + } +} +</script> +// child frame with no onbeforeunload listener. Should leave the parent as unsalvageable. +// Adding the iframe prevents potential implementation bugs where the the recursive steps of #prompt-to-unload-a-document +// would overwrite the salvageable state of the parent. +<iframe></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/004-2.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/004-2.html new file mode 100644 index 0000000000..1a605b1b3d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/004-2.html @@ -0,0 +1,5 @@ +<!doctype html> +004-2 +<script> +onload = function() {setTimeout(parent.t.step_func(function() {parent.start_test(); history.go(-1)}), 100)} +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/004.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/004.html new file mode 100644 index 0000000000..7076a4dd18 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/004.html @@ -0,0 +1,29 @@ +<!doctype html> +<title>salvagable state of document after setting beforeunload listener</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test(); + +var loaded = false; +var beforeunload_fired = false; +var timeout_fired = false; + +function start_test() { + step_timeout( + t.step_func(function() { + assert_true(beforeunload_fired); + assert_false(timeout_fired); + t.done() + }), 1000); +} + +onload = function() { + var iframe = document.getElementsByTagName("iframe")[0] + onload = null; + iframe.src="004-1.html?" + Math.random(); +}; + +</script> +<iframe></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/manual-001.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/manual-001.html new file mode 100644 index 0000000000..3b7ef74b71 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/manual-001.html @@ -0,0 +1,9 @@ +<!doctype html> +<title>Prompt when beforeunload is canceled</title> +<script> +addEventListener("beforeunload", +function(e) {e.preventDefault()}, +false); +</script> +<p>When clicking the link below, you should get a prompt asking if you want to unload the document</p> +<a href="next.html">Click here</a> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/manual-002.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/manual-002.html new file mode 100644 index 0000000000..7be8a3301f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/manual-002.html @@ -0,0 +1,9 @@ +<!doctype html> +<title>Prompt when beforeunload has returnValue set</title> +<script> +addEventListener("beforeunload", +function(e) {e.returnValue = "PASS if you see this"}, +false); +</script> +<p>When clicking the link below, you should get a prompt asking if you want to unload the document</p> +<a href="next.html">Click here</a> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/manual-003.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/manual-003.html new file mode 100644 index 0000000000..ff72b67055 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/manual-003.html @@ -0,0 +1,11 @@ +<!doctype html> +<title>Prompt when beforeunload is canceled</title> +<script> +addEventListener("beforeunload", +function(e) {e.preventDefault()}, +false); +</script> +<p>When clicking the button below, you should get a prompt asking if you want to unload the document</p> +<form method="get" action="next.html"> +<input type="submit" value="Click here"> +</form> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/manual-004.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/manual-004.html new file mode 100644 index 0000000000..a4d2968922 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/manual-004.html @@ -0,0 +1,11 @@ +<!doctype html> +<title>Prompt on form submit</title> +<script> +addEventListener("beforeunload", +function(e) {e.preventDefault()}, +false); +</script> +<p>When clicking the button below, you should get a prompt asking if you want to unload the document</p> +<form method="get" action="next.html"> +<input type="submit" value="Click here"> +</form> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/manual-005.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/manual-005.html new file mode 100644 index 0000000000..71ff0a241d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/manual-005.html @@ -0,0 +1,22 @@ +<!doctype html> +<title>Event loop pause for beforeunload</title> +<script> +var counter = 0; + +onload = function count() { + document.getElementById("log").textContent = counter++ + setTimeout(count, 200); +} + +addEventListener("beforeunload", +function(e) { + e.preventDefault() +}, +false); +</script> +<ul> +<li>Click on the link below. When the prompt appears the counter at the bottom must stop incrementing. +<li>Opt not to leave the page. The counter must start incrementing again +</ul> +<p><a href="">Click here</a> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/manual-006.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/manual-006.html new file mode 100644 index 0000000000..dae0340ad9 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/manual-006.html @@ -0,0 +1,9 @@ +<!doctype html> +<title>Prompt when beforeunload returns string value</title> +<script> +addEventListener("beforeunload", +function(e) {return "PASS if you see this"}, +false); +</script> +<p>When clicking the link below, you should get a prompt asking if you want to unload the document</p> +<a href="next.html">Click here</a> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/next.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/next.html new file mode 100644 index 0000000000..38e7cdd5e0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/prompt/next.html @@ -0,0 +1,2 @@ +<!doctype html> +<p>You should have seen a prompt asking you to unload the previous document diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/001-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/001-1.html new file mode 100644 index 0000000000..72f41ae3e8 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/001-1.html @@ -0,0 +1,23 @@ +<!DOCTYPE HTML> +<script> + t = opener.t; + do_test = t.step(function () { + localStorage.test6564729 += '4'; + var d = document; + var e = document.open(); // has no effect (ignore-opens-during-unload > 0) + localStorage.test6564729 += (e == d) ? '5' : 'A [' + e + '] '; + document.write('FAIL - document.write executed and blocked navigation!'); // has no effect (ignore-opens-during-unload > 0) + localStorage.test6564729 += document.body.textContent.match('FAIL') ? 'B' : '6'; + document.close(); // has no effect (no script-created parser) + localStorage.test6564729 += '7'; + }) +onload = t.step_func(function() { + localStorage.test6564729 = '0'; + setTimeout(t.step_func(function() {document.links[0].click()})); +}); +</script> +<body onbeforeunload="localStorage.test6564729 += '1'" + onpagehide="localStorage.test6564729 += '3'" + onunload="do_test()"> +<p><a href="001a.html">Follow this link to run the test.</a> +<p><iframe src="001b.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/001a.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/001a.html new file mode 100644 index 0000000000..36d4188b9e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/001a.html @@ -0,0 +1,7 @@ +<!DOCTYPE HTML> +<script> +opener.t.step(function() { + opener.assert_equals(localStorage.test6564729, '0123456789'); + opener.t.done(); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/001b.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/001b.html new file mode 100644 index 0000000000..eaafc371a1 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/001b.html @@ -0,0 +1,5 @@ +<!DOCTYPE HTML> +<body onbeforeunload="localStorage.test6564729 += '2'" + onpagehide="localStorage.test6564729 += '8'" + onunload="localStorage.test6564729 += '9'"> +<p>Inner frame
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/002-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/002-1.html new file mode 100644 index 0000000000..0e6f7d967b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/002-1.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<script> + var t = opener.t; + + var do_test = t.step_func(function() { + localStorage.test6564729 += '1'; + var d = document; + var e = document.open(); // unload triggered here - beforeunload 2, 3 in 002b; pagehide 4, unload 5, pagehide 6 in 002b, unload 7 in 002b + localStorage.test6564729 += (e == d) ? '8' : 'X'; + var s = 'FAIL if you see this | ' + localStorage.test6564729; + document.write(s); + localStorage.test6564729 += document.body.textContent == s ? '9' : 'x'; + document.close(); + localStorage.test6564729 += 'Z'; + document.body.textContent += ' // ' + localStorage.test6564729; + location = '002a.html'; // unload triggers again here, but they're not registered event listeners any more + }); + +onload = t.step_func(function() { + localStorage.test6564729 = '0'; + setTimeout(function() {document.getElementsByTagName("input")[0].click()}, 100); +}); +</script> +<body onbeforeunload="localStorage.test6564729 += '2'" + onpagehide="localStorage.test6564729 += '4'" + onunload="localStorage.test6564729 += '5'"> +<input type=button value="Activate this button to run the test" onclick="do_test()"> +<p><iframe src="002b.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/002a.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/002a.html new file mode 100644 index 0000000000..d11f670869 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/002a.html @@ -0,0 +1,7 @@ +<!DOCTYPE HTML> +<script> + opener.t.step(function() { + opener.assert_equals(localStorage.test6564729, '0123456789Z'); + opener.t.done(); + }); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/002b.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/002b.html new file mode 100644 index 0000000000..d08a7a8add --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/002b.html @@ -0,0 +1,5 @@ +<!DOCTYPE HTML> +<body onbeforeunload="localStorage.test6564729 += '3'" + onpagehide="localStorage.test6564729 += '6'" + onunload="localStorage.test6564729 += '7'"> +<p>Inner frame
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/003-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/003-1.html new file mode 100644 index 0000000000..b3a4754b85 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/003-1.html @@ -0,0 +1,23 @@ +<!DOCTYPE HTML> +<script> + var t = opener.t; + var do_test = t.step_func(function() { + localStorage.test6564729 += '1'; + var d = document; + var e = document.open(); // has no effect (ignore-opens-during-unload > 0 because we're in beforeunload) + localStorage.test6564729 += (e == d) ? '2' : 'A [' + e + '] '; + document.write('FAIL - document.write executed and blocked navigation!'); // has no effect (ignore-opens-during-unload > 0) + localStorage.test6564729 += document.body.textContent.match('FAIL') ? 'B' : '3'; + document.close(); // has no effect (no script-created parser) + localStorage.test6564729 += '4'; + }) + + onload=t.step_func(function() {localStorage.test6564729 = '0'; setTimeout(t.step_func(function() {document.links[0].click()}), 100)}) + +</script> +<body + onbeforeunload="do_test()" + onpagehide="localStorage.test6564729 += '6'" + onunload="localStorage.test6564729 += '7'"> +<p><a href="003a.html">Follow this link to run the test.</a> +<p><iframe src="003b.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/003a.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/003a.html new file mode 100644 index 0000000000..5393fa221e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/003a.html @@ -0,0 +1,8 @@ +<!DOCTYPE HTML> +<p>FAIL</p> +<script> +opener.t.step(function() { + opener.assert_equals(localStorage.test6564729, '0123456789') + opener.t.done(); +}) +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/003b.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/003b.html new file mode 100644 index 0000000000..c8f1917b85 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/003b.html @@ -0,0 +1,5 @@ +<!DOCTYPE HTML> +<body onbeforeunload="localStorage.test6564729 += '5'" + onpagehide="localStorage.test6564729 += '8'" + onunload="localStorage.test6564729 += '9'"> +<p>Inner frame
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/004-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/004-1.html new file mode 100644 index 0000000000..06aba08af6 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/004-1.html @@ -0,0 +1,25 @@ +<!DOCTYPE HTML> +<script> + var t = opener.t; + var do_test = t.step_func(function() { + localStorage.test6564729 += 'B'; + var d = document; + var e = document.open(); // unload triggered here - beforeunload C, D in 004b; pagehide E, unload F, pagehide G in 004b, unload HIJK in 004b + localStorage.test6564729 += (e == d) ? 'L' : 'Y'; + var s = 'FAIL if you see this | ' + localStorage.test6564729; + document.write(s); + localStorage.test6564729 += document.body.textContent == s ? 'M' : 'y'; + document.close(); + localStorage.test6564729 += 'N'; + location = '004a.html'; // unload triggers again here, but they're not registered event listeners any more + }) +onload = t.step_func(function() { + localStorage.test6564729 = 'A'; + setTimeout(t.step_func(function() {document.getElementsByTagName("input")[0].click()}), 100); +}) +</script> +<body onbeforeunload="localStorage.test6564729 += 'C'" + onpagehide="localStorage.test6564729 += 'E'" + onunload="localStorage.test6564729 += 'F'"> +<input type=button value="Activate this button to run the test" onclick="do_test()"> +<p><iframe src="004b.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/004a.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/004a.html new file mode 100644 index 0000000000..117e2b94ae --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/004a.html @@ -0,0 +1,8 @@ +<!DOCTYPE HTML> +<p>FAIL</p> +<script> +opener.t.step(function() { + opener.assert_equals(localStorage.test6564729, 'ABCDEFGHIJKLMN'); + opener.t.done(); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/004b.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/004b.html new file mode 100644 index 0000000000..788937a0b0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/004b.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML> +<script> + function testRun() { + localStorage.test6564729 += 'H'; + var d = parent.document; + var e = parent.document.open(); // no effect, since that document is already in unload + localStorage.test6564729 += (e == d) ? 'I' : 'X'; + var s = 'FAIL'; + document.write(s); + localStorage.test6564729 += document.body.textContent == s ? 'x' : 'J'; + document.close(); + localStorage.test6564729 += 'K'; + } +</script> +<body onbeforeunload="localStorage.test6564729 += 'D'" + onpagehide="localStorage.test6564729 += 'G'" + onunload="testRun()"> +<p>Inner frame
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/005-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/005-1.html new file mode 100644 index 0000000000..7b81a9f115 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/005-1.html @@ -0,0 +1,13 @@ +<!DOCTYPE HTML> +<script> +onload = opener.t.step_func(function() { + localStorage.test6564729 = '0' + setTimeout(opener.t.step_func(function() {document.links[0].click()}), 100); +}); +</script> +<body + onbeforeunload="localStorage.test6564729 += '1'" + onpagehide="localStorage.test6564729 += '3'" + onunload="localStorage.test6564729 += '4'"> +<p><a href="005a.html">Follow this link to run the test.</a> +<p><iframe src="005b.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/005a.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/005a.html new file mode 100644 index 0000000000..5185d3b921 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/005a.html @@ -0,0 +1,8 @@ +<!DOCTYPE HTML> +<p>FAIL</p> +<script> +opener.t.step(function() { + opener.assert_equals(localStorage.test6564729, '012345678') + opener.t.done(); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/005b.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/005b.html new file mode 100644 index 0000000000..476e8e38c4 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/005b.html @@ -0,0 +1,17 @@ +<!DOCTYPE HTML> +<script> + var t = parent.opener.t; + var do_test = t.step_func(function () { + localStorage.test6564729 += '5'; + var s = 'FAIL: document.open() has canceled the navigation (' + localStorage.test6564729 + ')'; + parent.document.open(); + parent.document.write(s); + parent.document.close(); + localStorage.test6564729 += parent.document.body.textContent.match('FAIL') == s ? 'X' : '6'; + localStorage.test6564729 += '7'; + }); +</script> +<body onbeforeunload="localStorage.test6564729 += '2'" + onpagehide="do_test()" + onunload="localStorage.test6564729 += '8'"> +<p>Inner frame diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/beforeunload-sticky-destination.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/beforeunload-sticky-destination.html new file mode 100644 index 0000000000..2edcf1a43b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/beforeunload-sticky-destination.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Beforeunload must be gated behind sticky activation: destination page</title> + +<p>If you reached this page without clicking through a confirmation dialog, then the test has passed! + +<script> +if (window.opener) { + window.opener.postMessage('navigated successfully'); +} else if (window.parent) { + window.parent.postMessage('navigated successfully'); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/beforeunload-sticky-start.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/beforeunload-sticky-start.html new file mode 100644 index 0000000000..37109feafe --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/support/beforeunload-sticky-start.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Beforeunload must be gated behind sticky activation: start page</title> + +<p>This page will immediately navigate. If a beforeunload dialog pops up, the test fails.</p> + +<script> +window.onbeforeunload = e => e.preventDefault(); +location.href = 'beforeunload-sticky-destination.html'; +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/001-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/001-1.html new file mode 100644 index 0000000000..74ba43954b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/001-1.html @@ -0,0 +1,4 @@ +<!doctype html> +<script> +addEventListener("pagehide", parent.t.step_func(function() {parent.pagehide_fired = true}), false); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/001-2.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/001-2.html new file mode 100644 index 0000000000..90e28ab7fb --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/001-2.html @@ -0,0 +1,2 @@ +<!doctype html> +Filler diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/001.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/001.html new file mode 100644 index 0000000000..444a2770c7 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/001.html @@ -0,0 +1,25 @@ +<!doctype html> +<title>pagehide event on unload</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +pagehide_fired = false; +var t = async_test(); + +onload = function() {setTimeout(function() { + var iframe = document.getElementsByTagName("iframe")[0] + + iframe.onload = function() { + setTimeout(function() { + iframe.contentWindow.location="001-2.html"; + }, 100); + iframe.onload = t.step_func(function() {assert_true(pagehide_fired); t.done()}); + }; + + iframe.src = "001-1.html?" + Math.random(); + +}, 100)}; + +</script> +<iframe></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/002-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/002-1.html new file mode 100644 index 0000000000..fd8e2b7262 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/002-1.html @@ -0,0 +1,4 @@ +<!doctype html> +<script> +addEventListener("pagehide", parent.t.step_func(parent.do_test()), false); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/002.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/002.html new file mode 100644 index 0000000000..d36011286c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/002.html @@ -0,0 +1,36 @@ +<!doctype html> +<title>pagehide event properties</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test(); + +onload = function() {setTimeout(function() { + var iframe = document.getElementsByTagName("iframe")[0] + + iframe.onload = function() { + setTimeout(function() { + iframe.contentWindow.location="001-2.html"; + }, 100); + iframe.onload = t.step_func(function() {t.done()}); + }; + + function do_test(e) { + assert_equals(e.type, "pagehide"); + assert_equals(e.target, iframe.contentDocument); + assert_equals(e.currentTarget, iframe.contentWindow); + + // https://github.com/whatwg/html/issues/6794 + assert_true(e.bubbles, "bubbles"); + assert_true(e.cancelable, "cancelable"); + + assert_true(e.persisted, "persisted"); + } + + iframe.src = "002-1.html?" + Math.random(); + +}, 100)}; + +</script> +<iframe></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/003-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/003-1.html new file mode 100644 index 0000000000..9838c79456 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/003-1.html @@ -0,0 +1,4 @@ +<!doctype html> +<script> +addEventListener("unload", parent.t.step_func(function(e) {parent.do_test(e)}), false); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/003.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/003.html new file mode 100644 index 0000000000..97821be484 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/003.html @@ -0,0 +1,33 @@ +<!doctype html> +<title>unload event properties</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test(); +var do_test; + +onload = function() {setTimeout(function() { + var iframe = document.getElementsByTagName("iframe")[0] + + iframe.onload = function() { + setTimeout(function() { + iframe.contentWindow.location="002-2.html"; + }, 100); + iframe.onload = t.step_func(function() {t.done()}); + }; + + do_test = function(e) { + assert_equals(e.type, "unload"); + assert_equals(e.target, iframe.contentDocument); + assert_equals(e.currentTarget, iframe.contentWindow); + assert_false(e.bubbles, "bubbles"); + assert_false(e.cancelable, "cancelable"); + } + + iframe.src = "003-1.html?" + Math.random(); + +}, 100)}; + +</script> +<iframe></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/004-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/004-1.html new file mode 100644 index 0000000000..5d0497556b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/004-1.html @@ -0,0 +1,5 @@ +<!doctype html> +<script> +addEventListener("pagehide", function() {parent.events.push("pagehide"); setTimeout(function() {parent.events.push("timeout")}, 0)}, false); +addEventListener("unload", function() {parent.events.push("unload")}, false); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/004.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/004.html new file mode 100644 index 0000000000..301baa3b8e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/004.html @@ -0,0 +1,28 @@ +<!doctype html> +<title>pagehide / unload event order</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test(); + +var events = []; + +onload = function() {setTimeout(function() { + var iframe = document.getElementsByTagName("iframe")[0] + + iframe.onload = function() { + setTimeout(function() { + iframe.contentWindow.location="001-2.html"; + }, 100); + iframe.onload = t.step_func(function() { + assert_array_equals(events, ["pagehide", "unload"]) + t.done()}); + }; + + iframe.src = "004-1.html?" + Math.random(); + +}, 100)}; + +</script> +<iframe></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/006-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/006-1.html new file mode 100644 index 0000000000..bc2e10bdc3 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/006-1.html @@ -0,0 +1,23 @@ +<!doctype html> +006-1 +<script> +onpagehide = function() { + onpagehide = null; + setTimeout(function() { + parent.t.unreached_func('setTimeout survived navigatoin'); + }, 1000); +} +if (parent.loaded) { + setTimeout(function() { parent.t.done(); }, 2000); +} +onload = function() { + if (!parent.loaded) { + parent.loaded = true; + setTimeout(parent.t.step_func( + function() { + location="006-2.html?" + Math.random(); + } + ), 100); + } +} +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/006-2.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/006-2.html new file mode 100644 index 0000000000..52365e55d8 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/006-2.html @@ -0,0 +1,5 @@ +<!doctype html> +006-2 +<script> +onload = function() {setTimeout(parent.t.step_func(function() {history.go(-1)}), 100)} +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/006.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/006.html new file mode 100644 index 0000000000..c9e4d68a10 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/006.html @@ -0,0 +1,18 @@ +<!doctype html> +<title>salvagable state of document after setting pagehide listener</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test(); + +var loaded = false; + +onload = function() {setTimeout(function() { + var iframe = document.getElementsByTagName("iframe")[0] + onload = null; + iframe.src="006-1.html?" + Math.random(); +}, 100)}; + +</script> +<iframe></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/007-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/007-1.html new file mode 100644 index 0000000000..ed19f4498a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/007-1.html @@ -0,0 +1,21 @@ +<!doctype html> +007-1 +<script> +onunload = function() { + onunload = null; + parent.unload_fired = true; + setTimeout(function() { + parent.timeout_fired = true; + }, 100); +} +onload = function() { + if (!parent.loaded) { + parent.loaded = true; + setTimeout(parent.t.step_func( + function() { + location="007-2.html?" + Math.random(); + } + ), 100); + } +} +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/007-2.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/007-2.html new file mode 100644 index 0000000000..f74cd1e67e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/007-2.html @@ -0,0 +1,5 @@ +<!doctype html> +007-2 +<script> +onload = function() {setTimeout(parent.t.step_func(function() {parent.start_test(); history.go(-1)}), 100)} +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/007.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/007.html new file mode 100644 index 0000000000..4a2fed5fac --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/007.html @@ -0,0 +1,28 @@ +<!doctype html> +<title>salvagable state of document after setting unload listener</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test(); + +var loaded = false; +var unload_fired = false; +var timeout_fired = false; + +function start_test() { + step_timeout(t.step_func(function() { + assert_true(unload_fired); + assert_false(timeout_fired); + t.done() + }), 1000); +} + +onload = function() {setTimeout(function() { + var iframe = document.getElementsByTagName("iframe")[0] + onload = null; + iframe.src="007-1.html?" + Math.random(); +}, 100)}; + +</script> +<iframe></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/008-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/008-1.html new file mode 100644 index 0000000000..29de29c911 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/008-1.html @@ -0,0 +1,4 @@ +<!doctype html> +<script> +onpagehide = parent.t.step_func(function() {parent.t.done()}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/008.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/008.html new file mode 100644 index 0000000000..015507d817 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/008.html @@ -0,0 +1,16 @@ +<!doctype html> +<title>pagehide IDL attribute</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var iframe; +var t = async_test(); +onload = function() { + setTimeout(function() { + var iframe = document.getElementsByTagName("iframe")[0]; + iframe.src="about:blank"; + }, 100) +}; +</script> +<iframe src="008-1.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/009-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/009-1.html new file mode 100644 index 0000000000..d69a05914a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/009-1.html @@ -0,0 +1,4 @@ +<!doctype html> +<script> +onunload = parent.t.step_func(function() {parent.t.done()}); +</script> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/009.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/009.html new file mode 100644 index 0000000000..0e93e04701 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/009.html @@ -0,0 +1,16 @@ +<!doctype html> +<title>unload IDL attribute</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var iframe; +var t = async_test(); +onload = function() { + setTimeout(function() { + var iframe = document.getElementsByTagName("iframe")[0]; + iframe.src="about:blank"; + }, 100) +} +</script> +<iframe src="009-1.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/pagehide-manual-1.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/pagehide-manual-1.html new file mode 100644 index 0000000000..3da0a0de3e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/pagehide-manual-1.html @@ -0,0 +1,2 @@ +<!doctype html> +<p>Now go back. PASS should be displayed after a short pause diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/pagehide-manual.html b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/pagehide-manual.html new file mode 100644 index 0000000000..ba34c3087f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/pagehide-manual.html @@ -0,0 +1,5 @@ +<!doctype html> +<title>Document salvagable state after setting pagehide handler</title> +<script>onpagehide = function() {setTimeout(function(){document.body.innerHTML = "PASS"}, 100)}</script> +<p>Click the link below then navigate back to this page. Shortly after returning you should see the text "PASS"</p> +<p><a href="pagehide-manual-1.html">Click here</a> diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/unload-main-frame-cross-origin.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/unload-main-frame-cross-origin.window.js new file mode 100644 index 0000000000..65b4e533f6 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/unload-main-frame-cross-origin.window.js @@ -0,0 +1,34 @@ +// META: title=Unload runs in main frame when navigating cross-origin. +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js + +promise_test(async t => { + const rcHelper = new RemoteContextHelper(); + + const rc1 = await rcHelper.addWindow(); + + t.add_cleanup(() => localStorage.removeItem('unload')); + + // Initialize storage and add "unload" event handler. + await rc1.executeScript(() => { + localStorage.setItem('unload', 'not yet'); + addEventListener('unload', () => { + localStorage.setItem('unload', 'ran'); + }); + }); + + // Navigate away. + const rc2 = await rc1.navigateToNew( + {extraRemoteContextConfig: {origin: 'HTTP_REMOTE_ORIGIN'}}); + + // Navigate back. + await rc2.historyBack(); + + // Test that the unload handler wrote to storage. + // Running it in the remote context after going back should ensure that the + // navigation (and therefore the unload handler) has completed. + assert_equals( + await rc1.executeScript(() => localStorage.getItem('unload')), 'ran'); +}); diff --git a/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/unload-main-frame-same-origin.window.js b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/unload-main-frame-same-origin.window.js new file mode 100644 index 0000000000..5a95455c4c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/unloading-documents/unload/unload-main-frame-same-origin.window.js @@ -0,0 +1,33 @@ +// META: title=Unload runs in main frame when navigating same-origin. +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js + +promise_test(async t => { + const rcHelper = new RemoteContextHelper(); + + const rc1 = await rcHelper.addWindow(); + + t.add_cleanup(() => localStorage.removeItem('unload')); + + // Initialize storage and add "unload" event handler. + await rc1.executeScript(() => { + localStorage.setItem('unload', 'not yet'); + addEventListener('unload', () => { + localStorage.setItem('unload', 'ran'); + }); + }); + + // Navigate away. + const rc2 = await rc1.navigateToNew(); + + // Navigate back. + await rc2.historyBack(); + + // Test that the unload handler wrote to storage. + // Running it in the remote context after going back should ensure that the + // navigation (and therefore the unload handler) has completed. + assert_equals( + await rc1.executeScript(() => localStorage.getItem('unload')), 'ran'); +}); diff --git a/testing/web-platform/tests/html/browsers/history/joint-session-history/joint-session-history-child1.html b/testing/web-platform/tests/html/browsers/history/joint-session-history/joint-session-history-child1.html new file mode 100644 index 0000000000..22bb0b2980 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/joint-session-history/joint-session-history-child1.html @@ -0,0 +1,18 @@ +<body> + <a id="link" href="joint-session-history-child2.html">Child1</a>. + <iframe id="grandchild"></iframe> +</body> +<script> + window.onload = function() { + var link = document.getElementById("link"); + var grandchild = document.getElementById("grandchild"); + var timer = window.setInterval(poll, 100); + function poll() { + if (grandchild.getAttribute("data-grandchild-loaded")) { + window.clearInterval(timer); + link.click(); + } + } + grandchild.src="joint-session-history-grandchild1.html"; + }; +</script> diff --git a/testing/web-platform/tests/html/browsers/history/joint-session-history/joint-session-history-child2.html b/testing/web-platform/tests/html/browsers/history/joint-session-history/joint-session-history-child2.html new file mode 100644 index 0000000000..24b4695166 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/joint-session-history/joint-session-history-child2.html @@ -0,0 +1,5 @@ +<body>Child 2.</body> +<script> + // Servo doesn't support postMessage yet, so we poll on attributes. + window.frameElement.setAttribute("data-child-loaded", true); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/joint-session-history/joint-session-history-filler.html b/testing/web-platform/tests/html/browsers/history/joint-session-history/joint-session-history-filler.html new file mode 100644 index 0000000000..b6d47c310b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/joint-session-history/joint-session-history-filler.html @@ -0,0 +1 @@ +<body>Filler</body> diff --git a/testing/web-platform/tests/html/browsers/history/joint-session-history/joint-session-history-grandchild1.html b/testing/web-platform/tests/html/browsers/history/joint-session-history/joint-session-history-grandchild1.html new file mode 100644 index 0000000000..d05e152425 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/joint-session-history/joint-session-history-grandchild1.html @@ -0,0 +1,8 @@ +<body> + <a id="link" href="joint-session-history-grandchild2.html">Grandchild1</a>. +</body> +<script> + window.onload = function() { + document.getElementById("link").click(); + } +</script> diff --git a/testing/web-platform/tests/html/browsers/history/joint-session-history/joint-session-history-grandchild2.html b/testing/web-platform/tests/html/browsers/history/joint-session-history/joint-session-history-grandchild2.html new file mode 100644 index 0000000000..b5c81e1fca --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/joint-session-history/joint-session-history-grandchild2.html @@ -0,0 +1,5 @@ +<body>Grandchild2.</body> +<script> + // Servo doesn't support postMessage yet, so we poll on attributes. + window.frameElement.setAttribute("data-grandchild-loaded", true); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/joint-session-history/joint-session-history-iframe-state.html b/testing/web-platform/tests/html/browsers/history/joint-session-history/joint-session-history-iframe-state.html new file mode 100644 index 0000000000..ffa64c0b35 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/joint-session-history/joint-session-history-iframe-state.html @@ -0,0 +1,41 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Joint session history should not override parent's state.</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<iframe id="frame" src="about:blank"></iframe> +<script> +async_test(function(t) { + // Setup. + var initialState = {foo: 'bar'}; + var newStateFromChild = {foo: 'baz'}; + var child = document.getElementById("frame"); + + // Child's initial state should be a default empty state. + assert_equals(child.contentWindow.history.state, null); + + // Perform navigation in the top-level container to set the state. + window.history.pushState(initialState, 'title'); + + // Validate initial state was properly set. + assert_object_equals(window.history.state, initialState); + + child.onload = t.step_func_done(() => { + // Child's initial state should be `null`. + assert_equals(child.contentWindow.history.state, null); + + // Navigate in the child. + child.contentWindow.history.pushState(newStateFromChild, 'title'); + + // Child's state should now equal the new state. + assert_object_equals(child.contentWindow.history.state, newStateFromChild); + // Parent's state should still be preserved, having exactly the same state as it + // had before parent navigated. + assert_object_equals(window.history.state, initialState); + }) + child.src = "joint-session-history-filler.html"; +}); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/history/joint-session-history/joint-session-history-only-fully-active.html b/testing/web-platform/tests/html/browsers/history/joint-session-history/joint-session-history-only-fully-active.html new file mode 100644 index 0000000000..c42d160a21 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/joint-session-history/joint-session-history-only-fully-active.html @@ -0,0 +1,30 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Do only fully active documents count for session history?</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> + <iframe id="child"></iframe> +</body> +<script> + async_test(function(t) { + var old_history_len = window.history.length; + var child = document.getElementById("child"); + var timer = window.setInterval(t.step_func(poll), 100); + function poll() { + if (child.getAttribute("data-child-loaded")) { + // Check to see how many entries have been added to the session history. + // The spec https://html.spec.whatwg.org/multipage/#joint-session-history + // says that only fully active documents are included in the joint session history. + // If only fully active documents count, then the only fully active document + // is the child, with session length 1, so the joint session length change will be 1. + // If all documents count, then the grandchild is reachable via the session history, + // and it has session length 1, so the joint session length change will be 2. + assert_equals(2, window.history.length - old_history_len); + window.clearInterval(timer); + t.done(); + } + } + child.src = "joint-session-history-child1.html"; + }); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/joint-session-history/joint-session-history-remove-iframe.html b/testing/web-platform/tests/html/browsers/history/joint-session-history/joint-session-history-remove-iframe.html new file mode 100644 index 0000000000..ee7aa368af --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/joint-session-history/joint-session-history-remove-iframe.html @@ -0,0 +1,24 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Joint session history length does not include entries from a removed iframe.</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<iframe id="frame" src="about:blank"></iframe> +<script> +async_test(function(t) { + t.step_timeout(() => { + var child = document.getElementById("frame"); + var old_history_len = history.length; + child.onload = () => { + assert_equals(old_history_len + 1, history.length); + document.body.removeChild(document.getElementById("frame")); + assert_equals(old_history_len, history.length); + t.done(); + } + child.src = "joint-session-history-filler.html"; + }, 1000); +}); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/001.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/001.html new file mode 100644 index 0000000000..16cb834c78 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/001.html @@ -0,0 +1,339 @@ +<!doctype html> +<html> + <head> + <title>history.pushState tests</title> + <script type="text/javascript" src="/resources/testharness.js"></script> + <script type="text/javascript" src="/resources/testharnessreport.js"></script> + <script type="text/javascript"> +//does not test for firing of popstate onload, because this was dropped from the specification on 25 March 2011 +//covers history.state after load, in accordance with the specification draft from 25 March 2011 +//history.state before load is tested in 006 and 007 +//does not test for structured cloning of FileList, File or Blob interfaces, as these require manual file selection + +//**This test assumes that assignments to location.hash will be synchronous - this is how all browsers implement it. +//The spec (as of 25 March 2011) disagrees.**// + +var histlength, atstep = 0, lasttimer; +setup({explicit_done:true}); //tests should take under 6 seconds + execution time + +window.onload = function () { + if( location.protocol == 'file:' ) { + document.getElementsByTagName('p')[0].innerHTML = 'ERROR: This test cannot be run from file: (URL resolving will not work). It must be loaded over HTTP.'; + return; + } else if( location.protocol == 'https:' ) { + document.getElementsByTagName('p')[0].innerHTML += '<br>WARNING: Browsers may intentionally fail to update history.length when pages are loaded over HTTPS, as a privacy restriction. If possible, load this page over HTTP.'; + } + //use a timeout, because some browsers intentionally do not add history entries for URL changes in the onload thread + setTimeout(testinit,100); +}; +function testinit() { + atstep = 1; + histlength = history.length; + iframe = document.getElementsByTagName('iframe')[0].src = 'blank2.html'; + //reportload will now be called by the onload handler for the iframe +} +function reportload() { + var iframe = document.getElementsByTagName('iframe')[0], hashchng = false; + var canvassup = false, cloneobj; + + function tests1() { + //Firefox may fail when reloading, because it recovers iframe state, and therefore does not see the need to alter history length + test(function () { assert_equals( history.length, histlength + 1, 'make sure that you loaded the test in a new tab/window' ); }, 'history.length should update when loading pages in an iframe'); + histlength = history.length; + iframe.contentWindow.location.hash = 'test'; //should be synchronous **SEE COMMENT AT TOP OF FILE + test(function () { + assert_equals( history.length, histlength + 1, 'make sure that you loaded the test in a new tab/window' ); + }, 'history.length should update when setting location.hash'); + test(function () { assert_true( !!history.pushState, 'critical test; ignore any failures after this' ); }, 'history.pushState must exist'); //assert_own_property does not allow prototype inheritance + test(function () { assert_true( !!iframe.contentWindow.history.pushState, 'critical test; ignore any failures after this' ); }, 'history.pushState must exist within iframes'); + test(function () { + assert_equals( iframe.contentWindow.history.state, null ); + }, 'initial history.state should be null'); + test(function () { + histlength = history.length; + iframe.contentWindow.history.pushState('',''); + assert_equals( history.length, histlength + 1 ); + }, 'history.length should update when pushing a state'); + test(function () { + assert_equals( iframe.contentWindow.history.state, '' ); + }, 'history.state should update after a state is pushed'); + histlength = history.length; + iframe.contentWindow.addEventListener("popstate", tests2, {once: true}); + history.back(); + } + function tests2() { + test(function () { + assert_equals( history.length, histlength ); + }, 'history.length should not decrease after going back'); + test(function () { + assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), 'test' ); + }, 'traversing history must traverse pushed states'); + iframe.contentWindow.addEventListener("hashchange", tests3, {once: true}); + history.go(-1); + } + function tests3() { + test(function () { + assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), '', '(this could cause other failures later on)' ); + }, 'traversing history must also traverse hash changes'); + + iframe.contentWindow.addEventListener("hashchange", tests4, {once: true}); + //Safari 5.0.3 fails here - it navigates *this* document to the *iframe's* location, instead of just navigating the iframe + history.go(2); + } + async function tests4() { + test(function () { + //Firefox 4 beta 11 has a messed up error object, which does not have the right error type or .SECURITY_ERR property + assert_throws_dom('SECURITY_ERR',function () { history.pushState('','','//exa mple'); }); + }, 'pushState must not be allowed to create invalid URLs'); + test(function () { + assert_throws_dom('SECURITY_ERR',function () { history.pushState('','','http://www.example.com/'); }); + }, 'pushState must not be allowed to create cross-origin URLs'); + test(function () { + assert_throws_dom('SECURITY_ERR',function () { history.pushState('','','about:blank'); }); + }, 'pushState must not be allowed to create cross-origin URLs (about:blank)'); + test(function () { + assert_throws_dom('SECURITY_ERR',function () { history.pushState('','','data:text/html,'); }); + }, 'pushState must not be allowed to create cross-origin URLs (data:URI)'); + test(function () { + assert_throws_dom('SECURITY_ERR', iframe.contentWindow.DOMException, function () { iframe.contentWindow.history.pushState('','','http://www.example.com/'); }); + }, 'security errors are expected to be thrown in the context of the document that owns the history object'); + let hashchange = + new Promise(function (resolve) { + iframe.contentWindow.addEventListener("hashchange", resolve, {once: true}); + }); + + test(function () { + iframe.contentWindow.location.hash = 'test2'; + assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), 'test2', 'location.hash did not change when told to' ); + }, 'location.hash must be allowed to change (part 1)'); + await hashchange; + history.go(-1); + iframe.contentWindow.addEventListener("hashchange", tests5, {once: true}); + } + function tests5() { + test(function () { + assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), 'test', 'location.hash did not change when going back' ); + }, 'location.hash must be allowed to change (part 2)'); + test(function () { + iframe.contentWindow.history.pushState('',''); + assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), 'test', 'location.hash changed when an unrelated state was pushed' ); + }, 'pushState must not alter location.hash when no URL is provided'); + history.go(1); //should do nothing, since the pushState should have removed the forward history + setTimeout(tests6,50); //.go is queued to end of thread + } + function tests6() { + test(function () { + assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), 'test' ); + }, 'pushState must remove all history after the current state'); + test(function () { + iframe.contentWindow.history.pushState('','','#test3'); + assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), 'test3' ); + }, 'pushState must be able to set location.hash'); + //begin setup for "remove any tasks queued by the history traversal task source" + iframe.contentWindow.location.hash = '#test4'; + iframe.contentWindow.history.go(-1); //must be queued + try { + //must remove the queued navigation in the same browsing context + iframe.contentWindow.history.pushState('',''); + } catch(unsuperr) {} + //allow the browser to mistakenly run the .go if it is going to + //do not put two .go commands in the same thread, in case the browser mistakenly calculates the history position when + //calling .go instead of when executing the traversal task - that could give a false PASS in the next test otherwise + setTimeout(tests7,50); + } + function tests7() { + iframe.contentWindow.history.go(-1); //must be queued, but should not be removed this time + iframe.contentWindow.addEventListener("popstate", tests8, {once: true}); + } + function tests8() { + test(function () { + assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), 'test4' ); + }, 'pushState must remove any tasks queued by the history traversal task source'); + //end "remove any tasks queued by the history traversal task source" + window.addEventListener('hashchange',function () { hashchng = true; },false); + try { + //push a state that changes the hash + iframe.contentWindow.history.pushState('','',iframe.contentWindow.location.pathname+'#test5'); + } catch(unsuperr) {} + setTimeout(tests9,50); //allow the hashchange event to process, if the browser has mistakenly fired it + } + function tests9() { + test(function () { + assert_false( hashchng ); + }, 'pushState must not fire hashchange events'); + test(function () { + iframe.contentWindow.history.pushState('','','/testing_ignore_me_404'); + assert_equals( iframe.contentWindow.location.pathname, '/testing_ignore_me_404' ); + }, 'pushState must be able to set location.pathname'); + test(function () { + var newURL = location.href.replace(/\/[^\/]*$/)+'/testing_ignore_me_404/'; + iframe.contentWindow.history.pushState('','',newURL); + assert_equals( iframe.contentWindow.location.href, newURL ); + }, 'pushState must be able to set absolute URLs to the same host'); + test(function () { + assert_throws_dom( 'DATA_CLONE_ERR', function () { + history.pushState({dummy:function () {}},''); + } ); + }, 'pushState must not be able to use a function as data'); + test(function () { + assert_throws_dom( 'DATA_CLONE_ERR', function () { + history.pushState({dummy:window},''); + } ); + }, 'pushState must not be able to use a DOM node as data'); + test(function () { + try { a.b = c; } catch(errdata) { + history.pushState({dummy:errdata},''); + assert_equals(ReferenceError.prototype, Object.getPrototypeOf(history.state.dummy)); + } + }, 'pushState must be able to use an error object as data'); + test(function () { + assert_throws_dom('DATA_CLONE_ERR', iframe.contentWindow.DOMException, function () { + iframe.contentWindow.history.pushState(document,''); + }); + }, 'security errors are expected to be thrown in the context of the document that owns the history object (2)'); + cloneobj = { + nulldata: null, + udefdata: window.undefined, + booldata: true, + numdata: 1, + strdata: 'string data', + boolobj: new Boolean(true), + numobj: new Number(1), + strobj: new String('string data'), + datedata: new Date(), + regdata: /a/g, + arrdata: [1] + }; + cloneobj.regdata.lastIndex = 1; + cloneobj.looped = cloneobj; + //test the ImageData type, if the browser supports it + var canvas = document.createElement('canvas'); + if( canvas.getContext && ( canvas = canvas.getContext('2d') ) && canvas.createImageData ) { + canvassup = true; + cloneobj.imgdata = canvas.createImageData(1,1); + } + test(function () { + try { + iframe.contentWindow.history.pushState(cloneobj,'new title'); + } catch(e) { + cloneobj.looped = null; + //try again because this object is needed for future tests + iframe.contentWindow.history.pushState(cloneobj,'new title'); + //rethrow so the browser gets a FAIL for not coping with the circular reference; "internal structured cloning algorithm" step 1 + throw(e); + } + }, 'pushState must be able to make structured clones of complex objects'); + test(function () { + assert_equals( iframe.contentWindow.history.state && iframe.contentWindow.history.state.strdata, 'string data' ); + }, 'history.state should also reference a clone of the original object'); + test(function () { + assert_not_equals( cloneobj, iframe.contentWindow.history.state ); + }, 'history.state should be a clone of the original object, not a reference to it'); + /* + behaviour is not defined per spec, and no known implementations do this + test(function () { + assert_equals( iframe.contentDocument.title, 'new title', 'not required for specification conformance' ); + }, 'pushState MIGHT set the document title'); + */ + history.go(-1); + iframe.contentWindow.addEventListener("popstate", tests10, {once: true}); + } + function tests10() { + var eventtime = setTimeout(function () { tests11(false); },500); //should be cleared by the event handler long before it has a chance to fire + iframe.contentWindow.addEventListener('popstate',function (e) { clearTimeout(eventtime); tests11(true,e); },false); + history.forward(); + } + function tests11(hasFired,ev) { + test(function () { + assert_true( hasFired ); + }, 'popstate event should fire when navigation occurs'); + test(function () { + assert_true( !!ev && typeof(ev.state) != 'undefined', 'state information was not passed' ); + assert_true( !!ev.state, 'state information does not contain the expected value - browser is probably stuck in the wrong history position' ); + assert_equals( ev.state.nulldata, null, 'state null data was not correct' ); + assert_equals( ev.state.udefdata, window.undefined, 'state undefined data was not correct' ); + assert_true( ev.state.booldata, 'state boolean data was not correct' ); + assert_equals( ev.state.numdata, 1, 'state numeric data was not correct' ); + assert_equals( ev.state.strdata, 'string data', 'state string data was not correct' ); + assert_true( !!ev.state.datedata.getTime, 'state date data was not correct' ); + assert_own_property( ev.state, 'regdata', 'state regex data was not correct' ); + assert_equals( ev.state.regdata.source, 'a', 'state regex pattern data was not correct' ); + assert_true( ev.state.regdata.global, 'state regex flag data was not correct' ); + assert_equals( ev.state.regdata.lastIndex, 0, 'state regex lastIndex data was not correct' ); + assert_equals( ev.state.arrdata.length, 1, 'state array data was not correct' ); + assert_true( ev.state.boolobj.valueOf(), 'state boolean data was not correct' ); + assert_equals( ev.state.numobj.valueOf(), 1, 'state numeric data was not correct' ); + assert_equals( ev.state.strobj.valueOf(), 'string data', 'state string data was not correct' ); + if( canvassup ) { + assert_equals( ev.state.imgdata.width, 1, 'state ImageData was not correct' ); + } + }, 'popstate event should pass the state data'); + test(function () { + assert_equals( ev.state.looped, ev.state ); + }, 'state data should cope with circular object references'); + test(function () { + assert_not_equals( cloneobj, ev.state ); + }, 'state data should be a clone of the original object, not a reference to it'); + test(function () { + assert_equals( iframe.contentWindow.history.state && iframe.contentWindow.history.state.strdata, 'string data' ); + }, 'history.state should also reference a clone of the original object (2)'); + test(function () { + assert_not_equals( cloneobj, iframe.contentWindow.history.state ); + }, 'history.state should be a clone of the original object, not a reference to it (2)'); + test(function () { + assert_equals( iframe.contentWindow.history.state, ev.state ); + }, 'history.state should be identical to the object passed to the event handler unless history.state is updated'); + try { + iframe.contentWindow.persistval = true; + iframe.contentWindow.history.pushState('','', location.href.replace(/\/[^\/]*$/,'/blank3.html') ); + } catch(unsuperr) {} + //it's already cached, so this should be very fast if the browser mistakenly loads it + //it should not need to load at all, since it's just a pushed state + setTimeout(tests12,1000); + } + function tests12() { + test(function () { + assert_true( iframe.contentWindow.persistval && !iframe.contentWindow.forreal ); + }, 'pushState should not actually load the new URL'); + atstep = 3; + iframe.contentWindow.location.reload(); //load the real URL + lasttimer = setTimeout(function () { tests13(false); },3000); //should be cleared by the onload handler long before it has a chance to fire + } + function tests13(passed) { + test(function () { + assert_true( passed, 'expected a load event to fire when reloading the URL from cache, gave up waiting after 3 seconds' ); + }, 'reloading a pushed state should actually load the new URL'); + //try to make browsers behave when reloading so that the correct URL is recovered - does not always work + iframe.contentWindow.location.href = location.href.replace(/\/[^\/]*$/,'/blank.html'); + done(); + } + + if( atstep == 1 ) { + //blank2 has loaded + atstep = 2; + //use a timeout, because some browsers intentionally do not add history entries for URL changes in an onload thread + setTimeout(tests1,100); + } else if( atstep == 3 ) { + //blank3 should now have loaded after the .reload() command + atstep = 4; + clearTimeout(lasttimer); + tests13(true); + } +} + + + + </script> + </head> + <body> + + <noscript><p>Enable JavaScript and reload</p></noscript> + <p>WARNING: This test should always be loaded in a new tab/window, to avoid browsers attempting to recover the state of frames, and history length. Do not reload the test.</p> + <div id="log">Running test...</div> + <p><iframe onload="reportload();" src="blank.html"></iframe></p> + <p><iframe src="blank.html"></iframe></p> + <p><iframe src="blank2.html"></iframe></p> + <p><iframe src="blank3.html"></iframe></p> + + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/002.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/002.html new file mode 100644 index 0000000000..8bce7e72ff --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/002.html @@ -0,0 +1,318 @@ +<!doctype html> +<html> + <head> + <title>history.replaceState tests</title> + <script type="text/javascript" src="/resources/testharness.js"></script> + <script type="text/javascript" src="/resources/testharnessreport.js"></script> + <script type="text/javascript"> +//does not test for firing of popstate onload, because this was dropped from the specification on 25 March 2011 +//covers history.state after load, in accordance with the specification draft from 25 March 2011 +//history.state before load is tested in 006 and 007 +//does not test for structured cloning of FileList, File or Blob interfaces, as these require manual file selection + +//**This test assumes that assignments to location.hash will be synchronous - this is how all browsers implement it. +//The spec (as of 25 March 2011) disagrees. + +var histlength, atstep = 0, lasttimer; +setup({explicit_done:true}); //tests should take under 6 seconds + execution time + +window.onload = function () { + if( location.protocol == 'file:' ) { + document.getElementsByTagName('p')[0].innerHTML = 'ERROR: This test cannot be run from file: (URL resolving will not work). It must be loaded over HTTP.'; + return; + } else if( location.protocol == 'https:' ) { + document.getElementsByTagName('p')[0].innerHTML += '<br>WARNING: Browsers may intentionally fail to update history.length when pages are loaded over HTTPS, as a privacy restriction. If possible, load this page over HTTP.'; + } + //use a timeout, because some browsers intentionally do not add history entries for URL changes in the onload thread + setTimeout(testinit,100); +}; +function testinit() { + atstep = 1; + histlength = history.length; + iframe = document.getElementsByTagName('iframe')[0].src = 'blank2.html'; + //reportload will now be called by the onload handler for the iframe +} +function reportload() { + var iframe = document.getElementsByTagName('iframe')[0], hashchng = false; + var canvassup = false, cloneobj; + + async function tests1() { + test(function () { assert_equals( history.length, histlength + 1, 'make sure that you loaded the test in a new tab/window' ); }, 'history.length should update when loading pages in an iframe'); + histlength = history.length; + let hashchange = new Promise(function(resolve) { + iframe.contentWindow.addEventListener("hashchange", resolve, {once: true}); + }); + iframe.contentWindow.location.hash = 'test'; //should be synchronous **SEE COMMENT AT TOP OF FILE + test(function () { + assert_equals( history.length, histlength + 1, 'make sure that you loaded the test in a new tab/window' ); + }, 'history.length should update when setting location.hash'); + test(function () { assert_true( !!history.replaceState, 'critical test; ignore any failures after this' ); }, 'history.replaceState must exist'); //assert_own_property does not allow prototype inheritance + test(function () { assert_true( !!iframe.contentWindow.history.replaceState, 'critical test; ignore any failures after this' ); }, 'history.replaceState must exist within iframes'); + test(function () { + assert_equals( iframe.contentWindow.history.state, null ); + }, 'initial history.state should be null'); + + await hashchange; + hashchange = new Promise(function(resolve) { + iframe.contentWindow.addEventListener("hashchange", resolve, {once: true}); + }); + iframe.contentWindow.location.hash = 'test2'; + await hashchange; + + iframe.contentWindow.addEventListener("hashchange", tests2, {once: true}); + history.back(); + } + function tests2() { + test(function () { + histlength = history.length; + iframe.contentWindow.history.replaceState('',''); + assert_equals( history.length, histlength ); + }, 'history.length should not update when replacing a state with no URL'); + test(function () { + assert_equals( iframe.contentWindow.history.state, '' ); + }, 'history.state should update after a state is pushed'); + test(function () { + assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), 'test' ); + }, 'hash should not change when replaceState is called without a URL'); + test(function () { + histlength = history.length; + iframe.contentWindow.history.replaceState('','','#test3'); + assert_equals( history.length, histlength ); + }, 'history.length should not update when replacing a state with a URL'); + test(function () { + assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), 'test3' ); + }, 'hash should change when replaceState is called with a URL'); + iframe.contentWindow.addEventListener("hashchange", tests3, {once: true}); + history.go(-1); + } + function tests3() { + test(function () { + assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), '' ); + }, 'replaceState must replace the existing state and not add an extra one'); + iframe.contentWindow.addEventListener("hashchange", tests4, {once: true}); + history.go(2); + } + function tests4() { + test(function () { + assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), 'test2' ); + }, 'replaceState must replace the existing state without altering the forward history'); + test(function () { + assert_throws_dom('SECURITY_ERR',function () { history.replaceState('','','//exa mple'); }); + }, 'replaceState must not be allowed to create invalid URLs'); + test(function () { + assert_throws_dom('SECURITY_ERR',function () { history.replaceState('','','http://www.example.com/'); }); + }, 'replaceState must not be allowed to create cross-origin URLs'); + test(function () { + assert_throws_dom('SECURITY_ERR',function () { history.replaceState('','','about:blank'); }); + }, 'replaceState must not be allowed to create cross-origin URLs (about:blank)'); + test(function () { + assert_throws_dom('SECURITY_ERR',function () { history.replaceState('','','data:text/html,'); }); + }, 'replaceState must not be allowed to create cross-origin URLs (data:URI)'); + test(function () { + assert_throws_dom('SECURITY_ERR',iframe.contentWindow.DOMException,function () { iframe.contentWindow.history.replaceState('','','http://www.example.com/'); }); + }, 'security errors are expected to be thrown in the context of the document that owns the history object'); + test(function () { + //avoids browsers running .go synchronously when only a hash change is involved + iframe.contentWindow.history.replaceState('','','/testing_ignore_me_404#test4'); + assert_equals( iframe.contentWindow.location.pathname, '/testing_ignore_me_404' ); + }, 'replaceState must be able to set location.pathname'); + test(function () { + var newURL = location.href.replace(/\/[^\/]*$/)+'/testing_ignore_me_404/'; + iframe.contentWindow.history.replaceState('','',newURL); + assert_equals( iframe.contentWindow.location.href, newURL ); + }, 'replaceState must be able to set absolute URLs to the same host'); + + //allow the browser to run the .go + iframe.contentWindow.addEventListener("popstate", tests5, {once: true}); + //begin setup for "[must not] remove any tasks queued by the history traversal task source" + iframe.contentWindow.history.go(-1); //must be queued so the next command takes place *beforehand* + try { + //must not remove the queued navigation in the same browsing context + iframe.contentWindow.history.replaceState('','',iframe.contentWindow.location.pathname+'#test5'); + } catch(unsuperr2) {} + } + function tests5() { + test(function () { + assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), 'test3' ); + }, 'replaceState must not remove any tasks queued by the history traversal task source'); + iframe.contentWindow.addEventListener("popstate", tests6, {once: true}); + //Safari 5.0.3 fails here - it navigates *this* document to the *iframe's* location, instead of just navigating the iframe + history.go(1); + } + function tests6() { + test(function () { + assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), 'test5' ); + }, '.go must queue a task with the history traversal task source (run asynchronously)'); + //end "[must not] remove any tasks queued by the history traversal task source" + window.addEventListener('hashchange',function () { hashchng = true; },false); + try { + //push a state that changes the hash + iframe.contentWindow.history.replaceState('','',iframe.contentWindow.location.pathname+'#test6'); + } catch(unsuperr) {} + setTimeout(tests7,50); //allow the hashchange event to process, if the browser has mistakenly fired it + } + function tests7() { + test(function () { + assert_false( hashchng ); + }, 'replaceState must not fire hashchange events'); + test(function () { + assert_throws_dom( 'DATA_CLONE_ERR', function () { + history.replaceState({dummy:function () {}},''); + } ); + }, 'replaceState must not be able to use a function as data'); + test(function () { + assert_throws_dom( 'DATA_CLONE_ERR', function () { + history.replaceState({dummy:window},''); + } ); + }, 'replaceState must not be able to use a DOM node as data'); + test(function () { + try { a.b = c; } catch(errdata) { + history.replaceState({dummy:errdata},''); + assert_equals(ReferenceError.prototype, Object.getPrototypeOf(history.state.dummy)); + } + }, 'replaceState must be able to use an error object as data'); + test(function () { + assert_throws_dom('DATA_CLONE_ERR', iframe.contentWindow.DOMException, function () { + iframe.contentWindow.history.replaceState(document,''); + }); + }, 'security errors are expected to be thrown in the context of the document that owns the history object (2)'); + cloneobj = { + nulldata: null, + udefdata: window.undefined, + booldata: true, + numdata: 1, + strdata: 'string data', + boolobj: new Boolean(true), + numobj: new Number(1), + strobj: new String('string data'), + datedata: new Date(), + regdata: /a/g, + arrdata: [1] + }; + cloneobj.regdata.lastIndex = 1; + cloneobj.looped = cloneobj; + //test the ImageData type, if the browser supports it + var canvas = document.createElement('canvas'); + if( canvas.getContext && ( canvas = canvas.getContext('2d') ) && canvas.createImageData ) { + canvassup = true; + cloneobj.imgdata = canvas.createImageData(1,1); + } + test(function () { + try { + iframe.contentWindow.history.replaceState(cloneobj,'new title'); + } catch(e) { + cloneobj.looped = null; + //try again because this object is needed for future tests + iframe.contentWindow.history.replaceState(cloneobj,'new title'); + //rethrow so the browser gets a FAIL for not coping with the circular reference; "internal structured cloning algorithm" step 1 + throw(e); + } + }, 'replaceState must be able to make structured clones of complex objects'); + test(function () { + assert_equals( iframe.contentWindow.history.state && iframe.contentWindow.history.state.strdata, 'string data' ); + }, 'history.state should also reference a clone of the original object'); + test(function () { + assert_not_equals( cloneobj, iframe.contentWindow.history.state ); + }, 'history.state should be a clone of the original object, not a reference to it'); + iframe.contentWindow.addEventListener("popstate", tests8, {once: true}); + history.go(-1); + } + function tests8() { + var eventtime = setTimeout(function () { tests9(false); },500); //should be cleared by the event handler long before it has a chance to fire + iframe.contentWindow.addEventListener('popstate',function (e) { clearTimeout(eventtime); tests9(true,e); },false); + history.forward(); + } + function tests9(hasFired,ev) { + test(function () { + assert_true( hasFired ); + }, 'popstate event should fire when navigation occurs'); + test(function () { + assert_true( !!ev && typeof(ev.state) != 'undefined', 'state information was not passed' ); + assert_true( !!ev.state, 'state information does not contain the expected value - browser is probably stuck in the wrong history position' ); + assert_equals( ev.state.nulldata, null, 'state null data was not correct' ); + assert_equals( ev.state.udefdata, window.undefined, 'state undefined data was not correct' ); + assert_true( ev.state.booldata, 'state boolean data was not correct' ); + assert_equals( ev.state.numdata, 1, 'state numeric data was not correct' ); + assert_equals( ev.state.strdata, 'string data', 'state string data was not correct' ); + assert_true( !!ev.state.datedata.getTime, 'state date data was not correct' ); + assert_own_property( ev.state, 'regdata', 'state regex data was not correct' ); + assert_equals( ev.state.regdata.source, 'a', 'state regex pattern data was not correct' ); + assert_true( ev.state.regdata.global, 'state regex flag data was not correct' ); + assert_equals( ev.state.regdata.lastIndex, 0, 'state regex lastIndex data was not correct' ); + assert_equals( ev.state.arrdata.length, 1, 'state array data was not correct' ); + assert_true( ev.state.boolobj.valueOf(), 'state boolean data was not correct' ); + assert_equals( ev.state.numobj.valueOf(), 1, 'state numeric data was not correct' ); + assert_equals( ev.state.strobj.valueOf(), 'string data', 'state string data was not correct' ); + if( canvassup ) { + assert_equals( ev.state.imgdata.width, 1, 'state ImageData was not correct' ); + } + }, 'popstate event should pass the state data'); + test(function () { + assert_equals( ev.state.looped, ev.state ); + }, 'state data should cope with circular object references'); + test(function () { + assert_not_equals( cloneobj, ev.state ); + }, 'state data should be a clone of the original object, not a reference to it'); + test(function () { + assert_equals( iframe.contentWindow.history.state && iframe.contentWindow.history.state.strdata, 'string data' ); + }, 'history.state should also reference a clone of the original object (2)'); + test(function () { + assert_not_equals( cloneobj, iframe.contentWindow.history.state ); + }, 'history.state should be a clone of the original object, not a reference to it (2)'); + test(function () { + assert_equals( iframe.contentWindow.history.state, ev.state ); + }, 'history.state should be identical to the object passed to the event handler unless history.state is updated'); + try { + iframe.contentWindow.persistval = true; + iframe.contentWindow.history.replaceState('','', location.href.replace(/\/[^\/]*$/,'/blank3.html') ); + } catch(unsuperr) {} + //it's already cached, so this should be very fast if the browser mistakenly loads it + //it should not need to load at all, since it's just a pushed state + setTimeout(tests10,1000); + } + function tests10() { + test(function () { + assert_true( iframe.contentWindow.persistval && !iframe.contentWindow.forreal ); + }, 'replaceState should not actually load the new URL'); + atstep = 3; + iframe.contentWindow.location.reload(); //load the real URL + lasttimer = setTimeout(function () { tests11(false); },3000); //should be cleared by the onload handler long before it has a chance to fire + } + function tests11(passed) { + test(function () { + assert_true( passed, 'expected a load event to fire when reloading the URL from cache, gave up waiting after 3 seconds' ); + }, 'reloading a replaced state should actually load the new URL'); + //try to make browsers behave when reloading so that the correct URL is recovered - does not always work + iframe.contentWindow.location.href = location.href.replace(/\/[^\/]*$/,'/blank.html'); + done(); + } + + if( atstep == 1 ) { + //blank2 has loaded + atstep = 2; + //use a timeout, because some browsers intentionally do not add history entries for URL changes in an onload thread + setTimeout(tests1,100); + } else if( atstep == 3 ) { + //blank3 should now have loaded after the .reload() command + atstep = 4; + clearTimeout(lasttimer); + tests11(true); + } +} + + + + </script> + </head> + <body> + + <noscript><p>Enable JavaScript and reload</p></noscript> + <p>WARNING: This test should always be loaded in a new tab/window, to avoid browsers attempting to recover the state of frames, and history length. Do not reload the test.</p> + <div id="log">Running test...</div> + <p><iframe onload="reportload();" src="blank.html"></iframe></p> + <p><iframe src="blank.html"></iframe></p> + <p><iframe src="blank2.html"></iframe></p> + <p><iframe src="blank3.html"></iframe></p> + + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/004.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/004.html new file mode 100644 index 0000000000..e69889724f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/004.html @@ -0,0 +1,62 @@ +<!doctype html> +<html> + <head> + <title>Final history position for history.go should be calculated when executing the task</title> + <script type="text/javascript" src="/resources/testharness.js"></script> + <script type="text/javascript" src="/resources/testharnessreport.js"></script> + <script type="text/javascript"> +setup({explicit_done:true}); +window.onload = function () { + var hashcount = 0; + if( location.hash && location.hash != '#' ) { + location.href = location.href.replace(/#.*$/,''); + return; + } + setTimeout(add1,100); + function add1() { + location.hash = '#foo'; + setTimeout(add2,100); + } + function add2() { + location.hash = '#bar'; + setTimeout(add3,100); + } + function add3() { + location.hash = '#baz'; + setTimeout(dojumps,100); + } + function dojumps() { + window.onhashchange = function () { + hashcount++; + }; + history.go(-2); + test(function () { + //many browsers special-case jumps that only imply hash changes and will do them synchronously - the spec does allow this + assert_equals( hashcount, 0, 'hashchange fired even though the location should not have changed' ); + assert_equals( location.hash.replace(/^#/,''), 'baz', 'the browser navigated synchronously' ); + }, '.go commands should be queued until the thread has ended'); + history.go(-1); + setTimeout(checkjumps,100); + } + function checkjumps() { + test(function () { + assert_true( !!hashcount, 'this testcase requires haschange support; the test cannot be used in this browser' ); + }, 'browser needs to support hashchange events for this testcase'); + test(function () { + assert_equals( hashcount, 2, 'the wrong number of queued commands were executed' ); + }, 'queued .go commands should all be executed when the queue is processed'); + test(function () { + assert_equals( location.hash.replace(/^#/,''), '' ); + }, 'history position should be calculated when executing, not when calling the .go command'); + done(); + } +}; + </script> + </head> + <body> + + <noscript><p>Enable JavaScript and reload</p></noscript> + <div id="log"></div> + + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/005.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/005.html new file mode 100644 index 0000000000..2152e85a3e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/005.html @@ -0,0 +1,47 @@ +<!doctype html> +<html> + <head> + <title>Popstate event listener registration</title> + <script type="text/javascript" src="/resources/testharness.js"></script> + <script type="text/javascript" src="/resources/testharnessreport.js"></script> + <script type="text/javascript"> + +//this test checks that onpopstate works on the body element + +var readyForPop = false, bodypop = false, inlinepop = false; +setup({explicit_done:true}); + +//use a timeout to avoid "popstate fires onload" from setting the variables too early +setTimeout(step1,1000); +function step1() { + readyForPop = true; + test(function () { + history.pushState('',''); + history.pushState('',''); + }, 'history.pushState support is needed for this testcase'); + history.go(-1); + setTimeout(step2,50); //.go is queued to end of thread +} +function step2() { + test(function () { + assert_true( bodypop ); + }, '<body onpopstate="..."> should register a listener for the popstate event'); + window.onpopstate = function () { inlinepop = true; }; + history.go(-1); + setTimeout(step3,50); //.go is queued to end of thread +} +function step3() { + test(function () { + assert_true( inlinepop ); + }, 'window.onpopstate should register a listener for the popstate event'); + done(); +} + </script> + </head> + <body onpopstate="if( readyForPop ) { bodypop = true; }"> + + <noscript><p>Enable JavaScript and reload</p></noscript> + <div id="log"></div> + + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/006.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/006.html new file mode 100644 index 0000000000..442b6f8f1e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/006.html @@ -0,0 +1,53 @@ +<!doctype html> +<html> + <head> + <title>Firing popstate after onload, even if there is no pushed/replaced state</title> + <script type="text/javascript" src="/resources/testharness.js"></script> + <script type="text/javascript" src="/resources/testharnessreport.js"></script> + <script type="text/javascript"> + +//spec (25 March 2011 draft) states that popstate must not fire after onload unless there is a pushed/replaced state that is navigated +var popfired = false; +setup({explicit_done:true}); +window.addEventListener('popstate',function (e) { popfired = true; },false); +test(function () { + assert_equals( history.state, null ); +}, 'history.state should initially be null'); +window.onload = function () { + test(function () { + assert_false( popfired ); + }, 'popstate event should not fire before onload fires'); + test(function () { + assert_equals( history.state, null ); + }, 'history.state should still be null onload'); + popfired = false; + setTimeout(function () { + test(function () { + assert_false( popfired ); + }, 'popstate event should not fire after onload fires'); + test(function () { + assert_equals( history.state, null ); + }, 'history.state should still be null after onload'); + test(function () { + var failed = false, realstate = history.state; + try { + history.state = ''; + } catch(e) { + failed = e; + } + assert_equals(history.state,realstate,'property was read/write'); + assert_false(failed); + }, 'writing to history.state should be silently ignored and not throw an error'); + done(); + },100); +}; + + </script> + </head> + <body> + + <noscript><p>Enable JavaScript and reload</p></noscript> + <div id="log"></div> + + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/007.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/007.html new file mode 100644 index 0000000000..decb197624 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/007.html @@ -0,0 +1,66 @@ +<!doctype html> +<html> + <head> + <title>Firing popstate after onload with pushed state</title> + <meta name=timeout content=long> + <script type="text/javascript" src="/resources/testharness.js"></script> + <script type="text/javascript" src="/resources/testharnessreport.js"></script> + </head> + <body> + + <noscript><p>Enable JavaScript and reload</p></noscript> + <div id="log">It looks like the browser stopped loading the page when encountering a .go(-1) command pointing to a pushed state. This will break the tests.</div> + <script type="text/javascript"> + +//spec (25 March 2011 draft) states that popstate must fire before onload if there is a pushed/replaced state that is navigated +var popfired = false; +setup({explicit_done:true}); +test(function () { + assert_equals( history.state, null ); +}, 'history.state should initially be null'); +window.addEventListener('popstate',function (e) { popfired = e.state; },false); +test(function () { + history.pushState('state1',''); + history.pushState('state2',''); +}, 'history.pushState support is needed for this testcase'); +test(function () { + assert_equals( history.state, 'state2' ); +}, 'history.state should reflect pushed state'); +if( history.pushState ) { history.go(-1); } +window.onload = function () { + test(function () { + assert_true( !!popfired ); + }, 'popstate event should fire before onload fires'); + test(function () { + assert_equals( popfired, 'state1' ); + }, 'the correct state should be restored when navigating during initial load'); + test(function () { + assert_equals( history.state, 'state1' ); + }, 'history.state should reflect the navigated state onload'); + popfired = false; + setTimeout(function () { + test(function () { + assert_false( !!popfired ); + }, 'popstate event should not fire after onload fires'); + test(function () { + assert_equals( history.state, 'state1' ); + }, 'history.state should reflect the navigated state after onload'); + done(); + if( history.pushState ) { history.go(-1); } //go back to the start to avoid state recovery when reloading + },100); +}; + + </script> + + <!-- + Reuse an existing server side script to slow down page load so that + history.go(-1); above gets run before load event fires. + --> + <script> + // define TEST_DELAY so that executing delay.py doesn't warn about use + // of undefined variable. + var TEST_DELAY; + </script> + <script src="/xhr/resources/delay.py?ms=2500"></script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/008.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/008.html new file mode 100644 index 0000000000..c8071e3156 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/008.html @@ -0,0 +1,40 @@ +<!doctype html> +<html> + +<!-- configure this test below to point to the script --> + + <head> + <title>history.pushState/replaceState resolving</title> + <script type="text/javascript" src="/resources/testharness.js"></script> + <script type="text/javascript" src="/resources/testharnessreport.js"></script> + </head> + <body> + + <p></p> + <noscript><p>Enable JavaScript and reload</p></noscript> + <div id="log"></div> + <script type="text/javascript"> + +/* +Location of the script (which must be hosted on a separate domain from this test) containing the test code: +var beforehref = location.href; +test(function () { + history.pushState('','','/testing_ignore_me_404'); + assert_equals(location.href,beforehref.replace(/^(\w*:\/\/[^\/]*\/)[\w\W]*$/,'$1testing_ignore_me_404')); +}, 'history.pushState URL resolving should be done relative to the document, not the script'); +test(function () { + history.replaceState('','','/testing_ignore_me_404_2'); + assert_equals(location.href,beforehref.replace(/^(\w*:\/\/[^\/]*\/)[\w\W]*$/,'$1testing_ignore_me_404_2')); +}, 'history.replaceState URL resolving should be done relative to the document, not the script'); +*/ +var scriptlocation = 'http://www.' + location.host + location.pathname.split("/").slice(0,-1).join("/") + "/008.js"; + +if( location.protocol == 'file:' ) { + document.getElementsByTagName('p')[0].innerHTML = 'ERROR: This test cannot be run from file: (URL resolving will not work). It must be loaded over HTTP.'; +} else { + document.write('<script type="text\/javascript" src="'+scriptlocation+'"><\/script>'); +} + </script> + + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/008.js b/testing/web-platform/tests/html/browsers/history/the-history-interface/008.js new file mode 100644 index 0000000000..96a1fe5d4a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/008.js @@ -0,0 +1,11 @@ +var beforehref = location.href; + +test(function () { + history.pushState('','','/testing_ignore_me_404'); + assert_equals(location.href,beforehref.replace(/^(\w*:\/\/[^\/]*\/)[\w\W]*$/,'$1testing_ignore_me_404')); +}, 'history.pushState URL resolving should be done relative to the document, not the script'); + +test(function () { + history.replaceState('','','/testing_ignore_me_404_2'); + assert_equals(location.href,beforehref.replace(/^(\w*:\/\/[^\/]*\/)[\w\W]*$/,'$1testing_ignore_me_404_2')); +}, 'history.replaceState URL resolving should be done relative to the document, not the script'); diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/009-1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/009-1.html new file mode 100644 index 0000000000..00b72e8ec6 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/009-1.html @@ -0,0 +1,20 @@ +<!doctype html> +<html> + <head> + <title>history.pushState/replaceState and referer headers</title> + </head> + <body> + + <noscript><p>Enable JavaScript and reload</p></noscript> + <div id="log"></div> + <script type="text/javascript"> +window.onload = function () { + setTimeout(function () { + try { history.pushState('','','009-2.html?1234'); } catch(e) {} + location.href = '009-3.html?pipe=sub'; + },10); +}; + </script> + + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/009-3.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/009-3.html new file mode 100644 index 0000000000..e58b8fa5e7 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/009-3.html @@ -0,0 +1,28 @@ +<!doctype html> +<html> + <head> + <title>history.pushState/replaceState and referer headers</title> + </head> + <body> + + <noscript><p>Enable JavaScript and reload</p></noscript> + <div id="log"></div> + <script type="text/javascript"> +var httpReferer = "{{headers[referer]}}"; +var lastUrl = location.href.replace(/\/[^\/]*$/,'\/009-2.html?1234'); +parent.test(function () { + parent.assert_equals( httpReferer, lastUrl ); +}, 'HTTP Referer should use the pushed state'); +parent.test(function () { + parent.assert_equals( document.referrer, lastUrl ); +}, 'document.referrer should use the pushed state'); +window.onload = function () { + setTimeout(function () { + try { history.pushState('','','009-4.html?2345'); } catch(e) {} + location.href = '009-5.html?pipe=sub'; + },10); +}; + </script> + + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/009-5.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/009-5.html new file mode 100644 index 0000000000..068a089af4 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/009-5.html @@ -0,0 +1,23 @@ +<!doctype html> +<html> + <head> + <title>history.pushState/replaceState and referer headers</title> + </head> + <body> + + <noscript><p>Enable JavaScript and reload</p></noscript> + <div id="log"></div> + <script type="text/javascript"> +var httpReferer = unescape("{{headers[referer]}}"); +var lastUrl = location.href.replace(/\/[^\/]*$/,'\/009-4.html?2345'); +parent.test(function () { + parent.assert_equals( httpReferer, lastUrl ); +}, 'HTTP Referer should use the replaced state'); +parent.test(function () { + parent.assert_equals( document.referrer, lastUrl ); +}, 'document.referrer should use the replaced state'); +parent.done(); + </script> + + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/009.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/009.html new file mode 100644 index 0000000000..c1ae0bbe03 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/009.html @@ -0,0 +1,22 @@ +<!doctype html> +<html> + <head> + <title>history.pushState/replaceState and referer headers</title> + <script type="text/javascript" src="/resources/testharness.js"></script> + <script type="text/javascript" src="/resources/testharnessreport.js"></script> + </head> + <body> + + <noscript><p>Enable JavaScript and reload</p></noscript> + <div id="log"></div> + <script type="text/javascript"> +setup({explicit_done:true}); +var iframe = document.createElement('iframe'); +window.onload = function () { + iframe.setAttribute('src','009-1.html'); + document.body.appendChild(iframe) +}; + </script> + + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/010-1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/010-1.html new file mode 100644 index 0000000000..683397745c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/010-1.html @@ -0,0 +1,16 @@ +<!doctype html> +<html> + <head> + <title>history.pushState/replaceState and referer headers (before onload)</title> + </head> + <body> + + <noscript><p>Enable JavaScript and reload</p></noscript> + <div id="log"></div> + <script type="text/javascript"> +try { history.pushState('','','010-2.html?1234'); } catch(e) {} +location.href = '010-3.html?pipe=sub'; + </script> + + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/010-3.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/010-3.html new file mode 100644 index 0000000000..b80f56c3dd --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/010-3.html @@ -0,0 +1,24 @@ +<!doctype html> +<html> + <head> + <title>history.pushState/replaceState and referer headers (before onload)</title> + </head> + <body> + + <noscript><p>Enable JavaScript and reload</p></noscript> + <div id="log"></div> + <script type="text/javascript"> + var httpReferer = "{{headers[referer]}}"; +var lastUrl = location.href.replace(/\/[^\/]*$/,'\/010-2.html?1234'); +parent.test(function () { + parent.assert_equals( httpReferer, lastUrl ); +}, 'HTTP Referer should use the pushed state (before onload)'); +parent.test(function () { + parent.assert_equals( document.referrer, lastUrl ); +}, 'document.referrer should use the pushed state (before onload)'); +try { history.pushState('','','010-4.html?2345'); } catch(e) {} +location.href = '010-5.html?pipe=sub'; + </script> + + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/010-5.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/010-5.html new file mode 100644 index 0000000000..d150449eb2 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/010-5.html @@ -0,0 +1,23 @@ +<!doctype html> +<html> + <head> + <title>history.pushState/replaceState and referer headers (before onload)</title> + </head> + <body> + + <noscript><p>Enable JavaScript and reload</p></noscript> + <div id="log"></div> + <script type="text/javascript"> +var httpReferer = "{{headers[referer]}}"; +var lastUrl = location.href.replace(/\/[^\/]*$/,'\/010-4.html?2345'); +parent.test(function () { + parent.assert_equals( httpReferer, lastUrl ); +}, 'HTTP Referer should use the replaced state (before onload)'); +parent.test(function () { + parent.assert_equals( document.referrer, lastUrl ); +}, 'document.referrer should use the replaced state (before onload)'); +parent.done(); + </script> + + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/010.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/010.html new file mode 100644 index 0000000000..ca109a744b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/010.html @@ -0,0 +1,22 @@ +<!doctype html> +<html> + <head> + <title>history.pushState/replaceState and referer headers (before onload)</title> + <script type="text/javascript" src="/resources/testharness.js"></script> + <script type="text/javascript" src="/resources/testharnessreport.js"></script> + </head> + <body> + + <noscript><p>Enable JavaScript and reload</p></noscript> + <div id="log"></div> + <script type="text/javascript"> +setup({explicit_done:true}); +var iframe = document.createElement('iframe'); +window.onload = function () { + iframe.setAttribute('src','010-1.html'); + document.body.appendChild(iframe) +}; + </script> + + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/011.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/011.html new file mode 100644 index 0000000000..4043aff7fd --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/011.html @@ -0,0 +1,32 @@ +<!doctype html> +<html> + <head> + <title>history.pushState before onload</title> + <script type="text/javascript" src="/resources/testharness.js"></script> + <script type="text/javascript" src="/resources/testharnessreport.js"></script> + </head> + <body> + + <noscript><p>Enable JavaScript and reload</p></noscript> + <div id="log"></div> + <script type="text/javascript"> +var newUrl = location.href.replace(/\/[^\/]*$/,'\/011-1.html'); +setup({explicit_done:true}); +test(function () { + history.pushState('','','011-1.html'); +}, 'pushState should be able to set the location state'); +test(function () { + assert_equals( location.href, newUrl ); +}, 'pushed location should be reflected immediately'); +window.onload = function () { + setTimeout(function () { + test(function () { + assert_equals( location.href, newUrl ); + }, 'pushed location should be retained after the page has loaded'); + done(); + },10); +}; + </script> + + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/012.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/012.html new file mode 100644 index 0000000000..f5e6251671 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/012.html @@ -0,0 +1,32 @@ +<!doctype html> +<html> + <head> + <title>history.replaceState before onload</title> + <script type="text/javascript" src="/resources/testharness.js"></script> + <script type="text/javascript" src="/resources/testharnessreport.js"></script> + </head> + <body> + + <noscript><p>Enable JavaScript and reload</p></noscript> + <div id="log"></div> + <script type="text/javascript"> +var newUrl = location.href.replace(/\/[^\/]*$/,'\/011-1.html'); +setup({explicit_done:true}); +test(function () { + history.replaceState('','','011-1.html'); +}, 'replaceState should be able to set the location state'); +test(function () { + assert_equals( location.href, newUrl ); +}, 'replaced location should be reflected immediately'); +window.onload = function () { + setTimeout(function () { + test(function () { + assert_equals( location.href, newUrl ); + }, 'replaced location should be retained after the page has loaded'); + done(); + },10); +}; + </script> + + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/blank-new.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/blank-new.html new file mode 100644 index 0000000000..2a545af0ed --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/blank-new.html @@ -0,0 +1,5 @@ +<!DOCTYPE html> +<head> + <title>New page</title> +</head> +<body>This is a new page.</body> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/blank-old.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/blank-old.html new file mode 100644 index 0000000000..a77c00fcc6 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/blank-old.html @@ -0,0 +1,5 @@ +<!DOCTYPE html> +<head> + <title>Old page</title> +</head> +<body>This is an old page.</body> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/blank.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/blank.html new file mode 100644 index 0000000000..89c8724c09 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/blank.html @@ -0,0 +1,8 @@ +<!doctype html> +<html> + <head> + <title>Dummy page 1</title> + </head> + <body> + </body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/blank2.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/blank2.html new file mode 100644 index 0000000000..f79982e328 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/blank2.html @@ -0,0 +1,13 @@ +<!doctype html> +<html> + <head> + <title>Dummy page 2</title> + </head> + <body> + <script type="text/javascript"> +if( self == top || !parent.reportload ) { + document.write("<p>FAIL. Browser got confused when navigating forwards, and navigated the whole window to the iframe's location, instead of just navigating the iframe. It is not possible to run the testsuite.<\/p>"); +} + </script> + </body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/blank3.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/blank3.html new file mode 100644 index 0000000000..2a8989f272 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/blank3.html @@ -0,0 +1,11 @@ +<!doctype html> +<html> + <head> + <title>Dummy page 3</title> + <script type="text/javascript"> +var forreal = true; + </script> + </head> + <body> + </body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/combination_history_001.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/combination_history_001.html new file mode 100644 index 0000000000..21ba22f6fe --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/combination_history_001.html @@ -0,0 +1,20 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>combination_history_001(Combine pushState and replaceSate methods.)</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <script> + test(function () { + window.history.pushState(1, document.title, '?x=1'); + assert_equals(history.state, 1, "first"); + + window.history.replaceState(2, document.title, '?x=1'); + assert_equals(history.state, 2, "second") + }, "Combine pushState and replaceSate methods"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/combination_history_002.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/combination_history_002.html new file mode 100644 index 0000000000..29e82f51be --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/combination_history_002.html @@ -0,0 +1,22 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>combination_history_002(After calling of pushState method, check length.)</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <script> + test(function () { + var first; + var second; + first = window.history.length; + window.history.pushState(1, document.title, '?x=1'); + second = window.history.length; + + assert_equals(second - first, 1, "second - first"); + }, "After calling of pushState method, check length"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/combination_history_003.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/combination_history_003.html new file mode 100644 index 0000000000..7467d9b294 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/combination_history_003.html @@ -0,0 +1,26 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>combination_history_003(After calling of pushState and replaceState methods, check length.)</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <script> + test(function () { + var first; + var second; + var third; + first = window.history.length; + window.history.pushState(1, document.title, '?x=1'); + second = window.history.length; + window.history.replaceState(2, document.title, '?x=2'); + third = window.history.length; + + assert_equals(second - first, 1, "second - first"); + assert_equals(third, second, "third"); + }, "After calling of pushState and replaceState methods, check length"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/combination_history_004.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/combination_history_004.html new file mode 100644 index 0000000000..4e38b56205 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/combination_history_004.html @@ -0,0 +1,29 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>combination_history_004(After calling of back method, check length.)</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <script> + var t = async_test("After calling of back method, check length"); + + var last; + t.step(function () { + window.history.pushState(1, document.title, '?x=1'); + window.history.pushState(2, document.title, '?x=2'); + last = window.history.length; + + window.history.back(); + }); + + window.addEventListener('popstate', t.step_func(function(e) { + assert_equals(e.state, 1, "state"); + assert_equals(window.history.length, last, "last"); + t.done(); + }), false); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/combination_history_005.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/combination_history_005.html new file mode 100644 index 0000000000..4487678015 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/combination_history_005.html @@ -0,0 +1,34 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>combination_history_005(After calling of forward method, check length.)</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <script> + var t = async_test("After calling of forward method, check length"); + + var last; + var fired = false; + t.step(function () { + window.history.pushState(1, document.title, '?x=1'); + window.history.pushState(2, document.title, '?x=2'); + last = window.history.length; + + window.history.back(); + }); + + window.addEventListener('popstate', t.step_func(function(e) { + if(fired) { + assert_equals(e.state, 2, "state"); + assert_equals(window.history.length, last, "last"); + t.done(); + } + fired = true; + window.history.forward(); + }), false); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/combination_history_006.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/combination_history_006.html new file mode 100644 index 0000000000..305f593c09 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/combination_history_006.html @@ -0,0 +1,30 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>combination_history_006(After calling of go method, check length.)</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <script> + var t = async_test("After calling of go method, check length"); + + var last; + t.step(function () { + window.history.pushState(1, document.title, '?x=1'); + window.history.pushState(2, document.title, '?x=2'); + + last = window.history.length; + + window.history.go(-1); + }); + + window.addEventListener('popstate', t.step_func(function(e) { + assert_equals(e.state, 1, "state"); + assert_equals(window.history.length, last, "last"); + t.done(); + }), false); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/combination_history_007.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/combination_history_007.html new file mode 100644 index 0000000000..cec9ea0981 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/combination_history_007.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>combination_history_007(After calling of back and pushState method, check length.)</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <script> + var t = async_test("After calling of back and pushState method, check length"); + + var last; + t.step(function () { + window.history.pushState(1, document.title, '?x=1'); + window.history.pushState(2, document.title, '?x=2'); + + last = window.history.length; + + window.history.back(); + }); + + window.addEventListener('popstate', t.step_func(function(e) { + assert_equals(e.state, 1, "state"); + assert_equals(window.history.length, last, "last"); + window.history.pushState(3, document.title, '?x=3'); + assert_equals(window.history.length, last, "last"); + t.done(); + }), false); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history-associated-with-document.window.js b/testing/web-platform/tests/html/browsers/history/the-history-interface/history-associated-with-document.window.js new file mode 100644 index 0000000000..94c1b2cf6b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history-associated-with-document.window.js @@ -0,0 +1,6 @@ +// META: title=the History object must be associated with the Document object, not the Window object +// META: script=/common/object-association.js + +// See https://github.com/whatwg/html/issues/2566. + +testIsPerDocument("history"); diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history-state-after-bfcache.window.js b/testing/web-platform/tests/html/browsers/history/the-history-interface/history-state-after-bfcache.window.js new file mode 100644 index 0000000000..5f04890a72 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history-state-after-bfcache.window.js @@ -0,0 +1,43 @@ +// META: title=Navigating back to a bfcached page does not reset history.state +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/utils.js +// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js + +// See https://github.com/whatwg/html/issues/6652. + +'use strict'; + +promise_test(async t => { + const rcHelper = new RemoteContextHelper(); + + // Open a window with noopener so that BFCache will work. + const rc = await rcHelper.addWindow(null, { features: "noopener" }); + + // Add a pageshow listener to stash the event, and set history.state using replaceState(). + await rc.executeScript(() => { + window.addEventListener('pageshow', (event) => { + window.pageshowEvent = event; + }); + + history.replaceState({ foo: 'bar' }, '', ''); + window.stashedHistoryState = history.state; + }); + + const rc2 = await rc.navigateToNew(); + await rc2.historyBack(); + + assert_implements_optional( + await rc.executeScript(() => window.pageshowEvent.persisted), + 'precondition: document was bfcached' + ); + + assert_equals( + await rc.executeScript(() => history.state.foo), + 'bar', + 'history.state was restored correctly' + ); + + assert_true( + await rc.executeScript(() => window.stashedHistoryState === history.state), + 'history.state did not get serialized and deserialized'); +}); diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history.js b/testing/web-platform/tests/html/browsers/history/the-history-interface/history.js new file mode 100644 index 0000000000..bb5ee6dde0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history.js @@ -0,0 +1,35 @@ +function parse_query() { + var query = location.search.slice(1); + var vars = query.split("&"); + var fields = {}; + vars.forEach( + function (x) { + var split = x.split("="); + return fields[split[0]] = split.slice(1).join("="); + }); + return fields; +} + +var query_parts = parse_query(); +var id = "id" in query_parts ? parseInt(query_parts.id) : 1; +var urls_to_load = query_parts.urls.split(","); + +document.write(id); + +onunload = function() {}; + +function queue_next() { + t = opener.t; + setTimeout(t.step_func( + function() { +// opener.assert_equals(history.length, id); + if (urls_to_load[0]) { + var next_page = urls_to_load[0]; + (next_page.indexOf("?") > -1) ? (next_page += "&") : (next_page += "?"); + next_page += "urls=" + urls_to_load.slice(1).join(","); + next_page += "&id=" + ++id; + location = next_page; + } + } + ), 100); +} diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_back-1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_back-1.html new file mode 100644 index 0000000000..78547019f3 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_back-1.html @@ -0,0 +1,17 @@ +<!doctype html> +<script src="history.js"></script> +<script> + onunload = function() {} + + opener.pages.push(id); + if (!opener.started) { + onload = function() { + setTimeout(function() { + opener.started = true; + history.back(); + }, 100); + } + } else { + opener.start_test_wait(); + } +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_back.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_back.html new file mode 100644 index 0000000000..042da4e61b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_back.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>history_back</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <script> + var t = async_test("history back"); + + t.step(function () { + window.history.pushState(1, document.title, '?x=1'); + window.history.pushState(2, document.title, '?x=2'); + + window.history.back(); + }); + + window.addEventListener('popstate', t.step_func(function(e) { + assert_equals(e.state, 1, "history state"); + + t.done(); + }), false); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_back_1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_back_1.html new file mode 100644 index 0000000000..8ccf205956 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_back_1.html @@ -0,0 +1,28 @@ +<!doctype html> +<title>history.back() with session history</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> + var t = async_test(); + started = false; + pages = [] + timer = null; + start_test_wait = t.step_func( + function() { + clearTimeout(timer); + timer = setTimeout(t.step_func( + function() { + try { + assert_array_equals(pages, [2, 1], "Pages opened during history navigation"); + t.done(); + } finally { + win.close(); + } + } + ), 500); + } + ); + t.step(function() {win = window.open("history_entry.html?urls=history_back-1.html"); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_back_cross_realm_method.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_back_cross_realm_method.html new file mode 100644 index 0000000000..47937ef583 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_back_cross_realm_method.html @@ -0,0 +1,22 @@ +<!doctype html> +<meta charset="utf-8"> +<title>history.back() uses this's associated document's browsing context to determine if navigation is allowed</title> +<link rel="help" href="https://html.spec.whatwg.org/multipage/history.html#dom-history-back"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<iframe id="sandboxedIframe" srcdoc="hello" sandbox="allow-scripts allow-same-origin"></iframe> +<script> +const t = async_test(); + +t.step(() => { + history.pushState({}, null, "?prev"); + history.pushState({}, null, "?current"); + + sandboxedIframe.contentWindow.history.back.call(history); +}); + +window.onpopstate = t.step_func_done(() => { + assert_equals(location.search, "?prev"); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_entry.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_entry.html new file mode 100644 index 0000000000..e5929ddbe8 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_entry.html @@ -0,0 +1,12 @@ +<!doctype html> +<script src="history.js"></script> +<script> + onload = function() { + if (!opener.started) { + queue_next(); + } else { + opener.pages.push(id); + opener.start_test_wait(); + } +}; +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_forward-1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_forward-1.html new file mode 100644 index 0000000000..5880eacf04 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_forward-1.html @@ -0,0 +1,15 @@ +<!doctype html> +<script src="history.js"></script> +<script> + onunload = function() {} + + onload = function() { + if (!opener.started) { + queue_next(); + } else { + opener.pages.push(id); + opener.start_test_wait(); + history.forward(); + } + }; +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_forward-2.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_forward-2.html new file mode 100644 index 0000000000..c7a9a10682 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_forward-2.html @@ -0,0 +1,17 @@ +<!doctype html> +<script src="history.js"></script> +<script> + onunload = function() {} + + opener.pages.push(id); + if (!opener.started) { + onload = function() { + setTimeout(function() { + opener.started = true; + history.go(-1); + }, 100); + } + } else { + opener.start_test_wait(); + } +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_forward.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_forward.html new file mode 100644 index 0000000000..6c37f25215 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_forward.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>history_forward</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <script> + var t = async_test("history forward"); + + var fired = false; + t.step(function () { + window.history.pushState(1, document.title, '?x=1'); + window.history.pushState(2, document.title, '?x=2'); + + window.history.back(); + }); + + window.addEventListener('popstate', t.step_func(function(e) { + if(fired) { + assert_equals(e.state, 2, "history state"); + + t.done(); + } + fired = true; + window.history.forward(); + }), false); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_forward_1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_forward_1.html new file mode 100644 index 0000000000..220495c7f6 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_forward_1.html @@ -0,0 +1,28 @@ +<!doctype html> +<title>history.forward() with session history</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> + var t = async_test(); + started = false; + pages = [] + timer = null; + start_test_wait = t.step_func( + function() { + clearTimeout(timer); + timer = setTimeout(t.step_func( + function() { + try { + assert_array_equals(pages, [3, 2, 3], "Pages opened during history navigation"); + t.done(); + } finally { + win.close(); + } + } + ), 500); + } + ); + t.step(function() {win = window.open("history_entry.html?urls=history_forward-1.html,history_forward-2.html"); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_forward_cross_realm_method.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_forward_cross_realm_method.html new file mode 100644 index 0000000000..7456099b8f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_forward_cross_realm_method.html @@ -0,0 +1,28 @@ +<!doctype html> +<meta charset="utf-8"> +<title>history.forward() uses this's associated document's browsing context to determine if navigation is allowed</title> +<link rel="help" href="https://html.spec.whatwg.org/multipage/history.html#dom-history-forward"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<iframe id="sandboxedIframe" srcdoc="hello" sandbox="allow-scripts allow-same-origin"></iframe> +<script> +const t = async_test(); + +t.step(() => { + history.pushState({}, null, "?prev"); + history.pushState({}, null, "?current"); + history.back(); +}); + +let isCrossRealmForward = false; +window.onpopstate = t.step_func(() => { + if (isCrossRealmForward) { + assert_equals(location.search, "?current"); + t.done(); + } else { + sandboxedIframe.contentWindow.history.forward.call(history); + isCrossRealmForward = true; + } +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_cross_realm_method.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_cross_realm_method.html new file mode 100644 index 0000000000..d8852b9f6f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_cross_realm_method.html @@ -0,0 +1,23 @@ +<!doctype html> +<meta charset="utf-8"> +<title>history.go() uses this's associated document's browsing context to determine if navigation is allowed</title> +<link rel="help" href="https://html.spec.whatwg.org/multipage/history.html#dom-history-go"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<iframe id="sandboxedIframe" srcdoc="hello" sandbox="allow-scripts allow-same-origin"></iframe> +<script> +const t = async_test(); + +t.step(() => { + history.pushState({}, null, "?prev=2"); + history.pushState({}, null, "?prev=1"); + history.pushState({}, null, "?current"); + + sandboxedIframe.contentWindow.history.go.call(history, -2); +}); + +window.onpopstate = t.step_func_done(() => { + assert_equals(location.search, "?prev=2"); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_minus.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_minus.html new file mode 100644 index 0000000000..b8fe75573d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_minus.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>history_go_minus</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <script> + var t = async_test("history go minus"); + + t.step(function () { + window.history.pushState(1, document.title, '?x=1'); + window.history.pushState(2, document.title, '?x=2'); + + window.history.go(-1); + }); + + window.addEventListener('popstate', t.step_func(function(e) { + assert_equals(e.state, 1, "history state"); + + t.done(); + }), false); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_no_argument-1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_no_argument-1.html new file mode 100644 index 0000000000..acd9bda31d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_no_argument-1.html @@ -0,0 +1,18 @@ +<!doctype html> +<script src="history.js"></script> +<script> + onunload = function() {} + + onload = function() { + if (!opener.started) { + queue_next(); + } else { + opener.pages.push(id); + opener.start_test_wait(); + if (!opener.gone) { + history.go(); + opener.gone = true; + } + } + }; +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_no_argument.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_no_argument.html new file mode 100644 index 0000000000..68aeab2027 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_no_argument.html @@ -0,0 +1,29 @@ +<!doctype html> +<title>history.go()</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> + var t = async_test(); + started = false; + gone = false; + pages = [] + timer = null; + start_test_wait = t.step_func( + function() { + clearTimeout(timer); + timer = setTimeout(t.step_func( + function() { + try { + assert_array_equals(pages, [3, 2, 2], "Pages opened during history navigation"); + t.done(); + } finally { + win.close(); + } + } + ), 500); + } + ); + t.step(function() {win = window.open("history_entry.html?urls=history_go_no_argument-1.html,history_forward-2.html"); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_plus.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_plus.html new file mode 100644 index 0000000000..74d4c588ca --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_plus.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>history_go_plus</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <script> + var t = async_test("history go plus"); + + var fired = false; + t.step(function () { + window.history.pushState(1, document.title, '?x=1'); + window.history.pushState(2, document.title, '?x=2'); + + window.history.back(); + }); + + window.addEventListener('popstate', t.step_func(function(e) { + if(fired) { + assert_equals(e.state, 2, "history state"); + + t.done(); + } + fired = true; + window.history.go(1); + + }), false); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_to_uri-1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_to_uri-1.html new file mode 100644 index 0000000000..46c744e95d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_to_uri-1.html @@ -0,0 +1,23 @@ +<!doctype html> +<script src="history.js"></script> +<script> + onunload = function() {} + + onload = function() { + if (!opener.started) { + queue_next(); + } else { + opener.pages.push(id); + opener.start_test_wait(); + if (!opener.gone) { + // This is meant to test that passing a string is not supported. + // According to the spec, the value passed to 'go' must be an int. + // Internet Explorer supports passing a string and will navigate + // to that Url. This test will protect against regressing in + // this area and reverting back to IE's incorrect behavior. + history.go("history_entry.html"); + opener.gone = true; + } + } + }; +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_to_uri.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_to_uri.html new file mode 100644 index 0000000000..43d4a6b001 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_to_uri.html @@ -0,0 +1,32 @@ +<!doctype html> +<title>history.go() negative tests</title> +<link rel="author" title="John Jansen" href="mailto:johnjan@microsoft.com"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/browsers.html#dom-history-go"> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> + var t = async_test(); + started = false; + gone = false; + pages = [] + timer = null; + start_test_wait = t.step_func( + function() { + clearTimeout(timer); + timer = setTimeout(t.step_func( + function() { + try { + assert_array_equals(pages, [3, 2, 2], "Pages opened during history navigation"); + t.done(); + } finally { + win.close(); + } + } + ), 500); + } + ); + t.step(function() {win = window.open("history_entry.html?urls=history_go_to_uri-1.html,history_forward-2.html"); + }); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_undefined-1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_undefined-1.html new file mode 100644 index 0000000000..5880eacf04 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_undefined-1.html @@ -0,0 +1,15 @@ +<!doctype html> +<script src="history.js"></script> +<script> + onunload = function() {} + + onload = function() { + if (!opener.started) { + queue_next(); + } else { + opener.pages.push(id); + opener.start_test_wait(); + history.forward(); + } + }; +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_undefined.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_undefined.html new file mode 100644 index 0000000000..ddb0c858c2 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_undefined.html @@ -0,0 +1,28 @@ +<!doctype html> +<title>history.forward() with session history</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> + var t = async_test(); + started = false; + pages = [] + timer = null; + start_test_wait = t.step_func( + function() { + clearTimeout(timer); + timer = setTimeout(t.step_func( + function() { + try { + assert_array_equals(pages, [3, 2, 2], "Pages opened during history navigation"); + t.done(); + } finally { + win.close(); + } + } + ), 500); + } + ); + t.step(function() {win = window.open("history_entry.html?urls=history_go_undefined-1.html,history_forward-2.html"); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_zero-1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_zero-1.html new file mode 100644 index 0000000000..d9d4f330b2 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_zero-1.html @@ -0,0 +1,18 @@ +<!doctype html> +<script src="history.js"></script> +<script> + onunload = function() {} + + onload = function() { + if (!opener.started) { + queue_next(); + } else { + opener.pages.push(id); + opener.start_test_wait(); + if (!opener.gone) { + history.go(0); + opener.gone = true; + } + } + }; +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_zero.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_zero.html new file mode 100644 index 0000000000..88750921b0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_zero.html @@ -0,0 +1,29 @@ +<!doctype html> +<title>history.go(0)</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> + var t = async_test(); + started = false; + gone = false; + pages = [] + timer = null; + start_test_wait = t.step_func( + function() { + clearTimeout(timer); + timer = setTimeout(t.step_func( + function() { + try { + assert_array_equals(pages, [3, 2, 2], "Pages opened during history navigation"); + t.done(); + } finally { + win.close(); + } + } + ), 500); + } + ); + t.step(function() {win = window.open("history_entry.html?urls=history_go_zero-1.html,history_forward-2.html"); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_zero_which_document.window.js b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_zero_which_document.window.js new file mode 100644 index 0000000000..f697783f3e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_go_zero_which_document.window.js @@ -0,0 +1,27 @@ +// META: title=history.go(0) on an iframe must reload the iframe's document, not the parent document +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/utils.js +// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js + +promise_test(async () => { + const rcHelper = new RemoteContextHelper(); + const main = await rcHelper.addWindow(); + await main.addIframe(); + + await main.executeScript(() => { + window.didNotGetReloaded = true; + + const iframe = document.querySelector("iframe"); + + // This goes beyond the original test case in https://github.com/whatwg/html/issues/2436, and + // tests where current realm != relevant realm. The spec says to use relevant realm so the + // result is still, iframe must reload, not parent. + History.prototype.go.call(iframe.contentWindow.history, 0); + + return new Promise(resolve => { + iframe.addEventListener("load", resolve); + }); + }); + + assert_true(await main.executeScript(() => window.didNotGetReloaded)); +}); diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_properties_only_fully_active.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_properties_only_fully_active.html new file mode 100644 index 0000000000..2b2c308526 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_properties_only_fully_active.html @@ -0,0 +1,23 @@ +<!doctype html> +<title>history properties should throw SecurityError when not in a fully active Document</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> + <iframe id="child"></iframe> +</body> +<script> + test(function(t) { + var ifr = document.getElementById("child"); + var cached_history = ifr.contentWindow.history; + var cached_DOMException = ifr.contentWindow.DOMException; + ifr.remove(); + assert_throws_dom("SecurityError", cached_DOMException, function() { cached_history.length; }); + assert_throws_dom("SecurityError", cached_DOMException, function() { cached_history.scrollRestoration; }); + assert_throws_dom("SecurityError", cached_DOMException, function() { cached_history.state; }); + assert_throws_dom("SecurityError", cached_DOMException, function() { cached_history.go(0); }); + assert_throws_dom("SecurityError", cached_DOMException, function() { cached_history.back(); }); + assert_throws_dom("SecurityError", cached_DOMException, function() { cached_history.forward(); }); + assert_throws_dom("SecurityError", cached_DOMException, function() { cached_history.pushState(1, document.title, "?x=1"); }); + assert_throws_dom("SecurityError", cached_DOMException, function() { cached_history.replaceState(2, document.title, "?x=2"); }); + }); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_pushstate.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_pushstate.html new file mode 100644 index 0000000000..5180a3f6e5 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_pushstate.html @@ -0,0 +1,19 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>history_pushState</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <script> + test(function () { + window.history.pushState(1, document.title, '?x=1'); + var state; + state = window.history.state; + assert_equals(state, 1, "history state"); + }, "history pushState"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_pushstate_err.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_pushstate_err.html new file mode 100644 index 0000000000..6fa0a8589d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_pushstate_err.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>history_pushState SECURITY_ERR</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <script> + test(function () { + assert_throws_dom("SecurityError", function () { + window.history.pushState(1, document.title, 'http://www.microsoft.com/test.html'); + }); + }, "history pushState SECURITY_ERR"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_pushstate_nooptionalparam.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_pushstate_nooptionalparam.html new file mode 100644 index 0000000000..8e4b049a19 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_pushstate_nooptionalparam.html @@ -0,0 +1,20 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>history_pushState_NoOptionalParam</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <script> + test(function () { + window.history.pushState(1, document.title); + + var state; + state = window.history.state; + assert_equals(state, 1, "history state"); + }, "history pushState NoOptionalParam"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_pushstate_url.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_pushstate_url.html new file mode 100644 index 0000000000..cfbca35bfd --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_pushstate_url.html @@ -0,0 +1,24 @@ +<!doctype html> +<html> +<head> +<meta charset="utf-8"> +<title>History pushState sets the url</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +</head> +<body> +<div id="log"></div> +<script> +async_test(function(t) { + var oldLocation = window.location.toString(); + window.history.pushState(null, "", "#hash"); + assert_equals(oldLocation + "#hash", window.location.toString(), "pushState updates url"); + history.back(); + window.onhashchange = () => { + assert_equals(oldLocation, window.location.toString(), 'history traversal restores old url'); + t.done(); + }; +}, "history pushState sets url"); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_pushstate_url_rewriting.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_pushstate_url_rewriting.html new file mode 100644 index 0000000000..03c2afe8ea --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_pushstate_url_rewriting.html @@ -0,0 +1,176 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>URL rewriting allowed/disallowed for history.pushState()</title> +<link rel="help" href="https://github.com/whatwg/html/issues/6836"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<body> +<script> +"use strict"; +setup({ explicit_done: true }); + +const baseWithUsernamePassword = new URL(location.href); +baseWithUsernamePassword.username = "username"; +baseWithUsernamePassword.password = "password"; + +const blobURL = URL.createObjectURL(new Blob(["foo"], { type: "text/html" })); +const blobURL2 = URL.createObjectURL(new Blob(["bar"], { type: "text/html" })); + +const basicCases = [ + [new URL("/common/blank.html", location.href), new URL("/common/blank.html#newhash", location.href), true], + [new URL("/common/blank.html", location.href), new URL("/common/blank.html?newsearch", location.href), true], + [new URL("/common/blank.html", location.href), new URL("/newpath", location.href), true], + [new URL("/common/blank.html", location.href), new URL("/common/blank.html", baseWithUsernamePassword), false], + [new URL("/common/blank.html", location.href), blobURL, false], + [new URL("/common/blank.html", location.href), "about:blank", false], + [new URL("/common/blank.html", location.href), "about:srcdoc", false], + [blobURL, blobURL, true], + [blobURL, blobURL + "#newhash", true], + [blobURL, blobURL + "?newsearch", false], + [blobURL, "blob:newpath", false], + [blobURL, "blob:" + self.origin + "/syntheticblob", false], + [blobURL, blobURL2, false], + + // Note: these are cases where we create the iframe pointing at the initial URL, + // so its origin will actually be self.origin. + ["about:blank", "about:blank", true], + ["about:blank", "about:srcdoc", false], + ["about:blank", "about:blank?newsearch", false], + ["about:blank", "about:blank#newhash", true], + ["about:blank", self.origin + "/blank", false], + + // javascript: URL navigation changes the URL to the creator document's URL, so these should all + // not work because you can't rewrite a HTTP(S) URL to a javascript: URL. + [new URL("/common/blank.html", location.href), "javascript:'foo'", false], + ["javascript:'foo'", "javascript:'foo'", false], + ["javascript:'foo'", "javascript:'foo'?newsearch", false], + ["javascript:'foo'", "javascript:'foo'#newhash", false], +].map(([from, to, expectedToWork]) => [from.toString(), to.toString(), expectedToWork]); + +for (const [from, to, expectedToWork] of basicCases) { + // Otherwise the messages are not consistent between runs which breaks some systems. + const fromForTitle = from.replaceAll(blobURL, "blob:(a blob URL for this origin)") + .replaceAll(blobURL2, "blob:(another blob URL for this origin)"); + const toForTitle = to.replaceAll(blobURL, "blob:(a blob URL for this origin)") + .replaceAll(blobURL2, "blob:(another blob URL for this origin)"); + + promise_test(async () => { + const iframe = document.createElement("iframe"); + iframe.src = from; + const loadPromise = new Promise(r => iframe.onload = r); + + document.body.append(iframe); + await loadPromise; + + if (expectedToWork) { + iframe.contentWindow.history.pushState(null, "", to); + assert_equals(iframe.contentWindow.location.href, to); + } else { + assert_throws_dom("SecurityError", iframe.contentWindow.DOMException, () => { + iframe.contentWindow.history.pushState(null, "", to); + }); + } + }, `${fromForTitle} to ${toForTitle} should ${expectedToWork ? "" : "not"} work`); +} + +const srcdocCases = [ + ["about:srcdoc", true], + ["about:srcdoc?newsearch", false], + ["about:srcdoc#newhash", true], + [self.origin + "/srcdoc", false] +]; + +for (const [to, expectedToWork] of srcdocCases) { + promise_test(async () => { + const iframe = document.createElement("iframe"); + iframe.srcdoc = "foo"; + const loadPromise = new Promise(r => iframe.onload = r); + + document.body.append(iframe); + await loadPromise; + + if (expectedToWork) { + iframe.contentWindow.history.pushState(null, "", to); + assert_equals(iframe.contentWindow.location.href, to); + } else { + assert_throws_dom("SecurityError", iframe.contentWindow.DOMException, () => { + iframe.contentWindow.history.pushState(null, "", to); + }); + } + }, `about:srcdoc to ${to} should ${expectedToWork ? "" : "not"} work`); +} + +// We need to test these separately since they're cross-origin. + +const sandboxedCases = [ + [new URL("resources/url-rewriting-helper.html", location.href), new URL("resources/url-rewriting-helper.html", location.href), true], + [new URL("resources/url-rewriting-helper.html", location.href), new URL("resources/url-rewriting-helper.html#newhash", location.href), true], + [new URL("resources/url-rewriting-helper.html", location.href), new URL("resources/url-rewriting-helper.html?newsearch", location.href), true], + [new URL("resources/url-rewriting-helper.html", location.href), new URL("/newpath", location.href), true], + [new URL("resources/url-rewriting-helper.html", location.href), new URL("resources/url-rewriting-helper.html", baseWithUsernamePassword), false], +].map(([from, to, expectedToWork]) => [from.toString(), to.toString(), expectedToWork]); + +for (const [from, to, expectedToWork] of sandboxedCases) { + promise_test(async () => { + const iframe = document.createElement("iframe"); + iframe.src = from; + iframe.sandbox = "allow-scripts"; + const loadPromise = new Promise(r => iframe.onload = r); + + document.body.append(iframe); + await loadPromise; + + const messagePromise = new Promise(r => window.addEventListener("message", r, { once: true })); + iframe.contentWindow.postMessage(to, "*"); + const { data } = await messagePromise; + + if (expectedToWork) { + assert_equals(data.result, "no exception"); + assert_equals(data.locationHref, to); + } else { + assert_equals(data.result, "exception"); + assert_equals(data.exceptionName, "SecurityError"); + } + }, `sandboxed ${from} to ${to} should ${expectedToWork ? "" : "not"} work`); +} + +fetch("resources/url-rewriting-helper.html").then(r => r.text()).then(htmlInside => { + const dataURLStart = "data:text/html;base64," + btoa(htmlInside); + + const dataURLCases = [ + [dataURLStart, dataURLStart, true], + [dataURLStart, dataURLStart + "#newhash", true], + [dataURLStart, dataURLStart + "?newsearch", false], + [dataURLStart, "data:newpath", false] + ]; + + for (const [from, to, expectedToWork] of dataURLCases) { + // Otherwise the messages are unreadably long. + const fromForTitle = from.replaceAll(dataURLStart, "data:(script to run this test)"); + const toForTitle = to.replaceAll(dataURLStart, "data:(script to run this test)"); + + promise_test(async () => { + const iframe = document.createElement("iframe"); + iframe.src = from; + const loadPromise = new Promise(r => iframe.onload = r); + + document.body.append(iframe); + await loadPromise; + + const messagePromise = new Promise(r => window.addEventListener("message", r, { once: true })); + iframe.contentWindow.postMessage(to, "*"); + const { data } = await messagePromise; + if (expectedToWork) { + assert_equals(data.result, "no exception"); + assert_equals(data.locationHref, to); + } else { + assert_equals(data.result, "exception"); + assert_equals(data.exceptionName, "SecurityError"); + } + }, `${fromForTitle} to ${toForTitle} should ${expectedToWork ? "" : "not"} work`); + } + + done(); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_replacestate.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_replacestate.html new file mode 100644 index 0000000000..794c2f3713 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_replacestate.html @@ -0,0 +1,20 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>history_replaceState</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <script> + test(function () { + window.history.replaceState(1, document.title, '?x=1'); + + var second; + second = window.history.state; + assert_equals(second, 1, "history state"); + }, "history replaceState"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_replacestate_err.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_replacestate_err.html new file mode 100644 index 0000000000..15d2181820 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_replacestate_err.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>history_replaceState SECURITY_ERR</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <script> + test(function () { + assert_throws_dom("SecurityError", function () { + window.history.replaceState(1, document.title, 'http://www.microsoft.com/test.html'); + }); + }, "history replaceState SECURITY_ERR"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_replacestate_nooptionalparam.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_replacestate_nooptionalparam.html new file mode 100644 index 0000000000..838467d782 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_replacestate_nooptionalparam.html @@ -0,0 +1,20 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>history_replaceStateNoOptionalParam</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <script> + test(function () { + window.history.replaceState(1, document.title); + + var second; + second = window.history.state; + assert_equals(second, 1, "history state"); + }, "history replaceStateNoOptionalParam"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/history_state.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_state.html new file mode 100644 index 0000000000..2ee2356b1a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/history_state.html @@ -0,0 +1,24 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>history_state</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <script> + test(function () { + var first; + var second; + + first = window.history.state; + window.history.pushState(1, document.title, '?x=1'); + + second = window.history.state; + assert_equals(first, null, "first"); + assert_equals(second, 1, "second"); + }, "history state"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/iframe_history_go_0.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/iframe_history_go_0.html new file mode 100644 index 0000000000..f93f4c864e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/iframe_history_go_0.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<head> + <title>iframe_history_go_0</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> +</head> +<body> +<iframe></iframe> +<script> +promise_test(async (t) => { + let iframe = null; + const OLD_URL = 'blank-old.html'; + const NEW_URL = 'blank-new.html'; + + await new Promise(resolve => { + iframe = document.createElement('iframe'); + iframe.onload = () => resolve(); + iframe.src = OLD_URL; + document.body.appendChild(iframe); + t.add_cleanup(() => iframe.remove()); + }); + + assert_equals(iframe.contentDocument.body.textContent, 'This is an old page.\n'); + + await new Promise(resolve => { + iframe.onload = () => resolve(); + iframe.src = NEW_URL; + }); + + assert_equals(iframe.contentDocument.body.textContent, 'This is a new page.\n'); + + await new Promise(resolve => { + iframe.onload = () => resolve(); + iframe.contentWindow.history.go(0); + }); + + assert_equals(iframe.contentDocument.body.textContent, 'This is a new page.\n'); +}, 'iframe\'s history.go(0) performs a location.reload()'); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/joint_session_history/001-1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/joint_session_history/001-1.html new file mode 100644 index 0000000000..9aa5d30d16 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/joint_session_history/001-1.html @@ -0,0 +1,72 @@ +<!doctype html> +<script> +var o = opener; + +var frameloaded = null; + +o.t.step(function() {o.assert_equals(history.length, 1)}); + +onload = function () { + o.t.step(function() { + o.assert_equals(history.length, 1); + o.t.done(); + }); + + o.t1.step(function() { + var iframe = document.createElement("iframe"); + iframe.src = "filler.html?id=2"; + document.body.appendChild(iframe); + frameloaded = o.t1.step_func(function () { + o.assert_equals(history.length, 1); + setTimeout(o.t1.step_func(function () { + o.assert_equals(history.length, 1); + iframe.src = "filler.html?id=3"; + frameloaded = o.t2.step_func(function() { + o.assert_equals(history.length, 2); + history.go(-1); + frameloaded = o.t3.step_func(function() { + o.assert_equals(history.length, 2); + var parts = iframe.contentWindow.location.href.split("/") + o.assert_equals(parts[parts.length - 1], "filler.html?id=2"); + o.t3.done(); + o.t4.step(function() { + var iframe0 = document.getElementsByTagName("iframe")[0]; + iframe0.src = "filler.html?id=4" + frameloaded = o.t4.step_func(function() { + o.assert_equals(history.length, 2); + var parts = iframe0.contentWindow.location.href.split("/") + o.assert_equals(parts[parts.length - 1], "filler.html?id=4"); + //This is the point at which gecko and webkit stop running tests + history.go(-1); + frameloaded = o.t5.step_func(function() { + o.assert_equals(history.length, 2); + var parts = iframe0.contentWindow.location.href.split("/") + o.assert_equals(parts[parts.length - 1], "filler.html?id=1"); + var parts = iframe.contentWindow.location.href.split("/") + o.assert_equals(parts[parts.length - 1], "filler.html?id=2"); + history.go(1); + frameloaded = o.t6.step_func(function() { + o.assert_equals(history.length, 2); + var parts = iframe0.contentWindow.location.href.split("/") + o.assert_equals(parts[parts.length - 1], "filler.html?id=4"); + var parts = iframe.contentWindow.location.href.split("/") + o.assert_equals(parts[parts.length - 1], "filler.html?id=2"); + o.t6.done(); + }); + o.t5.done(); + }); + o.t4.done(); + }); + }); + }); + o.t2.done(); + }); + o.t1.done(); + }, 500)) + }); + }); + +} +</script> + +<iframe src="filler.html?id=1"></iframe> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/joint_session_history/001.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/joint_session_history/001.html new file mode 100644 index 0000000000..c9d1c6416d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/joint_session_history/001.html @@ -0,0 +1,18 @@ +<!doctype html> +<title>Joint session history with single iframe</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +setup({timeout:10000}); +var t = async_test("Session history length on initial load"); +var t1 = async_test("Session history length on adding new iframe"); +var t2 = async_test("Navigating second iframe"); +var t3 = async_test("Traversing history back (1)"); +var t4 = async_test("Navigating first iframe"); +var t5 = async_test("Traversing history back (2)"); +var t6 = async_test("Traversing history forward"); +var w = window.open("001-1.html"); +//add_completion_callback(function() {w.close()}); +</script> + diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/joint_session_history/002-1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/joint_session_history/002-1.html new file mode 100644 index 0000000000..ed69d679da --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/joint_session_history/002-1.html @@ -0,0 +1,35 @@ +<!doctype html> +<script> +var o = opener; + +var frameloaded = null; + +o.t.step(function() {o.assert_equals(history.length, 1)}); + +onload = function () { + o.t.step(function() { + o.assert_equals(history.length, 1); + o.t.done(); + }); + + o.t1.step(function() { + var iframe = document.createElement("iframe"); + iframe.src = "filler.html?id=2"; + document.body.appendChild(iframe); + o.assert_equals(history.length, 1); + frameloaded = o.t2.step_func(function() { + iframe.contentDocument.open(); + iframe.contentDocument.write("3<script>onpageshow = function() {alert('pageshow'); parent.frameloaded()}<\/script>"); + iframe.contentDocument.close(); + frameloaded = o.t2.step_func(function () { + o.assert_equals(history.length, 2); + o.t2.done(); + }); + }); + o.t1.done(); + }); + +} +</script> + +<iframe src="filler.html?id=1"></iframe> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/joint_session_history/002.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/joint_session_history/002.html new file mode 100644 index 0000000000..b08c19e52d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/joint_session_history/002.html @@ -0,0 +1,18 @@ +<!doctype html> +<title>Joint session history with single iframe</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +setup({timeout:10000}); +var t = async_test("Session history length on initial load"); +var t1 = async_test("Session history length on adding new iframe"); +var t2 = async_test("Navigating second iframe"); +<!-- var t3 = async_test("Traversing history back (1)"); --> +<!-- var t4 = async_test("Navigating first iframe"); --> +<!-- var t5 = async_test("Traversing history back (2)"); --> +<!-- var t6 = async_test("Traversing history forward"); --> +var w = window.open("002-1.html"); +//add_completion_callback(function() {w.close()}); +</script> + diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/joint_session_history/filler.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/joint_session_history/filler.html new file mode 100644 index 0000000000..93e3c7ccfc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/joint_session_history/filler.html @@ -0,0 +1,5 @@ +<!doctype html> +<script> +document.write(location.search) +onpageshow = function() {if (parent.frameloaded) {parent.frameloaded()}} +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/non-automated/history.js b/testing/web-platform/tests/html/browsers/history/the-history-interface/non-automated/history.js new file mode 100644 index 0000000000..bb5ee6dde0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/non-automated/history.js @@ -0,0 +1,35 @@ +function parse_query() { + var query = location.search.slice(1); + var vars = query.split("&"); + var fields = {}; + vars.forEach( + function (x) { + var split = x.split("="); + return fields[split[0]] = split.slice(1).join("="); + }); + return fields; +} + +var query_parts = parse_query(); +var id = "id" in query_parts ? parseInt(query_parts.id) : 1; +var urls_to_load = query_parts.urls.split(","); + +document.write(id); + +onunload = function() {}; + +function queue_next() { + t = opener.t; + setTimeout(t.step_func( + function() { +// opener.assert_equals(history.length, id); + if (urls_to_load[0]) { + var next_page = urls_to_load[0]; + (next_page.indexOf("?") > -1) ? (next_page += "&") : (next_page += "?"); + next_page += "urls=" + urls_to_load.slice(1).join(","); + next_page += "&id=" + ++id; + location = next_page; + } + } + ), 100); +} diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/non-automated/history_entry.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/non-automated/history_entry.html new file mode 100644 index 0000000000..e5929ddbe8 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/non-automated/history_entry.html @@ -0,0 +1,12 @@ +<!doctype html> +<script src="history.js"></script> +<script> + onload = function() { + if (!opener.started) { + queue_next(); + } else { + opener.pages.push(id); + opener.start_test_wait(); + } +}; +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/non-automated/traverse_the_history_unload_prompt_1-1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/non-automated/traverse_the_history_unload_prompt_1-1.html new file mode 100644 index 0000000000..8c4401836a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/non-automated/traverse_the_history_unload_prompt_1-1.html @@ -0,0 +1,18 @@ +<!doctype html> +<script src="history.js"></script> +<script> + onbeforeunload = function() {opener.beforeunload_ran = true; return "Opt to stay on the page"}; + + opener.pages.push(id); + if (!opener.started) { + onload = function() { + setTimeout(function() { + opener.started = true; + history.back(); + }, 100); + } + } +</script> +<p>You should see/have seen a prompt asking if you want to leave the page.</p> +<p>Opt to stay on the page</p> +<button onclick="onbeforeunload = null; opener.start_test_wait(); document.getElementsByTagName('button')[0].disabled = true;">Click here</button> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/non-automated/traverse_the_history_unload_prompt_1-manual.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/non-automated/traverse_the_history_unload_prompt_1-manual.html new file mode 100644 index 0000000000..587cdfb124 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/non-automated/traverse_the_history_unload_prompt_1-manual.html @@ -0,0 +1,31 @@ +<!doctype html> +<title>Traversing the history, prompt in before unload, navigation denied</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> + setup({timeout:3600000}); + var t = async_test(); + started = false; + pages = [] + timer = null; + beforeunload_ran = false; + start_test_wait = t.step_func( + function() { + clearTimeout(timer); + timer = setTimeout(t.step_func( + function() { + try { + assert_true(beforeunload_ran, "beforeunload event handler ran"); + assert_array_equals(pages, [2], "Pages opened during history navigation"); + t.done(); + } finally { + win.close(); + } + } + ), 500); + } + ); + t.step(function() {win = window.open("history_entry.html?urls=traverse_the_history_unload_prompt_1-1.html"); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/non-automated/traverse_the_history_unload_prompt_2-1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/non-automated/traverse_the_history_unload_prompt_2-1.html new file mode 100644 index 0000000000..608a579e69 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/non-automated/traverse_the_history_unload_prompt_2-1.html @@ -0,0 +1,18 @@ +<!doctype html> +<script src="history.js"></script> +<script> + onbeforeunload = function() {opener.beforeunload_ran = true; return "Opt to leave the page"}; + + opener.pages.push(id); + if (!opener.started) { + onload = function() { + setTimeout(function() { + opener.started = true; + history.back(); + }, 100); + } + } +</script> +<p>You should see/have seen a prompt asking if you want to leave the page.</p> +<p>Opt to leave the page</p> +<p>If you weren't navigated away after opting to leave the page, that's a FAIL</p> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/non-automated/traverse_the_history_unload_prompt_2-manual.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/non-automated/traverse_the_history_unload_prompt_2-manual.html new file mode 100644 index 0000000000..94b66f8b55 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/non-automated/traverse_the_history_unload_prompt_2-manual.html @@ -0,0 +1,31 @@ +<!doctype html> +<title>Traversing the history, prompt in before unload, navigation allowed</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> + setup({timeout:3600000}); + var t = async_test(); + started = false; + pages = [] + timer = null; + beforeunload_ran = false; + start_test_wait = t.step_func( + function() { + clearTimeout(timer); + timer = setTimeout(t.step_func( + function() { + try { + assert_true(beforeunload_ran, "beforeunload event handler ran"); + assert_array_equals(pages, [2,1], "Pages opened during history navigation"); + t.done(); + } finally { + win.close(); + } + } + ), 500); + } + ); + t.step(function() {win = window.open("history_entry.html?urls=traverse_the_history_unload_prompt_2-1.html"); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/non-automated/traverse_the_session_history_unload_prompt_1-1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/non-automated/traverse_the_session_history_unload_prompt_1-1.html new file mode 100644 index 0000000000..c0079b6bec --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/non-automated/traverse_the_session_history_unload_prompt_1-1.html @@ -0,0 +1,17 @@ +<!doctype html> +<script src="history.js"></script> +<script> + onunload = function(e) {opener.unload_ran = true; return "Now refuse to leave the current page"} + + opener.pages.push(id); + if (!opener.started) { + onload = function() { + setTimeout(function() { + opener.started = true; + history.back(); + }, 100); + } + } else { + opener.start_test_wait(); + } +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/non-automated/traverse_the_session_history_unload_prompt_1-manual.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/non-automated/traverse_the_session_history_unload_prompt_1-manual.html new file mode 100644 index 0000000000..7f96a39240 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/non-automated/traverse_the_session_history_unload_prompt_1-manual.html @@ -0,0 +1,30 @@ +<!doctype html> +<title>Traversing the history, unload event is fired on doucment</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> + var t = async_test(); + started = false; + pages = [] + timer = null; + unload_ran = false; + start_test_wait = t.step_func( + function() { + clearTimeout(timer); + timer = setTimeout(t.step_func( + function() { + try { + assert_array_equals(pages, [2], "Pages opened during history navigation"); + assert_true(unload_ran, "Unload event handler ran"); + t.done(); + } finally { + // win.close(); + } + } + ), 500); + } + ); + t.step(function() {win = window.open("history_entry.html?urls=traverse_the_history_unload_prompt_1-1.html"); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/resources/message-opener.sub.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/resources/message-opener.sub.html new file mode 100644 index 0000000000..441c08c0ea --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/resources/message-opener.sub.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<meta charset="utf-8"> + +<script> +"use strict"; + +opener.postMessage("{{GET[id]}}", "*"); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/resources/traverse-during-beforeunload.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/resources/traverse-during-beforeunload.html new file mode 100644 index 0000000000..53a4a1886e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/resources/traverse-during-beforeunload.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<meta charset="utf-8"> + +<script> +"use strict"; + +window.addEventListener("beforeunload", () => { + history.back(); +}); + +location.href = "message-opener.sub.html?id=destination"; +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/resources/traverse-during-unload.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/resources/traverse-during-unload.html new file mode 100644 index 0000000000..d5ffb7abae --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/resources/traverse-during-unload.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<meta charset="utf-8"> + +<script> +"use strict"; + +window.addEventListener("unload", () => { + history.back(); +}); + +location.href = "message-opener.sub.html?id=destination"; +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/resources/url-rewriting-helper.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/resources/url-rewriting-helper.html new file mode 100644 index 0000000000..847d16cd1a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/resources/url-rewriting-helper.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<script> + window.onmessage = ({ data }) => { + try { + history.pushState(null, "", data); + } catch (e) { + parent.postMessage({ result: "exception", exceptionName: e.name }, "*"); + return; + } + parent.postMessage({ result: "no exception", locationHref: location.href }, "*"); + }; +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse-during-beforeunload.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse-during-beforeunload.html new file mode 100644 index 0000000000..cb8cdca7ff --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse-during-beforeunload.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Traversing the history during beforeunload</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<body> +<script> +"use strict"; + +async_test(t => { + const w = window.open("resources/message-opener.sub.html?id=start"); + t.add_cleanup(() => w.close()); + + const messages = []; + window.addEventListener("message", t.step_func(({ data }) => { + messages.push(data); + + if (messages.length === 1) { + assert_array_equals(messages, ["start"]); + w.location.href = "resources/traverse-during-beforeunload.html"; + } else if (messages.length === 2) { + assert_array_equals(messages, ["start", "destination"]); + t.done(); + } + })); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse-during-unload.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse-during-unload.html new file mode 100644 index 0000000000..6f6e984402 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse-during-unload.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Traversing the history during unload</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<body> +<script> +"use strict"; + +async_test(t => { + const w = window.open("resources/message-opener.sub.html?id=start"); + t.add_cleanup(() => w.close()); + + const messages = []; + window.addEventListener("message", t.step_func(({ data }) => { + messages.push(data); + + if (messages.length === 1) { + assert_array_equals(messages, ["start"]); + w.location.href = "resources/traverse-during-unload.html"; + } else if (messages.length === 2) { + assert_array_equals(messages, ["start", "destination"]); + t.done(); + } + })); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_1-1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_1-1.html new file mode 100644 index 0000000000..a11fcf2d2d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_1-1.html @@ -0,0 +1,18 @@ +<!doctype html> +4 +<script> + onunload = function() {} + + opener.pages.push(4); + if (!opener.started) { + onload = function() { + setTimeout(function() { + opener.started = true; + history.go(-2); + history.go(-1); + }, 100); + } + } else { + opener.start_test_wait(); + } +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_1.html new file mode 100644 index 0000000000..9b59bb0587 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_1.html @@ -0,0 +1,25 @@ +<!doctype html> +<title>Multiple history traversals from the same task</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> + var t = async_test(); + started = false; + pages = [] + timer = null; + start_test_wait = t.step_func( + function() { + clearTimeout(timer); + timer = setTimeout(t.step_func( + function() { + assert_array_equals(pages, [4, 2], "Pages opened during history navigation"); + t.done(); + } + ), 500); + } + ); + t.step(function() {win = window.open("history_entry.html?urls=history_entry.html,history_entry.html,traverse_the_history_1-1.html"); + t.add_cleanup(() => { win.close() }); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_2-1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_2-1.html new file mode 100644 index 0000000000..64920b4f4f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_2-1.html @@ -0,0 +1,17 @@ +<!doctype html> +<script src="history.js"></script> +<script> + opener.pages.push(id); + if (!opener.started) { + onload = function() { + setTimeout(function() { + opener.started = true; + history.go(-3); + history.go(-2); + history.go(1); + }, 100); + } + } else { + opener.start_test_wait(); + } +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_2.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_2.html new file mode 100644 index 0000000000..1d10033808 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_2.html @@ -0,0 +1,28 @@ +<!doctype html> +<title>Multiple history traversals, last would be aborted</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> + var t = async_test(); + started = false; + pages = [] + timer = null; + start_test_wait = t.step_func( + function() { + clearTimeout(timer); + timer = setTimeout(t.step_func( + function() { + try { + assert_array_equals(pages, [6, 3], "Pages opened during history navigation"); + t.done(); + } finally { + // win.close(); + } + } + ), 500); + } + ); + t.step(function() {win = window.open("history_entry.html?urls=history_entry.html,history_entry.html,history_entry.html,history_entry.html,traverse_the_history_2-1.html"); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_3-1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_3-1.html new file mode 100644 index 0000000000..c49bfd384a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_3-1.html @@ -0,0 +1,17 @@ +<!doctype html> +<script src="history.js"></script> +<script> + opener.pages.push(id); + if (!opener.started) { + onload = function() { + setTimeout(function() { + opener.started = true; + history.go(-2); + history.go(-1); + history.go(3); + }, 100); + } + } else { + opener.start_test_wait(); + } +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_3.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_3.html new file mode 100644 index 0000000000..1d10033808 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_3.html @@ -0,0 +1,28 @@ +<!doctype html> +<title>Multiple history traversals, last would be aborted</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> + var t = async_test(); + started = false; + pages = [] + timer = null; + start_test_wait = t.step_func( + function() { + clearTimeout(timer); + timer = setTimeout(t.step_func( + function() { + try { + assert_array_equals(pages, [6, 3], "Pages opened during history navigation"); + t.done(); + } finally { + // win.close(); + } + } + ), 500); + } + ); + t.step(function() {win = window.open("history_entry.html?urls=history_entry.html,history_entry.html,history_entry.html,history_entry.html,traverse_the_history_2-1.html"); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_4-1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_4-1.html new file mode 100644 index 0000000000..cf7f72379a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_4-1.html @@ -0,0 +1,17 @@ +<!doctype html> +<script src="history.js"></script> +<script> + opener.pages.push(id); + if (!opener.started) { + onload = function() { + setTimeout(function() { + opener.started = true; + history.go(-10); //Outside the range + history.go(-1); + history.go(-2); + }, 100); + } + } else { + opener.start_test_wait(); + } +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_4.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_4.html new file mode 100644 index 0000000000..2856bf8d73 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_4.html @@ -0,0 +1,28 @@ +<!doctype html> +<title>Multiple history traversals, last would be aborted</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> + var t = async_test(); + started = false; + pages = [] + timer = null; + start_test_wait = t.step_func( + function() { + clearTimeout(timer); + timer = setTimeout(t.step_func( + function() { + try { + assert_array_equals(pages, [6, 5], "Pages opened during history navigation"); + t.done(); + } finally { + // win.close(); + } + } + ), 500); + } + ); + t.step(function() {win = window.open("history_entry.html?urls=history_entry.html,history_entry.html,history_entry.html,history_entry.html,traverse_the_history_4-1.html"); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_5-1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_5-1.html new file mode 100644 index 0000000000..a3f2553fa0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_5-1.html @@ -0,0 +1,17 @@ +<!doctype html> +<script src="history.js"></script> +<script> + opener.pages.push(id); + if (!opener.started) { + onload = function() { + setTimeout(function() { + opener.started = true; + history.go(10); //Outside the range + history.go(-1); + history.go(-2); + }, 100); + } + } else { + opener.start_test_wait(); + } +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_5.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_5.html new file mode 100644 index 0000000000..8725497b50 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_5.html @@ -0,0 +1,28 @@ +<!doctype html> +<title>Multiple history traversals, last would be aborted</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> + var t = async_test(); + started = false; + pages = [] + timer = null; + start_test_wait = t.step_func( + function() { + clearTimeout(timer); + timer = setTimeout(t.step_func( + function() { + try { + assert_array_equals(pages, [6, 5], "Pages opened during history navigation"); + t.done(); + } finally { + // win.close(); + } + } + ), 500); + } + ); + t.step(function() {win = window.open("history_entry.html?urls=history_entry.html,history_entry.html,history_entry.html,history_entry.html,traverse_the_history_5-1.html"); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_unload_1-1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_unload_1-1.html new file mode 100644 index 0000000000..d3f3f6d5d0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_unload_1-1.html @@ -0,0 +1,17 @@ +<!doctype html> +<script src="history.js"></script> +<script> + onunload = function() {opener.unload_ran = true;} + + opener.pages.push(id); + if (!opener.started) { + onload = function() { + setTimeout(function() { + opener.started = true; + history.back(); + }, 100); + } + } else { + opener.start_test_wait(); + } +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_unload_1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_unload_1.html new file mode 100644 index 0000000000..b8215b29f3 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_unload_1.html @@ -0,0 +1,30 @@ +<!doctype html> +<title>Traversing the history, unload event is fired on doucment</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> + var t = async_test(); + started = false; + pages = [] + timer = null; + unload_ran = false; + start_test_wait = t.step_func( + function() { + clearTimeout(timer); + timer = setTimeout(t.step_func( + function() { + try { + assert_array_equals(pages, [2, 1], "Pages opened during history navigation"); + assert_true(unload_ran, "Unload event handler ran"); + t.done(); + } finally { + // win.close(); + } + } + ), 500); + } + ); + t.step(function() {win = window.open("history_entry.html?urls=traverse_the_history_unload_1-1.html"); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_after_load_1-1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_after_load_1-1.html new file mode 100644 index 0000000000..945c8d81f8 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_after_load_1-1.html @@ -0,0 +1,12 @@ +<!doctype html> +2 +<script> + onunload = function() {} + opener.pages.push(2); + onload = function() { + setTimeout(function() { + document.write("<!doctype html>3<script>opener.pages.push(3); if(!opener.started) {opener.started = true; history.go(-1);}<\/script>"); + document.close(); + }, 100); + } +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_after_load_1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_after_load_1.html new file mode 100644 index 0000000000..404d61d0cf --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_after_load_1.html @@ -0,0 +1,29 @@ +<!doctype html> +<title>Traverse the history after document.write after the load event</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> + var t = async_test(); + started = false; + pages = [] + start_test_wait = t.step_func( + function() { + check_result = t.step_func( + function() { + if (pages.length < 3) { + setTimeout(check_result, 500); + return + } + assert_array_equals(pages, [2, 3, 1], "Pages opened during history navigation"); + t.done(); + } + ) + setTimeout(check_result, 500); + } + ); + t.step(function() { + win = window.open("history_entry.html?urls=traverse_the_history_write_after_load_1-1.html"); + t.add_cleanup(function() {win.close()}); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_after_load_2-1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_after_load_2-1.html new file mode 100644 index 0000000000..0e58cf573d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_after_load_2-1.html @@ -0,0 +1,12 @@ +<!doctype html> +3 +<script> + onunload = function() {} + opener.pages.push(3); + onload = function() { + document.write("<!doctype html>4<script>opener.pages.push(4); if(!opener.started) {opener.started = true; history.go(-2);} opener.start_test_wait();<\/script>"); + if (opener.started) { + opener.start_test_wait(); + } + } +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_after_load_2.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_after_load_2.html new file mode 100644 index 0000000000..28e363f916 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_after_load_2.html @@ -0,0 +1,28 @@ +<!doctype html> +<title>Traverse the history back and forward when a history entry is written after the load event</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> + var t = async_test(); + started = false; + pages = [] + start_test_wait = t.step_func( + function() { + var check_result = t.step_func(function() { + if (pages.length < 5) { + setTimeout(check_result, 500); + return + } + //The pass condition here is based on the idea that the spec is wrong and browsers are right + assert_array_equals(pages, [3, 4, 2, 3, 4], "Pages opened during history navigation"); + t.done(); + }); + setTimeout(check_result, 500); + } + ); + t.step(function() { + win = window.open("history_entry.html?urls=history_forward-1.html,traverse_the_history_write_onload_2-1.html"); + t.add_cleanup(function() {win.close()}); + }); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_onload_1-1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_onload_1-1.html new file mode 100644 index 0000000000..261955533d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_onload_1-1.html @@ -0,0 +1,12 @@ +<!doctype html> +2 +<script> + onunload = function() {} + opener.pages.push(2); + onload = function() { + document.write("<!doctype html>3<script>opener.pages.push(3); if(!opener.started) {opener.started = true; history.go(-1);} opener.start_test_wait();<\/script>"); + if (opener.started) { + opener.start_test_wait(); + } + } +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_onload_1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_onload_1.html new file mode 100644 index 0000000000..ff2729c3cf --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_onload_1.html @@ -0,0 +1,29 @@ +<!doctype html> +<title>Traverse the history when a history entry is written in the load event</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> + var t = async_test(); + started = false; + pages = [] + timer = null; + start_test_wait = t.step_func( + function() { + clearTimeout(timer); + timer = setTimeout(t.step_func( + function() { + try { + //The pass condition here is based on the idea that the spec is wrong and browsers are right + assert_array_equals(pages, [2, 3, 1], "Pages opened durning history navigation"); + t.done(); + } finally { + // win.close(); + } + } + ), 500); + } + ); + t.step(function() {win = window.open("history_entry.html?urls=traverse_the_history_write_onload_1-1.html"); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_onload_2-1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_onload_2-1.html new file mode 100644 index 0000000000..f32bee5e12 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_onload_2-1.html @@ -0,0 +1,12 @@ +<!doctype html> +3 +<script> + onunload = function() {} + opener.pages.push(3); + onload = function() { + document.write("<!doctype html>4<script>opener.pages.push(4); if(!opener.started) {opener.started = true; history.go(-1);} opener.start_test_wait();<\/script>"); + if (opener.started) { + opener.start_test_wait(); + } + } +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_onload_2.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_onload_2.html new file mode 100644 index 0000000000..bc29174b0d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_onload_2.html @@ -0,0 +1,29 @@ +<!doctype html> +<title>Traverse the history back and forward when a history entry is written in the load event</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> + var t = async_test(); + started = false; + pages = [] + timer = null; + start_test_wait = t.step_func( + function() { + clearTimeout(timer); + timer = setTimeout(t.step_func( + function() { + try { + //The pass condition here is based on the idea that the spec is wrong and browsers are right + assert_array_equals(pages, [3, 4, 2, 3, 4], "Pages opened durning history navigation"); + t.done(); + } finally { + win.close(); + } + } + ), 500); + } + ); + t.step(function() {win = window.open("history_entry.html?urls=history_forward-1.html,traverse_the_history_write_onload_2-1.html"); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/allow_prototype_cycle_through_location.sub.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/allow_prototype_cycle_through_location.sub.html new file mode 100644 index 0000000000..f72ed1eaf2 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/allow_prototype_cycle_through_location.sub.html @@ -0,0 +1,197 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8"> + + <title>Location objects' custom [[GetPrototypeOf]] trap permit [[Prototype]] chain cycles to be created through them</title> + + <link rel="author" title="Jeff Walden" href="http://whereswalden.com/" /> + <link rel="help" href="https://tc39.github.io/ecma262/#sec-ordinarysetprototypeof" /> + <link rel="help" href="https://html.spec.whatwg.org/multipage/browsers.html#location-getprototypeof" /> + + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> +</head> +<body> +<div id="log"></div> + +<hr /> + +<iframe id="same-origin-different-window"></iframe> +<iframe id="cross-origin-joined-via-document-domain"></iframe> + +<script> +"use strict"; + +// Handle same-origin, same-window testing first, before any async-requiring +// testing. +test(function() { + var LocationPrototype = Location.prototype; + var ObjectPrototype = Object.prototype; + + var loc = window.location; + + var locProto = Object.getPrototypeOf(loc); + assert_equals(locProto, LocationPrototype, + "loc's initial [[Prototype]]"); + + var originalLocProtoProto = Object.getPrototypeOf(locProto); + assert_equals(originalLocProtoProto, ObjectPrototype, + "Location.prototype's initial [[Prototype]]"); + + Object.setPrototypeOf(locProto, loc); + + assert_equals(Object.getPrototypeOf(locProto), loc, + "LocationPrototype's new [[Prototype]]"); + assert_equals(Object.getPrototypeOf(loc), locProto, + "loc's new [[Prototype]]"); + + // Reset so as not to muck with testharness.js expectations. + Object.setPrototypeOf(locProto, originalLocProtoProto); +}, "same-origin, same-window location cycle"); + +var pathdir = + location.pathname.substring(0, location.pathname.lastIndexOf('/') + 1); + +var triggerCrossOriginTest = (function() { + var crossOrigin = + document.getElementById("cross-origin-joined-via-document-domain"); + + var t = async_test("cross-origin location has null prototype"); + + return new Promise(function(resolve, reject) { + crossOrigin.onload = t.step_func_done(function(e) { + try { + var win = crossOrigin.contentWindow; + + var loc = win.location; + + // Between un-opted-in windows, location objects appear to have null + // [[Prototype]]. + assert_equals(Object.getPrototypeOf(loc), null, + "cross-origin unjoined location's [[Prototype]"); + + resolve(); + } catch (e) { + reject(e); + throw e; + } + }); + + crossOrigin.src = + "//{{domains[www]}}:" + location.port + pathdir + "cross_origin_joined_frame.sub.html"; + }) + .catch(t.unreached_func("crossOrigin onload/src setting")); +})(); + +var triggerSameOriginTest = (function() { + var sameOriginDifferentWindow = + document.getElementById("same-origin-different-window"); + + var t = async_test("same-origin, different-window location cycle"); + + return new Promise(function(resolve, reject) { + sameOriginDifferentWindow.onload = t.step_func_done(function() { + try { + var win = sameOriginDifferentWindow.contentWindow; + + var loc = win.location; + var LocationPrototype = win.Location.prototype; + var ObjectPrototype = win.Object.prototype; + + var locProto = Object.getPrototypeOf(loc); + assert_equals(locProto, LocationPrototype, + "loc's initial [[Prototype]]"); + + var originalLocProtoProto = Object.getPrototypeOf(locProto); + assert_equals(originalLocProtoProto, ObjectPrototype, + "Location.prototype's initial [[Prototype]]"); + + Object.setPrototypeOf(locProto, loc); + + assert_equals(Object.getPrototypeOf(locProto), loc, + "LocationPrototype's new [[Prototype]]"); + assert_equals(Object.getPrototypeOf(loc), locProto, + "loc's new [[Prototype]]"); + + // Reset so as not to muck with testharness.js expectations. + Object.setPrototypeOf(locProto, originalLocProtoProto); + + resolve(); + } catch (e) { + reject(e); + throw e; + } + }); + + sameOriginDifferentWindow.src = "same_origin_frame.html"; + }) + .catch(t.unreached_func("sameOriginDifferentWindow onload/src setting")); +})(); + +function crossOriginJoinTest() { + var win = + document.getElementById("cross-origin-joined-via-document-domain") + .contentWindow; + + assert_equals(document.domain, "{{host}}"); + + var loc = win.location; + + var threw = false; + try { + // Still cross-origin until the document.domain set below. + win.Location; + } catch (e) { + threw = true; + } + + assert_equals(threw, true, + "accessing win.Location before joining win's origin"); + + // Join with other frames that have set |document.domain| to this same + // value -- namely, this cross-origin frame. Now access between the two + // windows should be permitted. + assert_equals(document.domain, "{{host}}", + "initial document.domain sanity check"); + document.domain = "{{host}}"; + + var LocationPrototype = win.Location.prototype; + var ObjectPrototype = win.Object.prototype; + + var locProto = Object.getPrototypeOf(loc); + assert_equals(locProto, LocationPrototype, + "loc's initial [[Prototype]]"); + + var originalLocProtoProto = Object.getPrototypeOf(locProto); + assert_equals(originalLocProtoProto, ObjectPrototype, + "Location.prototype's initial [[Prototype]]"); + + Object.setPrototypeOf(locProto, loc); + + assert_equals(Object.getPrototypeOf(locProto), loc, + "LocationPrototype's new [[Prototype]]"); + assert_equals(Object.getPrototypeOf(loc), locProto, + "loc's new [[Prototype]]"); + + // Reset so as not to muck with testharness.js expectations. + Object.setPrototypeOf(locProto, originalLocProtoProto); +} + +function run() { + var t = + async_test("cross-origin, but joined via document.domain, location cycle"); + + // The cross-origin/joined case must be tested after both unjoined same-origin + // and unjoined cross-origin tests: by mucking with document.domain, the + // cross-origin/joined case makes it impossible to perform those tests. + t.step(function() { + Promise.all([triggerCrossOriginTest, triggerSameOriginTest]) + .then(t.step_func_done(crossOriginJoinTest), + t.unreached_func("cross-origin joined error case")); + }); +} +run(); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/assign_after_load-1.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/assign_after_load-1.html new file mode 100644 index 0000000000..3d2b897221 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/assign_after_load-1.html @@ -0,0 +1,9 @@ +<!doctype html> +1 +<script> +onload = parent.t.step_func(function() { + setTimeout(function() { + location = location.toString().replace("assign_after_load-1.html", "assign_after_load-2.html"); + }, 100); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/assign_after_load-2.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/assign_after_load-2.html new file mode 100644 index 0000000000..94679571be --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/assign_after_load-2.html @@ -0,0 +1,7 @@ +<!doctype html> +2 +<script> +onload = parent.t.step_func(function() { + setTimeout(function() {parent.do_test()}, 100); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/assign_after_load.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/assign_after_load.html new file mode 100644 index 0000000000..00dc931d4e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/assign_after_load.html @@ -0,0 +1,23 @@ +<!doctype html> +<title>Assignment to location after document is completely loaded</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<iframe></iframe> +<script> +var t = async_test(); +var history_length; + +onload = t.step_func(function() { + setTimeout(function() { + history_length = history.length; + document.getElementsByTagName("iframe")[0].src = "assign_after_load-1.html"; + }, 100); +}); + +do_test = t.step_func(function() { + assert_equals(history.length, history_length + 2); + t.done(); +}); + +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/assign_before_load-1.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/assign_before_load-1.html new file mode 100644 index 0000000000..2549867c8f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/assign_before_load-1.html @@ -0,0 +1,7 @@ +<!doctype html> +1 +<script> +onload = parent.t.step_func(function() { + location = location.toString().replace("assign_before_load-1.html", "assign_before_load-2.html"); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/assign_before_load-2.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/assign_before_load-2.html new file mode 100644 index 0000000000..94679571be --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/assign_before_load-2.html @@ -0,0 +1,7 @@ +<!doctype html> +2 +<script> +onload = parent.t.step_func(function() { + setTimeout(function() {parent.do_test()}, 100); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/assign_before_load.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/assign_before_load.html new file mode 100644 index 0000000000..62a2aa7c60 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/assign_before_load.html @@ -0,0 +1,23 @@ +<!doctype html> +<title>Assignment to location before document is completely loaded</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<iframe></iframe> +<script> +var t = async_test(); +var history_length; + +onload = t.step_func(function() { + setTimeout(function() { + history_length = history.length; + document.getElementsByTagName("iframe")[0].src = "assign_before_load-1.html"; + }, 100); +}); + +do_test = t.step_func(function() { + assert_equals(history.length, history_length + 1); + t.done(); +}); + +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/cross_origin_joined_frame.sub.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/cross_origin_joined_frame.sub.html new file mode 100644 index 0000000000..a3ffdd005a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/cross_origin_joined_frame.sub.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8"> + <title>Cross-origin subframe for Location cyclic [[Prototype]] test</title> + <link rel="author" title="Jeff Walden" href="http://whereswalden.com/" /> +</head> +<body> +<script> +document.domain = "{{host}}"; +</script> +<!-- this should be accessible to the parent once it sets document.domain --> +<p>Cross-origin iframe with joined <code>document.domain</code></p> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/document_location.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/document_location.html new file mode 100644 index 0000000000..3bdb028212 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/document_location.html @@ -0,0 +1,39 @@ +<!doctype html> +<title>document.location</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +test(function() { + var doc = document.implementation.createHTMLDocument(""); + assert_equals(doc.location, null); +}, "document not in a browsing context"); + +test(function() { + assert_equals(document.location, location); +}, "document.location equals window.location"); + +test(function() { + var desc1 = Object.getOwnPropertyDescriptor(new Document(), "location"); + assert_not_equals(desc1, undefined); + assert_equals(typeof desc1.get, "function"); + + var desc2 = Object.getOwnPropertyDescriptor(new Document(), "location"); + assert_not_equals(desc2, undefined); + assert_equals(typeof desc2.get, "function"); + + assert_equals(desc1.get, desc2.get); +}, "Attribute getter deduplication"); + +test(function() { + var desc1 = Object.getOwnPropertyDescriptor(new Document(), "location"); + assert_not_equals(desc1, undefined); + assert_equals(typeof desc1.set, "function"); + + var desc2 = Object.getOwnPropertyDescriptor(new Document(), "location"); + assert_not_equals(desc2, undefined); + assert_equals(typeof desc2.set, "function"); + + assert_equals(desc1.set, desc2.set); +}, "Attribute setter deduplication"); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location-non-configurable-toString-valueOf.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-non-configurable-toString-valueOf.html new file mode 100644 index 0000000000..80760ac9e4 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-non-configurable-toString-valueOf.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Same-origin Location objects have non-configurable "toString" and "valueOf" properties</title> +<link rel="help" href="https://html.spec.whatwg.org/multipage/history.html#location-defineownproperty"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> +"use strict"; + +test(() => { + assert_own_property(location, "toString"); + const origToString = location.toString; + + assert_throws_js(TypeError, () => { + Object.defineProperty(location, "toString", { + get() {}, + set(_v) {}, + enumerable: true, + configurable: true, + }); + }); + + assert_equals(location.toString, origToString); +}, "'toString' redefinition with accessor fails"); + +test(() => { + assert_own_property(location, "valueOf"); + const origValueOf = location.valueOf; + + assert_throws_js(TypeError, () => { + Object.defineProperty(location, "valueOf", { + get() {}, + enumerable: false, + configurable: true, + }); + }); + + assert_equals(location.valueOf, origValueOf); +}, "'valueOf' redefinition with accessor fails"); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location-origin-idna.sub.window.js b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-origin-idna.sub.window.js new file mode 100644 index 0000000000..83b030f886 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-origin-idna.sub.window.js @@ -0,0 +1,11 @@ +async_test(t => { + const frame = document.createElement("iframe"), + asciiOrigin = location.protocol + "//{{domains[天気の良い日]}}:" + location.port, + path = new URL("resources/post-your-origin.html", location).pathname; + frame.src = asciiOrigin + path; + self.onmessage = t.step_func_done(e => { + assert_equals(e.data.origin, asciiOrigin); + }); + document.body.appendChild(frame); + t.add_cleanup(() => frame.remove()); +}, "Test that location.origin returns ASCII"); diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location-pathname-setter-question-mark.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-pathname-setter-question-mark.html new file mode 100644 index 0000000000..09546020f7 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-pathname-setter-question-mark.html @@ -0,0 +1,16 @@ +<!doctype html> +<title>Set location.pathname to ?</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<div id=log></div> +<iframe src=/common/blank.html></iframe> +<script> +async_test((t) => { + onload = t.step_func(() => { + self[0].frameElement.onload = t.step_func_done(() => { + assert_equals(self[0].location.pathname, "/%3F") + }) + self[0].location.pathname = "?" + }) +}) +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location-prevent-extensions.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-prevent-extensions.html new file mode 100644 index 0000000000..a8c777aded --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-prevent-extensions.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>[[PreventExtensions]] on a Location object should return false</title> +<link rel="help" href="https://html.spec.whatwg.org/multipage/history.html#location-preventextensions"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> +"use strict"; + +test(() => { + assert_throws_js(TypeError, () => { + Object.preventExtensions(location); + }); +}, "Object.preventExtensions throws a TypeError"); + +test(() => { + assert_false(Reflect.preventExtensions(location)); +}, "Reflect.preventExtensions returns false"); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location-protocol-setter-non-broken-weird.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-protocol-setter-non-broken-weird.html new file mode 100644 index 0000000000..544eb4ad9a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-protocol-setter-non-broken-weird.html @@ -0,0 +1,35 @@ +<!doctype html> +<title>Set location.protocol from an HTTP URL</title> +<!-- In particular, valid non-broken schemes that are nevertheless not going to work --> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<div id=log></div> +<iframe src=/common/blank.html></iframe> +<iframe src=/common/blank.html></iframe> +<iframe src=/common/blank.html></iframe> +<iframe src=/common/blank.html></iframe> +<iframe src=/common/blank.html></iframe> +<iframe src=/common/blank.html></iframe> +<script> +self.onload = () => { + [ + 'x', + 'data', + 'file', + 'ftp', + 'http+x' + ].forEach((val, index) => { + async_test((t) => { + self[index].location.protocol = val + t.step_timeout(() => { + assert_equals(self[index].location.protocol, location.protocol) + assert_equals(self[index].location.host, location.host) + assert_equals(self[index].location.port, location.port) + t.done() + // Experimentally, 4 seconds is long enough for the navigation to + // complete, if it happens. + }, 4000) + }, "Set location.protocol to " + val) + }) +} +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location-protocol-setter-non-broken.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-protocol-setter-non-broken.html new file mode 100644 index 0000000000..585016eae9 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-protocol-setter-non-broken.html @@ -0,0 +1,63 @@ +<!doctype html> +<title>Set location.protocol to a non-broken-non-functioning scheme</title> +<!-- In particular, valid non-broken schemes that are nevertheless not going to work --> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<div id=log></div> +<script> +self.onload = () => { + [ + 'x', + 'data', + // 'mailto' opens an email client in Chrome and Firefox and then ends up passing anyway... + 'file', + 'ftp', + 'http+x' + ].forEach((val) => { + async_test((t) => { + // HTTP URL <iframe> + const frame = document.createElement("iframe") + t.add_cleanup(() => frame.remove()) + frame.src = "/common/blank.html" + frame.onload = t.step_func(() => { + frame.contentWindow.location.protocol = val + t.step_timeout(() => { + assert_equals(frame.contentWindow.location.protocol, location.protocol) + assert_equals(frame.contentWindow.location.host, location.host) + assert_equals(frame.contentWindow.location.port, location.port) + t.done() + // Matches the timeout from location-protocol-setter-non-broken-weird.html which suggests + // that 4 seconds is enough for a navigation to complete. + }, 4000) + }) + document.body.appendChild(frame) + }, "Set HTTP URL frame location.protocol to " + val) + + async_test((t) => { + // data URL <iframe> + const dataFrame = document.createElement("iframe") + t.add_cleanup(() => dataFrame.remove()) + const channel = new MessageChannel() + dataFrame.src = `data:text/html,<script> +onmessage = (e) => { + let result = false; + try { + location.protocol = e.data + } catch(e) { + result = true + } + setTimeout(() => e.ports[0].postMessage([result, location.protocol]), 4000) +} +<\/script>` + dataFrame.onload = t.step_func(() => { + dataFrame.contentWindow.postMessage(val, "*", [channel.port2]) + }) + channel.port1.onmessage = t.step_func_done((e) => { + assert_false(e.data[0]) + assert_equals(e.data[1], "data:") + }) + document.body.appendChild(dataFrame) + }, "Set data URL frame location.protocol to " + val) + }) +} +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location-protocol-setter-sameish.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-protocol-setter-sameish.html new file mode 100644 index 0000000000..c581ae35aa --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-protocol-setter-sameish.html @@ -0,0 +1,26 @@ +<!doctype html> +<title>Set location.protocol to the scheme it already was</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<div id=log></div> +<iframe src=/common/blank.html></iframe> +<iframe src=/common/blank.html></iframe> +<iframe src=/common/blank.html></iframe> +<iframe src=/common/blank.html></iframe> +<script> +self.onload = () => { + [ + "http", + "ht\x0Atp", + "http\x0A", + "\x09ht\x09\x0AtP" + ].forEach((val, index) => { + async_test(t => { + self[index].frameElement.onload = t.step_func_done(() => { + assert_equals(self[index].location.protocol, "http:"); + }); + self[index].location.protocol = val; + }, `Set location.protocol to ${encodeURI(val)} (percent-encoded here for clarity)`); + }); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location-protocol-setter-with-colon.sub.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-protocol-setter-with-colon.sub.html new file mode 100644 index 0000000000..0612b5c709 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-protocol-setter-with-colon.sub.html @@ -0,0 +1,52 @@ +<!doctype html> +<meta charset=utf-8> +<title></title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<div id=log></div> +<iframe id="existing" src="resources/post-your-protocol.html?existing"></iframe> +<iframe id="http-and-gunk" src="resources/post-your-protocol.html?http-and-gunk"></iframe> +<!-- iframe id="https-and-gunk" src="resources/post-your-protocol.html?https-and-gunk"></iframe --> +<script> +// NOTE: we do not listen to message events until our load event fires, so we +// only get them for the things we actually care about. +var wrapper_test = async_test("General setup"); +var tests = { + "existing": { test: async_test("Set location.protocol = location.protocol"), + result: location.protocol }, + "http-and-gunk": { test: async_test("Set location.protocol to http:gunk"), + result: "http:" }, + // We should really test the "https:gunk" case too, and assert that it ends up + // with a protocol of "https:", but can't. See comments below for why. +}; + +function messageListener(e) { + var data = e.data; + var id = data.id; + var t = tests[id].test; + t.step(function() { + assert_equals(data.protocol, tests[id].result, "Protocol should match"); + }) + t.done(); +} + +addEventListener("load", wrapper_test.step_func_done(function() { + addEventListener("message", messageListener); + + tests["existing"].test.step(function() { + var loc = document.getElementById("existing").contentWindow.location; + loc.protocol = loc.protocol; + }); + tests["http-and-gunk"].test.step(function() { + var loc = document.getElementById("http-and-gunk").contentWindow.location; + loc.protocol = "http:gunk"; + }); + // I wish we could test the https bit, but can't figure out a non-racy way to + // do it, because we need to change both protocol (to https) _and_ port to + // {{ports[https][0]}} to get a successful load unless we're running on the + // default http port, but the setter uses the current value, which doesn't get + // updated sync, as the url to start with for the set. Oh, and there's no + // good way to detect when the port set is "done" either. +})); + +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location-protocol-setter.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-protocol-setter.html new file mode 100644 index 0000000000..3d051e8cfb --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-protocol-setter.html @@ -0,0 +1,104 @@ +<!doctype html> +<title>Set location.protocol to schemes that throw</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<div id=log></div> +<iframe src="data:text/html,<script> +onmessage = (e) => { + let results = []; + e.data.forEach((val) => { + try { + location.protocol = val; + results.push('failure') + } catch(e) { + results.push(e.name) + } + }); + parent.postMessage(results, '*') +} +</script>"></iframe> +<iframe srcdoc="<script> +onmessage = (e) => { + let results = []; + e.data.forEach((val) => { + try { + location.protocol = val; + results.push('failure') + } catch(e) { + results.push(e.name) + } + }); + parent.postMessage(results, '*') +} +</script>"></iframe> +<script> + const broken = [ + '\x00', + '\x01', + '\x09', // becomes the empty string + '\x0A', // becomes the empty string + '\x0C', + '\x0D', + '\x20', + '\x21', + '\x7F', + '\x80', + '\xFF', + ':', + '†', + '\x00x', + '\x01x', + '\x20x', + '\x21x', + '\x7Fx', + '\x80x', + '\xFFx', + ':x', + '†x', + '\x00X', + '\x01X', + '\x20X', + '\x21X', + '\x7FX', + '\x80X', + '\xFFX', + ':X', + '†X', + 'x\x00', + 'x\x01', + 'x\x20', + 'x\x21', + 'x\x7F', + 'x\x80', + 'x\xFF', + 'x†', + 'X\x00', + 'X\x01', + 'X\x20', + 'X\x21', + 'X\x7F', + 'X\x80', + 'X\xFF', + 'X†', + ]; + + broken.forEach(val => { + test(() => { + assert_throws_dom("SyntaxError", () => { location.protocol = val }) + }, `${encodeURI(val)} (percent-encoded here for clarity) is not a scheme`) + }) + let c = 0 + async_test((t) => { + self.onload = t.step_func(() => { + self.onmessage = t.step_func((e) => { + assert_array_equals(e.data, broken.map(() => "SyntaxError")) + c++ + if(c === 2) { + t.done() + } + }) + self[0].postMessage(broken, "*") + self[1].postMessage(broken, "*") + }) + }, "Equivalent tests for data URL and srcdoc <iframe>s") +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location-prototype-no-toString-valueOf.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-prototype-no-toString-valueOf.html new file mode 100644 index 0000000000..56316320af --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-prototype-no-toString-valueOf.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Location.prototype objects don't have own "toString" and "valueOf" properties</title> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> +"use strict"; + +test(t => { + assert_not_own_property(Location.prototype, "toString"); + t.add_cleanup(() => { delete Location.prototype.toString; }); + + let val; + Object.defineProperty(Location.prototype, "toString", { + get: () => val, + set: newVal => { val = newVal; }, + enumerable: false, + configurable: true, + }); + + Location.prototype.toString = 2; + assert_equals(Location.prototype.toString, 2); +}, "'toString' accessor property is defined"); + +test(t => { + assert_not_own_property(Location.prototype, "toString"); + t.add_cleanup(() => { delete Location.prototype.toString; }); + + Location.prototype.toString = 4; + assert_equals(Location.prototype.toString, 4); +}, "'toString' data property is created via [[Set]]"); + +test(t => { + assert_not_own_property(Location.prototype, "valueOf"); + t.add_cleanup(() => { delete Location.prototype.valueOf; }); + + Object.defineProperty(Location.prototype, "valueOf", { + get: () => 6, + enumerable: true, + configurable: true, + }); + + assert_equals(Location.prototype.valueOf, 6); +}, "'valueOf' accessor property is defined"); + +test(t => { + assert_not_own_property(Location.prototype, "valueOf"); + t.add_cleanup(() => { delete Location.prototype.valueOf; }); + + Location.prototype.valueOf = 8; + assert_equals(Object.getOwnPropertyDescriptor(Location.prototype, "valueOf").value, 8); +}, "'valueOf' data property is created via [[Set]]"); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location-prototype-setting-cross-origin-domain.sub.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-prototype-setting-cross-origin-domain.sub.html new file mode 100644 index 0000000000..1e677b0365 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-prototype-setting-cross-origin-domain.sub.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>[[SetPrototypeOf]] on a Location object should not allow changing its value: cross-origin via document.domain</title> +<link rel="help" href="http://html.spec.whatwg.org/multipage/#location-setprototypeof"> +<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/test-setting-immutable-prototype.js"></script> + +<iframe src="/common/domain-setter.sub.html"></iframe> + +<script> +"use strict"; +// This page does *not* set document.domain, so it's cross-origin with the iframe +setup({ explicit_done: true }); + +window.onload = () => { + const targetLocation = frames[0].location; + const origProto = Object.getPrototypeOf(targetLocation); + + test(() => { + assert_equals(Object.getPrototypeOf(targetLocation), null); + }, "Cross-origin via document.domain: the prototype is null"); + + testSettingImmutablePrototype("Cross-origin via document.domain", targetLocation, origProto, + { isSameOriginDomain: false }); + + done(); +}; +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location-prototype-setting-cross-origin.sub.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-prototype-setting-cross-origin.sub.html new file mode 100644 index 0000000000..7bb6a27e21 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-prototype-setting-cross-origin.sub.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>[[SetPrototypeOf]] on a Location object should not allow changing its value: cross-origin</title> +<link rel="help" href="http://html.spec.whatwg.org/multipage/#location-setprototypeof"> +<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/test-setting-immutable-prototype.js"></script> + +<iframe src="//{{domains[www]}}:{{ports[http][1]}}/common/blank.html"></iframe> + +<script> +"use strict"; +setup({ explicit_done: true }); + +window.onload = () => { + const targetLocation = frames[0].location; + + test(() => { + assert_equals(Object.getPrototypeOf(targetLocation), null); + }, "Cross-origin: the prototype is null"); + + testSettingImmutablePrototype("Cross-origin", targetLocation, null, { isSameOriginDomain: false }); + + done(); +}; +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location-prototype-setting-goes-cross-origin-domain.sub.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-prototype-setting-goes-cross-origin-domain.sub.html new file mode 100644 index 0000000000..8966a814c5 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-prototype-setting-goes-cross-origin-domain.sub.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>[[SetPrototypeOf]] on a Location object should not allow changing its value: cross-origin via document.domain after initially getting the object</title> +<link rel="help" href="http://html.spec.whatwg.org/multipage/#location-setprototypeof"> +<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/test-setting-immutable-prototype.js"></script> + +<iframe src="/common/blank.html"></iframe> + +<script> +"use strict"; +setup({ explicit_done: true }); + +window.onload = () => { + const targetLocation = frames[0].location; + const origProto = Object.getPrototypeOf(targetLocation); + + test(() => { + assert_not_equals(origProto, null); + }, "Same-origin (for now): the prototype is accessible"); + + document.domain = "{{host}}"; + + test(() => { + assert_equals(Object.getPrototypeOf(targetLocation), null); + }, "Became cross-origin via document.domain: the prototype is now null"); + + testSettingImmutablePrototype("Became cross-origin via document.domain", targetLocation, null, { isSameOriginDomain: false }); + + testSettingImmutablePrototypeToNewValueOnly( + "Became cross-origin via document.domain", targetLocation, origProto, + "the original value from before going cross-origin", { isSameOriginDomain: false }); + + done(); +}; +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location-prototype-setting-same-origin-domain.sub.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-prototype-setting-same-origin-domain.sub.html new file mode 100644 index 0000000000..8ec7585daa --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-prototype-setting-same-origin-domain.sub.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>[[SetPrototypeOf]] on a Location object should not allow changing its value: cross-origin, but same-origin-domain</title> +<link rel="help" href="http://html.spec.whatwg.org/multipage/#location-setprototypeof"> +<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/test-setting-immutable-prototype.js"></script> + +<iframe src="//{{domains[www]}}:{{ports[http][1]}}/common/domain-setter.sub.html"></iframe> + +<script> +"use strict"; +document.domain = "{{host}}"; +setup({ explicit_done: true }); + +window.onload = () => { + const targetLocation = frames[0].location; + const origProto = Object.getPrototypeOf(targetLocation); + + test(() => { + assert_not_equals(origProto, null); + }, "Same-origin-domain prerequisite check: the original prototype is accessible"); + + testSettingImmutablePrototype("Same-origin-domain", targetLocation, origProto, { isSameOriginDomain: true }, frames[0]); + + done(); +}; +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location-prototype-setting-same-origin.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-prototype-setting-same-origin.html new file mode 100644 index 0000000000..f912004f73 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-prototype-setting-same-origin.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>[[SetPrototypeOf]] on a location object should not allow changing its value: same-origin</title> +<link rel="help" href="http://html.spec.whatwg.org/multipage/#location-setprototypeof"> +<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"> + +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src="/common/test-setting-immutable-prototype.js"></script> + +<script> +"use strict"; + +const origProto = Object.getPrototypeOf(location); + +test(() => { + assert_not_equals(origProto, null); +}, "Same-origin prerequisite check: the original prototype is accessible"); + +testSettingImmutablePrototype("Same-origin", location, origProto, { isSameOriginDomain: true }); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location-stringifier.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-stringifier.html new file mode 100644 index 0000000000..48bad7af0e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-stringifier.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<title>Location stringifier</title> +<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com"> +<link rel="help" href="https://webidl.spec.whatwg.org/#es-stringifier"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/common/stringifiers.js></script> +<div id=log></div> +<script> +test_stringifier_attribute(location, "href", true); + +test(function() { + const prop1 = Object.getOwnPropertyDescriptor(location, "toString"), + prop2 = Object.getOwnPropertyDescriptor(location, "href") + + assert_true(prop1.enumerable) + assert_false(prop1.writable) + assert_false(prop1.configurable) + + assert_true(prop2.enumerable) + assert_false(prop2.configurable) + assert_equals(typeof prop2.get, "function") +}) +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location-symbol-toprimitive.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-symbol-toprimitive.html new file mode 100644 index 0000000000..e666a3e703 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-symbol-toprimitive.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<title>Location Symbol.toPrimitive</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<div id=log></div> +<script> +test(() => { + assert_equals(location[Symbol.toPrimitive], undefined) + const prop = Object.getOwnPropertyDescriptor(location, Symbol.toPrimitive) + assert_false(prop.enumerable) + assert_false(prop.writable) + assert_false(prop.configurable) +}) +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location-tojson.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-tojson.html new file mode 100644 index 0000000000..5f20a6e15c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-tojson.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<title>Location has no toJSON</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<div id=log></div> +<script> +test(() => { + assert_equals(location.toJSON, undefined) + assert_equals(Object.getOwnPropertyDescriptor(location, "toJSON"), undefined) + assert_false(location.hasOwnProperty("toJSON")) +}) +</script> +<!-- See https://github.com/whatwg/html/pull/2294 for context. (And the HTML Standard of course.) --> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location-valueof.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-valueof.html new file mode 100644 index 0000000000..978bbb63a0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location-valueof.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<title>Location valueOf</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<div id=log></div> +<script> +test(() => { + assert_equals(location.valueOf, Object.prototype.valueOf) + assert_equals(typeof location.valueOf.call(5), "object") + const prop = Object.getOwnPropertyDescriptor(location, "valueOf") + assert_false(prop.enumerable) + assert_false(prop.writable) + assert_false(prop.configurable) +}) +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location_assign.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_assign.html new file mode 100644 index 0000000000..55f26d5660 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_assign.html @@ -0,0 +1,26 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>location_assign</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <script> + test(function () { + var href = location.href; + location.assign('#x'); + + assert_equals((href + "#x"), location.href, "location href"); + + }, "location assign"); + + test(function () { + var href = location.href; + assert_throws_dom('SYNTAX_ERR', function() { location.assign("http://:"); }); + assert_equals(location.href, href); + }, "URL that fails to parse"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location_assign_about_blank-1.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_assign_about_blank-1.html new file mode 100644 index 0000000000..b43598f2cd --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_assign_about_blank-1.html @@ -0,0 +1,2 @@ +<!doctype html> +Filler text diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location_assign_about_blank.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_assign_about_blank.html new file mode 100644 index 0000000000..f3f7cf26b8 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_assign_about_blank.html @@ -0,0 +1,24 @@ +<!doctype html> +<title>location.assign with initial about:blank browsing context</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<iframe></iframe> +<script> +var t = async_test(); +var history_length; +onload = t.step_func(function() { + setTimeout(t.step_func(function() { + var iframe = document.getElementsByTagName("iframe")[0]; + iframe.onload = t.step_func(function() { + setTimeout(t.step_func(function() { + assert_equals(history.length, history_length); + t.done(); + }), 100); + }); + history_length = history.length; + iframe.src = "location_assign_about_blank-1.html" + }), 100); +}); +</script> + diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location_hash.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_hash.html new file mode 100644 index 0000000000..c063e285ea --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_hash.html @@ -0,0 +1,62 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>location_hash</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <iframe id="srcdoc-iframe" + srcdoc="<div style='height: 200vh'></div><div id='test'></div>"></iframe> + <script> + function resetHash() { + location.hash = ""; + } + + test(function (t) { + t.add_cleanup(resetHash); + window.history.pushState(1, document.title, '#x=1'); + var hash = location.hash; + + assert_equals(hash, "#x=1", "hash"); + + }, "location hash"); + + var t = async_test("Setting location.hash on srcdoc iframe"); + addEventListener("load", t.step_func_done(function() { + var frameWin = document.getElementById("srcdoc-iframe").contentWindow; + assert_equals(frameWin.location.href, "about:srcdoc"); + assert_equals(frameWin.scrollY, 0, "Should not have scrolled yet"); + frameWin.location.hash = "test"; + assert_equals(frameWin.location.href, "about:srcdoc#test"); + assert_true(frameWin.scrollY > frameWin.innerHeight, + "Should have scrolled by more than one viewport height"); + })); + + test(function(t) { + t.add_cleanup(resetHash); + location.hash = "test"; + assert_equals(location.hash, "#test"); + }, "Setting hash should automatically include hash character"); + + test(function(t) { + t.add_cleanup(resetHash); + location.hash = "#not encoded"; + assert_equals(location.hash, "#not%20encoded"); + }, "Setting hash should encode incompatible characters"); + + test(function(t) { + t.add_cleanup(resetHash); + location.hash = "#already%20encoded"; + assert_equals(location.hash, "#already%20encoded"); + }, "Setting hash to an already encoded value should not double encode it"); + + test(function(t) { + t.add_cleanup(resetHash); + location.hash = "#mixed encoding%20here"; + assert_equals(location.hash, "#mixed%20encoding%20here"); + }, "Setting hash which is partially encoded should only encode incompatible characters"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location_host.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_host.html new file mode 100644 index 0000000000..d93bf47e50 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_host.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>location_host</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <script> + test(function () { + var host = location.host; + var url = location.href; + + var pos = url.indexOf("//"); + if (pos != -1) { + url = url.substr(pos+2, url.length-pos-2); + pos = url.indexOf("/"); + if (pos != -1) + url = url.substr(0, pos); + } + + assert_equals(host, url, "host"); + + }, "location host"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location_hostname.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_hostname.html new file mode 100644 index 0000000000..2ffa0e5fc8 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_hostname.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>location_hostname</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <script> + test(function () { + var hostname = location.hostname; + var url = location.href; + + var pos = url.indexOf("//"); + if (pos != -1) { + url = url.substr(pos+2, url.length-pos-2); + pos = url.indexOf(":"); + if (pos != -1) { + url = url.substr(0, pos); + } else { + pos = url.indexOf("/"); + if (pos != -1) + url = url.substr(0, pos); + } + } + + assert_equals(hostname, url, "hostname"); + + }, "location hostname"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location_href.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_href.html new file mode 100644 index 0000000000..1aa85dcdc8 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_href.html @@ -0,0 +1,19 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>location_href</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <script> + test(function () { + var href = location.href; + + assert_equals(href, document.URL, "href"); + + }, "location href"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location_origin.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_origin.html new file mode 100644 index 0000000000..2325f4018a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_origin.html @@ -0,0 +1,14 @@ +<!doctype html> +<meta charset="utf-8"> +<title></title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + test(function () { + assert_equals( + location.origin, + location.protocol + '//' + location.host, + "origin" + ); + }, "location origin"); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location_pathname.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_pathname.html new file mode 100644 index 0000000000..dea05d2f37 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_pathname.html @@ -0,0 +1,22 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>location_pathname</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <script> + test(function () { + var pathname = location.pathname; + var url = location.href + + url = url.replace(location.protocol + "//" + location.host, ""); + + assert_equals(pathname, url, "pathname"); + + }, "location pathname"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location_port.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_port.html new file mode 100644 index 0000000000..fa1308ca5d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_port.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>location_port</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <script> + test(function () { + var port = location.port; + var url = location.href; + + var pos = url.indexOf("//"); + if (pos != -1) { + url = url.substr(pos+2, url.length-pos-2); + pos = url.indexOf("/"); + if (pos != -1) + url = url.substr(0, pos); + pos = url.indexOf(":"); + if (pos != -1) + url = url.substr(pos+1, url.length-pos-1); + } + + assert_equals(port, url, "port"); + + }, "location port"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location_protocol.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_protocol.html new file mode 100644 index 0000000000..d28bd56393 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_protocol.html @@ -0,0 +1,25 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>location_protocol</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <script> + test(function () { + var protocol = location.protocol; + var url = location.href; + + var pos = url.indexOf("//"); + if (pos != -1) { + url = url.substr(0, pos); + } + + assert_equals(protocol, url, "protocol"); + + }, "location protocol"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location_reload-iframe.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_reload-iframe.html new file mode 100644 index 0000000000..f08cf5de3e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_reload-iframe.html @@ -0,0 +1,4 @@ +<script> + parent._ping(window.location.href) + if (parent._pingCount < 5) { location.reload(); } +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location_reload.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_reload.html new file mode 100644 index 0000000000..0a2d21d8d2 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_reload.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>location_reload</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body onload="startTest();"> + <div id="log"></div> + + <iframe></iframe> + + <script> + var history_length; + async_test(function(t) { + + var url = new URL("./location_reload-iframe.html", window.location).href; + + window._pingCount = 0; + window._ping = t.step_func(function(innerURL) { + // Some browsers keep 'about:blank' in the session history + if (_pingCount == 0) { + history_length = history.length; + } + assert_equals(url, innerURL, "iframe url (" + _pingCount + ")"); + assert_equals(history_length, history.length, "history length (" + _pingCount + ")"); + _pingCount++; + if (_pingCount == 5) { + t.done(); + } + }); + }); + + function startTest() { + var url = new URL("./location_reload-iframe.html", window.location).href; + var iframe = document.querySelector("iframe"); + iframe.src = url; + history_length = history.length; + } + </script> + + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location_reload_javascript_url.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_reload_javascript_url.html new file mode 100644 index 0000000000..737cafbcd3 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_reload_javascript_url.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>location_reload_javascript_url</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + + <iframe></iframe> + + <script> + async_test(function(t) { + const URL = "/common/blank.html"; + const URL2 = "/common/blank.html#foo"; + const JS_URL_TEXT = "javascript generated page"; + const JS_URL = "javascript:'<html>" + JS_URL_TEXT + "</html>'"; + + var iframe = document.querySelector("iframe"); + var count = 0; + iframe.onload = t.step_func(function() { + // The URL should initially be "blank.html", and then "blank.html#foo"; + // The textContent of the iframe's document should initially be blank, + // then become js generated text, and then be blank again after reload. + switch (count) { + case 0: + assert_equals(iframe.contentWindow.document.URL, + location.href.replace(location.pathname, URL), + "iframe url (" + count + ")"); + assert_equals(iframe.contentDocument.body.textContent, "", + "text of blank page"); + iframe.contentWindow.location = JS_URL; + iframe.contentWindow.location = URL2; + break; + case 1: + assert_equals(iframe.contentWindow.document.URL, + location.href.replace(location.pathname, URL2), + "iframe url (" + count + ")"); + assert_equals(iframe.contentDocument.body.textContent, + JS_URL_TEXT, "text of js generated page"); + iframe.contentWindow.location.reload(); + break; + case 2: + assert_equals(iframe.contentWindow.document.URL, + location.href.replace(location.pathname, URL2), + "iframe url (" + count + ")"); + assert_equals(iframe.contentDocument.body.textContent, "", + "text of blank page"); + t.done(); + break; + } + count++; + }); + iframe.src = URL; + }); + </script> + + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location_replace.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_replace.html new file mode 100644 index 0000000000..0593420d40 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_replace.html @@ -0,0 +1,26 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>location_replace</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <script> + test(function () { + var href = location.href; + location.replace('#x'); + + assert_equals((href + "#x"), location.href, "location href"); + + }, "location replace"); + + test(function () { + var href = location.href; + assert_throws_dom('SYNTAX_ERR', function() { location.replace("//"); }); + assert_equals(location.href, href); + }, "URL that fails to parse"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/location_search.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_search.html new file mode 100644 index 0000000000..f9db757841 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/location_search.html @@ -0,0 +1,20 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>location_search</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <script> + test(function () { + window.history.pushState(1, document.title, '?x=1'); + var search = location.search; + + assert_equals(search, "?x=1", "search"); + + }, "location search"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/no-browsing-context.window.js b/testing/web-platform/tests/html/browsers/history/the-location-interface/no-browsing-context.window.js new file mode 100644 index 0000000000..4077d90971 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/no-browsing-context.window.js @@ -0,0 +1,86 @@ +test(() => { + const frame = document.body.appendChild(document.createElement("iframe")), + win = frame.contentWindow, + loc = win.location; + frame.remove(); + assert_equals(win.location, loc); +}, "Window and Location are 1:1 after browsing context removal"); + +function bcLessLocation() { + const frame = document.body.appendChild(document.createElement("iframe")), + win = frame.contentWindow, + loc = win.location; + frame.remove(); + return loc; +} + +[ + { + "property": "href", + "expected": "about:blank", + "values": ["https://example.com/", "/", "http://test:test/", "test test", "test:test", "chrome:fail"] + }, + { + "property": "protocol", + "expected": "about:", + "values": ["http", "about", "test"] + }, + { + "property": "host", + "expected": "", + "values": ["example.com", "test test", "()"] + }, + { + "property": "hostname", + "expected": "", + "values": ["example.com"] + }, + { + "property": "port", + "expected": "", + "values": ["80", "", "443", "notaport"] + }, + { + "property": "pathname", + "expected": "blank", + "values": ["/", "x"] + }, + { + "property": "search", + "expected": "", + "values": ["test"] + }, + { + "property": "hash", + "expected": "", + "values": ["test", "#"] + } +].forEach(testSetup => { + testSetup.values.forEach(value => { + test(() => { + const loc = bcLessLocation(); + loc[testSetup.property] = value; + assert_equals(loc[testSetup.property], testSetup.expected); + }, "Setting `" + testSetup.property + "` to `" + value + "` of a `Location` object sans browsing context is a no-op"); + }); +}); + +test(() => { + const loc = bcLessLocation(); + assert_equals(loc.origin, "null"); +}, "Getting `origin` of a `Location` object sans browsing context should be \"null\""); + +["assign", "replace", "reload"].forEach(method => { + ["about:blank", "https://example.com/", "/", "http://test:test/", "test test", "test:test", "chrome:fail"].forEach(value => { + test(() => { + const loc = bcLessLocation(); + loc[method](value); + assert_equals(loc.href, "about:blank"); + }, "Invoking `" + method + "` with `" + value + "` on a `Location` object sans browsing context is a no-op"); + }); +}); + +test(() => { + const loc = bcLessLocation(); + assert_array_equals(loc.ancestorOrigins, []); +}, "Getting `ancestorOrigins` of a `Location` object sans browsing context should be []"); diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_click_assign_during_load-1.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_click_assign_during_load-1.html new file mode 100644 index 0000000000..c762ece3bc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_click_assign_during_load-1.html @@ -0,0 +1,7 @@ +<!doctype html> +<script> +opener.history_length = history.length; +</script> +<a onclick="location = 'manual_click_assign_during_load-2.html'; return false;" href>Click Here</a> +<p>Filler image to keep the page loading:</p> +<img src="/images/smiley.png?pipe=trickle(20:d1:r2)"> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_click_assign_during_load-2.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_click_assign_during_load-2.html new file mode 100644 index 0000000000..1bf7f41e00 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_click_assign_during_load-2.html @@ -0,0 +1,7 @@ +<!doctype html> +<p>This window should close itself and the test result appear in the original window +<script> +onload = function() { + setTimeout(function() {opener.do_test(history.length); window.close();}, 100); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_click_assign_during_load-manual.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_click_assign_during_load-manual.html new file mode 100644 index 0000000000..45a0e3e2fd --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_click_assign_during_load-manual.html @@ -0,0 +1,17 @@ +<!doctype html> +<title>Assignment to location with click during load</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<p>The popup blocker must be disabled for this test</p> +<div id="log"></div> +<script> +setup({timeout:3600000}); +var t = async_test(); +var win = window.open("manual_click_assign_during_load-1.html"); + +var history_length; +do_test = t.step_func(function(new_length) { + assert_equals(new_length, history_length + 1); + t.done(); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_click_location_replace_during_load-1.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_click_location_replace_during_load-1.html new file mode 100644 index 0000000000..e9d03e9364 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_click_location_replace_during_load-1.html @@ -0,0 +1,10 @@ +<!doctype html> +<script> +opener.history_length = history.length; +</script> +<a onclick="location.replace('manual_click_location_replace_during_load-2.html'); return false;" href>Click Here</a> +<p>Filler image to keep the page loading:</p> +<img> +<script> +document.images[0].src = "/images/smiley.png?pipe=trickle(20:d1:r2)&random=" + Math.random(); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_click_location_replace_during_load-2.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_click_location_replace_during_load-2.html new file mode 100644 index 0000000000..1bf7f41e00 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_click_location_replace_during_load-2.html @@ -0,0 +1,7 @@ +<!doctype html> +<p>This window should close itself and the test result appear in the original window +<script> +onload = function() { + setTimeout(function() {opener.do_test(history.length); window.close();}, 100); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_click_location_replace_during_load-manual.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_click_location_replace_during_load-manual.html new file mode 100644 index 0000000000..a453de34bf --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_click_location_replace_during_load-manual.html @@ -0,0 +1,17 @@ +<!doctype html> +<title>location.replace with click during load</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<p>The popup blocker must be disabled for this test</p> +<div id="log"></div> +<script> +setup({timeout:3600000}); +var t = async_test(); +var win = window.open("manual_click_location_replace_during_load-1.html"); + +var history_length; +do_test = t.step_func(function(new_length) { + assert_equals(new_length, history_length); + t.done(); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_click_replace_during_load-manual.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_click_replace_during_load-manual.html new file mode 100644 index 0000000000..482858bcd7 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_click_replace_during_load-manual.html @@ -0,0 +1,17 @@ +<!doctype html> +<title>Assignment to location with click during load</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<p>The popup blocker must be disabled for this test</p> +<div id="log"></div> +<script> +setup({timeout:3600000}); +var t = async_test(); +var win = window.open("manual_click_replace_during_load-1.html"); + +var history_length; +do_test = t.step_func(function(new_length) { + assert_equals(new_length, history_length); + t.done(); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_form_submit_assign_during_load-1.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_form_submit_assign_during_load-1.html new file mode 100644 index 0000000000..08f7e2dd68 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_form_submit_assign_during_load-1.html @@ -0,0 +1,9 @@ +<!doctype html> +<script> +opener.history_length = history.length; +</script> +<form onsubmit="location = 'manual_form_submit_assign_during_load-2.html'; return false;"> +<input type=submit value="Click Me"> +</form> +<p>Filler image to keep the page loading:</p> +<img src="/images/smiley.png?pipe=trickle(20:d1:r2)"> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_form_submit_assign_during_load-2.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_form_submit_assign_during_load-2.html new file mode 100644 index 0000000000..1bf7f41e00 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_form_submit_assign_during_load-2.html @@ -0,0 +1,7 @@ +<!doctype html> +<p>This window should close itself and the test result appear in the original window +<script> +onload = function() { + setTimeout(function() {opener.do_test(history.length); window.close();}, 100); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_form_submit_assign_during_load-manual.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_form_submit_assign_during_load-manual.html new file mode 100644 index 0000000000..d71b206ac5 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/manual_form_submit_assign_during_load-manual.html @@ -0,0 +1,17 @@ +<!doctype html> +<title>Assignment to location with form submit during load</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<p>The popup blocker must be disabled for this test</p> +<div id="log"></div> +<script> +setup({timeout:3600000}); +var t = async_test(); +var win = window.open("manual_form_submit_assign_during_load-1.html"); + +var history_length; +do_test = t.step_func(function(new_length) { + assert_equals(new_length, history_length + 1); + t.done(); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/reload_in_resize-1.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/reload_in_resize-1.html new file mode 100644 index 0000000000..05b44f4c40 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/reload_in_resize-1.html @@ -0,0 +1,15 @@ +<!doctype html> +<p>Resize this window. FAIL if the window doesn't close shortly afterwards.</p> +<script> +onload = opener.t.step_func(function() { + opener.load_count++; + if (opener.load_count > 1) { + opener.do_test(); + } +}) + +onresize = opener.t.step_func(function() { + opener.flag_resized(); + location.reload(); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/reload_in_resize-manual.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/reload_in_resize-manual.html new file mode 100644 index 0000000000..5cb3fac964 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/non-automated/reload_in_resize-manual.html @@ -0,0 +1,26 @@ +<!doctype html> +<title>Reload called from resize event</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<p>Resize the popup window. That window should then close and the result be presented here. If that window doesn't close after resize that's a FAIL.</p> +<div id="log"></div> +<script> +setup({timeout:3600000}) +var t = async_test(); +var load_count = 0; +var resized = false; +var win = window.open("reload_in_resize-1.html") + +flag_resized = t.step_func(function() { + resized = true; + setTimeout(do_test, 1000); +}); + +do_test = t.step_func(function() { + win.close(); + assert_true(resized, "Resize event happened"); + assert_equals(load_count, 1, "Number of load events"); + t.done(); +}); +</script> + diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/per-global.window.js b/testing/web-platform/tests/html/browsers/history/the-location-interface/per-global.window.js new file mode 100644 index 0000000000..b2956fd21f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/per-global.window.js @@ -0,0 +1,3 @@ +// META: script=/common/object-association.js + +testIsPerWindow("location"); diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_document_open_write-1.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_document_open_write-1.html new file mode 100644 index 0000000000..e1a2e811c9 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_document_open_write-1.html @@ -0,0 +1,19 @@ +<!doctype html> +1 +<script> +function f() { + opener.postMessage("original", "*"); + if (opener.data.length >= 2) { + // If we proceed here, then our document.write will be racing with the + // setTimeout in our opener. Just stop. + return; + } + setTimeout(function () { + document.open(); + document.write("<!doctype html>2<script>opener.postMessage('written', '*');<\/script>"); + document.close(); + }); +} + +window.onload = f +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_document_open_write.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_document_open_write.html new file mode 100644 index 0000000000..905ef88743 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_document_open_write.html @@ -0,0 +1,26 @@ +<!doctype html> +<title>Reload document with document.open and document.written content</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var win = window.open("reload_document_open_write-1.html"); +var t = async_test(); + +var data = []; + +window.onmessage = t.step_func(function(e) { + data.push(e.data); + if (data.length == 2) { + win.location.reload(); + } else if (data.length >= 3) { + setTimeout(t.step_func(function() { + assert_array_equals(data, ["original", "written", "original"]); + t.done(); + }), 500); + } +}); + +add_completion_callback(function() {win.close()}); +</script> + diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_document_write-1.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_document_write-1.html new file mode 100644 index 0000000000..9a08433920 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_document_write-1.html @@ -0,0 +1,4 @@ +<script> +document.write(Math.random()); +opener.postMessage(document.body.innerHTML, "*"); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_document_write.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_document_write.html new file mode 100644 index 0000000000..fb5fddc7da --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_document_write.html @@ -0,0 +1,21 @@ +<!doctype html> +<title>Reload document with document.written content</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var win = window.open("reload_document_write-1.html"); +var t = async_test(); + +window.onmessage = t.step_func(function(e) { + var initial_value = e.data; + win.location.reload(); + window.onmessage = t.step_func(function(e) { + assert_not_equals(e.data, initial_value); + t.done(); + }); +}); + +add_completion_callback(function() {win.close()}); +</script> + diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_document_write_onload-1.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_document_write_onload-1.html new file mode 100644 index 0000000000..36445af3c8 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_document_write_onload-1.html @@ -0,0 +1,9 @@ +<script> +function f() { + opener.postMessage("original", "*"); + document.write("<!doctype html>2<script>opener.postMessage('written', '*');<\/script>"); + document.close(); +} + +window.onload = f +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_document_write_onload.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_document_write_onload.html new file mode 100644 index 0000000000..b2cf31147a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_document_write_onload.html @@ -0,0 +1,26 @@ +<!doctype html> +<title>Reload document with document.written content written in load event</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var win = window.open("reload_document_write_onload-1.html"); +var t = async_test(); + +var data = []; + +window.onmessage = t.step_func(function(e) { + data.push(e.data); + if (data.length < 3) { + win.location.reload(); + } else { + setTimeout(t.step_func(function() { + assert_array_equals(data, ["original", "written", "written"]); + t.done(); + }), 500); + } +}); + +add_completion_callback(function() {win.close()}); +</script> + diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_post_1-manual.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_post_1-manual.html new file mode 100644 index 0000000000..95ef660559 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_post_1-manual.html @@ -0,0 +1,27 @@ +<!doctype html> +<title>Reload document with POST</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var win = window.open("resources/reload_post_1-1.py"); +var t = async_test(); +var posted = false; +var reloaded = false; + +next = t.step_func(function() { + +if (posted && !reloaded) { + reloaded = true; + win.location.reload(); +} else if (posted && reloaded) { + t.done(); +} else { + posted = true; + win.document.forms[0].submit(); +} + +}); + +add_completion_callback(function() {win.close()}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/resources/post-your-origin.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/resources/post-your-origin.html new file mode 100644 index 0000000000..a8a614c182 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/resources/post-your-origin.html @@ -0,0 +1,3 @@ +<script> +parent.postMessage({origin: location.origin}, "*"); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/resources/post-your-protocol.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/resources/post-your-protocol.html new file mode 100644 index 0000000000..c50d623893 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/resources/post-your-protocol.html @@ -0,0 +1,4 @@ +<script> + var id = location.search.substring(1); + parent.postMessage({ id: id, protocol: location.protocol }, "*"); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/resources/reload_post_1-1.py b/testing/web-platform/tests/html/browsers/history/the-location-interface/resources/reload_post_1-1.py new file mode 100644 index 0000000000..56397f07b4 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/resources/reload_post_1-1.py @@ -0,0 +1,13 @@ +def main(request, response): + headers = [(b"Content-Type", b"text/html")] + return headers, u''' + <script> + onload = function() {opener.next()} + document.write(Math.random()); + </script> + <form method="POST" action=""> + <input type=hidden name=test value=test> + <input type=submit> + </form> + <button onclick="location.reload()">Reload</button> + ''' diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/same-hash.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/same-hash.html new file mode 100644 index 0000000000..430a57662a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/same-hash.html @@ -0,0 +1,101 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Using the location interface to navigate to the same hash as the current one</title> +<link rel="help" href="https://github.com/whatwg/html/issues/7386"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<iframe id="i" srcdoc="<div style='height: 200vh'></div><div id='te<st'></div>"></iframe> + +<script type="module"> +setup({ explicit_done: true }); +await new Promise(r => window.onload = r); + +for (const value of ["#te<st", "te<st", "#te%3Cst", "te%3Cst"]) { + promise_test(async t => { + t.add_cleanup(() => { i.contentWindow.location.hash = ""; }); + assert_equals(i.contentWindow.scrollY, 0, "Setup: iframe starts at top"); + + i.contentWindow.location.hash = "te<st"; + await delayForFragmentNavigationScrolling(t); + + assert_greater_than(i.contentWindow.scrollY, i.contentWindow.innerHeight, "First hash assignment scrolls the iframe"); + + i.contentWindow.scroll({ top: 0, behavior: "instant" }); + assert_equals(i.contentWindow.scrollY, 0, "Resetting the scroll position must work"); + + i.contentWindow.location.hash = value; + await delayForFragmentNavigationScrolling(t); + + assert_equals(i.contentWindow.scrollY, 0, "Reassigning the same hash must not change the scroll position"); + }, `Using location.hash = "${value}" must not reset scroll position`); +} + +// These don't canonicalize to the current value of location.hash; the post-parsing version of +// "te<st" is "te%3Cst", uppercase. +for (const value of ["#te%3cst", "te%3cst"]) { + promise_test(async t => { + t.add_cleanup(() => { i.contentWindow.location.hash = ""; }); + assert_equals(i.contentWindow.scrollY, 0, "Setup: iframe starts at top"); + + i.contentWindow.location.hash = "te<st"; + await delayForFragmentNavigationScrolling(t); + + assert_greater_than(i.contentWindow.scrollY, i.contentWindow.innerHeight, "First hash assignment scrolls the iframe"); + + i.contentWindow.scroll({ top: 0, behavior: "instant" }); + assert_equals(i.contentWindow.scrollY, 0, "Resetting the scroll position must work"); + + i.contentWindow.location.hash = value; + await delayForFragmentNavigationScrolling(t); + + assert_greater_than(i.contentWindow.scrollY, i.contentWindow.innerHeight, "Reassigning the same-ish hash scrolls the iframe"); + }, `Using location.hash = "${value}" must reset scroll position`); +} + +for (const value of ["about:srcdoc#te<st", "about:srcdoc#te%3cst", "about:srcdoc#te%3Cst"]) { + promise_test(async t => { + t.add_cleanup(() => { i.contentWindow.location.hash = ""; }); + assert_equals(i.contentWindow.scrollY, 0, "Setup: iframe starts at top"); + + i.contentWindow.location.hash = "te<st"; + await delayForFragmentNavigationScrolling(t); + + assert_greater_than(i.contentWindow.scrollY, i.contentWindow.innerHeight, "First hash assignment scrolls the iframe"); + + i.contentWindow.scroll({ top: 0, behavior: "instant" }); + assert_equals(i.contentWindow.scrollY, 0, "Resetting the scroll position must work"); + + i.contentWindow.location.href = value; + await delayForFragmentNavigationScrolling(t); + + assert_greater_than(i.contentWindow.scrollY, i.contentWindow.innerHeight, "Setting href must scroll the iframe"); + }, `Using location.href = "${value}" must reset scroll position`); + + promise_test(async t => { + t.add_cleanup(() => { i.contentWindow.location.hash = ""; }); + assert_equals(i.contentWindow.scrollY, 0, "Setup: iframe starts at top"); + + i.contentWindow.location.hash = "te<st"; + await delayForFragmentNavigationScrolling(t); + + assert_greater_than(i.contentWindow.scrollY, i.contentWindow.innerHeight, "First hash assignment scrolls the iframe"); + + i.contentWindow.scroll({ top: 0, behavior: "instant" }); + assert_equals(i.contentWindow.scrollY, 0, "Resetting the scroll position must work"); + + i.contentWindow.location.assign(value); + await delayForFragmentNavigationScrolling(t); + + assert_greater_than(i.contentWindow.scrollY, i.contentWindow.innerHeight, "Setting href must scroll the iframe"); + }, `Using location.assign("${value}") must reset scroll position`); +} + +function delayForFragmentNavigationScrolling(t) { + // Scroll behavior for fragment navigation is set to "auto" in the spec, so we can't guarantee it's instant. + // In practice 10 milliseconds seems to be enough. + return new Promise(r => t.step_timeout(r, 10)); +} + +done(); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/same_origin_frame.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/same_origin_frame.html new file mode 100644 index 0000000000..953e696b2a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/same_origin_frame.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8"> + <title>Same-origin subframe for Location cyclic [[Prototype]] test</title> + <link rel="author" title="Jeff Walden" href="http://whereswalden.com/" /> +</head> +<body> +<!-- nothing to do, this window should be accessible to the parent frame --> +<p>Same-origin iframe</p> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/scripted_click_assign_during_load-1.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/scripted_click_assign_during_load-1.html new file mode 100644 index 0000000000..9561cabdd1 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/scripted_click_assign_during_load-1.html @@ -0,0 +1,10 @@ +<!doctype html> +<script> +opener.history_length = history.length; +</script> +<a onclick="location = 'scripted_click_assign_during_load-2.html'; return false;" href>Click Here</a> +<script> +document.links[0].click() +</script> +<p>Filler image to keep the page loading:</p> +<img src="/images/smiley.png?pipe=trickle(20:d1:r2)"> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/scripted_click_assign_during_load-2.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/scripted_click_assign_during_load-2.html new file mode 100644 index 0000000000..1bf7f41e00 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/scripted_click_assign_during_load-2.html @@ -0,0 +1,7 @@ +<!doctype html> +<p>This window should close itself and the test result appear in the original window +<script> +onload = function() { + setTimeout(function() {opener.do_test(history.length); window.close();}, 100); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/scripted_click_assign_during_load.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/scripted_click_assign_during_load.html new file mode 100644 index 0000000000..7ccc6cdc09 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/scripted_click_assign_during_load.html @@ -0,0 +1,17 @@ +<!doctype html> +<title>Assignment to location with click during load</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<p>The popup blocker must be disabled for this test</p> +<div id="log"></div> +<script> +setup({timeout:3600000}); +var t = async_test(); +var win = window.open("scripted_click_assign_during_load-1.html"); + +var history_length; +do_test = t.step_func(function(new_length) { + assert_equals(new_length, history_length); + t.done(); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/scripted_click_location_assign_during_load-1.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/scripted_click_location_assign_during_load-1.html new file mode 100644 index 0000000000..05bb42f967 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/scripted_click_location_assign_during_load-1.html @@ -0,0 +1,13 @@ +<!doctype html> +<script> +opener.history_length = history.length; +</script> +<a onclick="location.assign('scripted_click_location_assign_during_load-2.html'); return false;" href>Click Here</a> +<script> +document.links[0].click() +</script> +<p>Filler image to keep the page loading:</p> +<img> +<script> +document.images[0].src = "/images/smiley.png?pipe=trickle(20:d1:r2)&random=" + Math.random() +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/scripted_click_location_assign_during_load-2.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/scripted_click_location_assign_during_load-2.html new file mode 100644 index 0000000000..1bf7f41e00 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/scripted_click_location_assign_during_load-2.html @@ -0,0 +1,7 @@ +<!doctype html> +<p>This window should close itself and the test result appear in the original window +<script> +onload = function() { + setTimeout(function() {opener.do_test(history.length); window.close();}, 100); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/scripted_click_location_assign_during_load.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/scripted_click_location_assign_during_load.html new file mode 100644 index 0000000000..64f3ff942f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/scripted_click_location_assign_during_load.html @@ -0,0 +1,16 @@ +<!doctype html> +<title>location.assign with click during load</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<p>The popup blocker must be disabled for this test</p> +<div id="log"></div> +<script> +var t = async_test(); +var win = window.open("scripted_click_location_assign_during_load-1.html"); + +var history_length; +do_test = t.step_func(function(new_length) { + assert_equals(new_length, history_length + 1); + t.done(); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/scripted_form_submit_assign_during_load-1.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/scripted_form_submit_assign_during_load-1.html new file mode 100644 index 0000000000..ae07ac5cfc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/scripted_form_submit_assign_during_load-1.html @@ -0,0 +1,12 @@ +<!doctype html> +<script> +opener.history_length = history.length; +</script> +<form onsubmit="location = 'scripted_form_submit_assign_during_load-2.html'; return false;"> +<input type=submit value="Click Me"> +</form> +<script> +document.forms[0].elements[0].click() +</script> +<p>Filler image to keep the page loading:</p> +<img src="/images/smiley.png?pipe=trickle(20:d1:r2)"> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/scripted_form_submit_assign_during_load-2.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/scripted_form_submit_assign_during_load-2.html new file mode 100644 index 0000000000..1bf7f41e00 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/scripted_form_submit_assign_during_load-2.html @@ -0,0 +1,7 @@ +<!doctype html> +<p>This window should close itself and the test result appear in the original window +<script> +onload = function() { + setTimeout(function() {opener.do_test(history.length); window.close();}, 100); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/scripted_form_submit_assign_during_load.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/scripted_form_submit_assign_during_load.html new file mode 100644 index 0000000000..2a6eba63ab --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/scripted_form_submit_assign_during_load.html @@ -0,0 +1,17 @@ +<!doctype html> +<title>Assignment to location with form submit during load</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<p>The popup blocker must be disabled for this test</p> +<div id="log"></div> +<script> +setup({timeout:3600000}); +var t = async_test(); +var win = window.open("scripted_form_submit_assign_during_load-1.html"); + +var history_length; +do_test = t.step_func(function(new_length) { + assert_equals(new_length, history_length); + t.done(); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/security_location_0.htm b/testing/web-platform/tests/html/browsers/history/the-location-interface/security_location_0.htm new file mode 100644 index 0000000000..f509c23b18 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/security_location_0.htm @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<html> + <head> + <title>Location interface Security</title> + <link rel="author" title="Microsoft" href="http://www.microsoft.com/" /> + <link rel="help" href="https://html.spec.whatwg.org/multipage/#security-location" /> + <meta name="assert" content="access location object from different origins doesn't raise SECURITY_ERR exception" /> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <p>Access location object from different origins doesn't raise SECURITY_ERR exception</p> + <div id=log></div> + <script src="/common/get-host-info.sub.js"></script> + <script> + var runTest = async_test("Accessing location object from different origins doesn't raise SECURITY_ERR exception").step_func_done(function() { + var frame = document.getElementById('testframe'); + frame.setAttribute('onload', ''); + frame.contentWindow.location = get_host_info().HTTP_REMOTE_ORIGIN + "/"; + }); + </script> + <iframe id='testframe' onload="runTest()">Test Frame</iframe> + <script> + document.getElementById('testframe').setAttribute('src', get_host_info().HTTP_REMOTE_ORIGIN + '/'); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-session-history-of-browsing-contexts/navigation-in-onload.html b/testing/web-platform/tests/html/browsers/history/the-session-history-of-browsing-contexts/navigation-in-onload.html new file mode 100644 index 0000000000..367d7eea3e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-session-history-of-browsing-contexts/navigation-in-onload.html @@ -0,0 +1,37 @@ + +<!doctype html> +<meta charset=utf-8> +<title>Navigation in onload handler</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script> + var testFiles = [ + "navigation-in-onload_form-submission-1.html", + "navigation-in-onload_form-submission-iframe.html", + "navigation-in-onload_form-submission-dynamic-iframe.html" + ] + + var t = async_test(); + + function scheduleNextTest() { + setTimeout(runNextTest, 0); + } + + function runNextTest() { + var file = testFiles.shift(); + if (!file) { + t.done(); + return; + } + + window.open(file); + } + + function verify(actual, expected, desc) { + setTimeout(t.step_func(function() { + assert_equals(actual, expected, desc); + }), 0); + } + +</script> +<body onload="scheduleNextTest();"></body> diff --git a/testing/web-platform/tests/html/browsers/history/the-session-history-of-browsing-contexts/navigation-in-onload_form-submission-1.html b/testing/web-platform/tests/html/browsers/history/the-session-history-of-browsing-contexts/navigation-in-onload_form-submission-1.html new file mode 100644 index 0000000000..2079224467 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-session-history-of-browsing-contexts/navigation-in-onload_form-submission-1.html @@ -0,0 +1,15 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <title>Navigation in onload handler through form submission</title> + <script> + function redirect() { + document.querySelector("#redirectionForm").submit(); + } + </script> + </head> + <body onload="redirect();"> + <form id="redirectionForm" action="navigation-in-onload_form-submission-2.html" method="get"></form> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-session-history-of-browsing-contexts/navigation-in-onload_form-submission-2.html b/testing/web-platform/tests/html/browsers/history/the-session-history-of-browsing-contexts/navigation-in-onload_form-submission-2.html new file mode 100644 index 0000000000..11fbe2a2ba --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-session-history-of-browsing-contexts/navigation-in-onload_form-submission-2.html @@ -0,0 +1,22 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <title>Navigation in onload handler through form submission</title> + <script> + + // Verify is called after onload event to ensure history has been stable. + function verify() { + // Navigation in onload handler through form submission should not + // increse history length. + var runner = window.top.opener; + runner.verify(history.length, 1, + "history.length of subtest '" + top.document.title + "'."); + runner.scheduleNextTest(); + setTimeout(window.close.bind(top), 0); + } + </script> + </head> + <body onload="setTimeout(verify, 0);"> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-session-history-of-browsing-contexts/navigation-in-onload_form-submission-dynamic-iframe.html b/testing/web-platform/tests/html/browsers/history/the-session-history-of-browsing-contexts/navigation-in-onload_form-submission-dynamic-iframe.html new file mode 100644 index 0000000000..aabae3d770 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-session-history-of-browsing-contexts/navigation-in-onload_form-submission-dynamic-iframe.html @@ -0,0 +1,16 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <title>Navigation in onload handler through form submission in a dynamically created iframe</title> + <script> + function test() { + let testFrame = document.createElement("iframe"); + testFrame.src = "navigation-in-onload_form-submission-1.html"; + document.body.appendChild(testFrame); + } + </script> + </head> + <body onload="test();"> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/history/the-session-history-of-browsing-contexts/navigation-in-onload_form-submission-iframe.html b/testing/web-platform/tests/html/browsers/history/the-session-history-of-browsing-contexts/navigation-in-onload_form-submission-iframe.html new file mode 100644 index 0000000000..5fa786d1f5 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/history/the-session-history-of-browsing-contexts/navigation-in-onload_form-submission-iframe.html @@ -0,0 +1,10 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <title>Navigation in onload handler through form submission in an iframe</title> + </head> + <body> + <iframe id="testFrame" src="navigation-in-onload_form-submission-1.html"></iframe> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/offline/application-cache-api/api_status_checking-manual.html b/testing/web-platform/tests/html/browsers/offline/application-cache-api/api_status_checking-manual.html new file mode 100644 index 0000000000..a4a3b41a7d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/application-cache-api/api_status_checking-manual.html @@ -0,0 +1,23 @@ +<!DOCTYPE HTML> +<html manifest="../resources/manifest/clock.manifest"> + <head> + <title>Offline Application Cache - API_status_CHECKING</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <ol> + <li>Refresh the page.</li> + </ol> + + <div id="log"></div> + <script> + var t = async_test("checking status test"), + cache = window.applicationCache; + + cache.onchecking = t.step_func_done(function() { + assert_equals(cache.status, cache.CHECKING, "cache.status should equals cache.CHECKING"); + }); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/offline/application-cache-api/api_status_downloading-manual.html b/testing/web-platform/tests/html/browsers/offline/application-cache-api/api_status_downloading-manual.html new file mode 100644 index 0000000000..c09d11d787 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/application-cache-api/api_status_downloading-manual.html @@ -0,0 +1,24 @@ +<!DOCTYPE HTML> +<html manifest="../resources/manifest/clock.manifest"> + <head> + <title>Offline Application Cache - API_status_DOWNLOADING</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <ol> + <li>Modify the commented part in the manifest file (manifest/clock.manifest) on the server.</li> + <li>Refresh the page.</li> + </ol> + <div id="log"></div> + + <script> + var t = async_test("downloading status test"), + cache = window.applicationCache; + + cache.ondownloading = t.step_func_done(function() { + assert_equals(cache.status, cache.DOWNLOADING, "cache.status should equals cache.DOWNLOADING"); + }); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/offline/application-cache-api/api_status_obsolete-manual.html b/testing/web-platform/tests/html/browsers/offline/application-cache-api/api_status_obsolete-manual.html new file mode 100644 index 0000000000..77005644a2 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/application-cache-api/api_status_obsolete-manual.html @@ -0,0 +1,24 @@ +<!DOCTYPE HTML> +<html manifest="../resources/manifest/clock.manifest"> + <head> + <title>Offline Application Cache - API_status_OBSOLETE</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <ol> + <li>Remove the manifest file (manifest/clock.manifest) from the server.</li> + <li>Refresh the page.</li> + </ol> + <div id="log"></div> + + <script> + var t = async_test("obsolete status test"), + cache = window.applicationCache; + + cache.onobsolete = t.step_func_done(function() { + assert_equals(cache.status, cache.OBSOLETE, "cache.status should equals cache.OBSOLETE"); + }); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/offline/application-cache-api/api_status_updateready-manual.html b/testing/web-platform/tests/html/browsers/offline/application-cache-api/api_status_updateready-manual.html new file mode 100644 index 0000000000..7e1533374b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/application-cache-api/api_status_updateready-manual.html @@ -0,0 +1,25 @@ +<!DOCTYPE HTML> +<html manifest="../resources/manifest/clock.manifest"> + <head> + <title>Offline Application Cache - API_status_UPDATEREADY</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <ol> + <li>Modify the commented part in the manifest file (manifest/clock.manifest) on the server.</li> + <li>Refresh the page.</li> + </ol> + + <div id="log"></div> + + <script> + var t = async_test("updateready status test"), + cache = window.applicationCache; + + cache.onupdateready = t.step_func_done(function() { + assert_equals(cache.status, cache.UPDATEREADY, "cache.status should equals cache.UPDATEREADY"); + }); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/offline/application-cache-api/api_swapcache-manual.html b/testing/web-platform/tests/html/browsers/offline/application-cache-api/api_swapcache-manual.html new file mode 100644 index 0000000000..6649d980f6 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/application-cache-api/api_swapcache-manual.html @@ -0,0 +1,30 @@ +<!DOCTYPE HTML> +<html manifest="../resources/manifest/clock.manifest"> + <head> + <title>Offline Application Cache - API_swapCache</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <ol> + <li>Modify the part of comment in manifest file of server.</li> + <li>Refresh the page.</li> + </ol> + + <div id="log"></div> + + <script> + var t = async_test("swapCache method test"); + var cache = window.applicationCache; + + cache.onupdateready = t.step_func(function() { + try { + cache.swapCache(); + t.done(); + } catch (e) { + assert_unreached("swapCache method failed."); + } + }); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/offline/browser-state/navigator_online_event-manual.https.html b/testing/web-platform/tests/html/browsers/offline/browser-state/navigator_online_event-manual.https.html new file mode 100644 index 0000000000..81cad4f4fe --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/browser-state/navigator_online_event-manual.https.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>Offline Application Cache</title> + <link rel="stylesheet" href="../resources/css/result.css"> + </head> + <body> + <h1>navigator_online_event</h1> + + <ol> + <li>Change the 'work offline' mode.</li> + <li>If actual result and expected result are same, then test is <span class="manualpass">Pass</span>, otherwise <span class="manualfail">Fail</span>.</li> + </ol> + + <hr> + + <h2>Actual Result</h2> + <div id="actualResult"> + <span id="actualMsg"></span> + </div> + + <h2>Expected Result</h2> + <div id="expectedResult"> + <span id="expectedMsg">apply 'work offline': offline event is raised.<p>release 'work offline': online event is raised.</span> + </div> + <script> + + function showOnline(e) { + let msg = 'online event is raised'; + if (e.target != window) + msg += ' (on the WRONG target)'; + document.getElementById('actualMsg').innerHTML = msg + '.'; + } + + function showOffline(e) { + let msg = 'offline event is raised'; + if (e.target != window) + msg += ' (on the WRONG target)'; + document.getElementById('actualMsg').innerHTML = msg + '.'; + } + + window.addEventListener("online", showOnline, false); + window.addEventListener("offline", showOffline, false); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/offline/browser-state/navigator_online_online.https.html b/testing/web-platform/tests/html/browsers/offline/browser-state/navigator_online_online.https.html new file mode 100644 index 0000000000..81547c3fb9 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/browser-state/navigator_online_online.https.html @@ -0,0 +1,17 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>Offline Application Cache - navigator_online_online</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + + <script> + test(function() { + assert_true(navigator.onLine, "onLine test"); + }); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/offline/changestonetworkingmodel/original-id.json b/testing/web-platform/tests/html/browsers/offline/changestonetworkingmodel/original-id.json new file mode 100644 index 0000000000..2f77367c8f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/changestonetworkingmodel/original-id.json @@ -0,0 +1 @@ +{"original_id":"changesToNetworkingModel"}
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/offline/introduction-4/event_downloading-manual.html b/testing/web-platform/tests/html/browsers/offline/introduction-4/event_downloading-manual.html new file mode 100644 index 0000000000..26b003f06e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/introduction-4/event_downloading-manual.html @@ -0,0 +1,23 @@ +<!DOCTYPE HTML> +<html manifest="../resources/manifest/clock.manifest"> + <head> + <title>Offline Application Cache - Event_downloading</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <ol> + <li>Modify the commented part of the manifest file (manifest/clock.manifest) on the server.</li> + <li>Refresh the page.</li> + </ol> + + <div id="log"></div> + + <script> + var t = async_test("downloading event test"); + var cache = window.applicationCache; + + cache.ondownloading = t.done(); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/offline/introduction-4/event_error-manual.html b/testing/web-platform/tests/html/browsers/offline/introduction-4/event_error-manual.html new file mode 100644 index 0000000000..19abb3d6bf --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/introduction-4/event_error-manual.html @@ -0,0 +1,23 @@ +<!DOCTYPE HTML> +<html manifest="../resources/manifest/clock.manifest"> + <head> + <title>Offline Application Cache - Event_error</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <ol> + <li>Add a dummy file in the manifest file (manifest/clock.manifest).</li> + <li>Refresh the page.</li> + </ol> + + <div id="log"></div> + + <script> + var t = async_test("error event test"); + var cache = window.applicationCache; + + cache.onerror = t.done(); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/offline/introduction-4/event_obsolete-manual.html b/testing/web-platform/tests/html/browsers/offline/introduction-4/event_obsolete-manual.html new file mode 100644 index 0000000000..cab5e01cc7 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/introduction-4/event_obsolete-manual.html @@ -0,0 +1,23 @@ +<!DOCTYPE HTML> +<html manifest="../resources/manifest/clock.manifest"> + <head> + <title>Offline Application Cache - Event_obsolete</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <ol> + <li>Remove the manifest file (manifest/clock.manifest) from the server.</li> + <li>Refresh the page.</li> + </ol> + + <div id="log"></div> + + <script> + var t = async_test("obsolete event test"); + var cache = window.applicationCache; + + cache.onobsolete = t.done(); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/offline/introduction-4/event_updateready-manual.html b/testing/web-platform/tests/html/browsers/offline/introduction-4/event_updateready-manual.html new file mode 100644 index 0000000000..4de435144d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/introduction-4/event_updateready-manual.html @@ -0,0 +1,22 @@ +<!DOCTYPE HTML> +<html manifest="../resources/manifest/clock.manifest"> + <head> + <title>Offline Application Cache - Event_updateready</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <ol> + <li>Modify the commented part in the manifest file (manifest/clock.manifest) on the server.</li> + <li>Refresh the page.</li> + </ol> + <div id="log"></div> + + <script> + var t = async_test("updateready event test"); + var cache = window.applicationCache; + + cache.onupdateready = t.done(); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/offline/introduction-4/event_updateready_swapcache-manual.html b/testing/web-platform/tests/html/browsers/offline/introduction-4/event_updateready_swapcache-manual.html new file mode 100644 index 0000000000..da6cead026 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/introduction-4/event_updateready_swapcache-manual.html @@ -0,0 +1,30 @@ +<!DOCTYPE HTML> +<html manifest="../resources/manifest/clock.manifest"> + <head> + <title>Offline Application Cache - Event_updateready_swapCache</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <ol> + <li>Modify the commented part in manifest file (manifest/clock.manifest) on the server.</li> + <li>Refresh the page.</li> + </ol> + + <div id="log"></div> + + <script> + var t = async_test("swapCache method test after updateready event is raised"); + var cache = window.applicationCache; + + cache.onupdateready = t.step_func(function() { + try { + cache.swapCache(); + t.done(); + } catch (e) { + assert_unreached("swapCache method failed."); + } + }) + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/offline/manifest_main_empty-manual.https.html b/testing/web-platform/tests/html/browsers/offline/manifest_main_empty-manual.https.html new file mode 100644 index 0000000000..317aaa1137 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/manifest_main_empty-manual.https.html @@ -0,0 +1,14 @@ +<!DOCTYPE HTML> +<html manifest="resources/manifest/clock.manifest"> + <head> + <title>Offline Application Cache - manifest_main_empty</title> + <link rel="stylesheet" href="resources/css/result.css"> + </head> + <body> + <ol> + <li>Disable the network connection.</li> + <li>Refresh the page.</li> + <li>If the page is normally displayed, then test is <span class="manualpass"><b>PASS</b></span>, otherwise <span class="manualfail"><b>FAIL</b></span>.</li> + </ol> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/offline/manifest_notchanged_online-manual.https.html b/testing/web-platform/tests/html/browsers/offline/manifest_notchanged_online-manual.https.html new file mode 100644 index 0000000000..a464b426a7 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/manifest_notchanged_online-manual.https.html @@ -0,0 +1,19 @@ +<!DOCTYPE HTML> +<html manifest="resources/manifest/clock.manifest"> + <head> + <title>Offline Application Cache - manifest_notchanged_online</title> + <script src="resources/js/clock.js"></script> + <link rel="stylesheet" href="resources/css/result.css"> + <link rel="stylesheet" href="resources/css/clock.css"> + <link rel="stylesheet" href="resources/css/online.css" type="text/css" media="screen"> + </head> + <body> + <ol> + <li>Remove time element of this html document and not change manifest file.</li> + <li>Refresh the page.</li> + <li>If the page is normally displayed, then test is <span class="manualpass"><b>PASS</b></span>, otherwise <span class="manualfail"><b>FAIL</b></span>.</li> + </ol> + + <p class="connectivity" width="600">The time is: <output id="clock"></output></p> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/offline/manifest_section_empty-manual.https.html b/testing/web-platform/tests/html/browsers/offline/manifest_section_empty-manual.https.html new file mode 100644 index 0000000000..eea2dbba35 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/manifest_section_empty-manual.https.html @@ -0,0 +1,19 @@ +<!DOCTYPE HTML> +<html manifest="resources/manifest/section_empty.manifest"> + <head> + <title>Offline Application Cache - manifest_section_empty</title> + <script src="resources/js/clock.js"></script> + <link rel="stylesheet" href="resources/css/result.css"> + <link rel="stylesheet" href="resources/css/clock.css"> + <link rel="stylesheet" href="resources/css/online.css" type="text/css" media="screen"> + </head> + <body> + <ol> + <li>Disable the network connection.</li> + <li>Refresh the page.</li> + <li>If the time element and colors of result elements are normally displayed, then test is <span class="manualpass"><b>PASS</b></span>, otherwise <span class="manualfail"><b>FAIL</b></span>.</li> + </ol> + + <p class="connectivity" width="600">The time is: <output id="clock"></output></p> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/offline/manifest_section_many-manual.https.html b/testing/web-platform/tests/html/browsers/offline/manifest_section_many-manual.https.html new file mode 100644 index 0000000000..9378df1b40 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/manifest_section_many-manual.https.html @@ -0,0 +1,19 @@ +<!DOCTYPE HTML> +<html manifest="resources/manifest/section_many.manifest"> + <head> + <title>Offline Application Cache - manifest_section_many</title> + <script src="resources/js/clock.js"></script> + <link rel="stylesheet" href="resources/css/result.css"> + <link rel="stylesheet" href="resources/css/clock.css"> + <link rel="stylesheet" href="resources/css/online.css" type="text/css" media="screen"> + </head> + <body> + <ol type="1"> + <li>Disable the network connection.</li> + <li>Refresh the page.</li> + <li>If the time element and colors of result elements are normally displayed, then test is <span class="manualpass"><b>PASS</b></span>, otherwise <span class="manualfail"><b>FAIL</b></span>.</li> + </ol> + + <p class="connectivity" width="600">The time is: <output id="clock"></output></p> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/offline/resources/css/clock.css b/testing/web-platform/tests/html/browsers/offline/resources/css/clock.css new file mode 100644 index 0000000000..fa406d0fbe --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/resources/css/clock.css @@ -0,0 +1 @@ +output { font: 1em sans-serif; }
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/offline/resources/css/offline.css b/testing/web-platform/tests/html/browsers/offline/resources/css/offline.css new file mode 100644 index 0000000000..76b7f39853 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/resources/css/offline.css @@ -0,0 +1,5 @@ +.connectivity { + color: #fff; + background: red; + padding: 20px; +}
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/offline/resources/css/online.css b/testing/web-platform/tests/html/browsers/offline/resources/css/online.css new file mode 100644 index 0000000000..39efcb2ab4 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/resources/css/online.css @@ -0,0 +1,5 @@ +.connectivity { + color: #fff; + background: blue; + padding: 20px; +}
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/offline/resources/css/result.css b/testing/web-platform/tests/html/browsers/offline/resources/css/result.css new file mode 100644 index 0000000000..7d784b8ab5 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/resources/css/result.css @@ -0,0 +1,11 @@ +.manualpass { + color: green; +} +.manualfail { + color: red; +}.pass { + color: green; +} +.fail { + color: red; +} diff --git a/testing/web-platform/tests/html/browsers/offline/resources/html/clock.html b/testing/web-platform/tests/html/browsers/offline/resources/html/clock.html new file mode 100644 index 0000000000..6b8949a6b1 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/resources/html/clock.html @@ -0,0 +1,12 @@ +<!-- clock.html --> +<!DOCTYPE HTML> +<html> + <head> + <title>Clock</title> + <script src="../js/clock.js"></script> + <link rel="stylesheet" href="../css/clock.css"> + </head> + <body> + <p>The time is: <output id="clock"></output></p> + </body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/offline/resources/js/clock.js b/testing/web-platform/tests/html/browsers/offline/resources/js/clock.js new file mode 100644 index 0000000000..1ac0dca539 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/resources/js/clock.js @@ -0,0 +1,3 @@ +setTimeout(function () { + document.getElementById('clock').value = new Date(); +}, 1000);
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/offline/resources/manifest/clock.manifest b/testing/web-platform/tests/html/browsers/offline/resources/manifest/clock.manifest new file mode 100644 index 0000000000..a61aae6c61 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/resources/manifest/clock.manifest @@ -0,0 +1,17 @@ +CACHE MANIFEST + +# Version 1 + +CACHE: +../css/clock.css +../js/clock.js +../css/result.css +../css/offline.css +/resources/testharness.js +/resources/testharnessreport.js + +NETWORK: +../html/clock.html + +FALLBACK: +../css/online.css ../css/offline.css
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/offline/resources/manifest/section_empty.manifest b/testing/web-platform/tests/html/browsers/offline/resources/manifest/section_empty.manifest new file mode 100644 index 0000000000..a23b9013be --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/resources/manifest/section_empty.manifest @@ -0,0 +1,10 @@ +CACHE MANIFEST + +# Version 1 + +../css/clock.css +../js/clock.js +../css/result.css +../css/online.css +/resources/testharness.js +/resources/testharnessreport.js
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/offline/resources/manifest/section_many.manifest b/testing/web-platform/tests/html/browsers/offline/resources/manifest/section_many.manifest new file mode 100644 index 0000000000..7e5e5e9995 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/resources/manifest/section_many.manifest @@ -0,0 +1,19 @@ +CACHE MANIFEST + +# Version 1 + +CACHE: +../css/clock.css +../js/clock.js + +CACHE: +../css/result.css +../css/offline.css +/resources/testharness.js +/resources/testharnessreport.js + +NETWORK: +../html/clock.html + +FALLBACK: +../css/online.css ../css/offline.css
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/offline/resources/manifest/url_check.manifest b/testing/web-platform/tests/html/browsers/offline/resources/manifest/url_check.manifest new file mode 100644 index 0000000000..041df5e55f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/resources/manifest/url_check.manifest @@ -0,0 +1,17 @@ +CACHE MANIFEST + +# Version 1 + +CACHE: +../css/cl#ock.css +../js/clock.js +../css/result.css +../css/offline.css +/resources/testharness.js +/resources/testharnessreport.js + +NETWORK: +../html/clock.html + +FALLBACK: +../css/online.css ../css/offline.css
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/offline/section_network_offline-manual.https.html b/testing/web-platform/tests/html/browsers/offline/section_network_offline-manual.https.html new file mode 100644 index 0000000000..c4121f5bc5 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/section_network_offline-manual.https.html @@ -0,0 +1,17 @@ +<!DOCTYPE HTML> +<html manifest="resources/manifest/clock.manifest"> + <head> + <title>Offline Application Cache - Section_network_offline</title> + <link rel="stylesheet" href="resources/css/result.css"> + </head> + <body> + <ol> + <li>Disable the network connection.</li> + <li>Refresh the page.</li> + <li>If only the frame element can't be loaded, then test is <span class="manualpass"><b>PASS</b></span>, otherwise <span class="manualfail"><b>FAIL</b></span>.</li> + </ol> + + <IFRAME id="TestFrame" name="TestWindow" src="html/clock.html" width="600" height="50" scrolling="auto" frameborder="1"> + </IFRAME> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/offline/section_network_online-manual.https.html b/testing/web-platform/tests/html/browsers/offline/section_network_online-manual.https.html new file mode 100644 index 0000000000..a5d8e59406 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/offline/section_network_online-manual.https.html @@ -0,0 +1,16 @@ +<!DOCTYPE HTML> +<html manifest="resources/manifest/clock.manifest"> + <head> + <title>Offline Application Cache - Section_network_online</title> + <link rel="stylesheet" href="resources/css/result.css"> + </head> + <body> + <ol> + <li>Refresh the page.</li> + <li>If the frame element is loaded, then test is <span class="manualpass"><b>PASS</b></span>, otherwise <span class="manualfail"><b>FAIL</b></span>.</li> + </ol> + + <IFRAME id="TestFrame" name="TestWindow" src="html/clock.html" width="600" height="50" scrolling="auto" frameborder="1"> + </IFRAME> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-due-to-document-domain-only.html b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-due-to-document-domain-only.html new file mode 100644 index 0000000000..425374faec --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-due-to-document-domain-only.html @@ -0,0 +1,33 @@ +<!doctype html> +<title>Cross-origin due to document.domain</title> +<meta charset=utf-8> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<div id=log></div> +<iframe src=resources/cross-origin-due-to-document-domain-only-helper.html></iframe> +<script> +async_test(t => { + onload = t.step_func_done(() => { + const frame = document.querySelector("iframe"); + const innerSelf = self[0]; + const innerLocation = innerSelf.location; + const innerDocument = innerSelf.document; + assert_equals(innerLocation.host, location.host); + assert_true(innerSelf.expandosForever); + assert_true(innerLocation.expandosForever); + assert_equals(frame.contentWindow, innerSelf); + assert_equals(frame.contentDocument, innerDocument); + innerSelf.setDocumentDomain(); + assert_throws_dom("SecurityError", () => innerSelf.expandosForever); + assert_throws_dom("SecurityError", () => innerLocation.expandosForever); + assert_throws_dom("SecurityError", () => innerLocation.host); + assert_equals(innerSelf.parent, self); + assert_throws_dom("SecurityError", () => innerSelf.frameElement); + assert_throws_dom("SecurityError", () => innerLocation.reload()); + assert_equals(frame.contentWindow, innerSelf); + assert_equals(frame.contentDocument, null); + // Cross-origin Document object obtained before it became cross-origin has no protections + assert_equals(innerDocument.URL, frame.src); + }); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-caching.html b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-caching.html new file mode 100644 index 0000000000..a8af18d106 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-caching.html @@ -0,0 +1,49 @@ +<!doctype html> +<meta charset=utf-8> +<title>Cross-origin methods and accessors are cached per Realm via[[CrossOriginPropertyDescriptorMap]]</title> +<link rel="help" href="https://html.spec.whatwg.org/multipage/browsers.html#crossorigingetownpropertyhelper-(-o,-p-)"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="cross-origin-objects-function-common.js"></script> +<div id=log></div> +<script> +"use strict"; + +promise_test(async t => { + const w = await makeCrossOriginWindow(t); + for (const {key} of crossOriginWindowMethods) { + assert_equals(w[key], w[key], `w.${key} via [[Get]]`); + const desc1 = Object.getOwnPropertyDescriptor(w, key); + const desc2 = Object.getOwnPropertyDescriptor(w, key); + assert_equals(desc1.value, desc2.value, `w.${key} via [[GetOwnProperty]]`); + } +}, "Cross-origin Window methods are cached"); + +promise_test(async t => { + const w = await makeCrossOriginWindow(t); + for (const {key} of crossOriginWindowAccessors) { + const desc1 = Object.getOwnPropertyDescriptor(w, key); + const desc2 = Object.getOwnPropertyDescriptor(w, key); + assert_equals(desc1.get, desc2.get, `w.${key} getter`); + if (key === "location") { + assert_equals(desc1.set, desc2.set, `w.${key} setter`); + } + } +}, "Cross-origin Window accessors are cached"); + +promise_test(async t => { + const w = await makeCrossOriginWindow(t); + assert_equals(w.location.replace, w.location.replace, "via [[Get]]"); + const desc1 = Object.getOwnPropertyDescriptor(w.location, "replace"); + const desc2 = Object.getOwnPropertyDescriptor(w.location, "replace"); + assert_equals(desc1.value, desc2.value, "via [[GetOwnProperty]]"); +}, "Cross-origin Location `replace` method is cached"); + +promise_test(async t => { + const w = await makeCrossOriginWindow(t); + const desc1 = Object.getOwnPropertyDescriptor(w.location, "href"); + const desc2 = Object.getOwnPropertyDescriptor(w.location, "href"); + assert_equals(desc1.set, desc2.set); +}, "Cross-origin Location `href` setter is cached"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-common.js b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-common.js new file mode 100644 index 0000000000..3b93b498a2 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-common.js @@ -0,0 +1,34 @@ +"use strict"; + +const crossOriginWindowMethods = [ + {key: "close", length: 0}, + {key: "focus", length: 0}, + {key: "blur", length: 0}, + {key: "postMessage", length: 1}, +]; + +const crossOriginWindowAccessors = [ + "window", + "self", + "location", + "closed", + "frames", + "length", + "top", + "opener", + "parent", +].map(key => ({key})); + +const makeCrossOriginWindow = t => { + const iframe = document.createElement("iframe"); + const path = location.pathname.slice(0, location.pathname.lastIndexOf("/")) + "/frame.html"; + iframe.src = get_host_info().HTTP_REMOTE_ORIGIN + path; + + return new Promise((resolve, reject) => { + iframe.onload = () => { resolve(iframe.contentWindow); }; + iframe.onerror = reject; + + document.body.append(iframe); + t.add_cleanup(() => { iframe.remove(); }); + }); +}; diff --git a/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-length.html b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-length.html new file mode 100644 index 0000000000..466915a461 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-length.html @@ -0,0 +1,45 @@ +<!doctype html> +<meta charset=utf-8> +<title>Cross-origin methods and accessors are created with correct 'length' property</title> +<link rel="help" href="https://html.spec.whatwg.org/multipage/browsers.html#crossorigingetownpropertyhelper-(-o,-p-)"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="cross-origin-objects-function-common.js"></script> +<div id=log></div> +<script> +"use strict"; + +promise_test(async t => { + const w = await makeCrossOriginWindow(t); + for (const {key, length} of crossOriginWindowMethods) { + assert_equals(w[key].length, length, `w.${key} via [[Get]]`); + const desc = Object.getOwnPropertyDescriptor(w, key); + assert_equals(desc.value.length, length, `w.${key} via [[GetOwnProperty]]`); + } +}, "Cross-origin Window methods have correct 'length'"); + +promise_test(async t => { + const w = await makeCrossOriginWindow(t); + for (const {key} of crossOriginWindowAccessors) { + const desc = Object.getOwnPropertyDescriptor(w, key); + assert_equals(desc.get.length, 0, `w.${key}`); + if (key === "location") { + assert_equals(desc.set.length, 1, `w.${key}`); + } + } +}, "Cross-origin Window accessors have correct 'length'"); + +promise_test(async t => { + const w = await makeCrossOriginWindow(t); + assert_equals(w.location.replace.length, 1); + const desc = Object.getOwnPropertyDescriptor(w.location, "replace"); + assert_equals(desc.value.length, 1); +}, "Cross-origin Location `replace` method has correct 'length'"); + +promise_test(async t => { + const w = await makeCrossOriginWindow(t); + const desc = Object.getOwnPropertyDescriptor(w.location, "href"); + assert_equals(desc.set.length, 1); +}, "Cross-origin Location `href` setter has correct 'length'"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-name.html b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-name.html new file mode 100644 index 0000000000..167c30e8f3 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-name.html @@ -0,0 +1,45 @@ +<!doctype html> +<meta charset=utf-8> +<title>Cross-origin methods and accessors are created with correct 'name' property</title> +<link rel="help" href="https://html.spec.whatwg.org/multipage/browsers.html#crossorigingetownpropertyhelper-(-o,-p-)"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="cross-origin-objects-function-common.js"></script> +<div id=log></div> +<script> +"use strict"; + +promise_test(async t => { + const w = await makeCrossOriginWindow(t); + for (const {key} of crossOriginWindowMethods) { + assert_equals(w[key].name, key, `w.${key} via [[Get]]`); + const desc = Object.getOwnPropertyDescriptor(w, key); + assert_equals(desc.value.name, key, `w.${key} via [[GetOwnProperty]]`); + } +}, "Cross-origin Window methods have correct 'name'"); + +promise_test(async t => { + const w = await makeCrossOriginWindow(t); + for (const {key} of crossOriginWindowAccessors) { + const desc = Object.getOwnPropertyDescriptor(w, key); + assert_equals(desc.get.name, `get ${key}`); + if (key === "location") { + assert_equals(desc.set.name, `set ${key}`); + } + } +}, "Cross-origin Window accessors have correct 'name'"); + +promise_test(async t => { + const w = await makeCrossOriginWindow(t); + assert_equals(w.location.replace.name, "replace"); + const desc = Object.getOwnPropertyDescriptor(w.location, "replace"); + assert_equals(desc.value.name, "replace"); +}, "Cross-origin Location `replace` method has correct 'name'"); + +promise_test(async t => { + const w = await makeCrossOriginWindow(t); + const desc = Object.getOwnPropertyDescriptor(w.location, "href"); + assert_equals(desc.set.name, "set href"); +}, "Cross-origin Location `href` setter has correct 'name'"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-on-new-window.html b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-on-new-window.html new file mode 100644 index 0000000000..3ad0de6a3a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-on-new-window.html @@ -0,0 +1,25 @@ +<!doctype html> +<meta charset=utf-8> +<meta name="timeout" content="long"> +<title>Cross-origin behavior of Window and Location on new Window</title> +<link rel="author" title="Bobby Holley (:bholley)" href="bobbyholley@gmail.com"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#security-window"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#security-location"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id=log></div> +<script> +setup({explicit_done: true}); + +window.addEventListener('message', function onmessage(evt) { + window.removeEventListener('message', onmessage); + test(function() { + var results = evt.data; + assert_true(results.length > 0, 'Need results'); + results.forEach(function(r) { assert_true(r.pass, r.message); }); + }, "Cross-origin object identity preserved across document.domain"); + win.close(); + done(); +}); +var win = window.open('win-documentdomain.sub.html'); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects.html b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects.html new file mode 100644 index 0000000000..d1b6cabc0f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects.html @@ -0,0 +1,718 @@ +<!doctype html> +<meta charset=utf-8> +<meta name="timeout" content="long"> +<title>Cross-origin behavior of Window and Location</title> +<link rel="author" title="Bobby Holley (:bholley)" href="bobbyholley@gmail.com"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#security-window"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#security-location"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<div id=log></div> +<iframe id="B"></iframe> +<iframe id="C"></iframe> +<iframe id="D"></iframe> +<iframe id="E"></iframe> +<iframe id="F"></iframe> +<iframe id="G"></iframe> +<iframe id="H"></iframe> +<script> + +/* + * Setup boilerplate. This gives us a same-origin window "B", cross-origin + * windows "C" and "D", initially same-origin but then changing document.domain + * windows "E" and "F", and not-same-site (also cross-origin, of course) windows + * "G" and "H". + */ +var host_info = get_host_info(); + +setup({explicit_done: true}); +path = location.pathname.substring(0, location.pathname.lastIndexOf('/')) + '/frame.html'; +pathWithThen = location.pathname.substring(0, location.pathname.lastIndexOf('/')) + '/frame-with-then.html'; +var B = document.getElementById('B').contentWindow; +var C = document.getElementById('C').contentWindow; +var D = document.getElementById('D').contentWindow; +var E = document.getElementById('E').contentWindow; +var F = document.getElementById('F').contentWindow; +var G = document.getElementById('G').contentWindow; +var H = document.getElementById('H').contentWindow; +B.frameElement.uriToLoad = path; +C.frameElement.uriToLoad = get_host_info().HTTP_REMOTE_ORIGIN + path; +D.frameElement.uriToLoad = get_host_info().HTTP_REMOTE_ORIGIN + pathWithThen; +E.frameElement.uriToLoad = path + "?setdomain"; +F.frameElement.uriToLoad = pathWithThen + "?setdomain"; +G.frameElement.uriToLoad = get_host_info().HTTP_NOTSAMESITE_ORIGIN + path; +H.frameElement.uriToLoad = get_host_info().HTTP_NOTSAMESITE_ORIGIN + pathWithThen; + +function winName(win) { + var iframes = document.getElementsByTagName('iframe'); + iframes.find = Array.prototype.find; + var found = iframes.find(function (ifr) { + return ifr.contentWindow == win; + }); + if (found) { + return found.id; + } + return "UNKNOWN"; +} + +function reloadSubframes(cb) { + var iframes = document.getElementsByTagName('iframe'); + iframes.forEach = Array.prototype.forEach; + var count = 0; + function frameLoaded() { + this.onload = null; + if (++count == iframes.length) + cb(); + } + iframes.forEach(function(ifr) { ifr.onload = frameLoaded; ifr.setAttribute('src', ifr.uriToLoad); }); +} +function isObject(x) { return Object(x) === x; } + +/* + * Note: we eschew assert_equals in a lot of these tests, since the harness ends + * up throwing when it tries to format a message involving a cross-origin object. + */ + +/* + * List of tests. Each test is actually a pair: an array of tests to run and a + * boolean for whether these are promise tests. We reload all the subframes in + * between running each toplevel test. This is done to avoid having to reload + * all the subframes for every single test, which is overkill: some of these + * tests are known to touch only one subframe. And doing it makes the test + * really slow. + */ +var testList = []; +function addTest(func, desc) { + testList.push( + {tests: [ + {func: func.bind(null, C), + desc: desc + " (cross-origin)"}, + {func: func.bind(null, E), + desc: desc + " (same-origin + document.domain)"}, + {func: func.bind(null, G), + desc: desc + " (cross-site)"}], + promiseTest: false}); +} + +function addPromiseTest(func, desc) { + testList.push( + {tests: [ + {func: func.bind(null, C), + desc: desc + " (cross-origin)"}, + {func: func.bind(null, E), + desc: desc + " (same-origin + document.domain)"}, + {func: func.bind(null, G), + desc: desc + " (cross-site)"}], + promiseTest: true}); +} + +/** + * Similar helpers, but for the subframes that load frame-with-then.html + */ +function addThenTest(func, desc) { + testList.push( + {tests: [ + {func: func.bind(null, D), + desc: desc + " (cross-origin)"}, + {func: func.bind(null, F), + desc: desc + " (same-origin + document.domain)"}, + {func: func.bind(null, H), + desc: desc + " (cross-site)"}], + promiseTest: false}); +} + +function addPromiseThenTest(func, desc) { + testList.push( + {tests: [ + {func: func.bind(null, D), + desc: desc + " (cross-origin)"}, + {func: func.bind(null, F), + desc: desc + " (same-origin + document.domain)"}, + {func: func.bind(null, H), + desc: desc + " (cross-site)"}], + promiseTest: true}); +} + +/* + * Basic smoke tests for same-origin and cross-origin behaviors. + */ + +addTest(function(win) { + // Note: we do not check location.host as its default port semantics are hard to reflect statically + assert_equals(location.hostname, host_info.ORIGINAL_HOST, 'Need to run the top-level test from domain ' + host_info.ORIGINAL_HOST); + assert_equals(get_port(location), host_info.HTTP_PORT, 'Need to run the top-level test from port ' + host_info.HTTP_PORT); + assert_equals(B.parent, window, "window.parent works same-origin"); + assert_equals(win.parent, window, "window.parent works cross-origin"); + assert_equals(B.location.pathname, path, "location.href works same-origin"); + assert_throws_dom("SecurityError", function() { win.location.pathname; }, "location.pathname throws cross-origin"); + assert_equals(B.frames, 'override', "Overrides visible in the same-origin case"); + assert_equals(win.frames, win, "Overrides invisible in the cross-origin case"); + assert_equals(B.focus, 'override', "Overrides visible in the same-origin case"); + checkFunction(win.focus, Function.prototype); + assert_not_equals(win.focus, focus, "Overrides invisible in the cross-origin case"); +}, "Basic sanity-checking"); + +/* + * Tests regarding which properties are allowed cross-origin. + * + * Also tests for [[GetOwnProperty]] and [[HasOwnProperty]] behavior. + */ + +var allowedSymbols = [Symbol.toStringTag, Symbol.hasInstance, + Symbol.isConcatSpreadable]; +var windowAllowlists = { + namedFrames: ['donotleakme'], + indices: ['0', '1'], + getters: ['location', 'window', 'frames', 'self', 'top', 'parent', + 'opener', 'closed', 'length'], + setters: ['location'], + methods: ['postMessage', 'close', 'blur', 'focus'], + // These are methods which return promises and, therefore, when called with a + // cross-origin `this` object, do not throw immediately, but instead return a + // Promise which rejects with the same SecurityError that they would + // otherwise throw. They are not, however, cross-origin accessible. + promiseMethods: ['createImageBitmap', 'fetch'], +} +windowAllowlists.propNames = Array.from(new Set([...windowAllowlists.indices, + ...windowAllowlists.getters, + ...windowAllowlists.setters, + ...windowAllowlists.methods, + 'then'])).sort(); +windowAllowlists.props = windowAllowlists.propNames.concat(allowedSymbols); + +var locationAllowlists = { + getters: [], + setters: ['href'], + methods: ['replace'], + promiseMethods: [], +} +locationAllowlists.propNames = Array.from(new Set([...locationAllowlists.getters, + ...locationAllowlists.setters, + ...locationAllowlists.methods, + 'then'])).sort(); + +// Define various sets of arguments to call cross-origin methods with. Arguments +// for any cross-origin-callable method must be valid, and should aim to have no +// side-effects. Any method without an entry in this list will be called with +// an empty arguments list. +var methodArgs = new Map(Object.entries({ + // As a basic smoke test, we call one cross-origin-inaccessible method with + // both valid and invalid arguments to make sure that it rejects with the + // same SecurityError regardless. + assign: [ + [], + ["javascript:undefined"], + ], + // Note: If we post a message to frame.html with a matching origin, its + // "onmessage" handler will change its `document.domain`, and potentially + // invalidate subsequent tests, so be sure to only pass non-matching origins. + postMessage: [ + ["foo", "http://does-not.exist/"], + ["foo", {}], + ], + replace: [["javascript:undefined"]], +})); + +addTest(function(win) { + for (var prop in window) { + if (windowAllowlists.props.indexOf(prop) != -1) { + win[prop]; // Shouldn't throw. + Object.getOwnPropertyDescriptor(win, prop); // Shouldn't throw. + assert_true(Object.prototype.hasOwnProperty.call(win, prop), "hasOwnProperty for " + String(prop)); + } else { + assert_throws_dom("SecurityError", function() { win[prop]; }, "Should throw when accessing " + String(prop) + " on Window"); + assert_throws_dom("SecurityError", function() { Object.getOwnPropertyDescriptor(win, prop); }, + "Should throw when accessing property descriptor for " + prop + " on Window"); + assert_throws_dom("SecurityError", function() { Object.prototype.hasOwnProperty.call(win, prop); }, + "Should throw when invoking hasOwnProperty for " + prop + " on Window"); + } + if (prop != 'location') + assert_throws_dom("SecurityError", function() { win[prop] = undefined; }, "Should throw when writing to " + prop + " on Window"); + } + for (var prop of windowAllowlists.namedFrames) { + win[prop]; // Shouldn't throw. + var desc = Object.getOwnPropertyDescriptor(win, prop); + assert_false(desc.writable, "[[Writable]] for named frame " + String(prop)); + assert_false(desc.enumerable, "[[Enumerable]] for named frame " + String(prop)); + assert_true(desc.configurable, "[[Configurable]] for named frame " + String(prop)); + assert_true(Object.prototype.hasOwnProperty.call(win, prop), "hasOwnProperty for " + String(prop)); + } + for (var prop in location) { + if (prop == 'replace') { + win.location[prop]; // Shouldn't throw. + Object.getOwnPropertyDescriptor(win.location, prop); // Shouldn't throw. + assert_true(Object.prototype.hasOwnProperty.call(win.location, prop), "hasOwnProperty for " + prop); + assert_throws_dom("SecurityError", function() { win.location[prop] = undefined; }, "Should throw when writing to " + prop + " on Location"); + } + else if (prop == 'href') { + Object.getOwnPropertyDescriptor(win.location, prop); // Shouldn't throw. + assert_true(Object.prototype.hasOwnProperty.call(win.location, prop), "hasOwnProperty for " + prop); + assert_throws_dom("SecurityError", function() { win.location[prop] }, + "Should throw reading href on Location"); + } + else { + assert_throws_dom("SecurityError", function() { win.location[prop]; }, "Should throw when accessing " + prop + " on Location"); + assert_throws_dom("SecurityError", function() { Object.getOwnPropertyDescriptor(win.location, prop); }, + "Should throw when accessing property descriptor for " + prop + " on Location"); + assert_throws_dom("SecurityError", function() { Object.prototype.hasOwnProperty.call(win.location, prop); }, + "Should throw when invoking hasOwnProperty for " + prop + " on Location"); + assert_throws_dom("SecurityError", function() { win.location[prop] = undefined; }, "Should throw when writing to " + prop + " on Location"); + } + } +}, "Only certain properties are accessible cross-origin"); + +addPromiseTest(async function(win, test_obj) { + async function checkProperties(objName, allowedlists) { + var localObj = window[objName]; + var otherObj = win[objName]; + + for (var prop in localObj) { + let desc; + for (let obj = localObj; !desc; obj = Object.getPrototypeOf(obj)) { + desc = Object.getOwnPropertyDescriptor(obj, prop); + + } + + if ("value" in desc) { + if (typeof desc.value === "function" && String(desc.value).includes("[native code]")) { + if (allowedlists.promiseMethods.includes(prop)) { + await promise_rejects_dom(test_obj, "SecurityError", desc.value.call(otherObj), + `Should throw when calling ${objName}.${prop} with cross-origin this object`); + } else if (!allowedlists.methods.includes(prop)) { + for (let args of methodArgs.get(prop) || [[]]) { + assert_throws_dom("SecurityError", desc.value.bind(otherObj, ...args), + `Should throw when calling ${objName}.${prop} with cross-origin this object`); + } + + } else { + for (let args of methodArgs.get(prop) || [[]]) { + desc.value.apply(otherObj, args); // Shouldn't throw. + } + } + } + } else { + if (desc.get) { + if (allowedlists.getters.includes(prop)) { + desc.get.call(otherObj); // Shouldn't throw. + } else { + assert_throws_dom("SecurityError", desc.get.bind(otherObj), + `Should throw when calling ${objName}.${prop} getter with cross-origin this object`); + } + } + if (desc.set) { + if (allowedlists.setters.includes(prop)) { + desc.set.call(otherObj, "javascript:undefined"); // Shouldn't throw. + } else { + assert_throws_dom("SecurityError", desc.set.bind(otherObj, "foo"), + `Should throw when calling ${objName}.${prop} setter with cross-origin this object`); + } + } + } + } + } + + await checkProperties("location", locationAllowlists); + await checkProperties("window", windowAllowlists); +}, "Only certain properties are usable as cross-origin this objects"); + +/* + * ES Internal Methods. + */ + +/* + * [[GetPrototypeOf]] + */ +addTest(function(win) { + assert_equals(Object.getPrototypeOf(win), null, "cross-origin Window proto is null"); + assert_equals(Object.getPrototypeOf(win.location), null, "cross-origin Location proto is null (__proto__)"); + var protoGetter = Object.getOwnPropertyDescriptor(Object.prototype, '__proto__').get; + assert_equals(protoGetter.call(win), null, "cross-origin Window proto is null"); + assert_equals(protoGetter.call(win.location), null, "cross-origin Location proto is null (__proto__)"); + assert_throws_dom("SecurityError", function() { win.__proto__; }, "__proto__ property not available cross-origin"); + assert_throws_dom("SecurityError", function() { win.location.__proto__; }, "__proto__ property not available cross-origin"); + +}, "[[GetPrototypeOf]] should return null"); + +/* + * [[SetPrototypeOf]] + */ +addTest(function(win) { + assert_throws_dom("SecurityError", function() { win.__proto__ = new Object(); }, "proto set on cross-origin Window"); + assert_throws_dom("SecurityError", function() { win.location.__proto__ = new Object(); }, "proto set on cross-origin Location"); + var setters = [Object.getOwnPropertyDescriptor(Object.prototype, '__proto__').set]; + if (Object.setPrototypeOf) + setters.push(function(p) { Object.setPrototypeOf(this, p); }); + setters.forEach(function(protoSetter) { + assert_throws_js(TypeError, function() { protoSetter.call(win, new Object()); }, "proto setter |call| on cross-origin Window"); + assert_throws_js(TypeError, function() { protoSetter.call(win.location, new Object()); }, "proto setter |call| on cross-origin Location"); + }); + // Hack to avoid "duplicate test name" harness issues. + setters.forEach(function(protoSetter) { + test(function() { protoSetter.call(win, null); }, + "proto setter |call| on cross-origin Window with null (" + protoSetter + ", " + winName(win) + ")"); + test(function() { protoSetter.call(win.location, null); }, + "proto setter |call| on cross-origin Location with null (" + protoSetter + ", " + winName(win) + ")"); + }); + if (Reflect.setPrototypeOf) { + assert_false(Reflect.setPrototypeOf(win, new Object()), + "Reflect.setPrototypeOf on cross-origin Window"); + assert_true(Reflect.setPrototypeOf(win, null), + "Reflect.setPrototypeOf on cross-origin Window with null"); + assert_false(Reflect.setPrototypeOf(win.location, new Object()), + "Reflect.setPrototypeOf on cross-origin Location"); + assert_true(Reflect.setPrototypeOf(win.location, null), + "Reflect.setPrototypeOf on cross-origin Location with null"); + } +}, "[[SetPrototypeOf]] should return false"); + +/* + * [[IsExtensible]] + */ +addTest(function(win) { + assert_true(Object.isExtensible(win), "cross-origin Window should be extensible"); + assert_true(Object.isExtensible(win.location), "cross-origin Location should be extensible"); +}, "[[IsExtensible]] should return true for cross-origin objects"); + +/* + * [[PreventExtensions]] + */ +addTest(function(win) { + assert_throws_js(TypeError, function() { Object.preventExtensions(win) }, + "preventExtensions on cross-origin Window should throw"); + assert_throws_js(TypeError, function() { Object.preventExtensions(win.location) }, + "preventExtensions on cross-origin Location should throw"); + assert_false(Reflect.preventExtensions(win), + "Reflect.preventExtensions on cross-origin Window"); + assert_false(Reflect.preventExtensions(win.location), + "Reflect.preventExtensions on cross-origin Location"); +}, "[[PreventExtensions]] should return false cross-origin objects"); + +/* + * [[GetOwnProperty]] + */ + +addTest(function(win) { + assert_true(isObject(Object.getOwnPropertyDescriptor(win, 'close')), "win.close is |own|"); + assert_true(isObject(Object.getOwnPropertyDescriptor(win, 'top')), "win.top is |own|"); + assert_true(isObject(Object.getOwnPropertyDescriptor(win.location, 'href')), "win.location.href is |own|"); + assert_true(isObject(Object.getOwnPropertyDescriptor(win.location, 'replace')), "win.location.replace is |own|"); +}, "[[GetOwnProperty]] - Properties on cross-origin objects should be reported |own|"); + +function checkPropertyDescriptor(desc, propName, expectWritable) { + const isSymbol = typeof(propName) === "symbol"; + const isArrayIndexPropertyName = !isSymbol && !isNaN(parseInt(propName, 10)); + propName = String(propName); + assert_true(isObject(desc), "property descriptor for " + propName + " should exist"); + assert_equals(desc.configurable, true, "property descriptor for " + propName + " should be configurable"); + if (!isArrayIndexPropertyName) { + assert_equals(desc.enumerable, false, "property descriptor for " + propName + " should not be enumerable"); + if (isSymbol || propName == "then") { + assert_true("value" in desc, + "property descriptor for " + propName + " should be a value descriptor"); + assert_equals(desc.value, undefined, + "symbol-named cross-origin visible prop " + propName + + " should come back as undefined"); + } + } else { + assert_equals(desc.enumerable, true, "property descriptor for " + propName + " should be enumerable"); + } + if ('value' in desc) + assert_equals(desc.writable, expectWritable, "property descriptor for " + propName + " should have writable: " + expectWritable); + else + assert_equals(typeof desc.set != 'undefined', expectWritable, + "property descriptor for " + propName + " should " + (expectWritable ? "" : "not ") + "have setter"); +} + +addTest(function(win) { + windowAllowlists.props.forEach(function(prop) { + var desc = Object.getOwnPropertyDescriptor(win, prop); + checkPropertyDescriptor(desc, prop, prop == 'location'); + }); + checkPropertyDescriptor(Object.getOwnPropertyDescriptor(win.location, 'replace'), 'replace', false); + checkPropertyDescriptor(Object.getOwnPropertyDescriptor(win.location, 'href'), 'href', true); + assert_equals(typeof Object.getOwnPropertyDescriptor(win.location, 'href').get, 'undefined', "Cross-origin location should have no href getter"); + allowedSymbols.forEach(function(prop) { + var desc = Object.getOwnPropertyDescriptor(win.location, prop); + checkPropertyDescriptor(desc, prop, false); + }); +}, "[[GetOwnProperty]] - Property descriptors for cross-origin properties should be set up correctly"); + +addThenTest(function(win) { + assert_equals(typeof win.then, "object"); +}, "[[GetOwnProperty]] - Subframe named 'then' should shadow the default 'then' value"); + +addThenTest(function(win) { + assert_equals(typeof win.close, "function"); + assert_equals(typeof win.open, "object"); +}, "[[GetOwnProperty]] - Subframes should be visible cross-origin only if their names don't match the names of cross-origin-exposed IDL properties"); + +addTest(function(win) { + assert_equals(typeof Object.getOwnPropertyDescriptor(win, '0').value, "object"); + assert_equals(typeof Object.getOwnPropertyDescriptor(win, '1').value, "object"); + assert_throws_dom("SecurityError", function() { + Object.getOwnPropertyDescriptor(win, '2'); + }); +}, "[[GetOwnProperty]] - Should be able to get a property descriptor for an indexed property only if it corresponds to a child window."); + +/* + * [[Delete]] + */ +addTest(function(win) { + assert_throws_dom("SecurityError", function() { delete win[0]; }, "Can't delete cross-origin indexed property"); + assert_throws_dom("SecurityError", function() { delete win[100]; }, "Can't delete cross-origin indexed property"); + assert_throws_dom("SecurityError", function() { delete win.location; }, "Can't delete cross-origin property"); + assert_throws_dom("SecurityError", function() { delete win.parent; }, "Can't delete cross-origin property"); + assert_throws_dom("SecurityError", function() { delete win.length; }, "Can't delete cross-origin property"); + assert_throws_dom("SecurityError", function() { delete win.document; }, "Can't delete cross-origin property"); + assert_throws_dom("SecurityError", function() { delete win.foopy; }, "Can't delete cross-origin property"); + assert_throws_dom("SecurityError", function() { delete win.location.href; }, "Can't delete cross-origin property"); + assert_throws_dom("SecurityError", function() { delete win.location.replace; }, "Can't delete cross-origin property"); + assert_throws_dom("SecurityError", function() { delete win.location.port; }, "Can't delete cross-origin property"); + assert_throws_dom("SecurityError", function() { delete win.location.foopy; }, "Can't delete cross-origin property"); +}, "[[Delete]] Should throw on cross-origin objects"); + +/* + * [[DefineOwnProperty]] + */ +function checkDefine(obj, prop) { + var valueDesc = { configurable: true, enumerable: false, writable: false, value: 2 }; + var accessorDesc = { configurable: true, enumerable: false, get: function() {} }; + assert_throws_dom("SecurityError", function() { Object.defineProperty(obj, prop, valueDesc); }, "Can't define cross-origin value property " + prop); + assert_throws_dom("SecurityError", function() { Object.defineProperty(obj, prop, accessorDesc); }, "Can't define cross-origin accessor property " + prop); +} +addTest(function(win) { + checkDefine(win, 'length'); + checkDefine(win, 'parent'); + checkDefine(win, 'location'); + checkDefine(win, 'document'); + checkDefine(win, 'foopy'); + checkDefine(win.location, 'href'); + checkDefine(win.location, 'replace'); + checkDefine(win.location, 'port'); + checkDefine(win.location, 'foopy'); +}, "[[DefineOwnProperty]] Should throw for cross-origin objects"); + +/* + * EnumerateObjectProperties (backed by [[OwnPropertyKeys]]) + */ + +addTest(function(win) { + let i = 0; + for (var prop in win) { + i++; + assert_true(windowAllowlists.indices.includes(prop), prop + " is not safelisted for a cross-origin Window"); + } + assert_equals(i, windowAllowlists.indices.length, "Enumerate all enumerable safelisted cross-origin Window properties"); + i = 0; + for (var prop in win.location) { + i++; + } + assert_equals(i, 0, "There's nothing to enumerate for cross-origin Location properties"); +}, "Can only enumerate safelisted enumerable properties"); + +/* + * [[OwnPropertyKeys]] + */ + +addTest(function(win) { + assert_array_equals(Object.getOwnPropertyNames(win).sort(), + windowAllowlists.propNames, + "Object.getOwnPropertyNames() gives the right answer for cross-origin Window"); + assert_array_equals(Object.keys(win).sort(), + windowAllowlists.indices, + "Object.keys() gives the right answer for cross-origin Window"); + assert_array_equals(Object.getOwnPropertyNames(win.location).sort(), + locationAllowlists.propNames, + "Object.getOwnPropertyNames() gives the right answer for cross-origin Location"); + assert_equals(Object.keys(win.location).length, 0, + "Object.keys() gives the right answer for cross-origin Location"); +}, "[[OwnPropertyKeys]] should return all properties from cross-origin objects"); + +addTest(function(win) { + assert_array_equals(Object.getOwnPropertySymbols(win), allowedSymbols, + "Object.getOwnPropertySymbols() should return the three symbol-named properties that are exposed on a cross-origin Window"); + assert_array_equals(Object.getOwnPropertySymbols(win.location), + allowedSymbols, + "Object.getOwnPropertySymbols() should return the three symbol-named properties that are exposed on a cross-origin Location"); +}, "[[OwnPropertyKeys]] should return the right symbol-named properties for cross-origin objects"); + +addTest(function(win) { + var allWindowProps = Reflect.ownKeys(win); + indexedWindowProps = allWindowProps.slice(0, windowAllowlists.indices.length); + stringWindowProps = allWindowProps.slice(0, -1 * allowedSymbols.length); + symbolWindowProps = allWindowProps.slice(-1 * allowedSymbols.length); + // stringWindowProps should have "then" last in this case. Do this + // check before we call stringWindowProps.sort() below. + assert_equals(stringWindowProps[stringWindowProps.length - 1], "then", + "'then' property should be added to the end of the string list if not there"); + assert_array_equals(indexedWindowProps, windowAllowlists.indices, + "Reflect.ownKeys should start with the indices exposed on the cross-origin window."); + assert_array_equals(stringWindowProps.sort(), windowAllowlists.propNames, + "Reflect.ownKeys should continue with the cross-origin window properties for a cross-origin Window."); + assert_array_equals(symbolWindowProps, allowedSymbols, + "Reflect.ownKeys should end with the cross-origin symbols for a cross-origin Window."); + + var allLocationProps = Reflect.ownKeys(win.location); + stringLocationProps = allLocationProps.slice(0, -1 * allowedSymbols.length); + symbolLocationProps = allLocationProps.slice(-1 * allowedSymbols.length); + assert_array_equals(stringLocationProps.sort(), locationAllowlists.propNames, + "Reflect.ownKeys should start with the cross-origin window properties for a cross-origin Location.") + assert_array_equals(symbolLocationProps, allowedSymbols, + "Reflect.ownKeys should end with the cross-origin symbols for a cross-origin Location.") +}, "[[OwnPropertyKeys]] should place the symbols after the property names after the subframe indices"); + +addThenTest(function(win) { + var stringProps = Object.getOwnPropertyNames(win); + // Named frames are not exposed via [[OwnPropertyKeys]]. + assert_equals(stringProps.indexOf("a"), -1); + assert_equals(stringProps.indexOf("b"), -1); + assert_equals(typeof win.a, "object"); + assert_equals(typeof win.b, "object"); + assert_equals(stringProps[stringProps.length - 1], "then"); + assert_equals(stringProps.indexOf("then"), stringProps.lastIndexOf("then")); +}, "[[OwnPropertyKeys]] should not reorder where 'then' appears if it's a named subframe, nor add another copy of 'then'"); + +addTest(function(win) { + assert_equals(B.eval('parent.' + winName(win)), win, "A and B observe the same identity for C's Window"); + assert_equals(B.eval('parent.' + winName(win) + '.location'), win.location, "A and B observe the same identity for C's Location"); +}, "A and B jointly observe the same identity for cross-origin Window and Location"); + +function checkFunction(f, proto) { + var name = f.name || '<missing name>'; + assert_equals(typeof f, 'function', name + " is a function"); + assert_equals(Object.getPrototypeOf(f), proto, f.name + " has the right prototype"); +} + +addTest(function(win) { + checkFunction(win.close, Function.prototype); + checkFunction(win.location.replace, Function.prototype); +}, "Cross-origin functions get local Function.prototype"); + +addTest(function(win) { + assert_true(isObject(Object.getOwnPropertyDescriptor(win, 'parent')), + "Need to be able to use Object.getOwnPropertyDescriptor do this test"); + checkFunction(Object.getOwnPropertyDescriptor(win, 'parent').get, Function.prototype); + checkFunction(Object.getOwnPropertyDescriptor(win.location, 'href').set, Function.prototype); +}, "Cross-origin Window accessors get local Function.prototype"); + +addTest(function(win) { + checkFunction(close, Function.prototype); + assert_not_equals(close, B.close, 'same-origin Window functions get their own object'); + assert_not_equals(close, win.close, 'cross-origin Window functions get their own object'); + var close_B = B.eval('parent.' + winName(win) + '.close'); + assert_not_equals(close, close_B, 'close_B is unique when viewed by the parent'); + assert_not_equals(close_B, win.close, 'different Window functions per-incumbent script settings object'); + checkFunction(close_B, B.Function.prototype); + + checkFunction(location.replace, Function.prototype); + assert_not_equals(location.replace, win.location.replace, "cross-origin Location functions get their own object"); + var replace_B = B.eval('parent.' + winName(win) + '.location.replace'); + assert_not_equals(replace_B, win.location.replace, 'different Location functions per-incumbent script settings object'); + checkFunction(replace_B, B.Function.prototype); +}, "Same-origin observers get different functions for cross-origin objects"); + +addTest(function(win) { + assert_true(isObject(Object.getOwnPropertyDescriptor(win, 'parent')), + "Need to be able to use Object.getOwnPropertyDescriptor do this test"); + var get_self_parent = Object.getOwnPropertyDescriptor(window, 'parent').get; + var get_parent_A = Object.getOwnPropertyDescriptor(win, 'parent').get; + var get_parent_B = B.eval('Object.getOwnPropertyDescriptor(parent.' + winName(win) + ', "parent").get'); + assert_not_equals(get_self_parent, get_parent_A, 'different Window accessors per-incumbent script settings object'); + assert_not_equals(get_parent_A, get_parent_B, 'different Window accessors per-incumbent script settings object'); + checkFunction(get_self_parent, Function.prototype); + checkFunction(get_parent_A, Function.prototype); + checkFunction(get_parent_B, B.Function.prototype); +}, "Same-origin observers get different accessors for cross-origin Window"); + +addTest(function(win) { + var set_self_href = Object.getOwnPropertyDescriptor(window.location, 'href').set; + var set_href_A = Object.getOwnPropertyDescriptor(win.location, 'href').set; + var set_href_B = B.eval('Object.getOwnPropertyDescriptor(parent.' + winName(win) + '.location, "href").set'); + assert_not_equals(set_self_href, set_href_A, 'different Location accessors per-incumbent script settings object'); + assert_not_equals(set_href_A, set_href_B, 'different Location accessors per-incumbent script settings object'); + checkFunction(set_self_href, Function.prototype); + checkFunction(set_href_A, Function.prototype); + checkFunction(set_href_B, B.Function.prototype); +}, "Same-origin observers get different accessors for cross-origin Location"); + +addTest(function(win) { + assert_equals({}.toString.call(win), "[object Object]"); + assert_equals({}.toString.call(win.location), "[object Object]"); +}, "{}.toString.call() does the right thing on cross-origin objects"); + +addPromiseTest(function(win) { + return Promise.resolve(win).then((arg) => { + assert_equals(arg, win); + }); +}, "Resolving a promise with a cross-origin window without a 'then' subframe should work"); + +addPromiseThenTest(function(win) { + return Promise.resolve(win).then((arg) => { + assert_equals(arg, win); + }); +}, "Resolving a promise with a cross-origin window with a 'then' subframe should work"); + +addPromiseThenTest(function(win) { + return Promise.resolve(win.location).then((arg) => { + assert_equals(arg, win.location); + }); +}, "Resolving a promise with a cross-origin location should work"); + +addTest(function(win) { + var desc = Object.getOwnPropertyDescriptor(window, "onmouseenter"); + var f = () => {}; + + // Check that it has [LegacyLenientThis] behavior + assert_equals(desc.get.call({}), undefined, "getter should return undefined"); + desc.set.call({}, f); // Should not throw. + + // Check that we can apply it to a same-origin window. + assert_equals(desc.get.call(B), null, "Should be able to read the value"); + desc.set.call(B, f); + assert_equals(desc.get.call(B), f, "Value should have updated"); + // And reset it for our next test + desc.set.call(B, null); + assert_equals(desc.get.call(B), null, "Should have been reset"); + + // Check that applying it to a cross-origin window throws instead of doing + // the [LegacyLenientThis] behavior. + assert_throws_dom("SecurityError", () => { + desc.get.call(win); + }, "Should throw when getting cross-origin"); + assert_throws_dom("SecurityError", () => { + desc.set.call(win, f); + }, "Should throw when setting cross-origin"); +}, "LegacyLenientThis behavior"); + +// We do a fresh load of the subframes for each test to minimize side-effects. +// It would be nice to reload ourselves as well, but we can't do that without +// disrupting the test harness. +function testDone() { + if (testList.length != 0) { + reloadSubframes(runNextTest); + } else { + done(); + } +} + +async function runNextTest() { + var entry = testList.shift(); + if (entry.promiseTest) { + for (let t of entry.tests) { + await new Promise(resolve => { + promise_test(test_obj => { + return new Promise(res => res(t.func(test_obj))).finally(resolve); + }, t.desc); + }); + } + } else { + for (let t of entry.tests) { + test(t.func, t.desc); + } + } + testDone(); +} +reloadSubframes(runNextTest); + +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/frame-with-then.html b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/frame-with-then.html new file mode 100644 index 0000000000..3eedeca38a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/frame-with-then.html @@ -0,0 +1,19 @@ +<!doctype html> +<html> + <script> + if (location.search == "?setdomain") { + document.domain = document.domain; + } + </script> + <body> + <!--- Some frames to test ordering --> + <iframe name="a"></iframe> + <!-- A subframe to test "then" behavior --> + <iframe name="then"></iframe> + <iframe name="b"></iframe> + <!-- Two subframes with names corresponding to IDL-defined properties; one + a cross-origin-exposed property and one not exposed cross-origin --> + <iframe name="close"></iframe> + <iframe name="open"></iframe> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/frame.html b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/frame.html new file mode 100644 index 0000000000..ca2dd8ebf8 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/frame.html @@ -0,0 +1,51 @@ +<!doctype html> +<html> +<head> +<script> + if (location.search == "?setdomain") { + document.domain = document.domain; + } + + // Override the |frames| and |focus| property to test that such overrides are + // properly ignored cross-origin. + window.frames = "override"; + window.focus = "override"; + + // Also add a |then| property to test that it doesn't get exposed. + window.then = "something"; + window.location.then = "something-else"; + + // If we get a postMessage, we grab references to everything and set + // document.domain to trim off our topmost subdomain. + window.onmessage = function(evt) { + window.windowReferences = []; + window.locationReferences = []; + for (var i = 0; i < parent.length; ++i) { + windowReferences.push(parent[i]); + locationReferences.push(parent[i].location); + } + try { + document.domain = document.domain.substring(document.domain.indexOf('.') + 1); + evt.source.postMessage('PASS', '*'); + } catch (e) { + evt.source.postMessage('FAIL: cannot trim off document.domain: ' + e, '*'); + } + } + + function checkWindowReferences() { + for (var i = 0; i < parent.length; ++i) { + if (windowReferences[i] != parent[i]) + throw new Error("Window references don't match for " + i + " after document.domain"); + if (locationReferences[i] != parent[i].location) + throw new Error("Location references don't match for " + i + " after document.domain"); + } + return true; + } +</script> +</head> +<body> + <!-- Two subframes to give us some indexed properties --> + <iframe></iframe> + <iframe name=donotleakme></iframe><!-- "donotleakme" is excluded as cross-origin named property due to [[HideFromKeys]] --> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/location-properties-smoke-test.html b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/location-properties-smoke-test.html new file mode 100644 index 0000000000..1d1c1136a6 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/location-properties-smoke-test.html @@ -0,0 +1,65 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title> + Verify that certain location interface properties are protected cross-origin. +</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/common/utils.js"></script> + +<body></body> <!-- Needed to append the iframe --> + +<script> + +const iframeToken = token(); +const responseToken = token(); +const iframeUrl = + get_host_info().REMOTE_ORIGIN + + "/common/dispatcher/executor.html?uuid=" + + iframeToken; + +const iframe = document.createElement("iframe"); +iframe.src = iframeUrl; +document.body.appendChild(iframe); + +[ + "assign", + "customproperty", + "hash", + "host", + "hostname", + "pathname", + "port", + "protocol", + "reload", + "search", + "toString", + "valueOf", +].forEach(property => { + promise_test(async t => { + // Make sure the cross-origin document is loaded in the iframe. + send(iframeToken, `send("${responseToken}", "Responsive");`); + assert_equals(await receive(responseToken), "Responsive"); + + assert_throws_dom("SecurityError", () => { + const unused = iframe.contentWindow.location[property]; + }, "Cross origin get of a location property should throw a security error"); + + assert_throws_dom("SecurityError", () => { + iframe.contentWindow.location[property] = "Random string"; + }, "Cross origin set of a location property should throw a security error"); + + // Verify that the property was indeed not modified. + send(iframeToken, `send("${responseToken}", location["${property}"])`); + assert_true(await receive(responseToken) != "Random string"); + + assert_throws_dom("SecurityError", () => { + const unused = Object.getOwnPropertyDescriptor( + iframe.contentWindow.location, property); + }, "Cross origin get of descriptors should throw a security error"); + }, `Verifying that cross-origin access of '${property}' is restricted`); +}); + +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/resources/cross-origin-due-to-document-domain-only-helper.html b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/resources/cross-origin-due-to-document-domain-only-helper.html new file mode 100644 index 0000000000..10ac8ece0e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/resources/cross-origin-due-to-document-domain-only-helper.html @@ -0,0 +1,9 @@ +<!doctype html> +<meta charset=utf-8> +<script> +self.expandosForever = true +self.location.expandosForever = true +function setDocumentDomain() { + document.domain = document.domain +} +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/win-documentdomain.sub.html b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/win-documentdomain.sub.html new file mode 100644 index 0000000000..a315e21208 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/win-documentdomain.sub.html @@ -0,0 +1,63 @@ +<!DOCTYPE html> +<html> +<head> + <script src="/common/get-host-info.sub.js"></script> + <script> + function loadFrames() { + window.A = document.getElementById('A').contentWindow; + window.B = document.getElementById('B').contentWindow; + window.C = document.getElementById('C').contentWindow; + window.D = document.getElementById('D').contentWindow; + + var path = location.pathname.substring(0, location.pathname.lastIndexOf('/')) + '/frame.html'; + A.location = 'frame.html'; + B.location = '//{{domains[www2]}}:' + get_port(location) + path; + C.location = '//{{domains[www2]}}:' + get_port(location) + path; + D.location = '//{{domains[www1]}}:' + get_port(location) + path; + + var loadCount = 0; + function frameLoaded() { + if (++loadCount == 4) + go(); + } + var iframes = document.getElementsByTagName('iframe'); + for (var i = 0; i < iframes.length; i++) { + iframes[i].onload = frameLoaded; + } + } + + var results = []; + function assert(cond, msg) { + results.push({pass: !!cond, message: msg}); + } + + function go() { + window.onmessage = function(evt) { + try { + assert(evt.data == "PASS", "frame.html processing should be PASS but got " + evt.data); + assert(B.checkWindowReferences(), "B's Window references are still self-consistent after document.domain"); + for (var i = 0; i < window.length; ++i) { + assert(window[i] === B.windowReferences[i], + "Window reference " + i + " consistent between globals after document.domain"); + assert(window[i].location === B.locationReferences[i], + "Location reference " + i + " consistent between globals after document.domain"); + } + } catch(e) { + assert(false, "Should not receive exception: " + e); + } + opener.postMessage(results, '*'); + }; + A.document.domain = A.document.domain; + document.domain = document.domain; + B.postMessage('', '*'); + } + + </script> +</head> +<body onload="loadFrames()"> + <iframe id="A"></iframe> + <iframe id="B"></iframe> + <iframe id="C"></iframe> + <iframe id="D"></iframe> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/window-location-and-location-href-cross-realm-set.html b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/window-location-and-location-href-cross-realm-set.html new file mode 100644 index 0000000000..f03550a141 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/window-location-and-location-href-cross-realm-set.html @@ -0,0 +1,106 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Cross-realm [[Set]] to window.location and location.href throws an error of correct realm</title> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#window"> +<link rel="help" href="https://webidl.spec.whatwg.org/#Unforgeable"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<body> +<script> +const URL_SAME_ORIGIN = get_host_info().ORIGINAL_HOST; +const URL_CROSS_ORIGIN = get_host_info().HTTP_REMOTE_ORIGIN; +const URL_VALID = "#foo"; +const URL_INVALID = "http://#"; + +const { get: locationGet, set: locationSet } = Object.getOwnPropertyDescriptor(window, "location"); +const { get: hrefGet, set: hrefSet } = Object.getOwnPropertyDescriptor(location, "href"); + + +promise_test(async t => { + const sameOriginWindow = await makeWindow(t, URL_SAME_ORIGIN); + assert_throws_js(sameOriginWindow.TypeError, () => { Object.create(sameOriginWindow).location; }); + assert_throws_js(sameOriginWindow.TypeError, () => { Reflect.get(sameOriginWindow, "location", {}); }); + assert_throws_js(TypeError, () => { locationGet.call({}); }); +}, "Same-origin window.location getter throws TypeError in holder's realm on invalid |this| value"); + +promise_test(async t => { + const sameOriginWindow = await makeWindow(t, URL_SAME_ORIGIN); + assert_throws_js(sameOriginWindow.TypeError, () => { Object.create(sameOriginWindow.location).href; }); + assert_throws_js(sameOriginWindow.TypeError, () => { Reflect.get(sameOriginWindow.location, "href", {}); }); + assert_throws_js(TypeError, () => { hrefGet(); }); +}, "Same-origin location.href getter throws TypeError in holder's realm on invalid |this| value"); + +promise_test(async t => { + const crossOriginWindow = await makeWindow(t, URL_CROSS_ORIGIN); + assert_throws_dom("SECURITY_ERR", () => { crossOriginWindow.location.href; }); + assert_throws_dom("SECURITY_ERR", () => { hrefGet.call(crossOriginWindow.location); }); + assert_equals(Object.getOwnPropertyDescriptor(crossOriginWindow.location, "href").get, undefined); +}, "Cross-origin location.href getter throws SecurityError in lexical realm"); + + +promise_test(async t => { + const sameOriginWindow = await makeWindow(t, URL_SAME_ORIGIN); + assert_throws_js(sameOriginWindow.TypeError, () => { Object.create(sameOriginWindow).location = URL_VALID; }); + assert_throws_js(sameOriginWindow.TypeError, () => { Reflect.set(sameOriginWindow, "location", URL_VALID, {}); }); + assert_throws_js(TypeError, () => { locationSet.call(() => {}, URL_VALID); }); +}, "Same-origin window.location setter throws TypeError in holder's realm on invalid |this| value"); + +promise_test(async t => { + const sameOriginWindow = await makeWindow(t, URL_SAME_ORIGIN); + assert_throws_js(sameOriginWindow.TypeError, () => { Object.create(sameOriginWindow.location).href = URL_VALID; }); + assert_throws_js(sameOriginWindow.TypeError, () => { Reflect.set(sameOriginWindow.location, "href", URL_VALID, {}); }); + assert_throws_js(TypeError, () => { hrefSet.call(undefined, URL_VALID); }); +}, "Same-origin location.href setter throws TypeError in holder's realm on invalid |this| value"); + +promise_test(async t => { + const crossOriginWindow = await makeWindow(t, URL_CROSS_ORIGIN); + assert_throws_js(TypeError, () => { Object.create(crossOriginWindow).location = URL_VALID; }); + assert_throws_js(TypeError, () => { Reflect.set(crossOriginWindow, "location", URL_VALID, {}); }); + assert_throws_js(TypeError, () => { locationSet.call([], URL_VALID); }); +}, "Cross-origin window.location setter throws TypeError in lexical realm on invalid |this| value"); + +promise_test(async t => { + const crossOriginWindow = await makeWindow(t, URL_CROSS_ORIGIN); + assert_throws_js(TypeError, () => { Object.create(crossOriginWindow.location).href = URL_VALID; }); + assert_throws_js(TypeError, () => { Reflect.set(crossOriginWindow.location, "href", URL_VALID, {}); }); + assert_throws_js(TypeError, () => { hrefSet.call(null, URL_VALID); }); +}, "Cross-origin location.href setter throws TypeError in lexical realm on invalid |this| value"); + + +promise_test(async t => { + const sameOriginWindow = await makeWindow(t, URL_SAME_ORIGIN); + assert_throws_js(sameOriginWindow.TypeError, () => { sameOriginWindow.location = Symbol(); }); + + // The error originates in sameOriginWindow.location.href setter, hence it's not in realm of locationSet. + assert_throws_js(sameOriginWindow.TypeError, () => { locationSet.call(sameOriginWindow, Symbol()); }); +}, "Same-origin window.location` setter throws TypeError in holder's realm on non-coercible URL argument"); + +promise_test(async t => { + const sameOriginWindow = await makeWindow(t, URL_SAME_ORIGIN); + assert_throws_js(sameOriginWindow.TypeError, () => { sameOriginWindow.location.href = Symbol(); }); + assert_throws_js(TypeError, () => { hrefSet.call(sameOriginWindow.location, Symbol()); }); +}, "Same-origin location.href setter throws TypeError in holder's realm on non-coercible URL argument"); + +promise_test(async t => { + const crossOriginWindow = await makeWindow(t, URL_CROSS_ORIGIN); + assert_throws_js(TypeError, () => { crossOriginWindow.location = Symbol(); }); + assert_throws_js(TypeError, () => { locationSet.call(crossOriginWindow, Symbol()); }); +}, "Cross-origin window.location setter throws TypeError in lexical realm on non-coercible URL argument"); + +promise_test(async t => { + const crossOriginWindow = await makeWindow(t, URL_CROSS_ORIGIN); + assert_throws_js(TypeError, () => { crossOriginWindow.location.href = Symbol(); }); + assert_throws_js(TypeError, () => { hrefSet.call(crossOriginWindow.location, Symbol()); }); +}, "Cross-origin location.href setter throws TypeError in lexical realm on non-coercible URL argument"); + +function makeWindow(t, src) { + return new Promise(resolve => { + const iframe = document.createElement("iframe"); + t.add_cleanup(() => { iframe.remove(); }); + iframe.onload = () => { resolve(iframe.contentWindow); }; + iframe.src = src; + document.body.append(iframe); + }); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/inheritance/about-blank-iframe.html b/testing/web-platform/tests/html/browsers/origin/inheritance/about-blank-iframe.html new file mode 100644 index 0000000000..fabde327a1 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/inheritance/about-blank-iframe.html @@ -0,0 +1,28 @@ +<!doctype html> +<html> + <head> + <title>about:blank in child browsing context aliases security origin</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <script> + test(() => { + let iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + // Should not throw: srcdoc should always be same-origin. + iframe.contentWindow.document.body.innerHTML = '<p>Hello world!</p>'; + + // Explicitly set `domain` component of origin: any other same-origin + // browsing contexts are now cross-origin unless they also explicitly + // set document.domain to the same value. + document.domain = document.domain; + // Should not throw: the origin should be aliased, so setting + // document.domain in one Document should affect both Documents. + assert_equals( + iframe.contentWindow.document.body.textContent, + 'Hello world!'); + }); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/origin/inheritance/about-blank-window.html b/testing/web-platform/tests/html/browsers/origin/inheritance/about-blank-window.html new file mode 100644 index 0000000000..cc3177f943 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/inheritance/about-blank-window.html @@ -0,0 +1,25 @@ +<!doctype html> +<html> + <head> + <title>about:blank in auxiliary browsing context aliases security origin</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <script> + test(() => { + let newWindow = window.open(); + // Should not throw: the newly-opened window should be same-origin. + newWindow.document.body.innerHTML = '<p>Hello world!</p>'; + + // Explicitly set `domain` component of origin: any other same-origin + // browsing contexts are now cross-origin unless they also explicitly + // set document.domain to the same value. + document.domain = document.domain; + // Should not throw: the origin should be aliased, so setting + // document.domain in one Document should affect both Documents. + assert_equals(newWindow.document.body.textContent, 'Hello world!'); + }); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/origin/inheritance/about-srcdoc.html b/testing/web-platform/tests/html/browsers/origin/inheritance/about-srcdoc.html new file mode 100644 index 0000000000..971811ee66 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/inheritance/about-srcdoc.html @@ -0,0 +1,29 @@ +<!doctype html> +<html> + <head> + <title>about:srcdoc aliases security origin</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <script> + test(() => { + let iframe = document.createElement('iframe'); + iframe.srcdoc = '<body></body>'; + document.body.appendChild(iframe); + // Should not throw: srcdoc should always be same-origin. + iframe.contentWindow.document.body.innerHTML = '<p>Hello world!</p>'; + + // Explicitly set `domain` component of origin: any other same-origin + // browsing contexts are now cross-origin unless they also explicitly + // set document.domain to the same value. + document.domain = document.domain; + // Should not throw: the origin should be aliased, so setting + // document.domain in one Document should affect both Documents. + assert_equals( + iframe.contentWindow.document.body.textContent, + 'Hello world!'); + }); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/origin/inheritance/javascript-url.html b/testing/web-platform/tests/html/browsers/origin/inheritance/javascript-url.html new file mode 100644 index 0000000000..7dfb1130ce --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/inheritance/javascript-url.html @@ -0,0 +1,33 @@ +<!doctype html> +<html> + <head> + <title>javascript: aliases security origin</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <script> + promise_test(t => { + let iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + // Should not throw: srcdoc should always be same-origin. + iframe.contentDocument; + + iframe.contentWindow.location = 'javascript:"Hello world!"'; + return new Promise(resolve => { + iframe.addEventListener('load', resolve); + }).then(() => { + // Explicitly set `domain` component of origin: any other same-origin + // browsing contexts are now cross-origin unless they also explicitly + // set document.domain to the same value. + document.domain = document.domain; + // Should not throw: the origin should be aliased, so setting + // document.domain in one Document should affect both Documents. + assert_equals( + iframe.contentWindow.document.body.textContent, + 'Hello world!'); + }); + }); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-bad-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-bad-subdomain.sub.https.html new file mode 100644 index 0000000000..3a45ee6d6a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-bad-subdomain.sub.https.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is site-keyed, child attempts to origin-key but uses a bad header value, child is a subdomain of the parent</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testSameAgentCluster, + testGetter +} from "../resources/helpers.mjs"; + +let frameIndex = 0; +for (const badValue of ["", "?0", "true", "\"?1\"", "1", "?2", "(?1)"]) { + promise_test(async () => { + await insertIframe("{{hosts[][www]}}", badValue); + }, `"${badValue}": frame insertion`); + + // Since the header values are bad they should be site-keyed. + testSameAgentCluster([self, frameIndex], `"${badValue}"`); + testGetter(frameIndex, false, `"${badValue}"`); + ++frameIndex; +} +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yes-port.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yes-port.sub.https.html new file mode 100644 index 0000000000..85fb1f64e0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yes-port.sub.https.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is site-keyed, child is origin-keyed, child is different-origin to the parent because of a port mismatch</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +promise_setup(async () => { + await insertIframe("{{hosts[][]}}:{{ports[https][1]}}", "?1"); +}); + +// Since they're different-origin, the child's request is respected, so the +// parent ends up in the site-keyed agent cluster and the child in the +// origin-keyed one. +testDifferentAgentClusters([self, 0]); + +testGetter(self, false, "parent"); +testGetter(0, true, "child"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yes-same.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yes-same.sub.https.html new file mode 100644 index 0000000000..7ece02c81a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yes-same.sub.https.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is site-keyed, child is origin-keyed, child is same-origin to the parent</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testSameAgentCluster, + testGetter +} from "../resources/helpers.mjs"; + +promise_setup(async () => { + await insertIframe("{{hosts[][]}}", "?1"); +}); + +// Since they're same-origin, and the parent loaded in the site-keyed agent +// cluster, the child's request for origin-keying gets ignored, and both end up +// site-keyed. +testSameAgentCluster([self, 0]); + +testGetter(self, false, "parent"); +testGetter(0, false, "child"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yes-subdomain-with-redirect.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yes-subdomain-with-redirect.sub.https.html new file mode 100644 index 0000000000..994f80876d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yes-subdomain-with-redirect.sub.https.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is site-keyed, child is reached via a redirect response with no header, child final response does have the header</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +promise_setup(async () => { + await insertIframe("{{hosts[][www]}}", "?1", { redirectFirst: true }); +}); + +// Since they're different-origin, the child's request is respected, so the +// parent ends up in the site-keyed agent cluster and the child in the +// origin-keyed one. +testDifferentAgentClusters([self, 0]); + +testGetter(self, false, "parent"); +testGetter(0, true, "child"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yes-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yes-subdomain.sub.https.html new file mode 100644 index 0000000000..5fc2fa29f3 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yes-subdomain.sub.https.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is site-keyed, child is origin-keyed, child is a subdomain of the parent</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +promise_setup(async () => { + await insertIframe("{{hosts[][www]}}", "?1"); +}); + +// Since they're different-origin, the child's request is respected, so the +// parent ends up in the site-keyed agent cluster and the child in the +// origin-keyed one. +testDifferentAgentClusters([self, 0]); + +testGetter(self, false, "parent"); +testGetter(0, true, "child"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yeswithparams-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yeswithparams-subdomain.sub.https.html new file mode 100644 index 0000000000..3e7c8419b3 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yeswithparams-subdomain.sub.https.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is site-keyed, child is origin-keyed using parameters on its structured header, child is a subdomain of the parent</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +promise_setup(async () => { + await insertIframe("{{hosts[][www]}}", "?1;param1;param2=value2"); +}); + +// Since they're different-origin, the child's request is respected, so the +// parent ends up in the site-keyed agent cluster and the child in the +// origin-keyed one. +testDifferentAgentClusters([self, 0]); + +testGetter(self, false, "parent"); +testGetter(0, true, "child"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-port.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-port.sub.https.html new file mode 100644 index 0000000000..f00814cfbf --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-port.sub.https.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is origin-keyed, child is site-keyed, child is is different-origin to the parent because of a port mismatch</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +promise_setup(async () => { + await insertIframe("{{hosts[][]}}:{{ports[https][1]}}"); +}); + +// Since they're different-origin, the parent's request is respected, as is the +// child's non-request. So the parent ends up in the origin-keyed agent cluster +// and the child ends up in the site-keyed one. +testDifferentAgentClusters([self, 0]); + +testGetter(self, true, "parent"); +testGetter(0, false, "child"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-port.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-port.sub.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-port.sub.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-same.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-same.sub.https.html new file mode 100644 index 0000000000..307f8c48d7 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-same.sub.https.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is origin-keyed, child is site-keyed, child is same-origin to the parent</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testSameAgentCluster, + testGetter +} from "../resources/helpers.mjs"; + +promise_setup(async () => { + await insertIframe("{{hosts[][]}}"); +}); + +// Since they're same-origin, and the parent loaded with origin-keying, the +// child's non-request gets ignored, and both end up origin-keyed. +testSameAgentCluster([self, 0]); + +testGetter(self, true, "parent"); +testGetter(0, true, "child"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-same.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-same.sub.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-same.sub.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-subdomain.sub.https.html new file mode 100644 index 0000000000..8c823fa36f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-subdomain.sub.https.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is origin-keyed, child is site-keyed, child is a subdomain of the parent</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +promise_setup(async () => { + await insertIframe("{{hosts[][www]}}"); +}); + +// Since they're different-origin, the parent's request is respected, as is the +// child's non-request. So the parent ends up in the origin-keyed agent cluster +// and the child ends up in the site-keyed one. +testDifferentAgentClusters([self, 0]); + +testGetter(self, true, "parent"); +testGetter(0, false, "child"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-subdomain.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-subdomain.sub.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-subdomain.sub.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-port.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-port.sub.https.html new file mode 100644 index 0000000000..5e431e6e41 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-port.sub.https.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is origin-keyed, child is site-keyed, child is different-origin to the parent because of a port mismatch</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +promise_setup(async () => { + await insertIframe("{{hosts[][]}}:{{ports[https][1]}}", "?1"); +}); + +// Both request origin-keying, so the parent ends up in one origin-keyed agent +// cluster (the default port's origin), and the child ends up in a different +// origin-keyed agent cluster (the other port's origin). +testDifferentAgentClusters([self, 0]); + +testGetter(self, true, "parent"); +testGetter(0, true, "child"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-port.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-port.sub.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-port.sub.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-same.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-same.sub.https.html new file mode 100644 index 0000000000..3b8c214a61 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-same.sub.https.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is origin-keyed, child is site-keyed, child is same-origin to the parent</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testSameAgentCluster, + testGetter +} from "../resources/helpers.mjs"; + +promise_setup(async () => { + await insertIframe("{{hosts[][]}}", "?1"); +}); + +// Both request origin-keying, and they're same-origin, so they both end up in +// the same origin-keyed agent cluster. +testSameAgentCluster([self, 0]); + +testGetter(self, true, "parent"); +testGetter(0, true, "child"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-same.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-same.sub.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-same.sub.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-subdomain.sub.https.html new file mode 100644 index 0000000000..136a3a0bba --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-subdomain.sub.https.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is origin-keyed, child is site-keyed, child is a subdomain of the parent</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +promise_setup(async () => { + await insertIframe("{{hosts[][www]}}", "?1"); +}); + +// Both request origin-keying, so the parent ends up in one origin-keyed agent +// cluster (the base domain's origin), and the child ends up in a different +// origin-keyed agent cluster (the www subdomain's origin). +testDifferentAgentClusters([self, 0]); + +testGetter(self, true, "parent"); +testGetter(0, true, "child"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-subdomain.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-subdomain.sub.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-subdomain.sub.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-no-subdomain-child2-yes-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-no-subdomain-child2-yes-subdomain.sub.https.html new file mode 100644 index 0000000000..1bb252f0ab --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-no-subdomain-child2-yes-subdomain.sub.https.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is site-keyed, subdomain child 1 is site-keyed, same-subdomain child 2 is origin-keyed</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testSameAgentCluster, + testGetter +} from "../resources/helpers.mjs"; + +promise_setup(async () => { + // Must be sequential, not parallel: the site-keyed frame must load first. + await insertIframe("{{hosts[][www]}}"); + await insertIframe("{{hosts[][www]}}", "?1"); +}); + + +// Since they're different-origin, the parent's non-request is respected, as is +// child 1's non-request. child 2 requests origin-keying but is ignored, since +// child 1 is in the same browsing context group. +// +// So, everyone ends up in the site-keyed agent cluster. +testSameAgentCluster([self, 0], "Parent to child1"); +testSameAgentCluster([self, 1], "Parent to child2"); +testSameAgentCluster([0, 1], "child1 to child2"); +testSameAgentCluster([1, 0], "child2 to child1"); + +testGetter(self, false, "parent"); +testGetter(0, false, "child1"); +testGetter(1, false, "child2"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-no-subdomain-child2-yes-subdomainport.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-no-subdomain-child2-yes-subdomainport.sub.https.html new file mode 100644 index 0000000000..5b80c528f0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-no-subdomain-child2-yes-subdomainport.sub.https.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is site-keyed, subdomain child 1 is site-keyed, different-port-same-subdomain child 2 is origin-keyed</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testSameAgentCluster, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +promise_setup(async () => { + // Order of loading should not matter, but we make it sequential to ensure the + // tests are deterministic. + await insertIframe("{{hosts[][www]}}"); + await insertIframe("{{hosts[][www]}}:{{ports[https][1]}}", "?1"); +}); + +// Since everybody is different-origin, everyone's requests/non-requests get +// respected. +// +// So, the parent and child 1 end up in the site-keyed agent cluster, and child +// 2 ends up in its own origin-keyed agent cluster. +testSameAgentCluster([self, 0], "Parent to child1"); +testDifferentAgentClusters([self, 1], "Parent to child2"); +testDifferentAgentClusters([0, 1], "child1 to child2"); +testDifferentAgentClusters([1, 0], "child2 to child1"); + +testGetter(self, false, "parent"); +testGetter(0, false, "child1"); +testGetter(1, true, "child2"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-no-subdomain1-child2-yes-subdomain2.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-no-subdomain1-child2-yes-subdomain2.sub.https.html new file mode 100644 index 0000000000..bba13b82a4 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-no-subdomain1-child2-yes-subdomain2.sub.https.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is site-keyed, subdomain child 1 is site-keyed, different-subdomain child 2 is origin-keyed</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testSameAgentCluster, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +promise_setup(async () => { + // Order of loading should not matter, but we make it sequential to ensure the + // tests are deterministic. + await insertIframe("{{hosts[][www]}}"); + await insertIframe("{{hosts[][www1]}}", "?1"); +}); + +// Since everybody is different-origin, everyone's requests/non-requests get +// respected. +// +// So, the parent and child 1 end up in the site-keyed agent cluster, and child +// 2 ends up in its own origin-keyed agent cluster. +testSameAgentCluster([self, 0], "Parent to child1"); +testDifferentAgentClusters([self, 1], "Parent to child2"); +testDifferentAgentClusters([0, 1], "child1 to child2"); +testDifferentAgentClusters([1, 0], "child2 to child1"); + +testGetter(self, false, "parent"); +testGetter(0, false, "child1"); +testGetter(1, true, "child2"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-yes-subdomain-child2-no-port.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-yes-subdomain-child2-no-port.sub.https.html new file mode 100644 index 0000000000..d01d180213 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-yes-subdomain-child2-no-port.sub.https.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is site-keyed, subdomain child 1 is origin-keyed, non-subdomain but different-port child 2 is site-keyed</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testSameAgentCluster, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +promise_setup(async () => { + // Order of loading should not matter, but we make it sequential to ensure the + // tests are deterministic. + await insertIframe("{{hosts[][www]}}", "?1"); + await insertIframe("{{hosts[][]}}:{{ports[https][1]}}"); +}); + +// Everyone is different-origin, so everyone's request/non-request is +// respected. +// +// So, the parent and child2 end up in the site-keyed agent cluster, and child1 +// ends up in an origin-keyed agent cluster. +testDifferentAgentClusters([self, 0], "Parent to child1"); +testSameAgentCluster([self, 1], "Parent to child2"); +testDifferentAgentClusters([0, 1], "child1 to child2"); +testDifferentAgentClusters([1, 0], "child2 to child1"); + +testGetter(self, false, "parent"); +testGetter(0, true, "child1"); +testGetter(1, false, "child2"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-yes-subdomain-child2-no-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-yes-subdomain-child2-no-subdomain.sub.https.html new file mode 100644 index 0000000000..9a245b3ace --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-yes-subdomain-child2-no-subdomain.sub.https.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is site-keyed, subdomain child 1 is origin-keyed, same-subdomain child 2 is site-keyed</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testSameAgentCluster, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +promise_setup(async () => { + // Must be sequential, not parallel: the origin-keyed frame must load first. + await insertIframe("{{hosts[][www]}}", "?1"); + await insertIframe("{{hosts[][www]}}"); +}); + + +// Since they're different-origin, the parent's non-request is respected, as is +// child 1's request. child 2's non-request is ignored, since child 1 is in the +// same browsing context group. +// +// So, the parent ends up in the site-keyed agent cluster, and both children end +// up in an origin-keyed agent cluster. +testDifferentAgentClusters([self, 0], "Parent to child1"); +testDifferentAgentClusters([self, 1], "Parent to child2"); +testSameAgentCluster([0, 1], "child1 to child2"); +testSameAgentCluster([1, 0], "child2 to child1"); + +testGetter(self, false, "parent"); +testGetter(0, true, "child1"); +testGetter(1, true, "child2"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html new file mode 100644 index 0000000000..c308b9a17a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is origin-keyed, subdomain child 1 is site-keyed, same-subdomain child 2 is site-keyed</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testSameAgentCluster, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +promise_setup(async () => { + // Order of loading should not matter, but we make it sequential to ensure the + // tests are deterministic. + await insertIframe("{{hosts[][www]}}"); + await insertIframe("{{hosts[][www]}}"); +}); + + +// Since everybody is different-origin, everyone's requests/non-requests get +// respected. +// +// So, the parent ends up in its origin-keyed agent cluster, and child 1 and +// child 2 both end up in the site-keyed agent cluster. +testDifferentAgentClusters([self, 0], "Parent to child1"); +testDifferentAgentClusters([self, 1], "Parent to child2"); +testSameAgentCluster([0, 1], "child1 to child2"); +testSameAgentCluster([1, 0], "child2 to child1"); + +testGetter(self, true, "parent"); +testGetter(0, false, "child1"); +testGetter(1, false, "child2"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html new file mode 100644 index 0000000000..767d908c21 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is origin-keyed, subdomain child 1 is site-keyed, different-subdomain child 2 is site-keyed</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testSameAgentCluster, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +promise_setup(async () => { + // Order of loading should not matter, but we make it sequential to ensure the + // tests are deterministic. + await insertIframe("{{hosts[][www]}}"); + await insertIframe("{{hosts[][www1]}}"); +}); + + +// Since everybody is different-origin, everyone's requests/non-requests get +// respected. +// +// So, the parent ends up in its origin-keyed agent cluster, and child 1 and +// child 2 both end up in the site-keyed agent cluster. +testDifferentAgentClusters([self, 0], "Parent to child1"); +testDifferentAgentClusters([self, 1], "Parent to child2"); +testSameAgentCluster([0, 1], "child1 to child2"); +testSameAgentCluster([1, 0], "child2 to child1"); + +testGetter(self, true, "parent"); +testGetter(0, false, "child1"); +testGetter(1, false, "child2"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html new file mode 100644 index 0000000000..45047f3ae1 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is origin-keyed, subdomain child 1 is site-keyed, same-subdomain child 2 is origin-keyed</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testSameAgentCluster, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +promise_setup(async () => { + // Must be sequential, not parallel: the non-isolaed frame must load first. + await insertIframe("{{hosts[][www]}}"); + await insertIframe("{{hosts[][www]}}", "?1"); +}); + + +// Since they're different-origin, the parent's request is respected, as is +// child 1's non-request. child 2 requests origin-keying but is ignored, since +// child 1 is in the same browsing context group. +// +// So, the parent ends up in the origin-keyed agent cluster, and both children +// ends up in the site-keyed one. +testDifferentAgentClusters([self, 0], "Parent to child1"); +testDifferentAgentClusters([self, 1], "Parent to child2"); +testSameAgentCluster([0, 1], "child1 to child2"); +testSameAgentCluster([1, 0], "child2 to child1"); + +testGetter(self, true, "parent"); +testGetter(0, false, "child1"); +testGetter(1, false, "child2"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html new file mode 100644 index 0000000000..202b916767 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is origin-keyed, subdomain child 1 is site-keyed, different-subdomain child 2 is origin-keyed</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +promise_setup(async () => { + // Order of loading should not matter, but we make it sequential to ensure the + // tests are deterministic. + await insertIframe("{{hosts[][www]}}"); + await insertIframe("{{hosts[][www1]}}", "?1"); +}); + + +// Since everybody is different-origin, everyone's requests/non-requests get +// respected. +// +// So, the parent ends up in its origin-keyed agent cluster, child 1 ends up in +// the site-keyed agent cluster, and child 2 ends up in a different origin-keyed +// agent cluster. +testDifferentAgentClusters([self, 0], "Parent to child1"); +testDifferentAgentClusters([self, 1], "Parent to child2"); +testDifferentAgentClusters([0, 1], "child1 to child2"); +testDifferentAgentClusters([1, 0], "child2 to child1"); + +testGetter(self, true, "parent"); +testGetter(0, false, "child1"); +testGetter(1, true, "child2"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html new file mode 100644 index 0000000000..a1316731ac --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is origin-keyed, subdomain child 1 is site-keyed, different-port-same-subdomain child 2 is origin-keyed</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +promise_setup(async () => { + // Order of loading should not matter, but we make it sequential to ensure the + // tests are deterministic. + await insertIframe("{{hosts[][www]}}"); + await insertIframe("{{hosts[][www]}}:{{ports[https][1]}}", "?1"); +}); + + +// Since everybody is different-origin, everyone's requests/non-requests get +// respected. +// +// So, the parent ends up in its origin-keyed agent cluster, child 1 ends up in +// the site-keyed agent cluster, and child 2 ends up in a different origin-keyed +// agent cluster. +testDifferentAgentClusters([self, 0], "Parent to child1"); +testDifferentAgentClusters([self, 1], "Parent to child2"); +testDifferentAgentClusters([0, 1], "child1 to child2"); +testDifferentAgentClusters([1, 0], "child2 to child1"); + +testGetter(self, true, "parent"); +testGetter(0, false, "child1"); +testGetter(1, true, "child2"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html new file mode 100644 index 0000000000..46bef4b9a9 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is origin-keyed, subdomain child 1 is origin-keyed, non-subdomain but different-port child 2 is site-keyed</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +promise_setup(async () => { + // Order of loading should not matter, but we make it sequential to ensure the + // tests are deterministic. + await insertIframe("{{hosts[][www]}}", "?1"); + await insertIframe("{{hosts[][]}}:{{ports[https][1]}}"); +}); + +// Everyone is different-origin, so everyone's request/non-request is +// respected. +// +// So, child2 ends up in the site-keyed agent cluster, and the parent and child1 +// end up in two separate origin-keyed agent clusters. +testDifferentAgentClusters([self, 0], "Parent to child1"); +testDifferentAgentClusters([self, 1], "Parent to child2"); +testDifferentAgentClusters([0, 1], "child1 to child2"); +testDifferentAgentClusters([1, 0], "child2 to child1"); + +testGetter(self, true, "parent"); +testGetter(0, true, "child1"); +testGetter(1, false, "child2"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-subdomain.sub.https.html new file mode 100644 index 0000000000..39dcfc04b0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-subdomain.sub.https.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is origin-keyed, subdomain child 1 is origin-keyed, same-subdomain child 2 is site-keyed</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testSameAgentCluster, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +promise_setup(async () => { + // Must be sequential, not parallel: the origin-keyed frame must load first. + await insertIframe("{{hosts[][www]}}", "?1"); + await insertIframe("{{hosts[][www]}}"); +}); + + +// Since they're different-origin, the parent's request is respected, as is +// child 1's request. child 2's non-request is ignored, since child 1 is in the +// same browsing context group. +// +// So, the parent ends up in the origin-keyed agent cluster, and both children +// ends up in a different origin-keyed agent cluster. +testDifferentAgentClusters([self, 0], "Parent to child1"); +testDifferentAgentClusters([self, 1], "Parent to child2"); +testSameAgentCluster([0, 1], "child1 to child2"); +testSameAgentCluster([1, 0], "child2 to child1"); + +testGetter(self, true, "parent"); +testGetter(0, true, "child1"); +testGetter(1, true, "child2"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-subdomain.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-subdomain.sub.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-subdomain.sub.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html new file mode 100644 index 0000000000..b6daf91b54 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is origin-keyed, subdomain child 1 is origin-keyed, same-subdomain child 2 is site-keyed</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testSameAgentCluster, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +promise_setup(async () => { + // Order of loading should not matter, but we make it sequential to ensure the + // tests are deterministic. + await insertIframe("{{hosts[][www]}}", "?1"); + await insertIframe("{{hosts[][www]}}", "?1"); +}); + + +// Since they're different-origin, the parent's request is respected, as is +// child 1's request. child 2's request is redundant, since child 1 is in the +// same browsing context group. +// +// So, the parent ends up in the origin-keyed agent cluster, and both children +// ends up in a different origin-keyed agent cluster. +testDifferentAgentClusters([self, 0], "Parent to child1"); +testDifferentAgentClusters([self, 1], "Parent to child2"); +testSameAgentCluster([0, 1], "child1 to child2"); +testSameAgentCluster([1, 0], "child2 to child1"); + +testGetter(self, true, "parent"); +testGetter(0, true, "child1"); +testGetter(1, true, "child2"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html new file mode 100644 index 0000000000..b94f9392d4 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is origin-keyed, subdomain child 1 is origin-keyed, different-subdomain child 2 is origin-keyed</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +promise_setup(async () => { + // Order of loading should not matter, but we make it sequential to ensure the + // tests are deterministic. + await insertIframe("{{hosts[][www]}}", "?1"); + await insertIframe("{{hosts[][www1]}}", "?1"); +}); + + +// Since everybody is different-origin, everyone's requests get +// respected. +// +// So, the parent ends up in its origin-keyed agent cluster, child 1 ends up in +// a second origin-keyed agent cluster, and child 2 ends up in a third +// origin-keyed agent cluster. +testDifferentAgentClusters([self, 0], "Parent to child1"); +testDifferentAgentClusters([self, 1], "Parent to child2"); +testDifferentAgentClusters([0, 1], "child1 to child2"); +testDifferentAgentClusters([1, 0], "child2 to child1"); + +testGetter(self, true, "parent"); +testGetter(0, true, "child1"); +testGetter(1, true, "child2"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html new file mode 100644 index 0000000000..fb3fda1bf2 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is origin-keyed, subdomain child 1 is origin-keyed, different-port-same-subdomain child 2 is origin-keyed</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +promise_setup(async () => { + // Order of loading should not matter, but we make it sequential to ensure the + // tests are deterministic. + await insertIframe("{{hosts[][www]}}", "?1"); + await insertIframe("{{hosts[][www]}}:{{ports[https][1]}}", "?1"); +}); + + +// Since everybody is different-origin, everyone's requests get +// respected. +// +// So, the parent ends up in its origin-keyed agent cluster, child 1 ends up in +// a second origin-keyed agent cluster, and child 2 ends up in a third +// origin-keyed agent cluster. +testDifferentAgentClusters([self, 0], "Parent to child1"); +testDifferentAgentClusters([self, 1], "Parent to child2"); +testDifferentAgentClusters([0, 1], "child1 to child2"); +testDifferentAgentClusters([1, 0], "child2 to child1"); + +testGetter(self, true, "parent"); +testGetter(0, true, "child1"); +testGetter(1, true, "child2"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/META.yml b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/META.yml new file mode 100644 index 0000000000..f21ce69f6a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/META.yml @@ -0,0 +1,5 @@ +spec: https://html.spec.whatwg.org/multipage/#origin-keyed-agent-clusters +suggested_reviewers: + - domenic + - annevk + - wjmaclean diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/README.md b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/README.md new file mode 100644 index 0000000000..85ba3bce7f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/README.md @@ -0,0 +1,30 @@ +# Origin-keyed agent clusters tests + +These are tests for the [origin-keyed agent clusters](https://html.spec.whatwg.org/multipage/origin.html#origin-keyed-agent-clusters) +feature. + +## Test filenames + +The tests in `2-iframes` follow the file naming pattern + +``` +parent-[yes|no]-child1-[yes|no]-[designator]-child2-[yes|no]-[designator] +``` + +Here: + +* `yes` or `no` refers to whether the `Origin-Agent-Cluster` header is set or + unset. +* `designator` explains how the child differs from the parent: e.g. by being a + subdomain, or having a different port, or both. There's also `same` if it's + same-origin. + +Other directories have variations on this, e.g. `1-iframe/` does the same thing +but for a single `child` instead of `child1` and `child2`, and `navigation/` +uses `1` and `2` to represent the two different locations the single iframe will +be navigated to. + +## Coverage + +Header parsing is covered by a few tests in the `1-iframe/` subdirectory, and +not duplicated to all other scenarios. diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/about-blank.https.sub.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/about-blank.https.sub.html new file mode 100644 index 0000000000..556d528aa0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/about-blank.https.sub.html @@ -0,0 +1,63 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>The initial about:blank respects origin isolation</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + setBothDocumentDomains, + testSameAgentCluster, + testDifferentAgentClusters, + testGetter +} from "./resources/helpers.mjs"; + +promise_setup(async () => { + await insertAboutBlankIframe(); + await insertIframe("{{hosts[][www]}}"); +}); + +// Since the initial about:blank inherits its origin from its parent, it is +// same-origin with the parent, and thus cross-origin with child2. +testSameAgentCluster([self, 0], "parent to about:blank"); +testDifferentAgentClusters([0, 1], "about:blank to child2"); +testDifferentAgentClusters([1, 0], "child2 to about:blank"); + +testGetter(self, true, "parent"); +testGetter(0, true, "about:blank"); +testGetter(1, false, "child2"); + +async function insertAboutBlankIframe() { + const iframe = await createBlankIframe(); + + // Now create and add the script, but don't navigate anywhere (since we want + // to stay on the initial about:blank). + // We need to absolutize the URL to since about:blank doesn't have a base URL. + const scriptURL = (new URL("./resources/send-header-page-script.mjs", import.meta.url)).href; + const script = iframe.contentDocument.createElement("script"); + script.type = "module"; + script.src = scriptURL; + + await new Promise((resolve, reject) => { + script.onload = resolve; + script.onerror = () => reject( + new Error("Could not load the child frame script into the about:blank page") + ); + iframe.contentDocument.body.append(script); + }); + + await setBothDocumentDomains(iframe.contentWindow); +} + +function createBlankIframe() { + const iframe = document.createElement("iframe"); + const promise = new Promise(resolve => { + iframe.addEventListener("load", resolve); + }); + document.body.append(iframe); + return promise; +} +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/about-blank.https.sub.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/about-blank.https.sub.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/about-blank.https.sub.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/document-domain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/document-domain.sub.https.html new file mode 100644 index 0000000000..b4535d9e54 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/document-domain.sub.https.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Setting document.domain does not change same-originness when origin-keyed</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- + Other tests check that using document.domain doesn't allow cross-origin + access. This test ensures a different, more subtle property: that + origin-keying makes document.domain into a no-op in other ways. +--> + +<iframe src="resources/frame.html"></iframe> +<iframe src="//{{domains[www1]}}:{{location[port]}}/html/browsers/origin/origin-keyed-agent-clusters/resources/frame.html"></iframe> + +<script type="module"> +setup({ explicit_done: true }); + +window.onload = () => { + test(() => { + // Normally, setting document.domain to itself would change the domain + // component of the origin. Since the iframe does *not* set document.domain, + // the two would then be considered cross-origin. + document.domain = document.domain; + + // However, because we're origin-keyed, this shouldn't have any impact. The + // test fails if this throws, and passes if it succeeds. + frames[0].document; + }, "Setting document.domain must not change same-originness"); + + test(() => { + assert_throws_dom("SecurityError", () => { + document.domain = "{{hosts[][nonexistent]}}"; + }); + }, "The registrable domain suffix check must happen before the bail-out"); + + async_test(t => { + frames[1].postMessage({ + type: "set document.domain", + newValue: "{{host}}" + }, "*"); + + window.onmessage = t.step_func_done(e => { + assert_equals(e.data.type, "new document.domain"); + assert_equals(e.data.result, "{{domains[www1]}}"); + }); + }, "Having an origin-keyed subdomain child try to set document.domain " + + "must not change the document.domain value it sees"); + + done(); +}; +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/document-domain.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/document-domain.sub.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/document-domain.sub.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/cross-origin-isolated.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/cross-origin-isolated.sub.https.html new file mode 100644 index 0000000000..a521934cc9 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/cross-origin-isolated.sub.https.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>window.originAgentCluster must be implied by cross-origin isolation</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<iframe src="//{{domains[www1]}}:{{location[port]}}/html/browsers/origin/origin-keyed-agent-clusters/resources/coep-frame.html"></iframe> + +<div id="log"></div> + +<script type="module"> +import { testGetter } from "../resources/helpers.mjs"; + +setup({ explicit_done: true }); + +window.onload = () => { + // Cross-origin isolated pages are always origin-keyed. + testGetter(self, true, "self"); + + // Child frames of cross-origin isolated pages must also be cross-origin + // isolated, and thus also origin-keyed. Make sure the implementation doesn't + // treat them specially in some weird way, for the purposes of this + // implication. + testGetter(0, true, "child"); + + done(); +}; +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/cross-origin-isolated.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/cross-origin-isolated.sub.https.html.headers new file mode 100644 index 0000000000..5f8621ef83 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/cross-origin-isolated.sub.https.html.headers @@ -0,0 +1,2 @@ +Cross-Origin-Embedder-Policy: require-corp +Cross-Origin-Opener-Policy: same-origin diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/csp-sandbox-no.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/csp-sandbox-no.https.html new file mode 100644 index 0000000000..e0b5f92376 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/csp-sandbox-no.https.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>window.originAgentCluster for a top-level frame sandboxed by CSP with no Origin-Agent-Cluster header</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { testGetter } from "../resources/helpers.mjs"; + +// Even without the header, sandboxing makes this page have an opaque origin, +// so it is origin-keyed. +testGetter(self, true); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/csp-sandbox-no.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/csp-sandbox-no.https.html.headers new file mode 100644 index 0000000000..4705ce9ded --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/csp-sandbox-no.https.html.headers @@ -0,0 +1 @@ +Content-Security-Policy: sandbox allow-scripts; diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/csp-sandbox-yes.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/csp-sandbox-yes.https.html new file mode 100644 index 0000000000..a2220c5acc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/csp-sandbox-yes.https.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>window.originAgentCluster for a top-level frame sandboxed by CSP with an Origin-Agent-Cluster header</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { testGetter } from "../resources/helpers.mjs"; + +// We're definitely origin-keyed: both the CSP sandboxing and the +// Origin-Agent-Cluster header should ensure this. +testGetter(self, true); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/csp-sandbox-yes.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/csp-sandbox-yes.https.html.headers new file mode 100644 index 0000000000..a52bf50900 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/csp-sandbox-yes.https.html.headers @@ -0,0 +1,2 @@ +Content-Security-Policy: sandbox allow-scripts; +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-to-javascript-no.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-to-javascript-no.https.html new file mode 100644 index 0000000000..06149cda8a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-to-javascript-no.https.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>window.originAgentCluster for a javascript: URL navigated to from a data: URL on a site-keyed page</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import runTest from "./resources/data-to-javascript-test.mjs"; +runTest(); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-to-javascript-yes.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-to-javascript-yes.https.html new file mode 100644 index 0000000000..af6fea0ad9 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-to-javascript-yes.https.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>window.originAgentCluster for a javascript: URL navigated to from a data: URL on an origin-keyed page</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import runTest from "./resources/data-to-javascript-test.mjs"; +runTest(); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-to-javascript-yes.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-to-javascript-yes.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-to-javascript-yes.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-url-no.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-url-no.https.html new file mode 100644 index 0000000000..8ae564a072 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-url-no.https.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>window.originAgentCluster for a data: URL on a site-keyed page</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import runTest from "./resources/data-url-test.mjs"; +runTest(); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-url-yes.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-url-yes.https.html new file mode 100644 index 0000000000..bcbf098a66 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-url-yes.https.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>window.originAgentCluster for a data: URL on an origin-keyed page</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import runTest from "./resources/data-url-test.mjs"; +runTest(); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-url-yes.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-url-yes.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-url-yes.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/javascript-url-no.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/javascript-url-no.https.html new file mode 100644 index 0000000000..1b54ad42a4 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/javascript-url-no.https.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>window.originAgentCluster for a javascript: URL on a site-keyed page</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import runTest from "./resources/data-url-test.mjs"; +runTest({ expected: false }); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/javascript-url-yes.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/javascript-url-yes.https.html new file mode 100644 index 0000000000..e2b7730dd2 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/javascript-url-yes.https.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>window.originAgentCluster for a javascript: URL on an origin-keyed page</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import runTest from "./resources/data-url-test.mjs"; +runTest({ expected: true }); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/javascript-url-yes.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/javascript-url-yes.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/javascript-url-yes.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/removed-iframe.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/removed-iframe.sub.https.html new file mode 100644 index 0000000000..fcf5068908 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/removed-iframe.sub.https.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>window.originAgentCluster for a removed frame</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { navigateIframe } from "../resources/helpers.mjs"; + +promise_test(async () => { + // We cannot use insertIframe because it sets both `document.domain`s. That + // shouldn't matter, but Chrome has a bug (https://crbug.com/1095145), so + // let's avoid making the test needlessly fail because of that bug. + const iframe = document.createElement("iframe"); + const navigatePromise = navigateIframe(iframe, "{{hosts[][]}}", "?1"); + document.body.append(iframe); + await navigatePromise; + + const frameWindow = iframe.contentWindow; + + assert_equals(frameWindow.originAgentCluster, true, "before"); + iframe.remove(); + assert_equals(frameWindow.originAgentCluster, true, "after"); +}, "Removing the iframe does not change originAgentCluster"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/removed-iframe.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/removed-iframe.sub.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/removed-iframe.sub.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/data-to-javascript-test.mjs b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/data-to-javascript-test.mjs new file mode 100644 index 0000000000..3a88253ee3 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/data-to-javascript-test.mjs @@ -0,0 +1,33 @@ +import { insertCustomIframe, testSupportScript } from "./helpers.mjs"; +import { waitForIframe, testGetter } from "../../resources/helpers.mjs"; + +const testSupportScriptSuitableForNesting = + testSupportScript.replace('</script>', '</scri` + `pt>'); + +export default () => { + promise_setup(async () => { + const jsURL = `javascript:'${testSupportScript}'`; + const iframe = await insertCustomIframe(`data:text/html, + Start page + <script> + window.onmessage = () => { + location.href = \`javascript:'End page${testSupportScriptSuitableForNesting}'\`; + }; + </script> + `); + + const waitPromise = waitForIframe(iframe, "javascript: URL"); + + // Kick off the navigation. We can't do it directly because only same-origin + // pages can navigate to a javascript: URL, and we're not same-origin with + // a data: URL. + iframe.contentWindow.postMessage(undefined, "*"); + + await waitPromise; + }); + + // The javascript: URL iframe inherits its origin from the previous occupant + // of the iframe, which is a data: URL, so it should always be true. + + testGetter(0, true); +}; diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/data-url-test.mjs b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/data-url-test.mjs new file mode 100644 index 0000000000..1a9b3be47f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/data-url-test.mjs @@ -0,0 +1,13 @@ +import { insertCustomIframe, testSupportScript } from "./helpers.mjs"; +import { testGetter } from "../../resources/helpers.mjs"; + +export default () => { + promise_setup(() => { + return insertCustomIframe(`data:text/html,${testSupportScript}`); + }); + + // The data: URL iframe has an opaque origin, so it should return true, since + // for them site === origin so they are always origin-keyed. + + testGetter(0, true, "data: URL child"); +}; diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/helpers.mjs b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/helpers.mjs new file mode 100644 index 0000000000..4610ffcad0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/helpers.mjs @@ -0,0 +1,28 @@ +import { waitForIframe } from "../../resources/helpers.mjs"; + +/** + * Inserts an iframe, not specialized for origin-keyed agent cluster testing, + * pointing to a custom URL. This is just a wrapper to remove some boilerplate. + * @param {string} src - The src="" value for the iframe + */ +export async function insertCustomIframe(src) { + const iframe = document.createElement("iframe"); + iframe.src = src; + + const waitPromise = waitForIframe(iframe); + document.body.append(iframe); + await waitPromise; + + return iframe; +} + +/** + * This is the part of send-oac-header.py that allows us to reuse testGetter. + */ +export const testSupportScript = ` + <script> + window.onmessage = () => { + parent.postMessage(self.originAgentCluster, "*"); + }; + </script> +`; diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/javascript-url-test.mjs b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/javascript-url-test.mjs new file mode 100644 index 0000000000..de474d8caf --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/javascript-url-test.mjs @@ -0,0 +1,14 @@ +import { insertCustomIframe, testSupportScript } from "./helpers.mjs"; +import { testGetter } from "../../resources/helpers.mjs"; + +export default ({ expected }) => { + promise_setup(() => { + return insertCustomIframe(`javascript:'${testSupportScript}'`); + }); + + // The javascript: URL iframe inherits its origin from the previous occupant + // of the iframe, which is about:blank, which in turn inherits from the + // parent. So, the caller needs to tell us what to expect. + + testGetter(0, expected); +}; diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/sandboxed-iframe-test.sub.mjs b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/sandboxed-iframe-test.sub.mjs new file mode 100644 index 0000000000..9357df00c5 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/sandboxed-iframe-test.sub.mjs @@ -0,0 +1,20 @@ +import { + navigateIframe, + testGetter +} from "../../resources/helpers.mjs"; + +export default () => { + // We do this manually instead of using insertIframe because we want to add a + // sandbox="" attribute and we don't want to set both document.domains. + promise_setup(() => { + const iframe = document.createElement("iframe"); + iframe.sandbox = "allow-scripts"; + const navigatePromise = navigateIframe(iframe, "{{hosts[][]}}", "?1"); + document.body.append(iframe); + return navigatePromise; + }); + + // Sandboxed iframes have an opaque origin, so it should return true, since + // for them site === origin so they are always origin-keyed. + testGetter(0, true); +}; diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/sandboxed-same-origin-iframe-test.sub.mjs b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/sandboxed-same-origin-iframe-test.sub.mjs new file mode 100644 index 0000000000..272f805870 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/sandboxed-same-origin-iframe-test.sub.mjs @@ -0,0 +1,20 @@ +import { + navigateIframe, + testGetter +} from "../../resources/helpers.mjs"; + +export default ({ expected }) => { + // We do this manually instead of using insertIframe because we want to add a + // sandbox="" attribute and we don't want to set both document.domains. + promise_setup(() => { + const iframe = document.createElement("iframe"); + iframe.sandbox = "allow-scripts allow-same-origin"; + const navigatePromise = navigateIframe(iframe, "{{hosts[][]}}", "?1"); + document.body.append(iframe); + return navigatePromise; + }); + + // Since the allow-same-origin token is set, this should behave like a normal + // iframe, and follow the embedder. + testGetter(0, expected); +}; diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-iframe-no.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-iframe-no.https.html new file mode 100644 index 0000000000..29758a17b8 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-iframe-no.https.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>window.originAgentCluster for a sandboxed iframe on a site-keyed page</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import runTest from "./resources/sandboxed-iframe-test.sub.mjs"; +runTest(); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-iframe-yes.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-iframe-yes.https.html new file mode 100644 index 0000000000..5eb5d08d10 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-iframe-yes.https.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>window.originAgentCluster for a sandboxed iframe on an origin-keyed page</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import runTest from "./resources/sandboxed-iframe-test.sub.mjs"; +runTest(); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-iframe-yes.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-iframe-yes.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-iframe-yes.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-no.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-no.https.html new file mode 100644 index 0000000000..3ed4096f39 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-no.https.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>window.originAgentCluster for a sandboxed, but same-origin, iframe on a site-keyed page</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import runTest from "./resources/sandboxed-same-origin-iframe-test.sub.mjs"; +runTest({ expected: false }); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-yes.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-yes.https.html new file mode 100644 index 0000000000..c7ea5f0693 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-yes.https.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>window.originAgentCluster for a sandboxed, but same-origin, iframe on an origin-keyed page</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import runTest from "./resources/sandboxed-same-origin-iframe-test.sub.mjs"; +runTest({ expected: true }); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-yes.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-yes.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-yes.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/going-back.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/going-back.sub.https.html new file mode 100644 index 0000000000..a593619ea6 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/going-back.sub.https.html @@ -0,0 +1,62 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is origin-keyed, child1 is site-keyed, child1 navigates to a different site, child2 gets inserted and is origin-keyed, child1 navigates back</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + navigateIframe, + waitForIframe, + setBothDocumentDomains, + testDifferentAgentClusters, + testSameAgentCluster, +} from "./resources/helpers.mjs"; + +let frame1; +promise_setup(async () => { + frame1 = await insertIframe("{{hosts[][www]}}"); +}); + +// Since they're different-origin, the parent's origin-keying request is +// respected, as is the child's non-request. So the parent ends up in the +// origin-keyed agent cluster and the child ends up in the site-keyed one. +testDifferentAgentClusters([self, 0], "Before navigation: parent to child1"); + +// Navigate the iframe to a different site. These, of course, must not be in the +// same agent cluster. + +promise_test(async () => { + await navigateIframe(frame1, "{{hosts[alt][]}}"); +}, "Navigation"); + +// Now insert a second iframe, pointing to the same place as the first one +// originally did, but this time with origin-keying requested. Because of the +// historical map of agent cluster keys for the browsing context group, the new +// iframe should still end up in the site-keyed agent cluster. + +promise_test(async () => { + await insertIframe("{{hosts[][www]}}", "?1"); +}, "Inserting a second iframe"); + +testDifferentAgentClusters([self, 1], "After navigation: parent to child2"); + +// Now navigate the first iframe back. The resulting Document should be put in +// the site-keyed agent cluster, together with the second iframe's Document. + +promise_test(async () => { + const waitPromise = waitForIframe(frame1); + history.back(); + await waitPromise; + + await setBothDocumentDomains(frames[0]); +}, "Going back in history (navigating back the first iframe)"); + +testDifferentAgentClusters([self, 0], "After back: parent to child1"); +testDifferentAgentClusters([self, 1], "After back: parent to child2"); +testSameAgentCluster([0, 1], "child1 to child2"); +testSameAgentCluster([1, 0], "child2 to child1"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/going-back.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/going-back.sub.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/going-back.sub.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-same-2-yes-port.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-same-2-yes-port.sub.https.html new file mode 100644 index 0000000000..8237f2f23f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-same-2-yes-port.sub.https.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is site-keyed, navigate a frame from same-origin site-keyed to different-origin (different-port) origin-keyed</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + navigateIframe, + setBothDocumentDomains, + testSameAgentCluster, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +let frame1; +promise_setup(async () => { + frame1 = await insertIframe("{{hosts[][]}}"); +}); + +// Nobody requested origin-keying yet. + +testSameAgentCluster([self, 0], "Before: parent to child"); +testGetter(self, false, "before parent"); +testGetter(0, false, "before child"); + +promise_test(async () => { + await navigateIframe(frame1, "{{hosts[][]}}:{{ports[https][1]}}", "?1"); + await setBothDocumentDomains(frames[0]); +}, "Navigation"); + +// Since the new page is different-origin, its origin-keying request should be +// respected. + +testDifferentAgentClusters([self, 0], "After: parent to child"); +testGetter(self, false, "after parent"); +testGetter(0, true, "after child"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-same-2-yes-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-same-2-yes-subdomain.sub.https.html new file mode 100644 index 0000000000..00d8c3164a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-same-2-yes-subdomain.sub.https.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is site-keyed, navigate a frame from same-origin site-keyed to different-origin (subdomain) origin-keyed</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + navigateIframe, + setBothDocumentDomains, + testSameAgentCluster, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +let frame1; +promise_setup(async () => { + frame1 = await insertIframe("{{hosts[][]}}"); +}); + +// Nobody requested origin-keying yet. + +testSameAgentCluster([self, 0], "Before: parent to child"); +testGetter(self, false, "before parent"); +testGetter(0, false, "before child"); + +promise_test(async () => { + await navigateIframe(frame1, "{{hosts[][www]}}", "?1"); + await setBothDocumentDomains(frames[0]); +}, "Navigation"); + +// Since the new page is different-origin, its origin-keying request should be +// respected. + +testDifferentAgentClusters([self, 0], "After: parent to child"); +testGetter(self, false, "after parent"); +testGetter(0, true, "after child"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-subdomain-2-yes-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-subdomain-2-yes-subdomain.sub.https.html new file mode 100644 index 0000000000..803e684e1c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-subdomain-2-yes-subdomain.sub.https.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is site-keyed, navigate a frame from a subdomain site-keyed to the same subdomain origin-keyed</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + navigateIframe, + setBothDocumentDomains, + testSameAgentCluster, + testGetter +} from "../resources/helpers.mjs"; + +let frame1; +promise_setup(async () => { + frame1 = await insertIframe("{{hosts[][www]}}"); +}); + +// Nobody requested origin-keying yet. + +testSameAgentCluster([self, 0], "Before: parent to child"); +testGetter(self, false, "before parent"); +testGetter(0, false, "before child"); + +promise_test(async () => { + await navigateIframe(frame1, "{{hosts[][www]}}", "?1"); + await setBothDocumentDomains(frames[0]); +}, "Navigation"); + +// Because this subdomain was previously site-keyed, the second load's +// origin-keying request is ignored; instead we continue with site-keying. + +testSameAgentCluster([self, 0], "After: parent to child"); +testGetter(self, false, "after parent"); +testGetter(0, false, "after child"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-subdomain-2-yes-subdomain2.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-subdomain-2-yes-subdomain2.sub.https.html new file mode 100644 index 0000000000..b96d10afd1 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-subdomain-2-yes-subdomain2.sub.https.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is site-keyed, navigate a frame from a subdomain site-keyed to a second-subdomain origin-keyed</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + navigateIframe, + setBothDocumentDomains, + testSameAgentCluster, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +let frame1; +promise_setup(async () => { + frame1 = await insertIframe("{{hosts[][www]}}"); +}); + +// Nobody requested origin-keying yet. + +testSameAgentCluster([self, 0], "Before: parent to child"); +testGetter(self, false, "before parent"); +testGetter(0, false, "before child"); + +promise_test(async () => { + await navigateIframe(frame1, "{{hosts[][www1]}}", "?1"); + await setBothDocumentDomains(frames[0]); +}, "Navigation"); + +// Because we're going to a different subdomain (and thus different origin), the +// origin-keying request is respected. + +testDifferentAgentClusters([self, 0], "After: parent to child"); +testGetter(self, false, "after parent"); +testGetter(0, true, "after child"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-subdomain-yes-2-subdomain2-no.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-subdomain-yes-2-subdomain2-no.sub.https.html new file mode 100644 index 0000000000..a70ed56670 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-subdomain-yes-2-subdomain2-no.sub.https.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is site-keyed, navigate a frame from a subdomain origin-keyed to a second-subdomain site-keyed</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + navigateIframe, + setBothDocumentDomains, + testSameAgentCluster, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +let frame1; +promise_setup(async () => { + frame1 = await insertIframe("{{hosts[][www]}}", "?1"); +}); + +// Since they are different-origin, the child's origin-keying request is +// respected. + +testDifferentAgentClusters([self, 0], "Before: parent to child"); +testGetter(self, false, "before parent"); +testGetter(0, true, "before child"); + +promise_test(async () => { + await navigateIframe(frame1, "{{hosts[][www1]}}"); + await setBothDocumentDomains(frames[0]); +}, "Navigation"); + +// Make sure that the different-subdomain page (which doesn't request +// origin-keying) doesn't somehow get origin-keyed just because its predecessor +// was. + +testSameAgentCluster([self, 0], "After: parent to child"); +testGetter(self, false, "after parent"); +testGetter(0, false, "after child"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-yes-subdomain-2-no-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-yes-subdomain-2-no-subdomain.sub.https.html new file mode 100644 index 0000000000..38e2630128 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-yes-subdomain-2-no-subdomain.sub.https.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is site-keyed, navigate a frame from a subdomain origin-keyed to the same subdomain site-keyed</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + navigateIframe, + setBothDocumentDomains, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +let frame1; +promise_setup(async () => { + frame1 = await insertIframe("{{hosts[][www]}}", "?1"); +}); + +// Since they are different-origin, the child's origin-keying request is +// respected. + +testDifferentAgentClusters([self, 0], "Before: parent to child"); +testGetter(self, false, "before parent"); +testGetter(0, true, "before child"); + +promise_test(async () => { + await navigateIframe(frame1, "{{hosts[][www]}}"); + await setBothDocumentDomains(frames[0]); +}, "Navigation"); + +// Because this subdomain was previously origin-keyed, the second load's +// non-request is ignored; instead we continue origin-keying. + +testDifferentAgentClusters([self, 0], "After: parent to child"); +testGetter(self, false, "after parent"); +testGetter(0, true, "after child"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-port.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-port.sub.https.html new file mode 100644 index 0000000000..6211845be1 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-port.sub.https.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is origin-keyed, navigate a frame from same-origin site-keyed to different-origin (different-port) origin-keyed</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + navigateIframe, + setBothDocumentDomains, + testSameAgentCluster, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +let frame1; +promise_setup(async () => { + frame1 = await insertIframe("{{hosts[][]}}"); +}); + +// Since the parent is origin-keyed, the same-origin child's non-request is +// ignored, so it gets origin-keyed too. + +testSameAgentCluster([self, 0], "Before: parent to child"); +testGetter(self, true, "before parent"); +testGetter(0, true, "before child"); + +promise_test(async () => { + await navigateIframe(frame1, "{{hosts[][]}}:{{ports[https][1]}}"); + await setBothDocumentDomains(frames[0]); +}, "Navigation"); + +// Since the new page is different-origin, its non-request should be respected. + +testDifferentAgentClusters([self, 0], "After: parent to child"); +testGetter(self, true, "after parent"); +testGetter(0, false, "after child"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-port.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-port.sub.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-port.sub.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-subdomain.sub.https.html new file mode 100644 index 0000000000..ead56754a7 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-subdomain.sub.https.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent is origin-keyed, navigate a frame from same-origin site-keyed to different-origin (subdomain) origin-keyed</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + navigateIframe, + setBothDocumentDomains, + testSameAgentCluster, + testDifferentAgentClusters, + testGetter +} from "../resources/helpers.mjs"; + +let frame1; +promise_setup(async () => { + frame1 = await insertIframe("{{hosts[][]}}"); +}); + +// Since the parent is origin-keyed, the same-origin child's non-request is +// ignored, so it gets origin-keyed too. + +testSameAgentCluster([self, 0], "Before: parent to child"); +testGetter(self, true, "before parent"); +testGetter(0, true, "before child"); + +promise_test(async () => { + await navigateIframe(frame1, "{{hosts[][www]}}"); + await setBothDocumentDomains(frames[0]); +}, "Navigation"); + +// Since the new page is different-origin, its non-request should be respected. + +testDifferentAgentClusters([self, 0], "After: parent to child"); +testGetter(self, true, "after parent"); +testGetter(0, false, "after child"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-subdomain.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-subdomain.sub.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-subdomain.sub.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/insecure-http.sub.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/insecure-http.sub.html new file mode 100644 index 0000000000..6f9e5d8b73 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/insecure-http.sub.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Parent requests origin-keying, child requests origin-keying, child is a subdomain of the parent, but all over insecure HTTP</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testSameAgentCluster, + testGetter +} from "./resources/helpers.mjs"; + +promise_setup(async () => { + await insertIframe("{{hosts[][www]}}", "?1"); +}); + +// All origin-keying requests are ignored, since this is over insecure HTTP. +// So both end up in the site-keyed agent cluster. +testSameAgentCluster([self, 0]); + +testGetter(self, false, "parent"); +testGetter(0, false, "child"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/insecure-http.sub.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/insecure-http.sub.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/insecure-http.sub.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups-crash.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups-crash.https.html new file mode 100644 index 0000000000..dcfb5eb277 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups-crash.https.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Crash test for https://crbug.com/1099718</title> + +<div id="log"></div> + +<script> +window.open("resources/crashy-popup.sub.html", "windowName1", "noopener"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-port.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-port.sub.https.html new file mode 100644 index 0000000000..a0bf569b12 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-port.sub.https.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Opener is site-keyed, openee is origin-keyed, openee is different-origin to the opener because of a port mismatch</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + openWindow, + testOpenedWindowIsInADifferentAgentCluster, + testGetter +} from "../resources/helpers.mjs"; + +let openee; +promise_setup(async () => { + openee = await openWindow("{{hosts[][]}}:{{ports[https][1]}}", "?1"); +}); + +// Since they're different-origin, the openee's origin-keying request is +// respected, so the opener ends up in the site-keyed agent cluster and the +// openee in the origin-keyed one. +testOpenedWindowIsInADifferentAgentCluster(() => openee); + +testGetter(self, false, "opener"); +testGetter(() => openee, true, "openee"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-same.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-same.sub.https.html new file mode 100644 index 0000000000..196dff1449 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-same.sub.https.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Opener is site-keyed, openee is origin-keyed, openee is same-origin to the opener</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + openWindow, + testOpenedWindowIsInSameAgentCluster, + testGetter +} from "../resources/helpers.mjs"; + +let openee; +promise_setup(async () => { + openee = await openWindow("{{hosts[][]}}", "?1"); +}); + +// Since they're same-origin, and the opener loaded with site-keying, the +// child's request for origin-keying gets ignored, and both end up site-keyed. +testOpenedWindowIsInSameAgentCluster(() => openee); + +testGetter(self, false, "opener"); +testGetter(() => openee, false, "openee"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-subdomain.sub.https.html new file mode 100644 index 0000000000..f96d2273d5 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-subdomain.sub.https.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Opener is site-keyed, openee is origin-keyed, openee is a subdomain of the opener</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + openWindow, + testOpenedWindowIsInADifferentAgentCluster, + testGetter +} from "../resources/helpers.mjs"; + +let openee; +promise_setup(async () => { + openee = await openWindow("{{hosts[][www]}}", "?1"); +}); + +// Since they're different-origin, the openee's origin-keying request is +// respected, so the opener ends up in the site-keyed agent cluster and the +// openee in the origin-keyed one. +testOpenedWindowIsInADifferentAgentCluster(() => openee); + +testGetter(self, false, "opener"); +testGetter(() => openee, true, "openee"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-port.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-port.sub.https.html new file mode 100644 index 0000000000..51c5a208c5 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-port.sub.https.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Opener is origin-keyed, openee is site-keyed, openee is different-origin to the opener because of a port mismatch</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + openWindow, + testOpenedWindowIsInADifferentAgentCluster, + testGetter +} from "../resources/helpers.mjs"; + +let openee; +promise_setup(async () => { + openee = await openWindow("{{hosts[][]}}:{{ports[https][1]}}"); +}); + +// Since they're different-origin, the openee's non-request is respected, so the +// opener ends up in the origin-keyed agent cluster and the openee in the +// site-keyed one. +testOpenedWindowIsInADifferentAgentCluster(() => openee); + +testGetter(self, true, "opener"); +testGetter(() => openee, false, "openee"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-port.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-port.sub.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-port.sub.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-same.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-same.sub.https.html new file mode 100644 index 0000000000..562ab40c68 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-same.sub.https.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Opener is origin-keyed, openee is site-keyed, openee is same-origin to the opener</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + openWindow, + testOpenedWindowIsInSameAgentCluster, + testGetter +} from "../resources/helpers.mjs"; + +let openee; +promise_setup(async () => { + openee = await openWindow("{{hosts[][]}}"); +}); + +// Since they're same-origin, the openee's non-request is ignored, so both end +// up in the origin-keyed agent cluster. +testOpenedWindowIsInSameAgentCluster(() => openee); + +testGetter(self, true, "opener"); +testGetter(() => openee, true, "openee"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-same.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-same.sub.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-same.sub.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-subdomain.sub.https.html new file mode 100644 index 0000000000..d7d4791459 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-subdomain.sub.https.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Opener is origin-keyed, openee is site-keyed, openee is a subdomain of the opener</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + openWindow, + testOpenedWindowIsInADifferentAgentCluster, + testGetter +} from "../resources/helpers.mjs"; + +let openee; +promise_setup(async () => { + openee = await openWindow("{{hosts[][www]}}"); +}); + +// Since they're different-origin, the openee's non-request is respected, so the +// opener ends up in the origin-keyed agent cluster and the openee in the +// site-keyed one. +testOpenedWindowIsInADifferentAgentCluster(() => openee); + +testGetter(self, true, "opener"); +testGetter(() => openee, false, "openee"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-subdomain.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-subdomain.sub.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-subdomain.sub.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-port.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-port.sub.https.html new file mode 100644 index 0000000000..32a5066d2e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-port.sub.https.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Opener is origin-keyed, openee is origin-keyed, openee is different-origin to the opener because of a port mismatch</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + openWindow, + testOpenedWindowIsInADifferentAgentCluster, + testGetter +} from "../resources/helpers.mjs"; + +let openee; +promise_setup(async () => { + openee = await openWindow("{{hosts[][]}}:{{ports[https][1]}}", "?1"); +}); + +// Both request origin-keying, so the opener ends up in one origin-keyed agent +// cluster (the default port's origin), and the openee ends up in a different +// origin-keyed agent cluster (the other port's origin). +testOpenedWindowIsInADifferentAgentCluster(() => openee); + +testGetter(self, true, "opener"); +testGetter(() => openee, true, "openee"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-port.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-port.sub.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-port.sub.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-same.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-same.sub.https.html new file mode 100644 index 0000000000..a85decac3c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-same.sub.https.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Opener is origin-keyed, openee is origin-keyed, openee is same-origin to the opener</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + openWindow, + testOpenedWindowIsInSameAgentCluster, + testGetter +} from "../resources/helpers.mjs"; + +let openee; +promise_setup(async () => { + openee = await openWindow("{{hosts[][]}}", "?1"); +}); + +// Both request origin-keying, and they're same-origin, so they both end up in +// the same origin-keyed agent cluster. +testOpenedWindowIsInSameAgentCluster(() => openee); + +testGetter(self, true, "opener"); +testGetter(() => openee, true, "openee"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-same.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-same.sub.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-same.sub.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-subdomain.sub.https.html new file mode 100644 index 0000000000..148b39af23 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-subdomain.sub.https.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Opener is origin-keyed, openee is origin-keyed, openee is a subdomain of the opener</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + openWindow, + testOpenedWindowIsInADifferentAgentCluster, + testGetter +} from "../resources/helpers.mjs"; + +let openee; +promise_setup(async () => { + openee = await openWindow("{{hosts[][www]}}", "?1"); +}); + +// Both request origin-keyed, so the opener ends up in one origin-keyed agent +// cluster (the base domain's origin), and the openee ends up in a different +// origin-keyed agent cluster (the www subdomain's origin). +testOpenedWindowIsInADifferentAgentCluster(() => openee); + +testGetter(self, true, "opener"); +testGetter(() => openee, true, "openee"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-subdomain.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-subdomain.sub.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-subdomain.sub.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/regression-1399759.https.sub.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/regression-1399759.https.sub.html new file mode 100644 index 0000000000..d0b09f335d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/regression-1399759.https.sub.html @@ -0,0 +1,100 @@ +<!DOCTYPE html> +<html> +<head> +<meta name="variant" content="?pipe=header(Origin-Agent-Cluster,%3F0)"> +<meta name="variant" content="?pipe=header(Origin-Agent-Cluster,%3F1)"> +<title>Origin-Isolation after navigating about:blank.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +</head> +<body> +</body> +<script> +// Regression test for crbug.com/1399759. This is mainly based on +// html/infrastructure/urls/terminology-0/document-base-url-initiated-grand-parent.https.window.html, +// but restricts itself to the exact error condition. +// +// This test is run in two variants which differ in the Origin-Agent-Cluster +// http header values, ?0 and ?1. The test should pass in either case, but the +// regression we're testing for involves inconsistent clustering decisions, +// which requires clustering to be enabled in the first place. +promise_test(async test => { + // Create a cross-origin iframe. Use the executor.html, so we can ask it + // to execute scripts for us. + const child_token = token(); + const iframe = document.createElement("iframe"); + iframe.src = get_host_info().HTTPS_REMOTE_ORIGIN + + `/common/dispatcher/executor.html?uuid=${child_token}`; + document.body.appendChild(iframe); + + // The child creates a grand child in an iframe. + const reply_token = token(); + send(child_token, ` + const iframe = document.createElement("iframe"); + iframe.src = "/common/blank.html"; + iframe.onload = () => { + send("${reply_token}", "grand child loaded"); + }; + document.body.appendChild(iframe); + `); + assert_equals(await receive(reply_token), "grand child loaded"); + const grandchild = iframe.contentWindow[0]; + + // Navigate the grand-child toward about:blank. + grandchild.location = "about:blank"; + assert_equals(await receive(reply_token), "grand child loaded"); + + // This document and grandchild are same-origin, because about:blank + // inherits its origin from the initiator of the navigation, which is us. + // This access should not throw. + grandchild.document; +}, "Check the baseURL of an about:blank document cross-origin with its parent"); + +promise_test(async test => { + // This tests the same setup as above, but with about:srcdoc. Since one + // cannot just navigate to about:srcdoc, we'll have to include an extra + // step: Create an iframe with srcdoc attribute; navigate away; then + // navigate to about:srcdoc. + // srcdoc does not inherit the origin from the initiator - unlike + // about:blank - and so in this case the grandchild.document access should + // throw. + + // Create a cross-origin iframe. Use the executor.html, so we can ask it + // to execute scripts for us. + const child_token = token(); + const iframe = document.createElement("iframe"); + iframe.src = get_host_info().HTTPS_REMOTE_ORIGIN + + `/common/dispatcher/executor.html?uuid=${child_token}`; + document.body.appendChild(iframe); + + // The child creates a grand child in an iframe, using the srcdoc attribute. + const reply_token = token(); + send(child_token, ` + const iframe = document.createElement("iframe"); + iframe.onload = () => { + send("${reply_token}", "grand child loaded"); + }; + iframe.srcdoc = "nothing interesting"; + document.body.appendChild(iframe); + `); + assert_equals(await receive(reply_token), "grand child loaded"); + const grandchild = iframe.contentWindow[0]; + + // Navigate the grand child toward a regular URL. + grandchild.location = get_host_info().HTTPS_REMOTE_ORIGIN + "/common/blank.html"; + assert_equals(await receive(reply_token), "grand child loaded"); + + // Navigate the grand-child back, to about:srcdoc. + grandchild.location = "about:srcdoc"; + assert_equals(await receive(reply_token), "grand child loaded"); + + // This document and grandchild are cross-origin. about:srcdoc does not + // inherits its origin from the initiator of the navigation. This access + // should throw: + assert_throws_dom("SecurityError", () => { grandchild.document; }); +}, "Check that about:srcdoc navigation does not follow about:blank rules."); +</script> +</html> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/removing-iframes.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/removing-iframes.sub.https.html new file mode 100644 index 0000000000..b83aa9f5be --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/removing-iframes.sub.https.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>A site-keyed child at a given origin causes future children to also be site-keyed even after the iframe is removed</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script type="module"> +import { + insertIframe, + testSameAgentCluster, + testDifferentAgentClusters, + testGetter +} from "./resources/helpers.mjs"; + +let frame1; +promise_setup(async () => { + frame1 = await insertIframe("{{hosts[][www]}}"); +}); + +// Since they're different-origin, the parent's origin-keying request is +// respected, as is the child's non-request. So the parent ends up in the +// origin-keyed agent cluster and the child ends up in the site-keyed one. +testDifferentAgentClusters([self, 0], "Before"); +testGetter(self, true, "parent"); +testGetter(0, false, "child1"); + +promise_test(async () => { + frame1.remove(); + + await insertIframe("{{hosts[][www]}}", "?1"); + await insertIframe("{{hosts[][www1]}}"); +}, "Remove the iframe and insert new ones"); + +// Because of the historical presence of a site-keyed {{hosts[][www]}} iframe, +// the origin-keying request for child 2 will be ignored. So, +// child 2 and child 3 both end up in the site-keyed agent cluster. +testDifferentAgentClusters([self, 0], "Parent to child2"); +testDifferentAgentClusters([self, 1], "Parent to child3"); +testSameAgentCluster([0, 1], "child2 to child3"); +testSameAgentCluster([1, 0], "child3 to child2"); + +testGetter(0, false, "child2"); +testGetter(1, false, "child3"); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/removing-iframes.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/removing-iframes.sub.https.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/removing-iframes.sub.https.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/README.md b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/README.md new file mode 100644 index 0000000000..9292fe3894 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/README.md @@ -0,0 +1,6 @@ +Why are there `.headers` files here for the `.mjs` scripts? + +Because `../getter-special-cases/sandboxed-iframe.sub.https.html` is testing an +opaque origin, which is cross-origin with these scripts. Since +`<script type="module">` respects the same-origin policy, we need CORS headers +to allow them to be accessed. diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/coep-frame.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/coep-frame.html new file mode 100644 index 0000000000..7cbd89b943 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/coep-frame.html @@ -0,0 +1,5 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>A page with COEP set that will respond when asked</title> + +<script type="module" src="send-header-page-script.mjs"></script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/coep-frame.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/coep-frame.html.headers new file mode 100644 index 0000000000..4e798cd9f5 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/coep-frame.html.headers @@ -0,0 +1,2 @@ +Cross-Origin-Embedder-Policy: require-corp +Cross-Origin-Resource-Policy: cross-origin diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/crashy-popup.sub.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/crashy-popup.sub.html new file mode 100644 index 0000000000..45c8d5074d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/crashy-popup.sub.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>This page helps exhibit a crash bug when window.open()ed (see ../popups-crash.https.html)</title> + +<iframe src="https://{{hosts[][www]}}:{{ports[https][0]}}/html/browsers/origin/origin-keyed-agent-clusters/resources/send-oac-header.py"></iframe> +<iframe src="https://{{hosts[][www]}}:{{ports[https][0]}}/html/browsers/origin/origin-keyed-agent-clusters/resources/send-oac-header.py?header=?1"></iframe> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/crashy-popup.sub.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/crashy-popup.sub.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/crashy-popup.sub.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/frame.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/frame.html new file mode 100644 index 0000000000..537de07b14 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/frame.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>A frame included by a test page</title> + +<script> +window.onmessage = e => { + if (e.data.type === "set document.domain") { + document.domain = e.data.newValue; + e.source.postMessage({ type: "new document.domain", result: document.domain }, "*"); + } +}; +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/frame.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/frame.html.headers new file mode 100644 index 0000000000..79a20f30fc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/frame.html.headers @@ -0,0 +1 @@ +Origin-Agent-Cluster: ?1 diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/helpers.mjs b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/helpers.mjs new file mode 100644 index 0000000000..6bad76e3d9 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/helpers.mjs @@ -0,0 +1,390 @@ +/** + * Inserts an iframe usable for origin-keyed agent cluster testing, and returns + * a promise fulfilled when the iframe is loaded and its document.domain is set. + * The iframe will point to the send-oac-header.py file, on the designated + * host. + * @param {string} host - The host used to calculate the iframe's src="" + * @param {string=} header - The value of the Origin-Agent-Cluster header that + * the iframe will set. Omit this to set no header. + * @param {object=} options - Rarely-used options. + * @param {boolean=} options.redirectFirst - Whether to do a 302 redirect first + * before arriving at the page that sets the header. The redirecting page will + * not set the Origin-Agent-Cluster header. + * @returns {HTMLIFrameElement} The created iframe element + */ +export async function insertIframe(host, header, { redirectFirst = false } = {}) { + const iframe = document.createElement("iframe"); + const navigatePromise = navigateIframe(iframe, host, header, { redirectFirst }); + document.body.append(iframe); + await navigatePromise; + await setBothDocumentDomains(iframe.contentWindow); + return iframe; +} + +/** + * Navigates an iframe to a page for origin-keyed agent cluster testing, similar + * to insertIframe but operating on an existing iframe. + * @param {HTMLIFrameElement} iframeEl - The <iframe> element to navigate + * @param {string} host - The host to calculate the iframe's new src="" + * @param {string=} header - The value of the Origin-Agent-Cluster header that + * the newly-navigated-to page will set. Omit this to set no header. + * @param {object=} options - Rarely-used options. + * @param {boolean=} options.redirectFirst - Whether to do a 302 redirect first + * before arriving at the page that sets the header. The redirecting page will + * not set the Origin-Agent-Cluster header. + * @returns {Promise} a promise fulfilled when the load event fires, or rejected + * if the error event fires + */ +export function navigateIframe(iframeEl, host, header, { redirectFirst = false } = {}) { + const url = getSendHeaderURL(host, header, { redirectFirst }); + + const waitPromise = waitForIframe(iframeEl, url); + iframeEl.src = url; + return waitPromise; +} + +/** + * Returns a promise that is fulfilled when an iframe's load event fires, or + * rejected when its error event fires. + * @param {HTMLIFrameElement} iframeEl - The <iframe> element to wait on + * @param {string} destinationForErrorMessage - A string used in the promise + * rejection error message, if the error event fires + * @returns {Promise} a promise fulfilled when the load event fires, or rejected + * if the error event fires + */ +export function waitForIframe(iframeEl, destinationForErrorMessage) { + return new Promise((resolve, reject) => { + iframeEl.addEventListener("load", () => resolve()); + iframeEl.addEventListener( + "error", + () => reject(new Error(`Could not navigate to ${destinationForErrorMessage}`)) + ); + }); +} + +/** + * Opens a new window usable for origin-keyed agent cluster testing, and returns + * a promise fulfilled when the window is loaded and its document.domain is set. + * The window will point to the send-oac-header.py file, on the designated host. + * + * The opened window will be automatically closed when all the tests complete. + * @param {string} host - The host used to calculate the window's URL + * @param {string=} header - The value of the Origin-Agent-Cluster header that + * the opened window's page will set. Omit this to set no header. + * @returns {WindowProxy} The created window + */ +export async function openWindow(host, header) { + const url = getSendHeaderURL(host, header, { sendLoadedMessage: true }); + const openedWindow = window.open(url); + + add_completion_callback(() => openedWindow.close()); + + const whatHappened = await waitForMessage(openedWindow); + assert_equals(whatHappened, "loaded"); + + await setBothDocumentDomains(openedWindow); + + return openedWindow; +} + +/** + * Expands into a pair of promise_test() calls to ensure that two Windows are in + * the same agent cluster, by checking both that we can send a + * WebAssembly.Module, and that we can synchronously access the DOM. + * @param {Array} testFrames - An array of either the form [self, frameIndex] or + * [frameIndex1, frameIndex2], indicating the two Windows under test. E.g. + * [self, 0] or [0, 1]. + * @param {string=} testLabelPrefix - A prefix used in the test names. This can + * be omitted if testSameAgentCluster is only used once in a test file. + */ +export function testSameAgentCluster(testFrames, testLabelPrefix) { + const prefix = testLabelPrefix === undefined ? "" : `${testLabelPrefix}: `; + + if (testFrames[0] === self) { + // Between parent and a child at the index given by testFrames[1] + + promise_test(async () => { + const frameWindow = frames[testFrames[1]]; + const frameElement = document.querySelectorAll("iframe")[testFrames[1]]; + + // Must not throw + frameWindow.document; + + // Must not throw + frameWindow.location.href; + + assert_not_equals(frameElement.contentDocument, null, "contentDocument"); + + const whatHappened = await accessFrameElement(frameWindow); + assert_equals(whatHappened, "frameElement accessed successfully"); + }, `${prefix}setting document.domain must give sync access`); + } else { + // Between the two children at the index given by testFrames[0] and + // testFrames[1] + + promise_test(async () => { + const whatHappened1 = await accessDocumentBetween(testFrames); + assert_equals(whatHappened1, "accessed document successfully"); + + const whatHappened2 = await accessLocationHrefBetween(testFrames); + assert_equals(whatHappened2, "accessed location.href successfully"); + + // We don't test contentDocument/frameElement for these because accessing + // those via siblings has to go through the parent anyway. + }, `${prefix}setting document.domain must give sync access`); + } +} + +/** + * Expands into a pair of promise_test() calls to ensure that two Windows are in + * different agent clusters, by checking both that we cannot send a + * WebAssembly.Module, and that we cannot synchronously access the DOM. + * @param {Array} testFrames - An array of either the form [self, frameIndex] or + * [frameIndex1, frameIndex2], indicating the two Windows under test. E.g. + * [self, 0] or [0, 1]. + * @param {string=} testLabelPrefix - A prefix used in the test names. This can + * be omitted if testDifferentAgentClusters is only used once in a test file. + */ +export function testDifferentAgentClusters(testFrames, testLabelPrefix) { + const prefix = testLabelPrefix === undefined ? "" : `${testLabelPrefix}: `; + + if (testFrames[0] === self) { + // Between parent and a child at the index given by testFrames[1] + + promise_test(async () => { + // In general, cross-origin sharing of WebAssembly.Module is prohibited, + // so if we're in different agent clusters, it's definitely prohibited. + // Basic tests for this cross-origin prohibition are elsewhere; we include + // these here as an extra check to make sure there's no weird interactions + // with Origin-Agent-Cluster. + const frameWindow = frames[testFrames[1]]; + const whatHappened = await sendWasmModule(frameWindow); + + assert_equals(whatHappened, "messageerror"); + }, `${prefix}messageerror event must occur`); + + promise_test(async () => { + const frameWindow = frames[testFrames[1]]; + const frameElement = document.querySelectorAll("iframe")[testFrames[1]]; + + assert_throws_dom("SecurityError", DOMException, () => { + frameWindow.document; + }); + + assert_throws_dom("SecurityError", DOMException, () => { + frameWindow.location.href; + }); + + assert_equals(frameElement.contentDocument, null, "contentDocument"); + + const whatHappened = await accessFrameElement(frameWindow); + assert_equals(whatHappened, "null"); + }, `${prefix}setting document.domain must not give sync access`); + } else { + // Between the two children at the index given by testFrames[0] and + // testFrames[1] + + promise_test(async () => { + const whatHappened = await sendWasmModuleBetween(testFrames); + assert_equals(whatHappened, "messageerror"); + }, `${prefix}messageerror event must occur`); + + promise_test(async () => { + const whatHappened1 = await accessDocumentBetween(testFrames); + assert_equals(whatHappened1, "SecurityError"); + + const whatHappened2 = await accessLocationHrefBetween(testFrames); + assert_equals(whatHappened2, "SecurityError"); + + // We don't test contentDocument/frameElement for these because accessing + // those via siblings has to go through the parent anyway. + }, `${prefix}setting document.domain must not give sync access`); + } +} + +/** + * Expands into a pair of promise_test() calls to ensure that the given window, + * opened by window.open(), is in a different agent cluster from the current + * (opener) window. + * @param {function} openedWindowGetter - A function that returns the opened + * window + */ +export function testOpenedWindowIsInADifferentAgentCluster(openedWindowGetter) { + promise_test(async () => { + const whatHappened = await sendWasmModule(openedWindowGetter()); + + assert_equals(whatHappened, "messageerror"); + }, `messageerror event must occur`); + + promise_test(async () => { + assert_throws_dom("SecurityError", DOMException, () => { + openedWindowGetter().document; + }); + + assert_throws_dom("SecurityError", DOMException, () => { + openedWindowGetter().location.href; + }); + }, `setting document.domain must not give sync access`); +} + +/** + * Expands into a pair of promise_test() calls to ensure that the given window, + * opened by window.open(), is in the same agent cluster as the current + * (opener) window. + * @param {function} openedWindowGetter - A function that returns the opened + * window + */ +export function testOpenedWindowIsInSameAgentCluster(openedWindowGetter) { + promise_test(async () => { + const whatHappened = await sendWasmModule(openedWindowGetter()); + + assert_equals(whatHappened, "WebAssembly.Module message received"); + }, `message event must occur`); + + promise_test(async () => { + // Must not throw + openedWindowGetter().document; + + // Must not throw + openedWindowGetter().location.href; + }, `setting document.domain must give sync access`); +} + +/** + * Creates a promise_test() to check the value of the originAgentCluster getter + * in the given testFrame. + * @param {Window|number|function} testFrame - Either self, or a frame index to + test, or a function that returns a Window to test. + * @param {boolean} expected - The expected value for originAgentCluster. + * @param {string=} testLabelPrefix - A prefix used in the test names. This can + * be omitted if the function is only used once in a test file. + */ +export function testGetter(testFrame, expected, testLabelPrefix) { + const prefix = testLabelPrefix === undefined ? "" : `${testLabelPrefix}: `; + + promise_test(async () => { + if (testFrame === self) { + assert_equals(self.originAgentCluster, expected); + } else if (typeof testFrame === "number") { + const frameWindow = frames[testFrame]; + const result = await accessOriginAgentCluster(frameWindow); + assert_equals(result, expected); + } else { + assert_equals(typeof testFrame, "function", + "testFrame argument must be self, a number, or a function"); + const result = await accessOriginAgentCluster(testFrame()); + assert_equals(result, expected); + } + }, `${prefix}originAgentCluster must equal ${expected}`); +} + +/** + * Sends a WebAssembly.Module instance to the given Window, and waits for it to + * send back a message indicating whether it got the module or got a + * messageerror event. (This relies on the given Window being derived from + * insertIframe or navigateIframe.) + * @param {Window} frameWindow - The destination Window + * @returns {Promise} A promise which will be fulfilled with either + * "WebAssembly.Module message received" or "messageerror" + */ +export async function sendWasmModule(frameWindow) { + // This function is coupled to ./send-oac-header.py, which ensures that + // sending such a message will result in a message back. + frameWindow.postMessage(await createWasmModule(), "*"); + return waitForMessage(frameWindow); +} + +/** + * Sets document.domain (to itself) for both the current Window and the given + * Window. The latter relies on the given Window being derived from insertIframe + * or navigateIframe. + * @param frameWindow - The other Window whose document.domain is to be set + * @returns {Promise} A promise which will be fulfilled after both + * document.domains are set + */ +export async function setBothDocumentDomains(frameWindow) { + // By setting both this page's document.domain and the iframe's + // document.domain to the same value, we ensure that they can synchronously + // access each other, unless they are origin-keyed. + // NOTE: document.domain being unset is different than it being set to its + // current value. It is a terrible API. + document.domain = document.domain; + + // This function is coupled to ./send-oac-header.py, which ensures that + // sending such a message will result in a message back. + frameWindow.postMessage({ command: "set document.domain", newDocumentDomain: document.domain }, "*"); + const whatHappened = await waitForMessage(frameWindow); + assert_equals(whatHappened, "document.domain is set"); +} + +async function accessOriginAgentCluster(frameWindow) { + // This function is coupled to ./send-oac-header.py, which ensures that + // sending such a message will result in a message back. + frameWindow.postMessage({ command: "get originAgentCluster" }, "*"); + return waitForMessage(frameWindow); +} + +function getSendHeaderURL(host, header, { sendLoadedMessage = false, redirectFirst = false } = {}) { + const url = new URL("send-oac-header.py", import.meta.url); + url.host = host; + if (header !== undefined) { + url.searchParams.set("header", header); + } + if (sendLoadedMessage) { + url.searchParams.set("send-loaded-message", ""); + } + if (redirectFirst) { + url.searchParams.set("redirect-first", ""); + } + + return url.href; +} + +async function sendWasmModuleBetween(testFrames) { + const sourceFrame = frames[testFrames[0]]; + const indexIntoParentFrameOfDestination = testFrames[1]; + + sourceFrame.postMessage({ command: "send WASM module", indexIntoParentFrameOfDestination }, "*"); + return waitForMessage(sourceFrame); +} + +async function accessDocumentBetween(testFrames) { + const sourceFrame = frames[testFrames[0]]; + const indexIntoParentFrameOfDestination = testFrames[1]; + + sourceFrame.postMessage({ command: "access document", indexIntoParentFrameOfDestination }, "*"); + return waitForMessage(sourceFrame); +} + +async function accessLocationHrefBetween(testFrames) { + const sourceFrame = frames[testFrames[0]]; + const indexIntoParentFrameOfDestination = testFrames[1]; + + sourceFrame.postMessage({ command: "access location.href", indexIntoParentFrameOfDestination }, "*"); + return waitForMessage(sourceFrame); +} + +async function accessFrameElement(frameWindow) { + frameWindow.postMessage({ command: "access frameElement" }, "*"); + return waitForMessage(frameWindow); +} + +function waitForMessage(expectedSource) { + return new Promise(resolve => { + const handler = e => { + if (e.source === expectedSource) { + resolve(e.data); + window.removeEventListener("message", handler); + } + }; + window.addEventListener("message", handler); + }); +} + +// Any WebAssembly.Module will work fine for our tests; we just want to find out +// if it gives message or messageerror. So, we reuse one from the /wasm/ tests. +async function createWasmModule() { + const response = await fetch("/wasm/serialization/module/resources/incrementer.wasm"); + const ab = await response.arrayBuffer(); + return WebAssembly.compile(ab); +} diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/helpers.mjs.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/helpers.mjs.headers new file mode 100644 index 0000000000..cb762eff80 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/helpers.mjs.headers @@ -0,0 +1 @@ +Access-Control-Allow-Origin: * diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/send-header-page-script.mjs b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/send-header-page-script.mjs new file mode 100644 index 0000000000..17b00684d6 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/send-header-page-script.mjs @@ -0,0 +1,63 @@ +import { sendWasmModule } from "./helpers.mjs"; + +// This is done for the window.open() case. For <iframe>s we use the +// <iframe> element's load event instead. +const usp = new URLSearchParams(location.search); +if (usp.has("send-loaded-message")) { + opener.postMessage("loaded", "*"); +} + +window.onmessage = async (e) => { + // These could come from the parent, opener, or siblings. + if (e.data.constructor === WebAssembly.Module) { + e.source.postMessage("WebAssembly.Module message received", "*"); + } + + // These could come from the parent or opener. + if (e.data.command === "set document.domain") { + document.domain = e.data.newDocumentDomain; + e.source.postMessage("document.domain is set", "*"); + } else if (e.data.command === "get originAgentCluster") { + e.source.postMessage(self.originAgentCluster, "*"); + } + + // These only come from the parent. + if (e.data.command === "send WASM module") { + const destinationFrameWindow = parent.frames[e.data.indexIntoParentFrameOfDestination]; + const whatHappened = await sendWasmModule(destinationFrameWindow); + parent.postMessage(whatHappened, "*"); + } else if (e.data.command === "access document") { + const destinationFrameWindow = parent.frames[e.data.indexIntoParentFrameOfDestination]; + try { + destinationFrameWindow.document; + parent.postMessage("accessed document successfully", "*"); + } catch (e) { + parent.postMessage(e.name, "*"); + } + } else if (e.data.command === "access location.href") { + const destinationFrameWindow = parent.frames[e.data.indexIntoParentFrameOfDestination]; + try { + destinationFrameWindow.location.href; + parent.postMessage("accessed location.href successfully", "*"); + } catch (e) { + parent.postMessage(e.name, "*"); + } + } else if (e.data.command === "access frameElement") { + if (frameElement === null) { + parent.postMessage("null", "*"); + } else if (frameElement?.constructor?.name === "HTMLIFrameElement") { + parent.postMessage("frameElement accessed successfully", "*"); + } else { + parent.postMessage("something wierd happened", "*"); + } + } + + // We could also receive e.data === "WebAssembly.Module message received", + // but that's handled by await sendWasmModule() above. +}; + +window.onmessageerror = e => { + e.source.postMessage("messageerror", "*"); +}; + +document.body.textContent = location.href; diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/send-header-page-script.mjs.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/send-header-page-script.mjs.headers new file mode 100644 index 0000000000..cb762eff80 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/send-header-page-script.mjs.headers @@ -0,0 +1 @@ +Access-Control-Allow-Origin: * diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/send-oac-header.py b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/send-oac-header.py new file mode 100644 index 0000000000..cc8860fe75 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/send-oac-header.py @@ -0,0 +1,43 @@ +def main(request, response): + """Send a response with the Origin-Agent-Cluster header given in the + "header" query parameter, or no header if that is not provided. Other query + parameters (only their presence/absence matters) are "send-loaded-message" + and "redirect-first", which modify the behavior a bit. + + In either case, the response will listen for various messages posted and + coordinate with the sender. See ./helpers.mjs for how these handlers are + used. + """ + + if b"redirect-first" in request.GET: + # Create a new query string, which is the same as the one we're given but + # with the redirect-first component stripped out. This allows tests to use + # any value (or no value) for the other query params, in combination with + # redirect-first. + query_string_pieces = [] + if b"header" in request.GET: + query_string_pieces.append(b"header=" + request.GET.first(b"header")) + if b"send-loaded-message" in request.GET: + query_string_pieces.append(b"send-loaded-message") + query_string = b"?" + b"&".join(query_string_pieces) + + return ( + 302, + [(b"Location", b"/html/browsers/origin/origin-keyed-agent-clusters/resources/send-oac-header.py" + query_string)], + u"" + ) + + if b"header" in request.GET: + header = request.GET.first(b"header") + response.headers.set(b"Origin-Agent-Cluster", header) + + response.headers.set(b"Content-Type", b"text/html") + + return u""" + <!DOCTYPE html> + <meta charset="utf-8"> + <title>Helper page for origin-keyed agent cluster tests</title> + + <body> + <script type="module" src="send-header-page-script.mjs"></script> + """ diff --git a/testing/web-platform/tests/html/browsers/origin/origin-of-data-document.html b/testing/web-platform/tests/html/browsers/origin/origin-of-data-document.html new file mode 100644 index 0000000000..448f47fa24 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/origin-of-data-document.html @@ -0,0 +1,30 @@ +<!doctype html> +<html> + <head> + <meta charset=utf-8> + <title>Origin of document produced from a 'data:' URL</title> + <link rel="help" href="https://html.spec.whatwg.org/multipage/browsers.html#origin"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <script> + async_test(function (t) { + var i = document.createElement('iframe'); + i.src = "data:text/html,<script>" + + " window.parent.postMessage('Hello!', '*');" + + "</scr" + "ipt>"; + + window.addEventListener("message", t.step_func_done(function (e) { + assert_equals(e.origin, "null", "Messages sent from a 'data:' URL should have an opaque origin (which serializes to 'null')."); + assert_throws_dom("SecurityError", function () { + var couldAccessCrossOriginProperty = e.source.location.href; + }, "The 'data:' frame should be cross-origin: 'window.location.href'"); + assert_equals(i.contentDocument, null, "The 'data:' iframe should be unable to access its contentDocument."); + })); + + document.body.appendChild(i); + }, "The origin of a 'data:' document in a frame is opaque."); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain.html b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain.html new file mode 100644 index 0000000000..d3af35c6d7 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain.html @@ -0,0 +1,35 @@ +<!doctype html> +<html> + <head> + <title>document.domain's getter</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script> + test(function() { + assert_equals(typeof document.domain, "string", "document.domain is a string"); + assert_not_equals(document.domain, "", "document.domain is not empty"); + }, "basics"); + + test(function() { + assert_equals(document.domain, window.location.hostname, "equals location.hostname"); + }, "current document"); + + test(function() { + var doc = new Document(); + assert_equals(doc.domain, window.location.hostname, "equals location.hostname"); + }, "new Document()"); + + async_test(t => { + const client = new XMLHttpRequest(); + client.open("GET", "/common/blank.html"); + client.responseType = "document" + client.send(); + client.onload = t.step_func_done(() => { + assert_equals(client.response.domain, window.location.hostname); + }); + }, "XMLHttpRequest's response document"); + </script> + </head> + <body> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_access_details.sub.html b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_access_details.sub.html new file mode 100644 index 0000000000..eb02c96f1d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_access_details.sub.html @@ -0,0 +1,305 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="support/document_domain_frame.sub.js"></script> +<body> +<script> +promise_test(async (t) => { + let frame1 = await createFrame(t, "control1-1", "{{domains[www1]}}"); + let frame2 = await createFrame(t, "control1-2", "{{domains[www1]}}"); + let result = await postMessageToFrame(frame1, { 'poke-at-sibling': "control1-2" }); + assert_equals(result.data, "omg!"); +}, "Access allowed if same-origin with no 'document.domain' modification. (Sanity check)"); + +promise_test(async (t) => { + let frame1 = await createFrame(t, "control2-1", "{{domains[www1]}}"); + let frame2 = await createFrame(t, "control2-2", "{{domains[www2]}}"); + let result = await postMessageToFrame(frame1, { 'poke-at-sibling': "control2-2" }); + assert_equals(result.data, "SecurityError"); +}, "Access not allowed if different-origin with no 'document.domain' modification. (Sanity check)"); + +promise_test(async (t) => { + let frame1 = await createFrame(t, "one-set-one-not-1", "{{domains[www1]}}"); + let frame2 = await createFrame(t, "one-set-one-not-2", "{{domains[www1]}}"); + await postMessageToFrame(frame1, { domain: "{{domains[www1]}}" }); + + let result = await postMessageToFrame(frame1, { 'poke-at-sibling': "one-set-one-not-2" }); + assert_equals(result.data, "SecurityError"); + + result = await postMessageToFrame(frame2, { 'poke-at-sibling': "one-set-one-not-1" }); + assert_equals(result.data, "SecurityError"); +}, "Access disallowed if same-origin but only one sets document.domain."); + +promise_test(async (t) => { + var frame1 = await createFrame(t, "both-set-to-existing-1", "{{domains[www1]}}"); + var frame2 = await createFrame(t, "both-set-to-existing-2", "{{domains[www1]}}"); + let result = await postMessageToFrame(frame1, { domain: "{{domains[www1]}}" }); + assert_equals(result.data, "Done"); + + result = await postMessageToFrame(frame2, { domain: "{{domains[www1]}}" }); + assert_equals(result.data, "Done"); + + result = await postMessageToFrame(frame1, { 'poke-at-sibling': "both-set-to-existing-2" }); + assert_equals(result.data, "omg!"); + + result = await postMessageToFrame(frame2, { 'poke-at-sibling': "both-set-to-existing-1" }); + assert_equals(result.data, "omg!"); +}, "Access allowed if same-origin and both set document.domain to existing value."); + +promise_test(async (t) => { + var frame1 = await createFrame(t, "both-set-to-parent-1", "{{domains[www1]}}"); + var frame2 = await createFrame(t, "both-set-to-parent-2", "{{domains[www2]}}"); + let result = await postMessageToFrame(frame1, { domain: "{{domains[]}}" }); + assert_equals(result.data, "Done"); + + result = await postMessageToFrame(frame2, { domain: "{{domains[]}}" }); + assert_equals(result.data, "Done"); + + result = await postMessageToFrame(frame1, { 'poke-at-sibling': "both-set-to-parent-2" }); + assert_equals(result.data, "omg!"); + + result = await postMessageToFrame(frame2, { 'poke-at-sibling': "both-set-to-parent-1" }); + assert_equals(result.data, "omg!"); +}, "Access allowed if different-origin but both set document.domain to parent domain."); + +promise_test(async (t) => { + var frame1 = await createFrame(t, "allow-then-revoke-1", "{{domains[www1]}}"); + var frame2 = await createFrame(t, "allow-then-revoke-2", "{{domains[www1]}}"); + let result = await postMessageToFrame(frame1, { domain: "{{domains[www1]}}" }); + assert_equals(result.data, "Done"); + + result = await postMessageToFrame(frame2, { domain: "{{domains[www1]}}" }); + assert_equals(result.data, "Done"); + + result = await postMessageToFrame(frame1, { 'poke-at-sibling': "allow-then-revoke-2" }); + assert_equals(result.data, "omg!"); + + result = await postMessageToFrame(frame2, { 'poke-at-sibling': "allow-then-revoke-1" }); + assert_equals(result.data, "omg!"); + + result = await postMessageToFrame(frame1, { domain: "{{domains[]}}" }); + assert_equals(result.data, "Done"); + + result = await postMessageToFrame(frame1, { 'poke-at-sibling': "allow-then-revoke-2" }); + assert_equals(result.data, "SecurityError"); + + result = await postMessageToFrame(frame2, { 'poke-at-sibling': "allow-then-revoke-1" }); + assert_equals(result.data, "SecurityError"); +}, "Access disallowed again if same-origin, both set document-domain to existing value, then one sets to parent."); + +promise_test(async (t) => { + let frame1 = await createFrame(t, "revoke-Window-1", "{{domains[www1]}}"); + let frame2 = await createFrame(t, "revoke-Window-2", "{{domains[www1]}}"); + + let result = await postMessageToFrame(frame1, { cache: ["parent", "revoke-Window-2"] }); + assert_equals(result.data, "cached"); + + result = await postMessageToFrame(frame1, 'touch-cached'); + assert_equals(result.data, "Reachable 1"); + + result = await postMessageToFrame(frame2, { cache: ["parent", "revoke-Window-1"] }); + assert_equals(result.data, "cached"); + + result = await postMessageToFrame(frame1, 'touch-cached'); + assert_equals(result.data, "Reachable 1"); + + result = await postMessageToFrame(frame1, { domain: "{{domains[www1]}}" }); + assert_equals(result.data, "Done"); + + result = await postMessageToFrame(frame1, 'touch-cached'); + assert_equals(result.data, "SecurityError"); + + result = await postMessageToFrame(frame2, 'touch-cached'); + assert_equals(result.data, "SecurityError"); +}, "Access is revoked to Window object when we stop being same effective script origin due to document.domain."); + +promise_test(async (t) => { + let frame1 = await createFrame(t, "revoke-Location-1", "{{domains[www1]}}"); + let frame2 = await createFrame(t, "revoke-Location-2", "{{domains[www1]}}"); + + let result = await postMessageToFrame(frame1, { cache: ["parent", "revoke-Location-2", "location"] }); + assert_equals(result.data, "cached"); + + result = await postMessageToFrame(frame1, 'touch-cached'); + assert_equals(result.data, "Reachable 3"); + + result = await postMessageToFrame(frame2, { cache: ["parent", "revoke-Location-1", "location"] }); + assert_equals(result.data, "cached"); + + result = await postMessageToFrame(frame1, 'touch-cached'); + assert_equals(result.data, "Reachable 3"); + + result = await postMessageToFrame(frame1, { domain: "{{domains[www1]}}" }); + assert_equals(result.data, "Done"); + + result = await postMessageToFrame(frame1, 'touch-cached'); + assert_equals(result.data, "SecurityError"); + + result = await postMessageToFrame(frame2, 'touch-cached'); + assert_equals(result.data, "SecurityError"); +}, "Access is revoked to Location object when we stop being same effective script origin due to document.domain."); + +promise_test(async (t) => { + let frame1 = await createFrame(t, "no-revoke-Document-1", "{{domains[www1]}}"); + let frame2 = await createFrame(t, "no-revoke-Document-2", "{{domains[www1]}}"); + + let result = await postMessageToFrame(frame1, { cache: ["parent", "no-revoke-Document-2", "document"] }); + assert_equals(result.data, "cached"); + + result = await postMessageToFrame(frame1, 'touch-cached'); + assert_equals(result.data, "Reachable 4"); + + result = await postMessageToFrame(frame2, { cache: ["parent", "no-revoke-Document-1", "document"] }); + assert_equals(result.data, "cached"); + + result = await postMessageToFrame(frame2, 'touch-cached'); + assert_equals(result.data, "Reachable 4"); + + result = await postMessageToFrame(frame1, { domain: "{{domains[www1]}}" }); + assert_equals(result.data, "Done"); + + result = await postMessageToFrame(frame1, 'touch-cached'); + assert_equals(result.data, "Reachable 4"); + + result = await postMessageToFrame(frame2, 'touch-cached'); + assert_equals(result.data, "Reachable 4"); +}, "Access is not revoked to Document object when we stop being same effective script origin due to document.domain."); + +promise_test(async (t) => { + let frame1 = await createFrame(t, "no-revoke-object-1", "{{domains[www1]}}"); + let frame2 = await createFrame(t, "no-revoke-object-2", "{{domains[www1]}}"); + + let result = await postMessageToFrame(frame1, { cache: ["parent", "no-revoke-object-2", "bar"] }); + assert_equals(result.data, "cached"); + + result = await postMessageToFrame(frame1, 'touch-cached'); + assert_equals(result.data, "Reachable 2"); + + result = await postMessageToFrame(frame2, { cache: ["parent", "no-revoke-object-1", "bar"] }); + assert_equals(result.data, "cached"); + + result = await postMessageToFrame(frame1, 'touch-cached'); + assert_equals(result.data, "Reachable 2"); + + result = await postMessageToFrame(frame1, { domain: "{{domains[www1]}}" }); + assert_equals(result.data, "Done"); + + result = await postMessageToFrame(frame1, 'touch-cached'); + assert_equals(result.data, "Reachable 2"); + + result = await postMessageToFrame(frame2, 'touch-cached'); + assert_equals(result.data, "Reachable 2"); +}, "Access is not revoked to random object when we stop being same effective script origin due to document.domain."); + +promise_test(async (t) => { + let frame1 = await createFrame(t, "join-and-diverge-1", "{{domains[www2.www1]}}"); + let frame2 = await createFrame(t, "join-and-diverge-2", "{{domains[www1.www1]}}"); + + // Make sure we can't touch each other. + let result = await postMessageToFrame(frame1, { 'poke-at-sibling': "join-and-diverge-2" }); + assert_equals(result.data, "SecurityError"); + + result = await postMessageToFrame(frame2, { 'poke-at-sibling': "join-and-diverge-1" }); + assert_equals(result.data, "SecurityError"); + + result = await postMessageToFrame(frame1, { cache: ["parent", "join-and-diverge-2", "bar"] }); + assert_equals(result.data, "SecurityError"); + + result = await postMessageToFrame(frame2, { cache: ["parent", "join-and-diverge-1", "document"] }); + assert_equals(result.data, "SecurityError"); + + // Let's join up now. + result = await postMessageToFrame(frame1, { domain: "{{domains[www1]}}" }); + assert_equals(result.data, "Done"); + + result = await postMessageToFrame(frame2, { domain: "{{domains[www1]}}" }); + assert_equals(result.data, "Done"); + + // Now we should be able to touch each other. + result = await postMessageToFrame(frame1, { 'poke-at-sibling': "join-and-diverge-2" }); + assert_equals(result.data, "omg!"); + + result = await postMessageToFrame(frame2, { 'poke-at-sibling': "join-and-diverge-1" }); + assert_equals(result.data, "omg!"); + + // Cache a random object and a document. + result = await postMessageToFrame(frame1, { cache: ["parent", "join-and-diverge-2", "bar"] }); + assert_equals(result.data, "cached"); + + result = await postMessageToFrame(frame1, 'touch-cached'); + assert_equals(result.data, "Reachable 2"); + + result = await postMessageToFrame(frame2, { cache: ["parent", "join-and-diverge-1", "document"] }); + assert_equals(result.data, "cached"); + + result = await postMessageToFrame(frame2, 'touch-cached'); + assert_equals(result.data, "Reachable 4"); + + // OK, now let's diverge + result = await postMessageToFrame(frame1, { domain: "{{domains[]}}" }); + assert_equals(result.data, "Done"); + + // We should still be able to touch our cached things. + result = await postMessageToFrame(frame1, 'touch-cached'); + assert_equals(result.data, "Reachable 2"); + + result = await postMessageToFrame(frame2, 'touch-cached'); + assert_equals(result.data, "Reachable 4"); +}, "Access evolves correctly for non-cross-origin objects when we join up via document.domain and then diverge again."); + +promise_test(async (t) => { + let frame1 = await createFrame(t, "join-and-diverge-cross-origin-1", "{{domains[www2.www1]}}"); + let frame2 = await createFrame(t, "join-and-diverge-cross-origin-2", "{{domains[www1.www1]}}"); + + // Make sure we can't touch each other. + let result = await postMessageToFrame(frame1, { 'poke-at-sibling': "join-and-diverge-cross-origin-2" }); + assert_equals(result.data, "SecurityError"); + + result = await postMessageToFrame(frame2, { 'poke-at-sibling': "join-and-diverge-cross-origin-1" }); + assert_equals(result.data, "SecurityError"); + + result = await postMessageToFrame(frame1, { cache: ["parent", "join-and-diverge-2", "bar"] }); + assert_equals(result.data, "SecurityError"); + + result = await postMessageToFrame(frame2, { cache: ["parent", "join-and-diverge-1", "document"] }); + assert_equals(result.data, "SecurityError"); + + // Let's join up now. + result = await postMessageToFrame(frame1, { domain: "{{domains[www1]}}" }); + assert_equals(result.data, "Done"); + + result = await postMessageToFrame(frame2, { domain: "{{domains[www1]}}" }); + assert_equals(result.data, "Done"); + + // Now we should be able to touch each other. + result = await postMessageToFrame(frame1, { 'poke-at-sibling': "join-and-diverge-cross-origin-2" }); + assert_equals(result.data, "omg!"); + + result = await postMessageToFrame(frame2, { 'poke-at-sibling': "join-and-diverge-cross-origin-1" }); + assert_equals(result.data, "omg!"); + + // Cache a window and a location + result = await postMessageToFrame(frame1, { cache: ["parent", "join-and-diverge-cross-origin-2"] }); + assert_equals(result.data, "cached"); + + result = await postMessageToFrame(frame1, 'touch-cached'); + assert_equals(result.data, "Reachable 1"); + + result = await postMessageToFrame(frame2, { cache: ["parent", "join-and-diverge-cross-origin-1", "location"] }); + assert_equals(result.data, "cached"); + + result = await postMessageToFrame(frame2, 'touch-cached'); + assert_equals(result.data, "Reachable 3"); + + // OK, now let's diverge + result = await postMessageToFrame(frame1, { domain: "{{domains[]}}" }); + assert_equals(result.data, "Done"); + + // Now our cross-origin objects should start denying access. + result = await postMessageToFrame(frame1, 'touch-cached'); + assert_equals(result.data, "SecurityError"); + + result = await postMessageToFrame(frame2, 'touch-cached'); + assert_equals(result.data, "SecurityError"); +}, "Access evolves correctly for cross-origin objects when we join up via document.domain and then diverge again."); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter.html b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter.html new file mode 100644 index 0000000000..2539528341 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter.html @@ -0,0 +1,76 @@ +<!doctype html> +<html> + <head> + <title>document.domain's setter</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/common/get-host-info.sub.js"></script> + </head> + <body> + <iframe id="iframe"></iframe> + <script> + var host_info = get_host_info(); + var HTTP_PORT = host_info.HTTP_PORT; + var ORIGINAL_HOST = host_info.ORIGINAL_HOST; + var SUFFIX_HOST = ORIGINAL_HOST.substring(ORIGINAL_HOST.lastIndexOf('.') + 1); // e.g. "test" + var REMOTE_HOST = host_info.REMOTE_HOST; + var iframe = document.getElementById("iframe"); + var iframe_url = new URL("support/document_domain_setter_iframe.html", document.location); + iframe_url.hostname = REMOTE_HOST; + iframe.src = iframe_url; + + test(function() { + assert_throws_dom("SecurityError", function() { document.domain = SUFFIX_HOST; }); + assert_throws_dom("SecurityError", function() { document.domain = "." + SUFFIX_HOST; }); + assert_throws_dom("SecurityError", function() { document.domain = REMOTE_HOST; }); + assert_throws_dom("SecurityError", function() { document.domain = "example.com"; }); + }, "failed setting of document.domain"); + + async_test(function(t) { + iframe.addEventListener("load", t.step_func_done(function() { + // Before setting document.domain, the iframe is not + // same-origin-domain, so security checks fail. + assert_equals(iframe.contentDocument, null); + assert_throws_dom("SecurityError", () => iframe.contentWindow.frameElement); + assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.origin; }); + assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.href; }); + assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.protocol; }); + assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.host; }); + assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.port; }); + assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.hostname; }); + assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.pathname; }); + assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.hash; }); + assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.search; }); + assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.toString(); }); + // Set document.domain + document.domain = ORIGINAL_HOST; + // After setting document.domain, the iframe is + // same-origin-domain, so security checks pass. + assert_equals(iframe.contentDocument.domain, document.domain); + assert_equals(iframe.contentWindow.frameElement, iframe); + assert_equals(iframe.contentWindow.origin, iframe_url.origin); + assert_equals(iframe.contentWindow.location.href, iframe_url.href); + assert_equals(iframe.contentWindow.location.protocol, iframe_url.protocol); + assert_equals(iframe.contentWindow.location.host, iframe_url.host); + assert_equals(iframe.contentWindow.location.port, iframe_url.port); + assert_equals(iframe.contentWindow.location.hostname, iframe_url.hostname); + assert_equals(iframe.contentWindow.location.pathname, iframe_url.pathname); + assert_equals(iframe.contentWindow.location.hash, iframe_url.hash); + assert_equals(iframe.contentWindow.location.search, iframe_url.search); + assert_equals(iframe.contentWindow.location.search, iframe_url.search); + assert_equals(iframe.contentWindow.location.toString(), iframe_url.toString()); + // document.open checks for same-origin, not same-origin-domain, + // https://github.com/whatwg/html/issues/2282 + assert_throws_dom("SecurityError", iframe.contentWindow.DOMException, + function() { iframe.contentDocument.open(); }); + })); + }, "same-origin-domain iframe"); + + test(() => { + assert_throws_dom("SecurityError", () => { (new Document).domain = document.domain }); + assert_throws_dom("SecurityError", () => { document.implementation.createHTMLDocument().domain = document.domain }); + assert_throws_dom("SecurityError", () => { document.implementation.createDocument(null, "").domain = document.domain }); + }, "failed setting of document.domain for documents without browsing context"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter_srcdoc.html b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter_srcdoc.html new file mode 100644 index 0000000000..65a7f5c898 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter_srcdoc.html @@ -0,0 +1,84 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<!-- SEKRITS! --> +<input id="sekrit" value="omg!"> + +<script> + function postMessageToFrame(frame, message) { + return new Promise(resolve => { + var c = new MessageChannel(); + c.port1.onmessage = e => { + resolve({ data: e.data, frame: frame }) + }; + frame.contentWindow.postMessage(message, '*', [c.port2]); + }); + } + + function createFrame() { + return new Promise(resolve => { + var i = document.createElement('iframe'); + i.srcdoc = ` + <script> + window.addEventListener('message', e => { + if (e.data.domain !== undefined) { + try { + document.domain = e.data.domain; + e.ports[0].postMessage('Done'); + } catch(error) { + e.ports[0].postMessage(error.name); + } + } else if (e.data == 'poke-at-parent') { + try { + var sekrit = window.parent.document.body.querySelector('#sekrit').value; + e.ports[0].postMessage(sekrit); + } catch(error) { + e.ports[0].postMessage(error.name); + } + } + }); + window.parent.postMessage('Hi!', '*'); + </scr` + `ipt>`; + window.addEventListener('message', m => { + if (m.source == i.contentWindow) + resolve(i); + }); + document.body.appendChild(i); + }); + } + + promise_test(t => { + return createFrame() + .then(f => postMessageToFrame(f, 'poke-at-parent')) + .then(result => { + assert_equals(result.data, document.querySelector('#sekrit').value); + result.frame.remove(); + }); + }, "srcdoc can access with no 'document.domain' modification."); + + promise_test(t => { + return createFrame() + .then(f => postMessageToFrame(f, { domain: window.location.hostname })) + .then(result => { + assert_equals(result.data, 'Done'); + return postMessageToFrame(result.frame, 'poke-at-parent') + .then(result => { + assert_equals(result.data, document.querySelector('#sekrit').value); + result.frame.remove(); + }); + }); + }, "srcdoc can access with valid 'document.domain'."); + + promise_test(t => { + return createFrame() + .then(f => { + document.domain = window.location.hostname; + return postMessageToFrame(f, 'poke-at-parent'); + }) + .then(result => { + assert_equals(result.data, document.querySelector('#sekrit').value); + result.frame.remove(); + }); + }, "srcdoc can access when parent modifies 'document.domain'."); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/sandboxed-document_domain.html b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/sandboxed-document_domain.html new file mode 100644 index 0000000000..ae1a0ccd56 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/sandboxed-document_domain.html @@ -0,0 +1,21 @@ +<!doctype html> +<title>Sandboxed document.domain</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + test(() => { + assert_throws_dom("SecurityError", () => { document.domain = document.domain }); + }); + test(() => { + assert_throws_dom("SecurityError", () => { (new Document).domain = document.domain }); + }); + test(() => { + assert_throws_dom("SecurityError", () => { document.implementation.createHTMLDocument().domain = document.domain }); + }); + test(() => { + assert_throws_dom("SecurityError", () => { document.implementation.createDocument(null, "").domain = document.domain }); + }); + test(() => { + assert_throws_dom("SecurityError", () => { document.createElement("template").content.ownerDocument.domain = document.domain }); + }); +</script> diff --git a/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/sandboxed-document_domain.html.headers b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/sandboxed-document_domain.html.headers new file mode 100644 index 0000000000..82e8023d0b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/sandboxed-document_domain.html.headers @@ -0,0 +1 @@ +Content-Security-Policy: sandbox allow-scripts allow-same-origin diff --git a/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/support/document_domain_frame.html b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/support/document_domain_frame.html new file mode 100644 index 0000000000..61f54af359 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/support/document_domain_frame.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<script> + let cache = window; + // "foo" needs to be a var so it's a property on the global. + var foo = 'Reachable 1'; + // "bar" needs to be a var so it's a property on the global. + var bar = { foo: 'Reachable 2' }; + location.foo = 'Reachable 3'; + document.foo = 'Reachable 4'; + window.addEventListener('message', e => { + if (e.data.domain !== undefined) { + try { + document.domain = e.data.domain; + e.ports[0].postMessage('Done'); + } catch(error) { + e.ports[0].postMessage(error.name); + } + } else if (e.data['poke-at-sibling'] !== undefined) { + try { + var sekrit = parent[e.data['poke-at-sibling']].document.body.querySelector('#sekrit').value; + e.ports[0].postMessage(sekrit); + } catch(error) { + e.ports[0].postMessage(error.name); + } + } else if (e.data.cache != undefined) { + let path = e.data.cache; + try { + while (path.length != 0) { + cache = cache[path.shift()]; + } + e.ports[0].postMessage('cached'); + } catch (error) { + e.ports[0].postMessage(error.name); + } + } else if (e.data == 'touch-cached') { + try { + e.ports[0].postMessage(cache.foo); + } catch (error) { + e.ports[0].postMessage(error.name); + } + } else if (e.data == 'poke-at-parent') { + try { + var sekrit = window.parent.document.body.querySelector('#sekrit').value; + e.ports[0].postMessage(sekrit); + } catch(error) { + e.ports[0].postMessage(error.name); + } + } + }); + window.parent.postMessage('Hi!', '*'); +</script> +<input id="sekrit" value="omg!"> diff --git a/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/support/document_domain_frame.sub.js b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/support/document_domain_frame.sub.js new file mode 100644 index 0000000000..b6631ea4f1 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/support/document_domain_frame.sub.js @@ -0,0 +1,65 @@ +/** + * Utilities to be used with document_domain_frame.html. + */ + +/** + * Send a message to the frame and resolve a promise when a response is received. + * + * Supported messages: + * + * 1) { domain: something }. Has the subframe try to set document.domain to the + * given value, and message back 'Done' if that succeeds or an error name if it + * fails. + * + * 2) 'poke-at-parent'. Has the subframe try to synchronously attempt to access + * the parent's DOM, read out a string value, and message it back to the parent. + * Again, sends back the error name if that fails. + * + * 3) { 'poke-at-sibling': name }. Has the subframe try to synchronously + * attempt to access the DOM of the sibling with the given name, read out a + * string value, and message it back to the parent. + */ +function postMessageToFrame(frame, message) { + return new Promise(resolve => { + var c = new MessageChannel(); + c.port1.onmessage = e => { + resolve({ data: e.data, frame: frame }) + }; + frame.contentWindow.postMessage(message, '*', [c.port2]); + }); +} + +/** + * Create a frame that loads document_domain_frame.html and resolves a promise + * when the frame is loaded enough to be sending and receiving messages. + * + * If a "name" argument is provided, that name is used for the iframe, so + * + * If a "hostname" argument is provided, that hostname is used for the load, to + * allow testing details of the behavior when different sorts of hostnames are + * used. + */ +function createFrame(t, name, hostname) { + return new Promise(resolve => { + var i = document.createElement('iframe'); + if (hostname) { + i.src = `//${hostname}:{{location[port]}}/html/browsers/origin/relaxing-the-same-origin-restriction/support/document_domain_frame.html`; + } else { + i.src = "support/document_domain_frame.html"; + } + if (name) { + i.name = name; + } + var listener = m => { + if (m.source == i.contentWindow) + resolve(i); + } + window.addEventListener('message', listener); + t.add_cleanup(() => { + i.remove(); + window.removeEventListener('message', listener); + }); + document.body.appendChild(i); + }); +} + diff --git a/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/support/document_domain_setter_iframe.html b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/support/document_domain_setter_iframe.html new file mode 100644 index 0000000000..d3d5260af3 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/support/document_domain_setter_iframe.html @@ -0,0 +1,12 @@ +<!doctype html> +<html> + <head> + <title></title> + <script src="/common/get-host-info.sub.js"></script> + <script> + document.domain = get_host_info().ORIGINAL_HOST; + </script> + </head> + <body> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/sandboxing/inner-iframe.html b/testing/web-platform/tests/html/browsers/sandboxing/inner-iframe.html new file mode 100644 index 0000000000..229f6b3d85 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/inner-iframe.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html> + <head> + <script> + window.onload = function() { + top.calledFromIframe(); + } + </script> + </head> + <body> + <div id="inner">foo</div> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/sandboxing/noscript-iframe.html b/testing/web-platform/tests/html/browsers/sandboxing/noscript-iframe.html new file mode 100644 index 0000000000..677b5fc83a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/noscript-iframe.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<noscript>PASS</noscript> diff --git a/testing/web-platform/tests/html/browsers/sandboxing/popup-from-initial-empty-sandboxed-document.window.js b/testing/web-platform/tests/html/browsers/sandboxing/popup-from-initial-empty-sandboxed-document.window.js new file mode 100644 index 0000000000..1ae4fad0cb --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/popup-from-initial-empty-sandboxed-document.window.js @@ -0,0 +1,46 @@ +// META: timeout=long +// META: script=/common/utils.js +// META: script=/common/dispatcher/dispatcher.js + +// Regression test for: https://crbug.com/1256822. +// +// From a sandboxed iframe allowing popups, scripts, and same-origin. Open a +// popup using the WindowProxy of a new iframe that is still on the initial +// empty document. Check that the sandbox flags are properly inherited. + +// Return true if the execution context is sandboxed. +const isSandboxed = () => { + try { + // Setting document.domain in sandboxed document throw errors. + document.domain = document.domain; + return false; + } catch (error) { + return true; + } +} + +promise_test(async test => { + // 1. Create a sandboxed iframe, allowing popups, same-origin and scripts. + const iframe_token = token(); + const iframe_document = new RemoteContext(iframe_token); + const iframe_url = remoteExecutorUrl(iframe_token); + const iframe = document.createElement("iframe"); + iframe.sandbox = "allow-same-origin allow-scripts allow-popups"; + iframe.src = iframe_url; + document.body.appendChild(iframe); + assert_true(await iframe_document.execute_script(isSandboxed), + "iframe is sandboxed"); + + // 2. From the sandboxed iframe, create an empty iframe, and open a popup + // using it's WindowProxy. The popup must inherit sandbox flags. + const popup_token = token(); + const popup_document = new RemoteContext(popup_token); + const popup_url = remoteExecutorUrl(popup_token); + iframe_document.execute_script((popup_url) => { + let iframe = document.createElement("iframe"); + iframe.name = "iframe_name"; + document.body.appendChild(iframe); + iframe_name.open(popup_url); + }, [popup_url.href]); + assert_true(await popup_document.execute_script(isSandboxed), "popup is sandboxed"); +}); diff --git a/testing/web-platform/tests/html/browsers/sandboxing/resources/check-sandbox-flags.html b/testing/web-platform/tests/html/browsers/sandboxing/resources/check-sandbox-flags.html new file mode 100644 index 0000000000..0dc95315f1 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/resources/check-sandbox-flags.html @@ -0,0 +1,8 @@ +<script> + try { + document.domain = document.domain; + parent.postMessage('document-domain-is-allowed', '*'); + } catch (error) { + parent.postMessage('document-domain-is-disallowed', '*'); + } +</script> diff --git a/testing/web-platform/tests/html/browsers/sandboxing/resources/document-open.html b/testing/web-platform/tests/html/browsers/sandboxing/resources/document-open.html new file mode 100644 index 0000000000..136c494d5a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/resources/document-open.html @@ -0,0 +1,14 @@ +<script> + onload = () => { + document.write(` + <script> + try { + document.domain = document.domain; + parent.postMessage('document-domain-is-allowed', '*'); + } catch (error) { + parent.postMessage('document-domain-is-disallowed', '*'); + } + </sc`+`ript> + `); + } +</script> diff --git a/testing/web-platform/tests/html/browsers/sandboxing/resources/execute-postmessage.html b/testing/web-platform/tests/html/browsers/sandboxing/resources/execute-postmessage.html new file mode 100644 index 0000000000..89bd268f9c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/resources/execute-postmessage.html @@ -0,0 +1,5 @@ +<script> + // Execute arbitrary code from somewhere else, via postMessage. + window.addEventListener("message", event => eval(event.data)); + window.opener.postMessage("ready", "*"); +</script> diff --git a/testing/web-platform/tests/html/browsers/sandboxing/resources/post-done-to-opener.html b/testing/web-platform/tests/html/browsers/sandboxing/resources/post-done-to-opener.html new file mode 100644 index 0000000000..b47f0f274e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/resources/post-done-to-opener.html @@ -0,0 +1,3 @@ +<script> + window.opener.top.postMessage("DONE", "*"); +</script> diff --git a/testing/web-platform/tests/html/browsers/sandboxing/resources/sandbox-inherited-from-initiator-response-helper.html b/testing/web-platform/tests/html/browsers/sandboxing/resources/sandbox-inherited-from-initiator-response-helper.html new file mode 100644 index 0000000000..29c7f12441 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/resources/sandbox-inherited-from-initiator-response-helper.html @@ -0,0 +1,15 @@ +<script> + const iframe_1_script = encodeURI(` + <script> + try { + document.domain = document.domain; + parent.postMessage("not sandboxed", "*"); + } catch (exception) { + parent.postMessage("sandboxed", "*"); + } + </scr`+`ipt> + `); + + const iframe_1 = parent.document.querySelector("#iframe_1"); + iframe_1.src = `data:text/html,${iframe_1_script}`; +</script> diff --git a/testing/web-platform/tests/html/browsers/sandboxing/resources/sandbox-inherited-from-initiator-response-helper.html.headers b/testing/web-platform/tests/html/browsers/sandboxing/resources/sandbox-inherited-from-initiator-response-helper.html.headers new file mode 100644 index 0000000000..82e8023d0b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/resources/sandbox-inherited-from-initiator-response-helper.html.headers @@ -0,0 +1 @@ +Content-Security-Policy: sandbox allow-scripts allow-same-origin diff --git a/testing/web-platform/tests/html/browsers/sandboxing/resources/sandbox-javascript-window-open.html b/testing/web-platform/tests/html/browsers/sandboxing/resources/sandbox-javascript-window-open.html new file mode 100644 index 0000000000..909956a54f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/resources/sandbox-javascript-window-open.html @@ -0,0 +1,18 @@ +<script> + // Forward message from the openee toward the parent. + window.addEventListener("message", event => top.postMessage(event.data, "*")); + + let check_sandboxed = `" + <script> + try { + document.domain = document.domain; + opener.postMessage('allow-document-domain', '*'); + } catch (error) { + opener.postMessage('disallow-document-domain', '*'); + } + </scr`+`ipt> + "`; + + window.open('about:blank', "window_name"); + window.open("javascript:" + check_sandboxed, "window_name"); +</script> diff --git a/testing/web-platform/tests/html/browsers/sandboxing/sandbox-allow-same-origin.html b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-allow-same-origin.html new file mode 100644 index 0000000000..d6b3b099f2 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-allow-same-origin.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<html> + <head> + <title>DOM access in sandbox="allow-same-origin" iframe</title> + <link rel="author" title="Kinuko Yasuda" href="mailto:kinuko@chromium.org"> + <link rel="help" href="http://www.w3.org/html/wg/drafts/html/master/browsers.html#sandboxing"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + + <body> + <h1>DOM access in sandbox="allow-same-origin" iframe</h1> + <script type="text/javascript"> + var t = async_test("DOM access in sandbox='allow-same-origin' iframe is allowed") + var called = 0; + function calledFromIframe() { + called++; + } + function loaded() { + assert_equals(document.getElementById('sandboxedframe').contentWindow.document.getElementById('inner').innerHTML, 'foo'); + assert_equals(called, 0); + t.done(); + } + </script> + + <iframe src="/html/browsers/sandboxing/inner-iframe.html" style="visibility:hidden;display:none" sandbox="allow-same-origin" id="sandboxedframe" onload="loaded();"></iframe> + + <div id="log"></div> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/sandboxing/sandbox-allow-scripts.html b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-allow-scripts.html new file mode 100644 index 0000000000..6cf3f5a4a8 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-allow-scripts.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html> + <head> + <title>Script execution in sandbox="allow-scripts" iframe</title> + <link rel="author" title="Kinuko Yasuda" href="mailto:kinuko@chromium.org"> + <link rel="help" href="http://www.w3.org/html/wg/drafts/html/master/browsers.html#sandboxing"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + + <body> + <h1>Script execution in sandbox="allow-scripts" iframe</h1> + <script type="text/javascript"> + var t = async_test("Running script from sandbox='allow-scripts' iframe is allowed") + var called = 0; + function calledFromIframe() { + called++; + } + function loaded() { + assert_equals(called, 1); + t.done(); + } + </script> + + <iframe src="/html/browsers/sandboxing/inner-iframe.html" style="visibility:hidden;display:none" sandbox="allow-scripts allow-same-origin" id="sandboxedframe" onload="loaded();"></iframe> + + <div id="log"></div> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/sandboxing/sandbox-disallow-popups.html b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-disallow-popups.html new file mode 100644 index 0000000000..8e4b34eb8b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-disallow-popups.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>window.open in sandbox iframe</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<body> +<script> +setup({single_test: true}); +// check that the popup's URL is not loaded +const uuid = token(); +async function assert_popup_not_loaded() { + const response = await fetch(`/fetch/api/resources/stash-take.py?key=${uuid}`); + assert_equals(await response.json(), null); // is "loaded" if it loads +} + +// check for message from the iframe +window.onmessage = e => { + assert_equals(e.data, 'null', 'return value of window.open (stringified)'); + step_timeout(async () => { + await assert_popup_not_loaded(); + done(); + }, 1000); +}; +const iframe = document.createElement('iframe'); +iframe.sandbox = 'allow-scripts'; +iframe.srcdoc = ` + <script> + let result; + try { + result = window.open('/fetch/api/resources/stash-put.py?key=${uuid}&value=loaded', '_blank'); + } catch(ex) { + result = ex; + } + parent.postMessage(String(result), '*'); + <\/script> +`; +document.body.appendChild(iframe); +</script> diff --git a/testing/web-platform/tests/html/browsers/sandboxing/sandbox-disallow-same-origin.html b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-disallow-same-origin.html new file mode 100644 index 0000000000..0dae0137ac --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-disallow-same-origin.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html> + <head> + <title>Access to sandbox iframe</title> + <link rel="author" title="Kinuko Yasuda" href="mailto:kinuko@chromium.org"> + <link rel="help" href="https://html.spec.whatwg.org/multipage/#sandboxed-origin-browsing-context-flag"> + <link rel="help" href="https://html.spec.whatwg.org/multipage/#integration-with-idl"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + + <body> + <h1>Access to sandbox iframe</h1> + <script type="text/javascript"> + var t = async_test("Access to sandbox iframe is disallowed") + var called = 0; + function calledFromIframe() { + called++; + } + function loaded() { + t.step(() => { + assert_throws_dom("SecurityError", () => { + document.getElementById('sandboxedframe').contentWindow.document; + }); + assert_equals(called, 0); + t.done(); + }); + } + </script> + + <iframe src="/html/browsers/sandboxing/inner-iframe.html" style="visibility:hidden;display:none" sandbox id="sandboxedframe" onload="loaded();"></iframe> + </body> + + <div id="log"></div> +</html> diff --git a/testing/web-platform/tests/html/browsers/sandboxing/sandbox-disallow-scripts-via-unsandboxed-popup.tentative.html b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-disallow-scripts-via-unsandboxed-popup.tentative.html new file mode 100644 index 0000000000..3c8c0b346a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-disallow-scripts-via-unsandboxed-popup.tentative.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> + <script> + async_test(t => { + let i = document.createElement('iframe'); + i.sandbox = "allow-same-origin allow-popups allow-popups-to-escape-sandbox"; + i.srcdoc = `<a target='_blank' rel='opener' + href="javascript:window.opener.top.postMessage('FAIL', '*');">Click me!</a> + <a target='_blank' rel='opener' + href="./resources/post-done-to-opener.html">Click me next!</a>`; + + i.onload = _ => { + // Since the frame is sandboxed, but allow-same-origin, we can reach into it to grab the + // anchor element to click. We'll click the `javascript:` URL first, then pop up a new + // window that posts `DONE`. + // + // TODO(mkwst): This feels like a race, but it's one that we consistently win when I'm + // running the test locally 10,000 times. Good enough!™ + i.contentDocument.body.querySelectorAll('a')[0].click(); + i.contentDocument.body.querySelectorAll('a')[1].click(); + }; + document.body.appendChild(i); + + window.addEventListener('message', t.step_func(e => { + assert_not_equals(e.data, "FAIL"); + if (e.data == "DONE") + t.done(); + })); + }, "Sandboxed => unsandboxed popup"); + </script> +</body> diff --git a/testing/web-platform/tests/html/browsers/sandboxing/sandbox-disallow-scripts.html b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-disallow-scripts.html new file mode 100644 index 0000000000..1bc116ada4 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-disallow-scripts.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html> + <head> + <title>Script execution in sandbox iframe</title> + <link rel="author" title="Kinuko Yasuda" href="mailto:kinuko@chromium.org"> + <link rel="help" href="http://www.w3.org/html/wg/drafts/html/master/browsers.html#sandboxing"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + + <body> + <h1>Script execution in sandbox iframe</h1> + <script type="text/javascript"> + var t = async_test("Running script from sandbox iframe is disallowed") + var called = 0; + function calledFromIframe() { + called++; + } + function loaded() { + assert_equals(called, 0); + t.done(); + } + </script> + + <iframe src="/html/browsers/sandboxing/inner-iframe.html" style="visibility:hidden;display:none" sandbox id="sandboxedframe" onload="loaded();"></iframe> + + <div id="log"></div> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/sandboxing/sandbox-document-open-mutation.window.js b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-document-open-mutation.window.js new file mode 100644 index 0000000000..713ca612c5 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-document-open-mutation.window.js @@ -0,0 +1,37 @@ +// Return whether the current context is sandboxed or not. The implementation do +// not matter much, but might have to change over time depending on what side +// effect sandbox flag have. Feel free to update as needed. +const is_sandboxed = () => { + try { + document.domain = document.domain; + return "not sandboxed"; + } catch (error) { + return "sandboxed"; + } +}; + +promise_test(async test => { + const message = new Promise(r => window.addEventListener("message", r)); + + const iframe_unsandboxed = document.createElement("iframe"); + document.body.appendChild(iframe_unsandboxed); + + const iframe_sandboxed = document.createElement("iframe"); + iframe_sandboxed.sandbox = "allow-same-origin allow-scripts"; + document.body.appendChild(iframe_sandboxed); + + iframe_sandboxed.srcdoc = ` + <script> + parent.frames[0].document.write(\` + <script> + const is_sandboxed = ${is_sandboxed}; + window.parent.postMessage(is_sandboxed(), '*'); + </scr\`+\`ipt> + \`); + parent.frames[0].document.close(); + </scr`+`ipt> + `; + assert_equals((await message).data, "not sandboxed"); + +}, "Using document.open() against a document from a different window must not" + + " mutate the other window's sandbox flags"); diff --git a/testing/web-platform/tests/html/browsers/sandboxing/sandbox-document-open.html b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-document-open.html new file mode 100644 index 0000000000..3f754374ba --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-document-open.html @@ -0,0 +1,50 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title> + Check sandbox-flags aren't lost after using document.open(). +</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +promise_test(async test => { + let message = new Promise(resolve => + window.addEventListener("message", event => resolve(event.data)) + ); + + let iframe = document.createElement("iframe"); + iframe.setAttribute("sandbox", "allow-scripts allow-same-origin"); + iframe.setAttribute("src", "./resources/document-open.html") + document.body.appendChild(iframe); + + assert_equals(await message, "document-domain-is-disallowed"); +}, "document.open()"); + +promise_test(async test => { + let iframe = document.createElement("iframe"); + iframe.setAttribute("sandbox", "allow-scripts allow-same-origin"); + iframe.setAttribute("src", "/common/blank.html"); + let loaded = new Promise(resolve => iframe.onload = resolve); + document.body.appendChild(iframe); + await loaded; + + let message = new Promise(resolve => + window.addEventListener("message", event => resolve(event.data)) + ); + + iframe.contentDocument.write(` + <script> + try { + document.domain = document.domain; + parent.postMessage('document-domain-is-allowed', '*'); + } catch (error) { + parent.postMessage('document-domain-is-disallowed', '*'); + } + </sc`+`ript> + `); + + assert_equals(await message, "document-domain-is-disallowed"); +}, "other_document.open()"); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/sandboxing/sandbox-inherited-from-initiator-frame.html b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-inherited-from-initiator-frame.html new file mode 100644 index 0000000000..ab87fce5e0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-inherited-from-initiator-frame.html @@ -0,0 +1,64 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Inherit sandbox flags from the initiator's frame</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +// Check sandbox flags are properly inherited when a document initiate a +// navigation inside another frame that it doesn't own directly. + +// This check the sandbox flags defined by the frame. See also the other test +// about sandbox flags defined by the response (e.g. CSP sandbox): +// => sandbox-inherited-from-initiators-response.html + +// Return a promise, resolving when |element| triggers |event_name| event. +let future = (element, event_name) => { + return new Promise(resolve => { + element.addEventListener(event_name, event => resolve(event)) + }); +}; + +promise_test(async test => { + const iframe_1 = document.createElement("iframe"); + const iframe_2 = document.createElement("iframe"); + + iframe_1.id = "iframe_1"; + iframe_2.id = "iframe_2"; + + const iframe_1_script = encodeURI(` + <script> + try { + document.domain = document.domain; + parent.postMessage("not sandboxed", "*"); + } catch (exception) { + parent.postMessage("sandboxed", "*"); + } + </scr`+`ipt> + `); + + const iframe_2_script = ` + <script> + const iframe_1 = parent.document.querySelector("#iframe_1"); + iframe_1.src = "data:text/html,${iframe_1_script}"; + </scr`+`ipt> + `; + + iframe_2.sandbox = "allow-scripts allow-same-origin"; + iframe_2.srcdoc = iframe_2_script; + + // Insert |iframe_1|. It will load the initial empty document, with no sandbox + // flags. + const iframe_1_load_1 = future(iframe_1, "load"); + document.body.appendChild(iframe_1); + await iframe_1_load_1; + + // Insert |iframe_2|. It will load with sandbox flags. It will make |iframe_1| + // to navigate toward a data-url, which should inherit the sandbox flags. + const iframe_1_reply = future(window, "message"); + document.body.appendChild(iframe_2); + const result = await iframe_1_reply; + + assert_equals("sandboxed", result.data); +}) +</script> diff --git a/testing/web-platform/tests/html/browsers/sandboxing/sandbox-inherited-from-initiator-response.html b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-inherited-from-initiator-response.html new file mode 100644 index 0000000000..638f1ba783 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-inherited-from-initiator-response.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Inherit sandbox flags from the initiator's response</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +// Check sandbox flags are properly inherited when a document initiate a +// navigation inside another frame that it doesn't own directly. + +// This check the sandbox flags defined by the response (e.g. CSP sandbox). See +// also the other test about sandbox flags inherited from the frame. +// => sandbox-inherited-from-initiators-frame.html + +// Return a promise, resolving when |element| triggers |event_name| event. +let future = (element, event_name) => { + return new Promise(resolve => { + element.addEventListener(event_name, event => resolve(event)) + }); +}; + +promise_test(async test => { + const iframe_1 = document.createElement("iframe"); + const iframe_2 = document.createElement("iframe"); + + iframe_1.id = "iframe_1"; + iframe_2.id = "iframe_2"; + + iframe_2.src = + "./resources/sandbox-inherited-from-initiator-response-helper.html"; + + // Insert |iframe_1|. It will load the initial empty document, with no sandbox + // flags. + const iframe_1_load_1 = future(iframe_1, "load"); + document.body.appendChild(iframe_1); + await iframe_1_load_1; + + // Insert |iframe_2|. It will load with sandbox flags. It will make |iframe_1| + // to navigate toward a data-url, which should inherit the sandbox flags. + const iframe_1_reply = future(window, "message"); + document.body.appendChild(iframe_2); + const result = await iframe_1_reply; + + assert_equals("sandboxed", result.data); +}) +</script> diff --git a/testing/web-platform/tests/html/browsers/sandboxing/sandbox-inherited-from-required-csp.html b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-inherited-from-required-csp.html new file mode 100644 index 0000000000..04f485cc66 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-inherited-from-required-csp.html @@ -0,0 +1,154 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Inherit sandbox from CSP embedded enforcement</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<body> +<script> +// Check sandbox flags are properly defined when its parent requires them and +// the child allows it. + +const same_origin = get_host_info().HTTP_ORIGIN; +const cross_origin = get_host_info().HTTP_REMOTE_ORIGIN; +const check_sandbox_url = + "/html/browsers/sandboxing/resources/check-sandbox-flags.html?pipe="; +const allow_csp_from_star = "|header(Allow-CSP-From,*)"; + +// Return a promise, resolving when |element| triggers |event_name| event. +const future = (element, event_name, source) => { + return new Promise(resolve => { + element.addEventListener(event_name, event => { + if (!source || source.contentWindow == event.source) + resolve(event) + }) + }); +}; + +const check_sandbox_script = ` +<script> + try { + document.domain = document.domain; + parent.postMessage("document-domain-is-allowed", "*"); + } catch (exception) { + parent.postMessage("document-domain-is-disallowed", "*"); + } +</scr`+`ipt> +`; + +const sandbox_policy = "sandbox allow-scripts allow-same-origin"; + +// Test using the modern async/await primitives are easier to read/write. +// However they run sequentially, contrary to async_test. This is the parallel +// version, to avoid timing out. +let promise_test_parallel = (promise, description) => { + async_test(test => { + promise(test) + .then(() => {test.done();}) + .catch(test.step_func(error => { throw error; })); + }, description); +}; + +promise_test_parallel(async test => { + const iframe = document.createElement("iframe"); + iframe.csp = sandbox_policy; + + // The <iframe> immediately hosts the initial empty document after being + // appended into the DOM. It will, as long as its 'src' isn't loaded. That's + // why a page do not load is being used. + iframe.src = "/fetch/api/resources/infinite-slow-response.py"; + document.body.appendChild(iframe); + + const iframe_reply = future(window, "message", iframe); + iframe.contentDocument.write(check_sandbox_script); + const result = await iframe_reply; + iframe.remove(); + + assert_equals(result.data, "document-domain-is-disallowed"); +}, "initial empty document"); + +promise_test_parallel(async test => { + const iframe = document.createElement("iframe"); + iframe.src = "data:text/html,dummy"; + + const iframe_load_1 = future(iframe, "load"); + document.body.appendChild(iframe); + await iframe_load_1; + + const iframe_load_2 = future(iframe, "load"); + iframe.csp = sandbox_policy; + iframe.src = "about:blank"; + await iframe_load_2; + + const iframe_reply = future(window, "message", iframe); + iframe.contentDocument.write(check_sandbox_script); + const result = await iframe_reply; + + assert_equals(result.data, "document-domain-is-disallowed"); +}, "about:blank"); + +promise_test_parallel(async test => { + const iframe = document.createElement("iframe"); + iframe.csp = sandbox_policy; + iframe.src = + `data:text/html,${encodeURI(check_sandbox_script)}`; + + const iframe_reply = future(window, "message", iframe); + document.body.appendChild(iframe); + const result = await iframe_reply; + + assert_equals(result.data, "document-domain-is-disallowed"); +}, "data-url"); + +promise_test_parallel(async test => { + const iframe = document.createElement("iframe"); + iframe.csp = sandbox_policy; + iframe.srcdoc = check_sandbox_script; + + const iframe_reply = future(window, "message", iframe); + document.body.appendChild(iframe); + const result = await iframe_reply; + + assert_equals(result.data, "document-domain-is-disallowed"); +}, "srcdoc"); + +promise_test_parallel(async test => { + const iframe = document.createElement("iframe"); + iframe.csp = sandbox_policy; + + const blob = new Blob([check_sandbox_script], { type: "text/html" }); + + iframe.src = URL.createObjectURL(blob); + + const iframe_reply = future(window, "message", iframe); + document.body.appendChild(iframe); + const result = await iframe_reply; + + assert_equals(result.data, "document-domain-is-disallowed"); +}, "blob URL"); + +promise_test_parallel(async test => { + const iframe = document.createElement("iframe"); + iframe.csp = sandbox_policy; + iframe.src = same_origin + check_sandbox_url + allow_csp_from_star; + + const iframe_reply = future(window, "message", iframe); + document.body.appendChild(iframe); + const result = await iframe_reply; + + assert_equals(result.data, "document-domain-is-disallowed"); +}, "same-origin"); + +promise_test_parallel(async test => { + const iframe = document.createElement("iframe"); + iframe.csp = sandbox_policy; + iframe.src = cross_origin + check_sandbox_url + allow_csp_from_star; + + const iframe_reply = future(window, "message", iframe); + document.body.appendChild(iframe); + const result = await iframe_reply; + + assert_equals(result.data, "document-domain-is-disallowed"); +}, "cross-origin"); + +</script> diff --git a/testing/web-platform/tests/html/browsers/sandboxing/sandbox-initial-empty-document-toward-same-origin.html b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-initial-empty-document-toward-same-origin.html new file mode 100644 index 0000000000..d1306c9703 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-initial-empty-document-toward-same-origin.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title> + Check sandbox-flags inheritance in case of javascript window reuse. +</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +promise_test(async test => { + let message = new Promise(resolve => + window.addEventListener("message", event => resolve(event.data)) + ); + + // Create an initial empty document in the iframe, sandboxed. It will attempt + // to load a slow page, but won't have time. + let iframe = document.createElement("iframe"); + iframe.setAttribute("sandbox", "allow-scripts allow-same-origin"); + iframe.src = "/fetch/api/resources/infinite-slow-response.py"; + document.body.appendChild(iframe); + + // Remove sandbox flags. This should apply to documents committed from + // navigations started after this instruction. + iframe.removeAttribute("sandbox"); + iframe.src = "./resources/check-sandbox-flags.html"; + + // The window is reused, but the new sandbox flags should be used. + assert_equals(await message, "document-domain-is-allowed"); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/sandboxing/sandbox-javascript-window-open.html b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-javascript-window-open.html new file mode 100644 index 0000000000..fd21e9bb02 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-javascript-window-open.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>window.open in sandbox iframe</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<body> +<script> +promise_test(async test => { + let message = new Promise(resolve => { + window.addEventListener("message", event => resolve(event.data)); + }); + let iframe = document.createElement("iframe"); + iframe.sandbox = "allow-scripts allow-popups allow-same-origin"; + iframe.src = "./resources/sandbox-javascript-window-open.html"; + document.body.appendChild(iframe); + assert_equals(await message, "disallow-document-domain"); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/sandboxing/sandbox-navigation-timing-iframe.tentative.html b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-navigation-timing-iframe.tentative.html new file mode 100644 index 0000000000..43726e7720 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-navigation-timing-iframe.tentative.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script> + let result; + try { + parent.document.getElementsByClassName('script'); + result = 'iframe not sandboxed' + } catch (e) { + result = 'iframe sandboxed(' + e.message + ')'; + } + window.onmessage = m => { + window.parent.postMessage({ + result: result + }, '*'); + }; +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/sandboxing/sandbox-navigation-timing.tentative.html b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-navigation-timing.tentative.html new file mode 100644 index 0000000000..686f1c0c9f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-navigation-timing.tentative.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Sandbox Navigation Timing</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<html></html> +<script> + const sandboxUrl = location.pathname.substring(0, location.pathname.lastIndexOf('/') + 1) + 'sandbox-navigation-timing-iframe.tentative.html'; + async_test(t => { + const iframe = document.createElement('iframe'); + iframe.src = sandboxUrl; + document.body.appendChild(iframe); // Navigation starts; value of sandbox flags locked on. + // This should not affect the sandbox value used for both about:blank document + // and the final document in iframe. + iframe.sandbox = 'allow-scripts'; + const iframeAboutBlankDocument = iframe.contentDocument; + + iframe.onload = t.step_func(() => { + const iframeAboutBlankContents = iframeAboutBlankDocument.querySelectorAll('body'); + assert_equals(iframeAboutBlankContents[0].tagName, "BODY", + "about:blank document's contents should still be accessible"); + + iframe.contentWindow.postMessage("is iframe sandboxed?", "*"); + }); + window.onmessage = t.step_func_done(e => { + assert_equals(e.data.result, 'iframe not sandboxed'); + }); + }, 'setting sandbox attribute should not affect current document in iframe'); +</script> diff --git a/testing/web-platform/tests/html/browsers/sandboxing/sandbox-new-execution-context-iframe.html b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-new-execution-context-iframe.html new file mode 100644 index 0000000000..801e78f9c0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-new-execution-context-iframe.html @@ -0,0 +1,5 @@ +<body> + <script> + Object.getPrototypeOf(document).changeFromSandboxedIframe = "change from sandboxed iframe"; + </script> +</body> diff --git a/testing/web-platform/tests/html/browsers/sandboxing/sandbox-new-execution-context.html b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-new-execution-context.html new file mode 100644 index 0000000000..dc1953aee6 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-new-execution-context.html @@ -0,0 +1,39 @@ +<!doctype html> +<html> + <head> + <title>Reuse of iframe about:blank document execution context</title> + <link rel="author" title="Dan Clark" href="mailto:daniec@microsoft.com"> + <link rel="help" href="http://www.w3.org/html/wg/drafts/html/master/browsers.html#sandboxing"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + + <body> + <h1>Reuse of iframe about:blank document execution context in sandbox="allow-scripts" iframe</h1> + <script type="text/javascript"> + async_test(t => { + let iframe = document.createElement("iframe"); + document.body.appendChild(iframe); + + let iframeAboutBlankDocument = iframe.contentDocument; + assert_equals(iframeAboutBlankDocument.URL, "about:blank"); + + iframe.sandbox = "allow-scripts"; + iframe.src = './sandbox-new-execution-context-iframe.html'; + + iframe.onload = t.step_func_done(() => { + assert_equals(iframe.contentDocument, null, + "New document in sandboxed iframe should have opaque origin"); + + assert_equals(Object.getPrototypeOf(iframeAboutBlankDocument).changeFromSandboxedIframe, undefined, + "Sandboxed iframe contents should not have been able to mess with type system of about:blank document"); + + let iframeAboutBlankContents = iframeAboutBlankDocument.querySelectorAll('body'); + assert_equals(iframeAboutBlankContents[0].tagName, "BODY", + "about:blank document's contents should still be accessible"); + }); + },"iframe with sandbox should load with new execution context"); + </script> + <div id="log"></div> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/sandboxing/sandbox-parse-noscript-ref.html b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-parse-noscript-ref.html new file mode 100644 index 0000000000..9cf92768f7 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-parse-noscript-ref.html @@ -0,0 +1,6 @@ +<!doctype html> +<meta charset=utf-8> +<title>noscript parsing when sandbox disables scripting</title> +<iframe srcdoc="PASS" sandbox></iframe> +<iframe srcdoc="PASS" sandbox></iframe> +<iframe srcdoc="P<b>AS</b>S" sandbox></iframe> diff --git a/testing/web-platform/tests/html/browsers/sandboxing/sandbox-parse-noscript.html b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-parse-noscript.html new file mode 100644 index 0000000000..bb7ced0a14 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-parse-noscript.html @@ -0,0 +1,7 @@ +<!doctype html> +<meta charset=utf-8> +<title>noscript parsing when sandbox disables scripting</title> +<link rel=match href=/html/browsers/sandboxing/sandbox-parse-noscript-ref.html> +<iframe srcdoc="<noscript>PASS</noscript>" sandbox></iframe> +<iframe src="noscript-iframe.html" sandbox></iframe> +<iframe srcdoc="<noscript>P<b>AS</b>S</noscript>" sandbox></iframe> diff --git a/testing/web-platform/tests/html/browsers/sandboxing/sandbox-window-open-srcdoc.html b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-window-open-srcdoc.html new file mode 100644 index 0000000000..6fbff6df82 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/sandbox-window-open-srcdoc.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>window.open("about:srcdoc") from a sandboxed iframe</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +// Check what happens when executing window.open("about:srcdoc") from a +// sandboxed iframe. Srcdoc can't be loaded in the main frame. It should +// result in an error page. The error page should be cross-origin with the +// opener. +// +// This test covers an interesting edge case. A main frame should inherit +// sandbox flags. However the document loaded is an internal error page. This +// might trigger some assertions, especially if the implementation wrongly +// applies the sandbox flags of the opener to the internal error page document. +// +// This test is mainly a coverage test. It passes if it doesn't crash. +async_test(test => { + let iframe = document.createElement("iframe"); + iframe.sandbox = "allow-scripts allow-popups allow-same-origin"; + iframe.srcdoc = ` + <script> + let w = window.open(); + onunload = () => w.close(); + + let notify = () => { + try { + w.origin; // Will fail after navigating to about:srcdoc. + parent.postMessage("pending", "*"); + } catch (e) { + parent.postMessage("done", "*"); + }; + }; + + addEventListener("message", notify); + notify(); + + w.location = "about:srcdoc"; // Error page. + </scr`+`ipt> + `; + + let closed = false; + addEventListener("message", event => { + closed = (event.data === "done"); + iframe.contentWindow.postMessage("ping","*"); + }); + + document.body.appendChild(iframe); + test.step_wait_func_done(()=>closed); +}, "window.open('about:srcdoc') from sandboxed srcdoc doesn't crash."); +</script> diff --git a/testing/web-platform/tests/html/browsers/sandboxing/window-open-blank-from-different-initiator.html b/testing/web-platform/tests/html/browsers/sandboxing/window-open-blank-from-different-initiator.html new file mode 100644 index 0000000000..91817c3db4 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/sandboxing/window-open-blank-from-different-initiator.html @@ -0,0 +1,90 @@ +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/common/utils.js"></script> +<script> + +// This is a regression test for https://crbug.com/1170038. +// +// A document creates a popup and makes it navigate elsewhere. The navigation +// will never commit. The popup has not completed any real load outside of the +// initial empty document. Then from a different window with a different CSP +// policy, make it navigate to about:blank. +// +// Web browser behavior might change depending on whether a pending navigation +// exists for in the popup or not. Both are tested here. + +const same_origin = get_host_info().HTTP_ORIGIN; + +// Return a promise, resolving when |element| triggers |event_name| event. +const future = (element, event_name) => { + return new Promise(resolve => element.addEventListener(event_name, resolve)); +}; + +// `createNewPopup` is a function returning a new window that has not committed +// any real load in its frame, outside of the initial empty document. The two +// tests below vary depending on whether there is a pending navigation in the +// frame or not. +const runTest = (description, createNewPopup) => { + promise_test(async test => { + // Open a same-origin window with a different CSP. + const executor_path = + "/html/browsers/sandboxing/resources/execute-postmessage.html?pipe="; + const csp = "|header(Content-Security-Policy, " + + "sandbox" + + " allow-scripts" + + " allow-popups" + + " allow-same-origin" + + " allow-popups-to-escape-sandbox"; + const executor = window.open(same_origin + executor_path + csp); + const executor_reply = await future(window, "message"); + assert_equals(executor_reply.data, "ready"); + + const popup_name = token(); + const popup = await createNewPopup(test, popup_name); + + // Request the first real load from a DIFFERENT window with different CSPs. + const first_real_load = future(popup, "load"); + executor.postMessage(`window.open("about:blank", "${popup_name}");`); + await first_real_load; + + // The new blank document in the popup must inherit CSPs from |executor|: + let is_sandboxed = future(window, "message"); + popup.document.write(` + <script> + try { + document.domain = document.domain; + opener.opener.postMessage("not sandboxed", "*"); + } catch (error) { + opener.opener.postMessage("sandboxed", "*"); + } + </scr`+`ipt> + `); + assert_equals((await is_sandboxed).data, "sandboxed"); + }, description); +} + +// Open a new window and start loading from an unresponsive server. The frame +// will be left with the initial empty document and a pending navigation. +runTest("One pending navigation", async (test, popup_name) => { + const unresponsive_path = + "/fetch/api/resources/infinite-slow-response.py"; + return window.open(same_origin + unresponsive_path, popup_name); +}); + +// Open a new window and start loading. The response is a 204 and the navigation +// is canceled. As a result, the frame will be left with the initial empty +// document and NO pending navigation. +runTest("No pending navigation", async (test, popup_name) => { + const no_content_path = "/common/blank.html?pipe=status(204)" + const popup = window.open(same_origin + no_content_path, popup_name); + + // Unfortunately, there are no web API to detect a navigation has been + // canceled. Waiting using setTimeout is the only possible way to wait for it. + await new Promise(r => test.step_timeout(r, 1000)); + + return popup; +}); + +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/BarProp.window.js b/testing/web-platform/tests/html/browsers/the-window-object/BarProp.window.js new file mode 100644 index 0000000000..27a357cab5 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/BarProp.window.js @@ -0,0 +1,59 @@ +function assert_barProps(barPropObjects, visible) { + let lastBarProp = undefined; + for (const currentBarProp of barPropObjects) { + assert_not_equals(currentBarProp, lastBarProp, "BarBrop objects of different properties are identical"); + assert_equals(currentBarProp.visible, visible, "a BarProp's visible is wrong"); + lastBarProp = currentBarProp; + } +} + +function assert_identical_barProps(barProps, w, oldBarPropObjects, visible) { + barProps.map(val => w[val]).map((val, index) => { + assert_equals(val, oldBarPropObjects[index], "BarProp identity not preserved"); + }); + assert_barProps(oldBarPropObjects, visible); +} + +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")), + frameW = frame.contentWindow, + barProps = ["locationbar", "menubar", "personalbar", "scrollbars", "statusbar", "toolbar"], + barPropObjects = barProps.map(val => frameW[val]); + + assert_barProps(barPropObjects, true); + frame.remove(); + assert_identical_barProps(barProps, frameW, barPropObjects, false); + t.step_timeout(() => { + assert_identical_barProps(barProps, frameW, barPropObjects, false); + t.done(); + }, 0); +}, "BarBrop objects of a nested Window"); + +async_test(t => { + const openee = window.open("/common/blank.html"), + barProps = ["locationbar", "menubar", "personalbar", "scrollbars", "statusbar", "toolbar"], + barPropObjects = barProps.map(val => openee[val]); + + // This is used to demonstrate that the Document is replaced while the global object (not the + // global this object) stays the same + openee.tiedToGlobalObject = openee.document; + + assert_barProps(barPropObjects, true); + openee.onload = t.step_func(() => { + assert_own_property(openee, "tiedToGlobalObject"); + assert_not_equals(openee.tiedToGlobalObject, openee.document); + + assert_identical_barProps(barProps, openee, barPropObjects, true); + + openee.onunload = t.step_func(() => { + assert_identical_barProps(barProps, openee, barPropObjects, true); + t.step_timeout(() => { + assert_identical_barProps(barProps, openee, barPropObjects, false); + t.done(); + }, 0); + }); + + openee.close(); + assert_identical_barProps(barProps, openee, barPropObjects, true); + }); +}, "BarProp objects of an auxiliary Window"); diff --git a/testing/web-platform/tests/html/browsers/the-window-object/Document-defaultView.html b/testing/web-platform/tests/html/browsers/the-window-object/Document-defaultView.html new file mode 100644 index 0000000000..dbc75d30b2 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/Document-defaultView.html @@ -0,0 +1,38 @@ +<!doctype html> +<meta charset=utf-8> +<title>Document#defaultView</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<div id=log></div> +<script> +test(function() { + assert_equals(document.defaultView, window); +}, "Document in a browsing context"); + +test(function() { + var d = new Document(); + assert_equals(d.defaultView, null); +}, "Document created with the Document constructor"); + +test(function() { + var d = document.implementation.createDocument(null, null); + assert_equals(d.defaultView, null); +}, "Document created with createDocument"); + +test(function() { + var d = document.implementation.createHTMLDocument(); + assert_equals(d.defaultView, null); +}, "Document created with createHTMLDocument"); + +test(function() { + var parser = new DOMParser(); + var d = parser.parseFromString("<foo\/\>", "application/xml"); + assert_equals(d.defaultView, null); +}, "Document created with XML DOMParser"); + +test(function() { + var parser = new DOMParser(); + var d = parser.parseFromString("bar", "text/html"); + assert_equals(d.defaultView, null); +}, "Document created with HTML DOMParser"); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/Window-document.html b/testing/web-platform/tests/html/browsers/the-window-object/Window-document.html new file mode 100644 index 0000000000..9b27f5f7c7 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/Window-document.html @@ -0,0 +1,25 @@ +<!doctype html> +<meta charset=utf-8> +<title>Window#document</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<div id=log></div> +<script> +async_test(function() { + var URL = "/common/blank.html"; + + var iframe = document.createElement("iframe"); + document.body.appendChild(iframe); + var initialWindow = iframe.contentWindow; + var initialDocument = initialWindow.document; + assert_equals(initialDocument.URL, "about:blank"); + iframe.src = URL; + iframe.onload = this.step_func_done(function() { + assert_equals(iframe.contentWindow, initialWindow); + assert_equals(initialDocument.URL, "about:blank"); + var loadedDocument = initialWindow.document; + assert_equals(loadedDocument.URL, location.href.replace(location.pathname, URL)); + assert_not_equals(initialDocument, loadedDocument); + }); +}, "Document in a browsing context"); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/accessing-other-browsing-contexts/indexed-browsing-contexts-01.html b/testing/web-platform/tests/html/browsers/the-window-object/accessing-other-browsing-contexts/indexed-browsing-contexts-01.html new file mode 100644 index 0000000000..9710d15fb2 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/accessing-other-browsing-contexts/indexed-browsing-contexts-01.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: the browsing contexts must be sorted in the order that their containers were inserted into the Document</title> +<link rel="author" title="Intel" href="http://www.intel.com/" /> +<link rel="help" href="https://html.spec.whatwg.org/multipage/multipage/browsers.html#accessing-other-browsing-contexts" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> + +var t1 = async_test("The window's length must return the number of child browsing contexts(in iframe)"); +function on_load1(fr) { + t1.step(function () { + var doc = fr.contentDocument; + var fr3 = doc.createElement("iframe"); + fr3.setAttribute("id", "fr3"); + doc.body.insertBefore(fr3, doc.getElementById("tbl")); + + assert_equals(fr.contentWindow.length, 3, "The window.length should be 3."); + assert_array_equals([fr.contentWindow[0].frameElement, fr.contentWindow[1].frameElement, fr.contentWindow[2].frameElement], + [fr.contentDocument.getElementById("fr4"), fr.contentDocument.getElementById("fr5"), fr.contentDocument.getElementById("fr3")], + "The child browsing contexts must be sorted in the order that their containers were inserted into the Document."); + }); + t1.done(); +} + +var t2 = async_test("The window's length must return zero if it has no child browsing context"); +function on_load2(fr) { + t2.step(function () { + assert_equals(fr.contentWindow.length, 0, "The window.length should be 0."); + }); + t2.done(); +} + +</script> +<iframe id="fr1" src="test1.html" style="display:none" onload="on_load1(this)"></iframe> +<iframe id="fr2" src="test2.html" style="display:none" onload="on_load2(this)"></iframe> +<script> + +test(function () { + assert_equals(window.length, 2, "The window.length should be 2."); + assert_array_equals([window[0].frameElement, window[1].frameElement], + [document.getElementById("fr1"), document.getElementById("fr2")], + "The child browsing contexts must be sorted in the tree order."); +}, "The window's length must return the number of child browsing contexts"); + +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/accessing-other-browsing-contexts/indexed-browsing-contexts-02.html b/testing/web-platform/tests/html/browsers/the-window-object/accessing-other-browsing-contexts/indexed-browsing-contexts-02.html new file mode 100644 index 0000000000..d09c944fd8 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/accessing-other-browsing-contexts/indexed-browsing-contexts-02.html @@ -0,0 +1,62 @@ +<!DOCTYPE html> +<head> + <meta charset="utf-8"> + <title>HTML Test: the browsing contexts created by various container elements</title> + <link rel="author" title="Intel" href="http://www.intel.com/" /> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script> + + var t1 = async_test("Accessing child browsing contexts 1"); + var t2 = async_test("Accessing child browsing contexts 2"); + var t3 = async_test("Accessing child browsing contexts 3"); + function on_load() { + //Child browsing contexts created by iframe, object and embed elements. + t1.step(function () { + assert_equals(window.length, 3, "The top browsing context should have 3 child browsing contexts."); + }); + t1.step(function () { + assert_equals(window[0].name, "win1", "The browsing context name should be 'win1'."); + assert_equals(window[1].name, "win2", "The browsing context name should be 'win2'."); + assert_equals(window[2].name, "win3", "The browsing context name should be 'win3'."); + }); + t1.done(); + + //Child browsing contexts created by frame elements. + t2.step(function () { + assert_equals(document.getElementById("fr").contentWindow.length, 2, + "The child browsing context created by the iframe element should have 2 child browsing contexts."); + }); + t2.step(function () { + assert_equals(document.getElementById("fr").contentWindow[0].name, "win4", + "The browsing context name should be 'win4'."); + assert_equals(document.getElementById("fr").contentWindow[1].name, "win5", + "The browsing context name should be 'win5'."); + }); + t2.done(); + + //The child browsing context will be removed if the data attribute of the associated object element is removed. + t3.step(function () { + document.getElementById("obj").removeAttribute("type"); + assert_equals(window.length, 3, "The top browsing context should have 3 child browsing contexts."); + document.getElementById("obj").removeAttribute("data"); + assert_equals(window.length, 3, "The top browsing context should have 3 child browsing contexts."); + + setTimeout(function () { + assert_equals(window.length, 2, "The top browsing context should have 2 child browsing contexts."); + }, 1); + }); + t3.done(); + } + + </script> +</head> +<body onload="on_load()"> + <div id="log"></div> + <div style="display:none"> + <iframe id="fr" name="win1" src="test3.html"></iframe> + <object id="obj" name="win2" type="text/html" data="about:blank"></object> + <object type="image/png" src="/images/green.png"></object> + <embed id="emb" name="win3" type="image/svg+xml" src="/images/green.svg"></embed> + </div> +</body> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/accessing-other-browsing-contexts/indexed-browsing-contexts-03.html b/testing/web-platform/tests/html/browsers/the-window-object/accessing-other-browsing-contexts/indexed-browsing-contexts-03.html new file mode 100644 index 0000000000..1548891175 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/accessing-other-browsing-contexts/indexed-browsing-contexts-03.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<head> + <meta charset="utf-8"> + <title>HTML Test: indexed property of a Window object</title> + <link rel="author" title="Intel" href="http://www.intel.com/" /> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script> + + var t1 = async_test("Indexed child browsing contexts"); + function on_load() { + t1.step(function () { + assert_equals(window[0], document.getElementsByTagName("object")[0].contentWindow, + "The first child browsing context's container should be the object element."); + assert_equals(window[1], document.getElementsByTagName("iframe")[0].contentWindow, + "The second child browsing context's container should be the iframe element."); + }); + t1.done(); + } + + </script> +</head> +<body onload="on_load()"> + <div id="log"></div> + <div style="display:none"> + <div id="0"></div> + <object name="0" type="text/html" data="test2.html"></object> + <iframe name="0" src="about:blank"></iframe> + </div> +</body> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/accessing-other-browsing-contexts/iterator.html b/testing/web-platform/tests/html/browsers/the-window-object/accessing-other-browsing-contexts/iterator.html new file mode 100644 index 0000000000..76dc7dbae6 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/accessing-other-browsing-contexts/iterator.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>window[@@iterator]</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +test(function() { + assert_false(Symbol.iterator in window); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/accessing-other-browsing-contexts/test1.html b/testing/web-platform/tests/html/browsers/the-window-object/accessing-other-browsing-contexts/test1.html new file mode 100644 index 0000000000..f85f90f7c6 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/accessing-other-browsing-contexts/test1.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: child browsing contexts created by iframe elements</title> +<link rel="author" title="Intel" href="http://www.intel.com/" /> +<table id="tbl"> + <tr> + <td> + <iframe id="fr4" src=""></iframe> + </td> + </tr> + <iframe id="fr5" src="about:blank"></iframe> +</table> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/accessing-other-browsing-contexts/test2.html b/testing/web-platform/tests/html/browsers/the-window-object/accessing-other-browsing-contexts/test2.html new file mode 100644 index 0000000000..d6a16647fe --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/accessing-other-browsing-contexts/test2.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: child browsing contexts created by object and embed elements</title> +<link rel="author" title="Intel" href="http://www.intel.com/" /> +<object type="image/png" src="/images/green.png"></object> +<embed type="image/png" src="/images/green.png"></embed> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/accessing-other-browsing-contexts/test3.html b/testing/web-platform/tests/html/browsers/the-window-object/accessing-other-browsing-contexts/test3.html new file mode 100644 index 0000000000..a62fdbaae7 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/accessing-other-browsing-contexts/test3.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: child browsing contexts created by frame elements</title> +<link rel="author" title="Intel" href="http://www.intel.com/" /> +<frameset> + <frame name="win4"></frame> + <frame name="win5"></frame> +</frameset> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/accessing-other-browsing-contexts/window_length.html b/testing/web-platform/tests/html/browsers/the-window-object/accessing-other-browsing-contexts/window_length.html new file mode 100644 index 0000000000..c9559b531a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/accessing-other-browsing-contexts/window_length.html @@ -0,0 +1,51 @@ +<!doctype html> +<title>window.length</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id=log></div> +<script> +var iframe; +var subframe; +var other_window; +test(function() {assert_equals(window.length, 0)}, "No child browsing contexts"); +test(function() { + iframe = document.createElement("iframe"); + assert_equals(window.length, 0) +}, "iframe not inserted into the document"); + +test(function() { + document.body.appendChild(iframe); + assert_equals(window.length, 1) +}, "One iframe inserted into the document"); + +test(function() { + subframe = document.createElement("iframe"); + iframe.contentDocument.body.appendChild(subframe); + assert_equals(window.length, 1); +}, "Child browsing context has a child browsing context"); + +test(function() { + try { + assert_equals(iframe.contentWindow.length, 1); + } finally { + subframe.parentNode.removeChild(subframe); + } +}, "window.length in child frame"); + +test(function() { + iframe.parentNode.removeChild(iframe); + other_window = window.open(); + assert_equals(window.length, 0); + assert_equals(other_window.length, 0); +}, "Opened window") + +test(function() { + other_window.document.body.appendChild(iframe); + try { + assert_equals(other_window.length, 1); + } finally { + other_window.close(); + } +}, "Iframe in opened window") + +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/close-method.window.js b/testing/web-platform/tests/html/browsers/the-window-object/close-method.window.js new file mode 100644 index 0000000000..0288f9cab8 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/close-method.window.js @@ -0,0 +1,39 @@ +function assert_closed_opener(w, closed, opener) { + assert_equals(w.closed, closed); + assert_equals(w.opener, opener); +} + +async_test(t => { + const openee = window.open(); + assert_closed_opener(openee, false, self); + openee.onunload = t.step_func(() => { + assert_closed_opener(openee, true, self); + t.step_timeout(() => { + assert_closed_opener(openee, true, null); + t.done(); + }, 0); + }); + openee.close(); + assert_closed_opener(openee, true, self); +}, "window.close() queues a task to discard, but window.closed knows immediately"); + +async_test(t => { + const openee = window.open("", "greatname"); + assert_closed_opener(openee, false, self); + openee.close(); + assert_closed_opener(openee, true, self); + const openee2 = window.open("", "greatname"); + assert_not_equals(openee, openee2); + assert_closed_opener(openee, true, self); // Ensure second window.open() call was synchronous + openee2.onunload = t.step_func(() => { + assert_closed_opener(openee2, true, self); + t.step_timeout(() => { + assert_closed_opener(openee, true, null); + assert_closed_opener(openee2, true, null); + t.done(); + }, 0); + }); + openee2.close(); + assert_closed_opener(openee, true, self); // Ensure second close() call was synchronous + assert_closed_opener(openee2, true, self); +}, "window.close() affects name targeting immediately"); diff --git a/testing/web-platform/tests/html/browsers/the-window-object/closed-attribute.window.js b/testing/web-platform/tests/html/browsers/the-window-object/closed-attribute.window.js new file mode 100644 index 0000000000..88a3beba6f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/closed-attribute.window.js @@ -0,0 +1,69 @@ +// META: script=/common/get-host-info.sub.js + +function closedTest(newWindow, closeNewWindowsBrowsingContext) { + assert_equals(newWindow.closed, false); + closeNewWindowsBrowsingContext(); + assert_equals(newWindow.closed, true); +} + +test(() => { + const frame = document.body.appendChild(document.createElement("iframe")); + closedTest(frame.contentWindow, () => frame.remove()); +}, "closed and same-origin nested browsing context"); + +test(() => { + const openee = window.open(); + closedTest(openee, () => openee.close()); + + // close() is a no-op once "is closing" is set + openee.close(); + assert_equals(openee.closed, true); +}, "closed/close() and same-origin auxiliary browsing context"); + +const support = new URL("support/closed.html", location.href).pathname; +[ + { + type: "cross-origin", + url: `${get_host_info().HTTP_REMOTE_ORIGIN}${support}` + }, + { + type: "cross-site", + url: `${get_host_info().HTTP_NOTSAMESITE_ORIGIN}${support}` + } +].forEach(val => { + async_test(t => { + const frame = document.createElement("iframe"), + ident = `${val.type}-nested-bc`; + frame.src = `${val.url}?window=parent&ident=${ident}`; + const listener = t.step_func(e => { + if (e.data === ident) { + closedTest(frame.contentWindow, () => frame.remove()); + self.removeEventListener("message", listener); + t.done(); + } + }); + // Use a message event rather than onload for consistency with auxiliary browsing contexts. + self.addEventListener("message", listener); + document.body.append(frame); + }, `closed and ${val.type} nested browsing context`); + + async_test(t => { + const ident = `${val.type}-auxiliary-bc`, + support = new URL("support/closed.html", location.href).pathname, + openee = window.open(`${val.url}?window=opener&ident=${ident}`), + listener = t.step_func(e => { + if (e.data === ident) { + closedTest(openee, () => openee.close()); + + // close() is a no-op once "is closing" is set + openee.close(); + assert_equals(openee.closed, true); + + self.removeEventListener("message", listener); + t.done(); + } + }); + // As there's no cross-origin onload, use a message event. + self.addEventListener("message", listener); + }, `closed/close() and ${val.type} auxiliary browsing context`); +}); diff --git a/testing/web-platform/tests/html/browsers/the-window-object/defaultstatus.html b/testing/web-platform/tests/html/browsers/the-window-object/defaultstatus.html new file mode 100644 index 0000000000..f683f130cd --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/defaultstatus.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>window.defaultStatus and window.defaultstatus are not supported</title> +<link rel="author" href="mailto:masonf@chromium.org"> +<link rel="help" href="https://developer.mozilla.org/en-US/docs/Web/API/Window/defaultStatus"> +<link rel="help" href="https://crbug.com/692835"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> +test(() => { + assert_false(window.hasOwnProperty('defaultStatus')); + assert_false(window.hasOwnProperty('defaultstatus')); + assert_equals(window.defaultStatus,undefined); + assert_equals(window.defaultstatus,undefined); +}, "The window.defaultStatus and window.defaultstatus attributes are not supported"); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/document-attribute.window.js b/testing/web-platform/tests/html/browsers/the-window-object/document-attribute.window.js new file mode 100644 index 0000000000..f13acdb8a3 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/document-attribute.window.js @@ -0,0 +1,15 @@ +async_test(t => { + const frame = document.createElement("iframe"); + frame.onload = t.step_func(() => { + const frameW = frame.contentWindow, + frameD = frame.contentDocument; + assert_equals(frameW.document, frameD); + frame.remove(); + assert_equals(frameW.document, frameD); + t.step_timeout(() => { + assert_equals(frameW.document, frameD); + t.done(); + }, 100); + }); + document.body.append(frame); +}, "Window object's document IDL attribute and discarding the browsing context"); diff --git a/testing/web-platform/tests/html/browsers/the-window-object/focus.window.js b/testing/web-platform/tests/html/browsers/the-window-object/focus.window.js new file mode 100644 index 0000000000..6ec7feee28 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/focus.window.js @@ -0,0 +1,15 @@ +async_test(t => { + const input = document.body.appendChild(document.createElement("input")); + input.onfocus = t.step_func(() => { + const frame = document.body.appendChild(document.createElement("iframe")), + frameW = frame.contentWindow; + frameW.onfocus = t.unreached_func(); + frame.remove(); + frameW.focus(); + t.step_timeout(() => { + assert_equals(document.activeElement, input); + t.done(); + }, 100); + }); + input.focus(); +}); diff --git a/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_1-1.html b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_1-1.html new file mode 100644 index 0000000000..217608e46e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_1-1.html @@ -0,0 +1,20 @@ +<!doctype html> +<iframe></iframe> +<script> +var t = opener.t; + +onload = t.step_func(function() { + setTimeout(t.step_func(function() { + var history_length = history.length; + var iframe = document.getElementsByTagName("iframe")[0]; + iframe.onload = t.step_func(function() { + opener.assert_equals(history.length, history_length + 1); + iframe.parentNode.removeChild(iframe); + opener.assert_equals(history.length, history_length); + t.done(); + window.close(); + }); + iframe.src = "discard_iframe_history_1-2.html;"; + }), 100); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_1-2.html b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_1-2.html new file mode 100644 index 0000000000..b43598f2cd --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_1-2.html @@ -0,0 +1,2 @@ +<!doctype html> +Filler text diff --git a/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_1.html b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_1.html new file mode 100644 index 0000000000..4d1e473fc0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_1.html @@ -0,0 +1,10 @@ +<!doctype html> +<title>Removing iframe from document removes it from history</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test(); +var w = window.open("discard_iframe_history_1-1.html"); +</script> + diff --git a/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_2-1.html b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_2-1.html new file mode 100644 index 0000000000..61e5891ebd --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_2-1.html @@ -0,0 +1,22 @@ +<!doctype html> +<iframe></iframe> +<script> +var t = opener.t; + +onload = t.step_func(function() { + setTimeout(t.step_func(function() { + var history_length = history.length; + var iframe = document.getElementsByTagName("iframe")[0]; + iframe.onload = t.step_func(function() { + setTimeout(t.step_func(function() { + opener.assert_equals(history.length, history_length + 1, "History length before iframe removal"); + document.body.innerHTML = ""; + opener.assert_equals(history.length, history_length, "History length after iframe removal"); + t.done(); + window.close(); + }), 100); + }); + iframe.src = "discard_iframe_history_1-2.html"; + }), 100); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_2.html b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_2.html new file mode 100644 index 0000000000..89d0fb4c64 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_2.html @@ -0,0 +1,10 @@ +<!doctype html> +<title>Removing iframe from document via innerHTML removes it from history</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test(); +var w = window.open("discard_iframe_history_2-1.html"); +</script> + diff --git a/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_3-1.html b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_3-1.html new file mode 100644 index 0000000000..de3f075d64 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_3-1.html @@ -0,0 +1,21 @@ +<script> +history_lengths = []; + +var t = opener.t; + +push_length = t.step_func(function () { + history_lengths.push(history.length) +}); + +do_test = t.step_func(function () { + try { + var start_length = history_lengths[0]; + expected = [start_length, start_length + 1, start_length]; + opener.assert_array_equals(history_lengths, expected); + t.done(); + } finally { + window.close(); + } +}); +</script> +<iframe src="discard_iframe_history_3-2.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_3-2.html b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_3-2.html new file mode 100644 index 0000000000..95f9fce5d8 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_3-2.html @@ -0,0 +1,4 @@ +<a href="discard_iframe_history_3-3.html" onclick="parent.push_length()">Click me</a> +<script> +onload = function() {setTimeout(parent.t.step_func(function() {document.links[0].click()}), 100)} +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_3-3.html b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_3-3.html new file mode 100644 index 0000000000..4672b0ec30 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_3-3.html @@ -0,0 +1,4 @@ +<button onclick="var p = parent; p.push_length(); frameElement.parentNode.removeChild(frameElement); p.push_length(); p.do_test();">Click me</button> +<script> +onload = function() {setTimeout(parent.t.step_func(function() {document.getElementsByTagName("button")[0].click()}), 100)} +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_3.html b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_3.html new file mode 100644 index 0000000000..38dbdbd0fa --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_3.html @@ -0,0 +1,9 @@ +<!doctype html> +<title>Removing iframe from document removes it from history</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test(); +var w = window.open("discard_iframe_history_3-1.html"); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_4-1.html b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_4-1.html new file mode 100644 index 0000000000..1b5726cdcd --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_4-1.html @@ -0,0 +1,21 @@ +<script> +history_lengths = []; + +var t = opener.t; + +push_length = t.step_func(function () { + history_lengths.push(history.length) +}); + +do_test = t.step_func(function () { + try { + var start_length = history_lengths[0]; + expected = [start_length, start_length + 1, start_length]; + opener.assert_array_equals(history_lengths, expected); + t.done(); + } finally { + window.close(); + } +}); +</script> +<iframe src="discard_iframe_history_4-2.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_4-2.html b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_4-2.html new file mode 100644 index 0000000000..979b2b28e4 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_4-2.html @@ -0,0 +1,4 @@ +<a href="discard_iframe_history_4-3.html" onclick="parent.push_length()">Click me</a> +<script> +onload = function() {setTimeout(parent.t.step_func(function() {document.links[0].click()}), 100)} +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_4-3.html b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_4-3.html new file mode 100644 index 0000000000..b4308f439a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_4-3.html @@ -0,0 +1,4 @@ +<button onclick="var p = parent; p.push_length(); frameElement.parentNode.innerHTML = ''; p.push_length(); p.do_test();">Click me</button> +<script> +onload = function() {setTimeout(parent.t.step_func(function() {document.getElementsByTagName("button")[0].click()}), 100)} +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_4.html b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_4.html new file mode 100644 index 0000000000..ffd444e3bf --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_4.html @@ -0,0 +1,9 @@ +<!doctype html> +<title>Removing iframe from document removes it from history</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test(); +var w = window.open("discard_iframe_history_4-1.html"); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/non-automated/discard_iframe_history_1-1.html b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/non-automated/discard_iframe_history_1-1.html new file mode 100644 index 0000000000..9969427989 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/non-automated/discard_iframe_history_1-1.html @@ -0,0 +1,27 @@ +<!doctype html> +<iframe></iframe> +<script> +var t = opener.t; +var iframe = document.getElementsByTagName("iframe")[0]; +var history_length; + +function load_frame(src) { + history_length = history.length; + iframe.src = src; + var button = document.getElementsByTagName("button")[0]; + button.parentNode.removeChild(button); +} + +remove_frame = t.step_func(function() { + try { + opener.assert_equals(history.length, history_length + 1, "History length after loading page in iframe"); + iframe.parentNode.removeChild(iframe); + opener.assert_equals(history.length, history_length, "History length after removing iframe"); + t.done(); + } finally { + window.close(); + } +}); + +</script> +<button onclick="load_frame('discard_iframe_history_1-2.html')">Click here</button> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/non-automated/discard_iframe_history_1-2.html b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/non-automated/discard_iframe_history_1-2.html new file mode 100644 index 0000000000..8c3d1a9dad --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/non-automated/discard_iframe_history_1-2.html @@ -0,0 +1,2 @@ +<!doctype html> +<button onclick="parent.remove_frame()">Click here</button> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/non-automated/discard_iframe_history_1-manual.html b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/non-automated/discard_iframe_history_1-manual.html new file mode 100644 index 0000000000..ca9dd91f2e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/non-automated/discard_iframe_history_1-manual.html @@ -0,0 +1,10 @@ +<!doctype html> +<title>Removing iframe from document removes it from history</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +setup({timeout:3600000}) +var t = async_test(); +var w = window.open("discard_iframe_history_1-1.html"); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/non-automated/discard_iframe_history_2-1.html b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/non-automated/discard_iframe_history_2-1.html new file mode 100644 index 0000000000..bc01cae88c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/non-automated/discard_iframe_history_2-1.html @@ -0,0 +1,19 @@ +<script> +history_lengths = []; + +function push_length() { + history_lengths.push(history.length) +} + +do_test = opener.t.step_func(function () { + try { + var start_length = history_lengths[0]; + expected = [start_length, start_length + 1, start_length]; + opener.assert_array_equals(history_lengths, expected); + opener.t.done(); + } finally { + window.close(); + } +}); +</script> +<iframe src="discard_iframe_history_2-2.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/non-automated/discard_iframe_history_2-2.html b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/non-automated/discard_iframe_history_2-2.html new file mode 100644 index 0000000000..b25bf5f001 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/non-automated/discard_iframe_history_2-2.html @@ -0,0 +1 @@ +<a href="discard_iframe_history_2-3.html" onclick="parent.push_length()">Click me</a> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/non-automated/discard_iframe_history_2-3.html b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/non-automated/discard_iframe_history_2-3.html new file mode 100644 index 0000000000..68847e9a7c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/non-automated/discard_iframe_history_2-3.html @@ -0,0 +1 @@ +<button onclick="var p = parent; p.push_length(); frameElement.parentNode.removeChild(frameElement); p.push_length(); p.do_test();">Click me</button> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/non-automated/discard_iframe_history_2-manual.html b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/non-automated/discard_iframe_history_2-manual.html new file mode 100644 index 0000000000..57eaabcd0c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/non-automated/discard_iframe_history_2-manual.html @@ -0,0 +1,10 @@ +<!doctype html> +<title>Removing iframe from document removes it from history</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +setup({timeout:3600000}) +var t = async_test(); +var w = window.open("discard_iframe_history_2-1.html"); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/historical.window.js b/testing/web-platform/tests/html/browsers/the-window-object/historical.window.js new file mode 100644 index 0000000000..653f12b464 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/historical.window.js @@ -0,0 +1,4 @@ +test(() => { + assert_false("showModalDialog" in window) + assert_false("showModalDialog" in Window.prototype) +}, "showModalDialog() has been removed from the platform") diff --git a/testing/web-platform/tests/html/browsers/the-window-object/length-attribute.window.js b/testing/web-platform/tests/html/browsers/the-window-object/length-attribute.window.js new file mode 100644 index 0000000000..d56e1e4692 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/length-attribute.window.js @@ -0,0 +1,24 @@ +async_test(t => { + const frame = document.createElement("iframe"); + frame.srcdoc = "<iframe name=x srcdoc='<iframe name=z></iframe>'></iframe><iframe name=y></iframe>"; + frame.onload = t.step_func_done(() => { + const frameW = frame.contentWindow; + assert_equals(frameW.length, 2); + assert_not_equals(frameW.x, undefined); + assert_not_equals(frameW.y, undefined); + assert_equals(frameW.z, undefined); + assert_equals(frameW.x, frameW[0]); + assert_equals(frameW.y, frameW[1]); + const xFrameW = frameW.x; + assert_equals(xFrameW.length, 1); + assert_not_equals(xFrameW.z, undefined); + assert_equals(xFrameW.z, xFrameW[0]); + frame.remove(); + assert_equals(frameW.length, 0); + assert_equals(frameW.x, undefined); + assert_equals(frameW[0], undefined); + assert_equals(xFrameW.length, 0); + assert_equals(xFrameW.z, undefined); + }); + document.body.append(frame); +}, "Window object's length IDL attribute (and named access)"); diff --git a/testing/web-platform/tests/html/browsers/the-window-object/name-attribute.window.js b/testing/web-platform/tests/html/browsers/the-window-object/name-attribute.window.js new file mode 100644 index 0000000000..f266dd7acb --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/name-attribute.window.js @@ -0,0 +1,18 @@ +test(() => { + const frame = document.createElement("iframe"), + name = "A", + name2 = "B"; + frame.setAttribute("name", name); + document.body.append(frame); + const frameW = frame.contentWindow; + assert_equals(frameW.name, name); + frameW.name = name2; + assert_equals(frame.getAttribute("name"), name); + assert_equals(frameW.name, name2); + frame.remove(); + assert_equals(frame.getAttribute("name"), name); + assert_equals(frameW.name, ""); + frameW.name = name2; + assert_equals(frame.getAttribute("name"), name); + assert_equals(frameW.name, ""); +}, "Window object's name IDL attribute"); diff --git a/testing/web-platform/tests/html/browsers/the-window-object/named-access-on-the-window-object/cross-global-npo.html b/testing/web-platform/tests/html/browsers/the-window-object/named-access-on-the-window-object/cross-global-npo.html new file mode 100644 index 0000000000..2acad0734c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/named-access-on-the-window-object/cross-global-npo.html @@ -0,0 +1,38 @@ +<!doctype html> +<meta charset=utf-8> +<title>Named access across globals</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id=log></div> +<script> +async_test(function() { + var iframe = document.createElement("iframe"); + iframe.src = "cross-global-support.html"; + document.body.appendChild(iframe); + iframe.onload = this.step_func_done(function() { + var name = "named"; + var win = iframe.contentWindow; + var element = win.document.getElementById(name); + + var expectedValues = [ + // [value, is own property] + [element, false, "window"], + [element, false, "Window.prototype"], + [element, true, "named prototype object"], + [undefined, false, "EventTarget.prototype"], + [undefined, false, "Object.prototype"], + ]; + for (var object = win; object; object = Object.getPrototypeOf(object)) { + var expected = expectedValues.shift(); + assert_equals(object[name], expected[0], "[[Get]] on " + expected[2]); + var desc = Object.getOwnPropertyDescriptor(object, name); + if (expected[1]) { + assert_not_equals(desc, undefined, "[[GetOwnProperty]] on " + expected[2] + " should return something"); + assert_equals(desc.value, element, "[[GetOwnProperty]] on " + expected[2]); + } else { + assert_equals(desc, undefined, "[[GetOwnProperty]] on " + expected[2]); + } + } + }); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/named-access-on-the-window-object/cross-global-support.html b/testing/web-platform/tests/html/browsers/the-window-object/named-access-on-the-window-object/cross-global-support.html new file mode 100644 index 0000000000..9d7b9f8a25 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/named-access-on-the-window-object/cross-global-support.html @@ -0,0 +1,4 @@ +<!doctype html> +<meta charset=utf-8> +<title>Named access across globals: support file</title> +<span id="named"></span> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/named-access-on-the-window-object/named-objects.html b/testing/web-platform/tests/html/browsers/the-window-object/named-access-on-the-window-object/named-objects.html new file mode 100644 index 0000000000..d5b1789d59 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/named-access-on-the-window-object/named-objects.html @@ -0,0 +1,76 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: Named access on the Window object</title> +<link rel="author" title="Intel" href="http://www.intel.com/"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/multipage/browsers.html#named-access-on-the-window-object"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<div style="display:none"> + <p name="a" id="p1"></p> + <a name="a" id="a1" href="#"></a> + <area name="a" id="area1"></area> + <embed name="a" id="embed1"></embed> + <form name="a" id="form1"></form> + <img name="a" id="img1"> + <object name="a" id="obj1"></object> + <span name="a" id="span1"></span> + + <b id="b" name="c"></b> + <a name="c"></a> + <iframe name="c" id="fm1"></iframe> + <iframe name="c" id="fm2" src="test.html" onload="on_load()"></iframe> + <input id="b"></input> + <span id="d"></span> + <a name=""></a> + <b id=""></b> +</div> +<script> + +test(function() { + assert_equals(window['c'], document.getElementById("fm1").contentWindow, "The first iframe's window should be returned."); +}, "Check if the first nested browsing context is returned by window['c']"); + +test(function() { + assert_true(window['a'] instanceof HTMLCollection); + assert_array_equals(window['a'], + [ document.getElementById('embed1'), + document.getElementById('form1'), document.getElementById('img1'), + document.getElementById('obj1') ], + "The elements are not in tree order."); + + document.getElementById('form1').setAttribute("name", ""); + document.getElementById('embed1').setAttribute("name", ""); + assert_array_equals(window['a'], + [ document.getElementById('img1'), + document.getElementById('obj1') ], + "Window['a'] should not contain the elements with empty name attribute."); +}, "Check if window['a'] contains all embed, form, img, and object elements, and their order"); + +var t = async_test("Check that window['fs'] does not return the frameset element with name='fs' (historical)"); +function on_load () { + t.step(function () { + assert_equals(document.getElementById('fm2').contentWindow['fs'], + undefined, + "The frameset element should not be returned."); + }); + t.done(); +} + +test(function() { + assert_true(window['b'] instanceof HTMLCollection); + assert_array_equals(window['b'], [document.getElementsByTagName('b')[0], document.getElementsByTagName('input')[0]]); + + document.getElementsByTagName('b')[0].setAttribute("id", ""); + assert_equals(window['b'], document.getElementsByTagName('input')[0], + "The window['b'] should not contain the elements with empty id attribute."); +}, "Check if window['b'] returns the elements with the id='b'"); + +test(function() { + assert_equals(window['d'], document.getElementById('d')); +}, "Check if window['d'] returns the element with id='d'"); + +test(function() { + assert_equals(window[''], undefined, "The window[''] should be undefined"); +}, "Check widow[''] when there are some elements with empty id or name attribute"); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/named-access-on-the-window-object/navigated-named-objects.window.js b/testing/web-platform/tests/html/browsers/the-window-object/named-access-on-the-window-object/navigated-named-objects.window.js new file mode 100644 index 0000000000..59d94efc99 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/named-access-on-the-window-object/navigated-named-objects.window.js @@ -0,0 +1,67 @@ +// META: script=/common/get-host-info.sub.js + +function echoURL(content) { + return `/common/echo.py?content=${encodeURIComponent(content)}`; +} + +function setSrc(frame, type, content) { + if (type === "same-origin") { + frame.src = echoURL(content); + } else if (type === "cross-site") { + frame.src = `${get_host_info().HTTP_NOTSAMESITE_ORIGIN}${echoURL(content)}`; + } else { + frame.srcdoc = content; + } +} + +["srcdoc", "same-origin", "cross-site"].forEach(type => { + const initialType = type === "srcdoc" ? type : "same-origin"; + + [ + { + "namedObject": "<div id=abc></div>", + "namedObjectLocalName": "div" + }, + { + "namedObject": "<object name=abc></object>", + "namedObjectLocalName": "object" + }, + { + "namedObject": "<iframe id=abc></iframe>", + "namedObjectLocalName": "iframe" + } + ].forEach(testData => { + async_test(t => { + const frame = document.createElement("iframe"); + t.add_cleanup(() => frame.remove()); + setSrc(frame, initialType, `<script>function f() { return abc }</script>${testData.namedObject}`); + frame.onload = t.step_func(() => { + const f = frame.contentWindow.f, + associatedAbc = f(); + frame.onload = t.step_func_done(() => { + assert_equals(f(), associatedAbc); + assert_equals(associatedAbc.localName, testData.namedObjectLocalName); + }); + setSrc(frame, type, "<span id=abc></span>"); + }); + document.body.append(frame); + }, `Window's associated Document object is used for finding named objects (<${testData.namedObjectLocalName}> via ${type} <iframe>)`); + }); + + async_test(t => { + const frame = document.createElement("iframe"); + t.add_cleanup(() => frame.remove()); + setSrc(frame, initialType, "<script>function f() { return abc }</script><object name=abc data='about:blank'></object>"); + frame.onload = t.step_func(() => { + const f = frame.contentWindow.f, + associatedAbc = f(), + associatedAbcContainer = associatedAbc.frameElement; + frame.onload = t.step_func_done(() => { + assert_equals(f(), associatedAbcContainer); + assert_equals(associatedAbcContainer.contentWindow, null); + }); + setSrc(frame, type, "<span id=abc></span>"); + }); + document.body.append(frame); + }, `Window's associated Document object is used for finding named objects (<object> with browsing ccontext via ${type} <iframe)>`); +}); diff --git a/testing/web-platform/tests/html/browsers/the-window-object/named-access-on-the-window-object/prototype.html b/testing/web-platform/tests/html/browsers/the-window-object/named-access-on-the-window-object/prototype.html new file mode 100644 index 0000000000..910374381b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/named-access-on-the-window-object/prototype.html @@ -0,0 +1,94 @@ +<!doctype html> +<meta charset=utf-8> +<title>Named access with shadowing properties</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id=log></div> +<script> +test(function() { + var name = "named1"; + window[name] = "shadowing"; + var element = document.createElement("span"); + element.id = name; + document.body.appendChild(element); + + assert_equals(window[name], "shadowing"); + assert_equals(Object.getOwnPropertyDescriptor(window, name).value, "shadowing"); + + assert_equals(Window.prototype[name], element); + assert_equals(Object.getOwnPropertyDescriptor(Window.prototype, name), undefined); + + var npo = Object.getPrototypeOf(Window.prototype); + assert_equals(npo[name], element); + assert_equals(Object.getOwnPropertyDescriptor(npo, name).value, element); + + assert_equals(EventTarget.prototype[name], undefined); + assert_equals(Object.getOwnPropertyDescriptor(EventTarget.prototype, name), undefined); +}, "Property on window."); + +test(function() { + var name = "named2"; + Window.prototype[name] = "shadowing"; + var element = document.createElement("span"); + element.id = name; + document.body.appendChild(element); + + assert_equals(window[name], "shadowing"); + assert_equals(Object.getOwnPropertyDescriptor(window, name), undefined); + + assert_equals(Window.prototype[name], "shadowing"); + assert_equals(Object.getOwnPropertyDescriptor(Window.prototype, name).value, "shadowing"); + + var npo = Object.getPrototypeOf(Window.prototype); + assert_equals(npo[name], element); + assert_equals(Object.getOwnPropertyDescriptor(npo, name).value, element); + + assert_equals(EventTarget.prototype[name], undefined); + assert_equals(Object.getOwnPropertyDescriptor(EventTarget.prototype, name), undefined); +}, "Property on Window.prototype."); + +test(function() { + var name = "named3"; + EventTarget.prototype[name] = "shadowing"; + var element = document.createElement("span"); + element.id = name; + document.body.appendChild(element); + + assert_equals(window[name], "shadowing"); + assert_equals(Object.getOwnPropertyDescriptor(window, name), undefined); + + assert_equals(Window.prototype[name], "shadowing"); + assert_equals(Object.getOwnPropertyDescriptor(Window.prototype, name), undefined); + + var npo = Object.getPrototypeOf(Window.prototype); + assert_equals(npo[name], "shadowing"); + assert_equals(Object.getOwnPropertyDescriptor(npo, name), undefined); + + assert_equals(EventTarget.prototype[name], "shadowing"); + assert_equals(Object.getOwnPropertyDescriptor(EventTarget.prototype, name).value, "shadowing"); +}, "Property on EventTarget.prototype."); + +test(function() { + var name = "named4"; + Object.prototype[name] = "shadowing"; + var element = document.createElement("span"); + element.id = name; + document.body.appendChild(element); + + assert_equals(window[name], "shadowing"); + assert_equals(Object.getOwnPropertyDescriptor(window, name), undefined); + + assert_equals(Window.prototype[name], "shadowing"); + assert_equals(Object.getOwnPropertyDescriptor(Window.prototype, name), undefined); + + var npo = Object.getPrototypeOf(Window.prototype); + assert_equals(npo[name], "shadowing"); + assert_equals(Object.getOwnPropertyDescriptor(npo, name), undefined); + + assert_equals(EventTarget.prototype[name], "shadowing"); + assert_equals(Object.getOwnPropertyDescriptor(EventTarget.prototype, name), undefined); + + assert_equals(Object.prototype[name], "shadowing"); + assert_equals(Object.getOwnPropertyDescriptor(Object.prototype, name).value, "shadowing"); +}, "Property on Object.prototype."); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/named-access-on-the-window-object/test.html b/testing/web-platform/tests/html/browsers/the-window-object/named-access-on-the-window-object/test.html new file mode 100644 index 0000000000..c3b3cc1852 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/named-access-on-the-window-object/test.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: Named Object</title> +<link rel="author" title="Intel" href="http://www.intel.com/"> +<frameset name="fs" id="fs1"> + <frame></frame> +</frameset> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/named-access-on-the-window-object/window-named-properties.html b/testing/web-platform/tests/html/browsers/the-window-object/named-access-on-the-window-object/window-named-properties.html new file mode 100644 index 0000000000..bd3929dd89 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/named-access-on-the-window-object/window-named-properties.html @@ -0,0 +1,83 @@ +<!doctype html> +<meta charset=utf-8> +<title>Changes to named properties of the window object</title> +<link rel="author" title="Ms2ger" href="ms2ger@gmail.com"> +<link rel="author" title="Boris Zbarsky" href="bzbarsky@mit.edu"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#window"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-window-nameditem"> +<link rel="help" href="https://webidl.spec.whatwg.org/#named-properties-object"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id=log></div> +<iframe name="bar"></iframe> +<iframe name="baz"></iframe> +<iframe name="baz"></iframe> +<iframe name="constructor"></iframe> +<iframe id="quux"></iframe> +<script> +function assert_data_propdesc(pd, Writable, Enumerable, Configurable) { + assert_equals(typeof pd, "object"); + assert_equals(pd.writable, Writable); + assert_equals(pd.enumerable, Enumerable); + assert_equals(pd.configurable, Configurable); +} +test(function() { + assert_true("bar" in window, "bar not in window"); + assert_equals(window["bar"], + document.getElementsByTagName("iframe")[0].contentWindow); +}, "Static name"); + +test(function() { + assert_true("quux" in window, "quux not in window"); + assert_equals(window["quux"], + document.getElementsByTagName("iframe")[4]); +}, "Static id"); + +test(function() { + assert_true("bar" in Window.prototype, "bar in Window.prototype"); + assert_false(Window.prototype.hasOwnProperty("bar"), "Window.prototype.hasOwnProperty(\"bar\")"); + + var gsp = Object.getPrototypeOf(Object.getPrototypeOf(window)); + assert_true("bar" in gsp, "bar in gsp"); + assert_true(gsp.hasOwnProperty("bar"), "gsp.hasOwnProperty(\"bar\")"); + assert_data_propdesc(Object.getOwnPropertyDescriptor(gsp, "bar"), + true, false, true); +}, "Static name on the prototype"); +test(function() { + assert_equals(window.constructor, Window); + assert_false(window.hasOwnProperty("constructor"), "window.constructor should not be an own property."); + + var proto = Object.getPrototypeOf(window); + assert_equals(proto.constructor, Window); + assert_true("constructor" in proto, "constructor in proto"); + assert_data_propdesc(Object.getOwnPropertyDescriptor(proto, "constructor"), + true, false, true); + + var gsp = Object.getPrototypeOf(proto); + assert_true("constructor" in gsp, "constructor in gsp"); + assert_false(gsp.hasOwnProperty("constructor"), "gsp.hasOwnProperty(\"constructor\")"); + assert_equals(Object.getOwnPropertyDescriptor(gsp, "constructor"), undefined); +}, "constructor"); +test(function() { + var gsp = Object.getPrototypeOf(Object.getPrototypeOf(window)); + assert_equals(gsp.baz, document.getElementsByTagName("iframe")[1].contentWindow); +}, "duplicate property names") +var t = async_test("Dynamic name") +var t2 = async_test("Ghost name") +t.step(function() { + var iframe = document.getElementsByTagName("iframe")[0]; + iframe.setAttribute("srcdoc", "<script>window.name='foo'<\/script>"); + iframe.onload = function() { + t.step(function() { + assert_true("foo" in window, "foo not in window"); + assert_equals(window["foo"], iframe.contentWindow); + }); + t.done(); + t2.step(function() { + assert_false("bar" in window, "bar still in window"); + assert_equals(window["bar"], undefined); + }); + t2.done(); + }; +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/named-access-on-the-window-object/window-null-names.html b/testing/web-platform/tests/html/browsers/the-window-object/named-access-on-the-window-object/window-null-names.html new file mode 100644 index 0000000000..6801ef9d8a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/named-access-on-the-window-object/window-null-names.html @@ -0,0 +1,20 @@ +<!doctype html> +<meta charset=utf-8> +<title>Named access with null characters</title> +<link rel="author" title="Ms2ger" href="ms2ger@gmail.com"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#window"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-window-nameditem"> +<link rel="help" href="https://webidl.spec.whatwg.org/#named-properties-object"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id=log></div> +<script> +test(function() { + var iframe = document.createElement("iframe") + iframe.name = "a\0b" + document.body.appendChild(iframe) + assert_equals(window["a\0b"], iframe.contentWindow) + assert_equals(window["ab"], undefined) + assert_equals(window["a"], undefined) +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/navigate-to-about-blank-while-initial-load-pending.html b/testing/web-platform/tests/html/browsers/the-window-object/navigate-to-about-blank-while-initial-load-pending.html new file mode 100644 index 0000000000..3a0def8ae6 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/navigate-to-about-blank-while-initial-load-pending.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Test navigating to about:blank while window.open initial load pending.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +async_test(t => { + // Open a new window and initiate a navigation. The test does not actually + // expect this navigation to complete so it does not matter what URL is + // used other than it must not be about:blank. The intent is to start a + // navigation to some URL and then assign about:blank to the location + // attribute. This assignment should stop the inital navigation and start a + // new navigation to about:blank. When the about:blank page finishes loading + // the load event is expected to fire and the document URL should to be set to + // about:blank. + var window1 = window.open('resources/post-to-opener.html', '_blank'); + t.add_cleanup(() => { + window1.close(); + }); + window1.location = 'about:blank'; + window1.onload = t.step_func_done(e => { + assert_equals(window1.document.URL, "about:blank"); + }); +}, 'Navigating to about:blank while window.open initial load pending.'); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/noopener-noreferrer-BarProp.window.js b/testing/web-platform/tests/html/browsers/the-window-object/noopener-noreferrer-BarProp.window.js new file mode 100644 index 0000000000..a75a034650 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/noopener-noreferrer-BarProp.window.js @@ -0,0 +1,23 @@ +const barProps = ["locationbar", "menubar", "personalbar", "scrollbars", "statusbar", "toolbar"]; + +test(() => { + for(const prop of barProps) { + assert_true(window[prop].visible); + } +}, "All bars visible"); + +["noopener", "noreferrer"].forEach(openerStyle => { + async_test(t => { + const channelName = "5454" + openerStyle + "34324"; + const channel = new BroadcastChannel(channelName); + window.open("support/BarProp-target.html?" + channelName, "", openerStyle); + channel.onmessage = t.step_func_done(e => { + // Send message first so if asserts throw the popup is still closed + channel.postMessage(null); + + for(const prop of barProps) { + assert_true(e.data[prop]); + } + }); + }, `window.open() with ${openerStyle} should have all bars visible`); +}); diff --git a/testing/web-platform/tests/html/browsers/the-window-object/noopener-noreferrer-sizing.window.js b/testing/web-platform/tests/html/browsers/the-window-object/noopener-noreferrer-sizing.window.js new file mode 100644 index 0000000000..cc53ba5f2f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/noopener-noreferrer-sizing.window.js @@ -0,0 +1,17 @@ +const windowProps = ["innerWidth", "innerHeight"]; + +["noopener", "noreferrer"].forEach(openerStyle => { + async_test(t => { + const channelName = "34342" + openerStyle + "8907"; + const channel = new BroadcastChannel(channelName); + window.open("support/sizing-target.html?" + channelName, "", openerStyle); + channel.onmessage = t.step_func_done(e => { + // Send message first so if asserts throw the popup is still closed + channel.postMessage(null); + + for(const prop of windowProps) { + assert_equals(window[prop], e.data[prop]); + } + }); + }, `window.open() with ${openerStyle} should have equal viewport width and height`); +}); diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/callback.js b/testing/web-platform/tests/html/browsers/the-window-object/open-close/callback.js new file mode 100644 index 0000000000..ae51265a21 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/callback.js @@ -0,0 +1 @@ +opener.callback()
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/close_beforeunload-1.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/close_beforeunload-1.html new file mode 100644 index 0000000000..6f44d8a83e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/close_beforeunload-1.html @@ -0,0 +1,7 @@ +<!doctype html> +<script> +onload = function() {opener.postMessage("loaded", "*")}; +onbeforeunload = function() { + opener.callback(); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/close_beforeunload.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/close_beforeunload.html new file mode 100644 index 0000000000..dcb8830ab6 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/close_beforeunload.html @@ -0,0 +1,16 @@ +<!doctype html> +<title>Running beforeunload handler in window.close()</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test(); +var w = window.open("close_beforeunload-1.html"); +onmessage = t.step_func(function(event) { + if (event.data != "loaded") { + return; + } + w.close(); +}); +callback = function() {t.done()} +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/close_script_defer-1.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/close_script_defer-1.html new file mode 100644 index 0000000000..c50eddd41f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/close_script_defer-1.html @@ -0,0 +1 @@ +<!doctype html> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/close_script_defer.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/close_script_defer.html new file mode 100644 index 0000000000..1217882b16 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/close_script_defer.html @@ -0,0 +1,18 @@ +<!doctype html> +<title>Running defer script in window.close()</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test(); +t.step(function() { + var w = window.open("close_script_defer-1.html"); + w.document.open() + w.document.write("<script defer src='callback.js'><\/script>") + setTimeout(function() { + w.close(); + }, 1000); +}) +setTimeout(function() {t.done();}, 1000) +callback = t.step(function() {assert_unreached()}) +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/close_unload-1.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/close_unload-1.html new file mode 100644 index 0000000000..9a9e304e84 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/close_unload-1.html @@ -0,0 +1,7 @@ +<!doctype html> +<script> +onload = function() {opener.postMessage("loaded", "*")}; +onunload = function() { + opener.callback(); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/close_unload.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/close_unload.html new file mode 100644 index 0000000000..e4d231b285 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/close_unload.html @@ -0,0 +1,16 @@ +<!doctype html> +<title>Running unload handler in window.close()</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test(); +var w = window.open("close_unload-1.html"); +onmessage = t.step_func(function(event) { + if (event.data != "loaded") { + return; + } + w.close(); +}); +callback = function() {t.done()} +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/creating_browsing_context_test_01.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/creating_browsing_context_test_01.html new file mode 100644 index 0000000000..062f61949d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/creating_browsing_context_test_01.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>[Browsing Context] : [APIs for creating browsing_contexts by name]</title> +<link rel="author" title="Duhyeong Kim" href="mailto:dduskim@gmail.com"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#apis-for-creating-and-navigating-browsing-contexts-by-name"> +<meta name=timeout content=long> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(function() { + var currentUrl = 'http://' + window.location.host + '/common/blank.html'; + var win = window.open(currentUrl, '', 'height=1,width=1'); + this.add_cleanup(function() { win.close(); }); + win.onload = this.step_func_done(function () { + assert_equals(win.location.href, currentUrl, 'should be equal to result url'); + }); +}, 'first argument: absolute url'); + +test(function() { + var win = window.open('', '', 'height=1,width=1'); + this.add_cleanup(function() { win.close(); }); + assert_equals(win.location.href, 'about:blank', 'win.location.href'); + assert_equals(win.document.charset, 'UTF-8', 'win.document.charset'); +}, 'first argument: empty url'); + +test(function () { + var win = window.open('', 'testWindow', 'height=1,width=1'); + win.close(); + assert_equals(win.name, 'testWindow', 'should have a browsing context name'); +}, 'second argument: passing a non-empty name'); + +test(function () { + var win = window.open('', '', 'height=1,width=1'); + this.add_cleanup(function() { win.close(); }); + assert_equals(win.name, '', 'window should not have a name'); + win.name = 'testWindow'; + assert_equals(win.name, 'testWindow', 'window should have a name'); +}, 'second argument: setting name after opening'); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/no_window_open_when_term_nesting_level_nonzero.window.js b/testing/web-platform/tests/html/browsers/the-window-object/open-close/no_window_open_when_term_nesting_level_nonzero.window.js new file mode 100644 index 0000000000..3dff403b9c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/no_window_open_when_term_nesting_level_nonzero.window.js @@ -0,0 +1,113 @@ +test(function() { + var test_window = window.open('', '', 'height=1,width=1'); + var test_document = test_window.document; + + var frame = test_document.createElement('iframe'); + test_document.body.appendChild(frame); + + frame.contentWindow.onpagehide = function(evt) { + assert_equals(frame.contentWindow.open('', '', 'height=1,width=1'), null, + "expected no popup during pagehide"); + }; + frame.contentDocument.onvisibilitychange = function(evt) { + assert_equals(frame.contentWindow.open('', '', 'height=1,width=1'), null, + "expected no popup during visibilitychange"); + }; + frame.contentWindow.onbeforeunload = function(evt) { + assert_equals(frame.contentWindow.open('', '', 'height=1,width=1'), null, + "expected no popup during beforeunload"); + }; + frame.contentWindow.onunload = function(evt) { + assert_equals(frame.contentWindow.open('', '', 'height=1,width=1'), null, + "expected no popup during unload"); + }; + + frame.remove(); +}, 'no popups with frame removal'); + +async_test(function(t) { + var test_window = window.open('', '', 'height=1,width=1'); + var test_document = test_window.document; + + var frame = test_document.createElement('iframe'); + test_document.body.appendChild(frame); + + frame.contentWindow.onpagehide = t.step_func(function(evt) { + assert_equals(frame.contentWindow.open('', '', 'height=1,width=1'), null, + "expected no popup during pagehide"); + }); + frame.contentDocument.onvisibilitychange = t.step_func(function(evt) { + assert_equals(frame.contentWindow.open('', '', 'height=1,width=1'), null, + "expected no popup during visibilitychange"); + }); + frame.contentWindow.onbeforeunload = t.step_func(function(evt) { + assert_equals(frame.contentWindow.open('', '', 'height=1,width=1'), null, + "expected no popup during beforeunload"); + }); + frame.contentWindow.onunload = t.step_func(function(evt) { + assert_equals(frame.contentWindow.open('', '', 'height=1,width=1'), null, + "expected no popup during unload"); + }); + + frame.onload = t.step_func_done(); + + frame.contentWindow.location.href = "about:blank"; +}, 'no popups with frame navigation'); + +async_test(function(t) { + var test_window = window.open('', '', 'height=1,width=1'); + var test_document = test_window.document; + + var frame = test_document.createElement('iframe'); + test_document.body.appendChild(frame); + + frame.contentWindow.onpagehide = t.step_func(function(evt) { + assert_equals(test_window.open('', '', 'height=1,width=1'), null, + "expected no popup during pagehide"); + }); + frame.contentDocument.onvisibilitychange = t.step_func(function(evt) { + assert_equals(test_window.open('', '', 'height=1,width=1'), null, + "expected no popup during visibilitychange"); + }); + frame.contentWindow.onbeforeunload = t.step_func(function(evt) { + assert_equals(test_window.open('', '', 'height=1,width=1'), null, + "expected no popup during beforeunload"); + }); + frame.contentWindow.onunload = t.step_func(function(evt) { + assert_equals(test_window.open('', '', 'height=1,width=1'), null, + "expected no popup during unload"); + }); + + frame.onload = t.step_func_done(); + + frame.contentWindow.location.href = "about:blank"; +}, 'no popups from synchronously reachable window'); + +async_test(function(t) { + var test_window = window.open('', '', 'height=1,width=1'); + var test_document = test_window.document; + + var frame = test_document.createElement('iframe'); + test_document.body.appendChild(frame); + + frame.contentWindow.onpagehide = t.step_func(function(evt) { + assert_equals(window.open('', '', 'height=1,width=1'), null, + "expected no popup during pagehide"); + }); + frame.contentDocument.onvisibilitychange = t.step_func(function(evt) { + assert_equals(window.open('', '', 'height=1,width=1'), null, + "expected no popup during visibilitychange"); + }); + frame.contentWindow.onbeforeunload = t.step_func(function(evt) { + assert_equals(window.open('', '', 'height=1,width=1'), null, + "expected no popup during beforeunload"); + }); + frame.contentWindow.onunload = t.step_func(function(evt) { + assert_equals(window.open('', '', 'height=1,width=1'), null, + "expected no popup during unload"); + }); + + frame.onload = t.step_func_done(); + + frame.contentWindow.location.href = "about:blank"; +}, 'no popups from another synchronously reachable window'); diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/non_automated/001-1.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/non_automated/001-1.html new file mode 100644 index 0000000000..7dd48b41c2 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/non_automated/001-1.html @@ -0,0 +1,2 @@ +<!doctype html> +<p>Now open a new tab and navigate to <a href="001-2.html">001-2</a></p> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/non_automated/001-2.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/non_automated/001-2.html new file mode 100644 index 0000000000..b1413861a3 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/non_automated/001-2.html @@ -0,0 +1,16 @@ +<!doctype html> +<script> +var result = "FAIL"; +if (opener != null) { + result = "FAIL (did you open this page in a new tab?)"; +} else { + var w = window.open("", "test_name"); + if (w.location.href !== "about:blank") { + result = "FAIL (didn't open an about:blank browsing context)"; + } else { + w.close(); + result = "PASS"; + } + document.write(result); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/non_automated/001.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/non_automated/001.html new file mode 100644 index 0000000000..7b0f21ec04 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/non_automated/001.html @@ -0,0 +1,3 @@ +<!doctype html> +<title>Accessing named windows from outside the unit of related browsing contexts</title> +<a href="001-1.html" target="test_name">Click here</a> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/non_automated/002-1.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/non_automated/002-1.html new file mode 100644 index 0000000000..0e210f351b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/non_automated/002-1.html @@ -0,0 +1,8 @@ +<!doctype html> +<p>Now open a new tab and navigate to <a></a></p> +<script> +href = window.location.href.replace("http://", "http://www.").replace("002-1.html", "002-2.html"); +var a = document.getElementsByTagName("a")[0]; +a.href = href; +a.textContent = href; +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/non_automated/002-2.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/non_automated/002-2.html new file mode 100644 index 0000000000..b1413861a3 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/non_automated/002-2.html @@ -0,0 +1,16 @@ +<!doctype html> +<script> +var result = "FAIL"; +if (opener != null) { + result = "FAIL (did you open this page in a new tab?)"; +} else { + var w = window.open("", "test_name"); + if (w.location.href !== "about:blank") { + result = "FAIL (didn't open an about:blank browsing context)"; + } else { + w.close(); + result = "PASS"; + } + document.write(result); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/non_automated/002.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/non_automated/002.html new file mode 100644 index 0000000000..b568ae8d48 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/non_automated/002.html @@ -0,0 +1,3 @@ +<!doctype html> +<title>Accessing different-origin named windows from outside the unit of related browsing contexts</title> +<a href="002-1.html" target="test_name">Click here</a> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-is-popup-condition.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-is-popup-condition.html new file mode 100644 index 0000000000..4f52e2e8de --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-is-popup-condition.html @@ -0,0 +1,149 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML: window.open `features`: condition for is popup</title> +<meta name="variant" content="?single-1"> +<meta name="variant" content="?single-2"> +<meta name="variant" content="?position"> +<meta name="variant" content="?combination"> +<meta name=timeout content=long> +<link rel="help" href="https://html.spec.whatwg.org/multipage/window-object.html#window-open-steps"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/PrefixedPostMessage.js"></script> +<script> +var windowURL = 'resources/is-popup-barprop.html'; + +var target = document.location.search.substring(1); + +// features, visible +// NOTE: visible == !isPopup +var tests = { + "single-1": [ + // Empty feature results in non-popup. + [undefined, true], + + // The explicit popup feature. + ["popup", false], + ["popup=1", false], + ["popup=true", false], + ["popup=0", true], + + // Other feature alone results in popup. + ["location", false], + ["location=yes", false], + ["location=true", false], + ["location=no", false], + + ["toolbar", false], + ["toolbar=yes", false], + ["toolbar=true", false], + ["toolbar=no", false], + + ["menubar", false], + ["menubar=yes", false], + ["menubar=true", false], + ["menubar=no", false], + + ["resizable", false], + ["resizable=yes", false], + ["resizable=true", false], + ["resizable=no", false], + ], + "single-2": [ + ["scrollbars", false], + ["scrollbars=yes", false], + ["scrollbars=true", false], + ["scrollbars=no", false], + + ["status", false], + ["status=yes", false], + ["status=true", false], + ["status=no", false], + + ["titlebar", false], + ["titlebar=yes", false], + ["titlebar=true", false], + ["titlebar=no", false], + + ["close", false], + ["close=yes", false], + ["close=true", false], + ["close=no", false], + + ["minimizable", false], + ["minimizable=yes", false], + ["minimizable=true", false], + ["minimizable=no", false], + + ["personalbar", false], + ["personalbar=yes", false], + ["personalbar=true", false], + ["personalbar=no", false], + ], + "position": [ + ["left=500", false], + ["screenX=500", false], + + ["top=500", false], + ["screenY=500", false], + + ["width=500", false], + ["innerWidth=500", false], + + ["outerWidth=500", false], + + ["height=500", false], + ["innerHeight=500", false], + + ["outerHeight=500", false], + ], + "combination": [ + // The following combination results in non-popup. + ["location,toolbar,menubar,resizable,scrollbars,status", true], + + // Either location or toolbar is required for non-popup. + ["location,menubar,resizable,scrollbars,status", true], + ["toolbar,menubar,resizable,scrollbars,status", true], + + ["resizable,scrollbars,status", false], + ["location=no,menubar=no,resizable,scrollbars,status", false], + + // menubar is required for non-popup. + ["location,toolbar,resizable,scrollbars,status", false], + + // resizable is required for non-popup, but defaults to true + ["location,toolbar,menubar,scrollbars,status", true], + ["location,toolbar,menubar,resizable=no,scrollbars,status", false], + + // scrollbars is required for non-popup. + ["location,toolbar,menubar,resizable,status", false], + + // status is required for non-popup. + ["location,toolbar,menubar,resizable,scrollbars", false], + + // The explicit popup feature has priority than others. + ["popup=1,location,toolbar,menubar,resizable,scrollbars,status", false], + ["popup=yes,location,toolbar,menubar,resizable,scrollbars,status", false], + ["popup=true,location,toolbar,menubar,resizable,scrollbars,status", false], + ["popup=0,location,toolbar,menubar,resizable,scrollbars", true], + ], +}; + +tests[target].forEach(([features, visible]) => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.locationbar, visible, `window.locationbar.visible`); + assert_equals(data.menubar, visible, `window.menubar.visible`); + assert_equals(data.personalbar, visible, `window.personalbar.visible`); + assert_equals(data.scrollbars, visible, `window.scrollbars.visible`); + assert_equals(data.statusbar, visible, `window.statusbar.visible`); + assert_equals(data.toolbar, visible, `window.toolbar.visible`); + })); + var win = window.open(prefixedMessage.url(windowURL), '', features); + }, `${format_value(features)} should set BarProp visibility to ${visible}`); +}); + +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-negative-innerwidth-innerheight.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-negative-innerwidth-innerheight.html new file mode 100644 index 0000000000..019ee9d730 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-negative-innerwidth-innerheight.html @@ -0,0 +1,75 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML: window.open `features`: negative values for legacy `innerwidth`, `innerheight`</title> +<meta name=timeout content=long> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#apis-for-creating-and-navigating-browsing-contexts-by-name"> + +<!-- user agents are not required to support open features other than `noopener` + and on some platforms position and size features don't make sense --> +<meta name="flags" content="may" /> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/PrefixedPostMessage.js"></script> +<script> +var windowURL = 'resources/message-opener.html'; +var featuresPrefix = `top=0,left=0,`; + +// https://html.spec.whatwg.org/multipage/infrastructure.html#rules-for-parsing-integers + +setup (() => { + // Before running tests, open a window using features that mimic + // what would happen if the features tested here were set to invalid + // values in later tests. + // In cases where the value for `innerwidth` or `innerheight` is + // is less than the browser's minimum allowed value for that dimension, + // but NOT 0, the value affected will become the browser's minimum allowed value. + + // This should result in a minimally-sized window for later comparison + var featureString = `${featuresPrefix}innerwidth=1,innerheight=1`; + var prefixedMessage = new PrefixedMessageTest(); + prefixedMessage.onMessage((data, e) => { + e.source.close(); + runWindowTests(data); + }); + var win = window.open(prefixedMessage.url(windowURL), '', featureString); +}); + +function runWindowTests (baselineDimensions) { + + // Negative values for `innerwidth` should result in a window with minimum + // valid allowed width + [ 'innerwidth=-404', + 'innerwidth=-404.5', + 'innerwidth=-404e1' + ].forEach(feature => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + var featureString = `${featuresPrefix}height=405,${feature}`; + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.width, baselineDimensions.width, `"${feature} is negative and should result in a minimally-wide window"`); + })); + var win = window.open(prefixedMessage.url(windowURL) + "&expected_innerWidth=" + baselineDimensions.width, '', featureString); + }, `features "${feature}" should NOT set "width=404"`); + }); + + // Negative values for `innerheight` should result in a window with minimum + // valid allowed height + [ 'innerheight=-404', + 'innerheight=-404.5', + 'innerheight=-404e1' + ].forEach(feature => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + var featureString = `${featuresPrefix}width=404,${feature}`; + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.height, baselineDimensions.height, `"${feature} is negative and should result in a minimal-height window"`); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_innerHeight=' + baselineDimensions.height, '', featureString); + }, `features "${feature}" should NOT set "height=404"`); + }); +} + +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-negative-screenx-screeny.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-negative-screenx-screeny.html new file mode 100644 index 0000000000..6e316db482 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-negative-screenx-screeny.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML: window.open `features`: negative values for legacy `screenx`, `screeny`</title> +<meta name=timeout content=long> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#apis-for-creating-and-navigating-browsing-contexts-by-name"> + +<!-- user agents are not required to support open features other than `noopener` + and on some platforms position and size features don't make sense --> +<meta name="flags" content="may" /> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/PrefixedPostMessage.js"></script> +<script> +var featuresPrefix = `width=401,height=404,`; +var windowURL = 'resources/message-opener.html'; + +// https://html.spec.whatwg.org/multipage/infrastructure.html#rules-for-parsing-integers + +setup (() => { + // Before running tests, open a window using features that mimic + // what would happen if the feature tested here were set to 0 + // for comparison later. + var featureString = `${featuresPrefix}top=0,left=0`; + var prefixedMessage = new PrefixedMessageTest(); + prefixedMessage.onMessage((data, e) => { + e.source.close(); + runWindowTests(data); + }); + var win = window.open(prefixedMessage.url(windowURL), '', featureString); +}); + +function runWindowTests (baselineDimensions) { + // Negative values should be interpreted as 0 + [ 'screeny=-204', + 'screeny=-204.5', + 'screeny=-0' + ].forEach(feature => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + var featureString = `${featuresPrefix}left=0,${feature}`; + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.top, baselineDimensions.top, `"${feature} is negative and should be set to 0"`); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_screenY=' + baselineDimensions.top, '', featureString); + }, `features "${feature}" should NOT set "top=204"`); + }); + + // Negative values should be interpreted as 0 + [ 'screenx=-204', + 'screenx=-204.5', + 'screenx=-0' + ].forEach(feature => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + var featureString = `${featuresPrefix}top=0,${feature}`; + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.left, baselineDimensions.left, `"${feature} is negative and should be set to 0"`); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_screenX=' + baselineDimensions.left, '', featureString); + }, `features "${feature}" should NOT set "left=204"`); + }); +} + +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-negative-top-left.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-negative-top-left.html new file mode 100644 index 0000000000..316b01a401 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-negative-top-left.html @@ -0,0 +1,68 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML: window.open `features`: negative values for `top`, `left`</title> +<meta name=timeout content=long> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#apis-for-creating-and-navigating-browsing-contexts-by-name"> + +<!-- user agents are not required to support open features other than `noopener` + and on some platforms position and size features don't make sense --> +<meta name="flags" content="may" /> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/PrefixedPostMessage.js"></script> +<script> +var featuresPrefix = `width=401,height=404,`; +var windowURL = 'resources/message-opener.html'; + +// https://html.spec.whatwg.org/multipage/infrastructure.html#rules-for-parsing-integers + +setup (() => { + // Before running tests, open a window using features that mimic + // what would happen if the feature tested here were set to 0 + // for comparison later. + var featureString = `${featuresPrefix}top=0,left=0`; + var prefixedMessage = new PrefixedMessageTest(); + prefixedMessage.onMessage((data, e) => { + e.source.close(); + runWindowTests(data); + }); + var win = window.open(prefixedMessage.url(windowURL), '', featureString); +}); + +function runWindowTests (baselineDimensions) { + + // Negative values for `top`, `left` should be interpreted as 0 + [ 'top=-204', + 'top=-204.5', + 'top=-0' + ].forEach(feature => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + var featureString = `${featuresPrefix}left=0,${feature}`; + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.top, baselineDimensions.top, `"${feature} is negative and should be set to 0"`); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_screenY=' + baselineDimensions.top, '', featureString); + }, `features "${feature}" should NOT set "top=204"`); + }); + +// Negative values for `top`, `left` should be interpreted as 0 + [ 'left=-204', + 'left=-204.5', + 'left=-0' + ].forEach(feature => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + var featureString = `${featuresPrefix}top=0,${feature}`; + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.left, baselineDimensions.left, `"${feature} is negative and should be set to 0"`); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_screenX=' + baselineDimensions.left, '', featureString); + }, `features "${feature}" should NOT set "left=204"`); + }); +} + +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-negative-width-height.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-negative-width-height.html new file mode 100644 index 0000000000..3cb155620d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-negative-width-height.html @@ -0,0 +1,75 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML: window.open `features`: negative values for `width`, `height`</title> +<meta name=timeout content=long> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#apis-for-creating-and-navigating-browsing-contexts-by-name"> + +<!-- user agents are not required to support open features other than `noopener` + and on some platforms position and size features don't make sense --> +<meta name="flags" content="may" /> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/PrefixedPostMessage.js"></script> +<script> +var featuresPrefix = `top=0,left=0,`; +var windowURL = 'resources/message-opener.html'; + +// https://html.spec.whatwg.org/multipage/infrastructure.html#rules-for-parsing-integers + +setup (() => { + // Before running tests, open a window using features that mimic + // what would happen if the features tested here were set to invalid + // values in later tests. + // In cases where the value for `width` or `height` is + // is less than the browser's minimum allowed value for that dimension, + // but NOT 0, the value affected will become the browser's minimum allowed value. + + // This should result in a minimally-sized window for later comparison + var featureString = `${featuresPrefix}width=1,height=1`; + var prefixedMessage = new PrefixedMessageTest(); + prefixedMessage.onMessage((data, e) => { + e.source.close(); + runWindowTests(data); + }); + var win = window.open(prefixedMessage.url(windowURL), '', featureString); +}); + +function runWindowTests (baselineDimensions) { + + // Negative values for `width` should result in a window with minimum + // valid allowed width + [ 'width=-404', + 'width=-404.5', + 'width=-404e1' + ].forEach(feature => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + var featureString = `${featuresPrefix}height=405,${feature}`; + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.width, baselineDimensions.width, `"${feature} is negative and should result in a minimally-wide window"`); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_innerWidth=' + baselineDimensions.width, '', featureString); + }, `features "${feature}" should NOT set "width=404"`); + }); + + // Negative values for `height` should result in a window with minimum + // valid allowed height + [ 'height=-404', + 'height=-404.5', + 'height=-404e1' + ].forEach(feature => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + var featureString = `${featuresPrefix}width=404,${feature}`; + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.height, baselineDimensions.height, `"${feature} is negative and should result in a minimal-height window"`); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_innerHeight=' + baselineDimensions.height, '', featureString); + }, `features "${feature}" should NOT set "height=404"`); + }); +} + +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-non-integer-height.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-non-integer-height.html new file mode 100644 index 0000000000..d8ee866c50 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-non-integer-height.html @@ -0,0 +1,91 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML: window.open `features`: non-integer values for feature `height`</title> +<meta name=timeout content=long> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#apis-for-creating-and-navigating-browsing-contexts-by-name"> + +<!-- user agents are not required to support open features other than `noopener` + and on some platforms position and size features don't make sense --> +<meta name="flags" content="may" /> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/PrefixedPostMessage.js"></script> +<script> +var featuresPrefix = `top=0,left=0,width=401,`; +var windowURL = 'resources/message-opener.html'; + +// https://html.spec.whatwg.org/multipage/infrastructure.html#rules-for-parsing-integers + +setup (() => { + // Before running tests, open a window using features that mimic + // what would happen if the feature tested here were set to 0 + // for comparison later. + var featureString = `${featuresPrefix}height=0`; + var prefixedMessage = new PrefixedMessageTest(); + prefixedMessage.onMessage((data, e) => { + e.source.close(); + runWindowTests(data); + }); + var win = window.open(prefixedMessage.url(windowURL), '', featureString); +}); + +function runWindowTests (baselineDimensions) { + // The absence of the sizing feature should have the same behavior + // as that feature set to 0 + [ featuresPrefix, + 'top=0,left=0', + ].forEach(feature => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.height, baselineDimensions.height); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_innerHeight=' + baselineDimensions.height, '', feature); + }, `${feature}: absence of feature "height" should be treated same as "height=0"`); + }); + + // When code point in first position is not an ASCII digit, "+" or "-", + // that's an error and the value becomes 0 + [ 'height=/404', + 'height=_404', + 'height=L404' + ].forEach(feature => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + var featureString = `${featuresPrefix}${feature}`; + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.height, baselineDimensions.height, `"${feature} begins with an invalid character and should be ignored"`); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_innerHeight=' + baselineDimensions.height, '', featureString); + }, `features "${feature}" should NOT set "height=404"`); + }); + + // Codepoints that are valid ASCII digits should be collected + // Non-ASCII digits and subsequent code points are ignored + [ 'height=405.5', + 'height=405.32', + 'height=405LLl', + 'height=405^4', + 'height=405*3', + 'height=405/5', + 'height=405 ', + 'height=405e1', + 'height=405e-1' + ].forEach(feature => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + var featureString = `${featuresPrefix}${feature}`; + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.height, 405, `"${featureString} value after first non-digit will be ignored"`); + })); + var win = window.open(prefixedMessage.url(windowURL) + "&expected_innerHeight=405", '', featureString); + }, `features "${feature}" should set "height=405"`); + }); + +} + +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-non-integer-innerheight.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-non-integer-innerheight.html new file mode 100644 index 0000000000..f191d875d8 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-non-integer-innerheight.html @@ -0,0 +1,76 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML: window.open `features`: non-integer values for legacy feature `innerheight`</title> +<meta name=timeout content=long> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#apis-for-creating-and-navigating-browsing-contexts-by-name"> + +<!-- user agents are not required to support open features other than `noopener` + and on some platforms position and size features don't make sense --> +<meta name="flags" content="may" /> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/PrefixedPostMessage.js"></script> +<script> +var featuresPrefix = `top=0,left=0,width=401,`; +var windowURL = 'resources/message-opener.html'; + +// https://html.spec.whatwg.org/multipage/infrastructure.html#rules-for-parsing-integers + +setup (() => { + // Before running tests, open a window using features that mimic + // what would happen if the feature tested here were set to 0 + // for comparison later. + var featureString = `${featuresPrefix}height=0`; + var prefixedMessage = new PrefixedMessageTest(); + prefixedMessage.onMessage((data, e) => { + e.source.close(); + runWindowTests(data); + }); + var win = window.open(prefixedMessage.url(windowURL), '', featureString); +}); + +function runWindowTests (baselineDimensions) { + // When code point in first position is not an ASCII digit, "+" or "-", + // that's an error and the value becomes 0 + [ 'innerheight=/404', + 'innerheight=_404', + 'innerheight=L404' + ].forEach(feature => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + var featureString = `${featuresPrefix}${feature}`; + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.height, baselineDimensions.height, `"${feature} begins with an invalid character and should be ignored"`); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_innerHeight=' + baselineDimensions.height, '', featureString); + }, `features "${feature}" should NOT set "height=404"`); + }); + + // Codepoints that are valid ASCII digits should be collected + // Non-ASCII digits and subsequent code points are ignored + [ 'innerheight=405.5', + 'innerheight=405.32', + 'innerheight=405LLl', + 'innerheight=405^4', + 'innerheight=405*3', + 'innerheight=405/5', + 'innerheight=405 ', + 'innerheight=405e1', + 'innerheight=405e-1' + ].forEach(feature => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + var featureString = `${featuresPrefix}${feature}`; + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.height, 405, `"${featureString} value after first non-digit will be ignored"`); + })); + var win = window.open(prefixedMessage.url(windowURL) + "&expected_innerHeight=405", '', featureString); + }, `features "${feature}" should set "height=405"`); + }); + +} + +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-non-integer-innerwidth.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-non-integer-innerwidth.html new file mode 100644 index 0000000000..d1ddc5e43a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-non-integer-innerwidth.html @@ -0,0 +1,75 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML: window.open `features`: non-integer values for legacy feature `innerwidth`</title> +<meta name=timeout content=long> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#apis-for-creating-and-navigating-browsing-contexts-by-name"> + +<!-- user agents are not required to support open features other than `noopener` + and on some platforms position and size features don't make sense --> +<meta name="flags" content="may" /> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/PrefixedPostMessage.js"></script> +<script> +var featuresPrefix = `top=0,left=0,height=401,`; +var windowURL = 'resources/message-opener.html'; + +// https://html.spec.whatwg.org/multipage/infrastructure.html#rules-for-parsing-integers + +setup (() => { + // Before running tests, open a window using features that mimic + // what would happen if the feature tested here were set to 0 + // for comparison later. + var featureString = `${featuresPrefix}width=0`; + var prefixedMessage = new PrefixedMessageTest(); + prefixedMessage.onMessage((data, e) => { + e.source.close(); + runWindowTests(data); + }); + var win = window.open(prefixedMessage.url(windowURL), '', featureString); +}); + +function runWindowTests (baselineDimensions) { + // When code point in first position is not an ASCII digit, "+" or "-", + // that's an error and the value becomes 0 + [ 'innerwidth=/404', + 'innerwidth=_404', + 'innerwidth=L404' + ].forEach(feature => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + var featureString = `${featuresPrefix}${feature}`; + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.width, baselineDimensions.width, `"${feature} begins with an invalid character and should be ignored"`); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_innerWidth=' + baselineDimensions.width, '', featureString); + }, `features "${feature}" should NOT set "width=404"`); + }); + + // Codepoints that are valid ASCII digits should be collected + // Non-ASCII digits and subsequent code points are ignored + [ 'innerwidth=405.5', + 'innerwidth=405.32', + 'innerwidth=405LLl', + 'innerwidth=405^4', + 'innerwidth=405*3', + 'innerwidth=405/5', + 'innerwidth=405 ', + 'innerwidth=405e1', + 'innerwidth=405e-1' + ].forEach(feature => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + var featureString = `${featuresPrefix}${feature}`; + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.width, 405, `"${featureString} value after first non-digit will be ignored"`); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_innerWidth=405', '', featureString); + }, `features "${feature}" should set "width=405"`); + }); +} + +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-non-integer-left.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-non-integer-left.html new file mode 100644 index 0000000000..c771204dc4 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-non-integer-left.html @@ -0,0 +1,76 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML: window.open `features`: non-integer values for feature `left`</title> +<meta name=timeout content=long> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#apis-for-creating-and-navigating-browsing-contexts-by-name"> + +<!-- user agents are not required to support open features other than `noopener` + and on some platforms position and size features don't make sense --> +<meta name="flags" content="may" /> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/PrefixedPostMessage.js"></script> +<script> + +var featuresPrefix = `width=401,height=204,top=0,`; +var windowURL = 'resources/message-opener.html'; + +// https://html.spec.whatwg.org/multipage/infrastructure.html#rules-for-parsing-integers + +setup (() => { + // Before running tests, open a window using features that mimic + // what would happen if the feature tested here were set to 0 + // for comparison later. + var featureString = `${featuresPrefix}left=0`; + var prefixedMessage = new PrefixedMessageTest(); + prefixedMessage.onMessage((data, e) => { + e.source.close(); + runWindowTests(data); + }); + var win = window.open(prefixedMessage.url(windowURL), '', featureString); +}); + +function runWindowTests (baselineDimensions) { + // When code point in first position is not an ASCII digit, "+" or "-", + // that's an error and the value becomes 0 + [ 'left=/104', + 'left=_104', + 'left=L104' + ].forEach(feature => { + async_test(t => { + var featureString = `${featuresPrefix}${feature}`; + var prefixedMessage = new PrefixedMessageTest(); + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.left, baselineDimensions.left, `"${feature} begins with an invalid character and should be ignored"`); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_screenX=' + baselineDimensions.left, '', featureString); + }, `features "${feature}" should NOT set "left=104"`); + }); + + // Codepoints that are valid ASCII digits should be collected + // Non-ASCII digits and subsequent code points are ignored + [ 'left=105.5', + 'left=105.32', + 'left=105LLl', + 'left=105^4', + 'left=105*3', + 'left=105/5', + 'left=105 ', + 'left=105e1', + 'left=105e-1' + ].forEach(feature => { + async_test(t => { + var featureString = `${featuresPrefix}${feature}`; + var prefixedMessage = new PrefixedMessageTest(); + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.left, 105, `"${featureString} value after first non-digit will be ignored"`); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_screenX=105', '', featureString); + }, `features "${feature}" should set "left=105"`); + }); +} + +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-non-integer-screenx.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-non-integer-screenx.html new file mode 100644 index 0000000000..49a3783257 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-non-integer-screenx.html @@ -0,0 +1,75 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML: window.open `features`: non-integer values for legacy feature `screenx`</title> +<meta name=timeout content=long> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#apis-for-creating-and-navigating-browsing-contexts-by-name"> + +<!-- user agents are not required to support open features other than `noopener` + and on some platforms position and size features don't make sense --> +<meta name="flags" content="may" /> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/PrefixedPostMessage.js"></script> +<script> +var featuresPrefix = `width=401,height=204,top=0,`; +var windowURL = 'resources/message-opener.html'; + +// https://html.spec.whatwg.org/multipage/infrastructure.html#rules-for-parsing-integers + +setup (() => { + // Before running tests, open a window using features that mimic + // what would happen if the feature tested here were set to 0 + // for comparison later. + var featureString = `${featuresPrefix}left=0`; + var prefixedMessage = new PrefixedMessageTest(); + prefixedMessage.onMessage((data, e) => { + e.source.close(); + runWindowTests(data); + }); + var win = window.open(prefixedMessage.url(windowURL), '', featureString); +}); + +function runWindowTests (baselineDimensions) { + // When code point in first position is not an ASCII digit, "+" or "-", + // that's an error and the value becomes 0 + [ 'screenx=/104', + 'screenx=_104', + 'screenx=L104' + ].forEach(feature => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + var featureString = `${featuresPrefix}${feature}`; + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.left, baselineDimensions.left, `"${feature} begins with an invalid character and should be ignored"`); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_screenX=' + baselineDimensions.left, '', featureString); + }, `features "${feature}" should NOT set "screenx=104"`); + }); + + // Codepoints that are valid ASCII digits should be collected + // Non-ASCII digits and subsequent code points are ignored + [ 'screenx=105.5', + 'screenx=105.32', + 'screenx=105LLl', + 'screenx=105^4', + 'screenx=105*3', + 'screenx=105/5', + 'screenx=105 ', + 'screenx=105e1', + 'screenx=105e-1' + ].forEach(feature => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + var featureString = `${featuresPrefix}${feature}`; + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.left, 105, `"${featureString} value after first non-digit will be ignored"`); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_screenX=105', '', featureString); + }, `features "${feature}" should set "screenx=105"`); + }); +} + +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-non-integer-screeny.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-non-integer-screeny.html new file mode 100644 index 0000000000..5183405445 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-non-integer-screeny.html @@ -0,0 +1,76 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML: window.open `features`: non-integer values for legacy feature `screeny`</title> +<meta name=timeout content=long> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#apis-for-creating-and-navigating-browsing-contexts-by-name"> + +<!-- user agents are not required to support open features other than `noopener` + and on some platforms position and size features don't make sense --> +<meta name="flags" content="may" /> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/PrefixedPostMessage.js"></script> +<script> +var featuresPrefix = `height=401,width=402,left=0,`; +var windowURL = 'resources/message-opener.html'; + +// https://html.spec.whatwg.org/multipage/infrastructure.html#rules-for-parsing-integers + +setup (() => { + // Before running tests, open a window using features that mimic + // what would happen if the feature tested here were set to 0 + // for comparison later. + var featureString = `${featuresPrefix}top=0`; + var prefixedMessage = new PrefixedMessageTest(); + prefixedMessage.onMessage((data, e) => { + e.source.close(); + runWindowTests(data); + }); + var win = window.open(prefixedMessage.url(windowURL), '', featureString); +}); + +function runWindowTests (baselineDimensions) { + // When code point in first position is not an ASCII digit, "+" or "-", + // that's an error and the value becomes 0 + [ 'screeny=/404', + 'screeny=_404', + 'screeny=L404' + ].forEach(feature => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + var featureString = `${featuresPrefix}${feature}`; + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.top, baselineDimensions.top, `"${feature} begins with an invalid character and should be ignored"`); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_screenY=' + baselineDimensions.top, '', featureString); + }, `features "${feature}" should NOT set "screeny=404"`); + }); + + // Codepoints that are valid ASCII digits should be collected + // Non-ASCII digits and subsequent code points are ignored + [ 'screeny=405.5', + 'screeny=405.32', + 'screeny=405LLl', + 'screeny=405^4', + 'screeny=405*3', + 'screeny=405/5', + 'screeny=405 ', + 'screeny=405e1', + 'screeny=405e-1' + ].forEach(feature => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + var featureString = `${featuresPrefix}${feature}`; + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.top, 405, `"${featureString} value after first non-digit will be ignored"`); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_screenY=405', '', featureString); + }, `features "${feature}" should set "screeny=405"`); + }); + +} + +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-non-integer-top.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-non-integer-top.html new file mode 100644 index 0000000000..a7926e748b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-non-integer-top.html @@ -0,0 +1,73 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML: window.open `features`: non-integer values for feature `top`</title> +<meta name=timeout content=long> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#apis-for-creating-and-navigating-browsing-contexts-by-name"> + +<!-- user agents are not required to support open features other than `noopener` + and on some platforms position and size features don't make sense --> +<meta name="flags" content="may" /> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/PrefixedPostMessage.js"></script> +<script> +var windowURL = 'resources/message-opener.html'; +var featuresPrefix = `width=401,height=204,left=0,`; + +setup (() => { + // Before running tests, open a window using features that mimic + // what would happen if the feature tested here were set to 0 + // for comparison later. + var featureString = `${featuresPrefix}top=0`; + var prefixedMessage = new PrefixedMessageTest(); + prefixedMessage.onMessage((data, e) => { + e.source.close(); + runWindowTests(data); + }); + var win = window.open(prefixedMessage.url(windowURL), '', featureString); +}); + +function runWindowTests (baselineDimensions) { + // When code point in first position is not an ASCII digit, "+" or "-", + // that's an error and the value becomes 0 + [ 'top=/104', + 'top=_104', + 'top=L104' + ].forEach(feature => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + var featureString = `${featuresPrefix}${feature}`; + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.top, baselineDimensions.top, `"${feature} begins with an invalid character and should be ignored"`); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_screenY=' + baselineDimensions.top, '', featureString); + }, `features "${feature}" should NOT set "top=104"`); + }); + + // Codepoints that are valid ASCII digits should be collected + // Non-ASCII digits and subsequent code points are ignored + [ 'top=105.5', + 'top=105.32', + 'top=105LLl', + 'top=105^4', + 'top=105*3', + 'top=105/5', + 'top=105 ', + 'top=105e1', + 'top=105e-1' + ].forEach(feature => { + async_test(t => { + var featureString = `${featuresPrefix}${feature}`; + var prefixedMessage = new PrefixedMessageTest(); + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.top, 105, `"${feature} value after first non-digit will be ignored"`); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_screenY=105', '', featureString); + }, `features "${feature}" should set "top=105"`); + }); +} + +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-non-integer-width.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-non-integer-width.html new file mode 100644 index 0000000000..6878063ed7 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-non-integer-width.html @@ -0,0 +1,91 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML: window.open `features`: non-integer values for feature `width`</title> +<meta name=timeout content=long> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#apis-for-creating-and-navigating-browsing-contexts-by-name"> + +<!-- user agents are not required to support open features other than `noopener` + and on some platforms position and size features don't make sense --> +<meta name="flags" content="may" /> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/PrefixedPostMessage.js"></script> +<script> +var featuresPrefix = `top=0,left=0,height=401,`; +var windowURL = 'resources/message-opener.html'; + +// https://html.spec.whatwg.org/multipage/infrastructure.html#rules-for-parsing-integers + +setup (() => { + // Before running tests, open a window using features that mimic + // what would happen if the feature tested here were set to 0 + // for comparison later. + var featureString = `${featuresPrefix}width=0`; + var prefixedMessage = new PrefixedMessageTest(); + prefixedMessage.onMessage((data, e) => { + e.source.close(); + runWindowTests(data); + }); + var win = window.open(prefixedMessage.url(windowURL), '', featureString); +}); + +function runWindowTests (baselineDimensions) { + + // The absence of the sizing feature should have the same behavior + // as that feature set to 0 + [ featuresPrefix, + 'top=0,left=0', + ].forEach(feature => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.width, baselineDimensions.width); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_innerWidth=' + baselineDimensions.width, '', feature); + }, `${feature}: absence of feature "width" should be treated same as "width=0"`); + }); + + // When code point in first position is not an ASCII digit, "+" or "-", + // that's an error and the value becomes 0 + [ 'width=/404', + 'width=_404', + 'width=L404' + ].forEach(feature => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + var featureString = `${featuresPrefix}${feature}`; + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.width, baselineDimensions.width, `"${feature} begins with an invalid character and should be ignored"`); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_innerWidth=' + baselineDimensions.width, '', featureString); + }, `features "${feature}" should NOT set "width=404"`); + }); + + // Codepoints that are valid ASCII digits should be collected + // Non-ASCII digits and subsequent code points are ignored + [ 'width=405.5', + 'width=405.32', + 'width=405LLl', + 'width=405^4', + 'width=405*3', + 'width=405/5', + 'width=405 ', + 'width=405e1', + 'width=405e-1' + ].forEach(feature => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + var featureString = `${featuresPrefix}${feature}`; + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.width, 405, `"${featureString} value after first non-digit will be ignored"`); + })); + var win = window.open(prefixedMessage.url(windowURL) + "&expected_innerWidth=405", '', featureString); + }, `features "${feature}" should set "width=405"`); + }); +} + +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-tokenization-innerheight-innerwidth.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-tokenization-innerheight-innerwidth.html new file mode 100644 index 0000000000..cf3ad25514 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-tokenization-innerheight-innerwidth.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML: window.open `features`: tokenization -- legacy size features `innerheight`, `innerwidth`</title> +<meta name=timeout content=long> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#apis-for-creating-and-navigating-browsing-contexts-by-name"> + +<!-- user agents are not required to support open features other than `noopener` + and on some platforms position and size features don't make sense --> +<meta name="flags" content="may" /> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/PrefixedPostMessage.js"></script> +<script> +var windowURL = 'resources/message-opener.html'; +var width = 'width=401,'; +var height = 'height=402,'; + +[ 'innerwidth=401', + ' innerwidth = 401', + 'innerwidth==401', + '\ninnerwidth= 401', + ',innerwidth=401,,', + 'INNERWIDTH=401', + 'innerWidth=401' +].forEach((features, idx, arr) => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.width, 401); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_innerWidth=401', '', height + features); + }, `${format_value(features)} should set width of opened window`); +}); + +[ 'innerheight=402', + ' innerheight = 402', + 'innerheight==402', + '\ninnerheight= 402', + ',innerheight=402,,', + 'INNERHEIGHT=402', + 'innerHeight=402' +].forEach((features, idx, arr) => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.height, 402); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_innerHeight=402', '', width + features); + }, `${format_value(features)} should set height of opened window`); +}); + +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-tokenization-noopener.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-tokenization-noopener.html new file mode 100644 index 0000000000..c955e67789 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-tokenization-noopener.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML: window.open `features`: tokenization -- `noopener`</title> +<meta name=timeout content=long> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#apis-for-creating-and-navigating-browsing-contexts-by-name"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/tokenization-noopener-noreferrer.js"></script> +<script> + booleanTests("noopener"); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-tokenization-noreferrer.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-tokenization-noreferrer.html new file mode 100644 index 0000000000..4807f634fd --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-tokenization-noreferrer.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML: window.open `features`: tokenization -- `noreferrer`</title> +<meta name=timeout content=long> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#apis-for-creating-and-navigating-browsing-contexts-by-name"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/tokenization-noopener-noreferrer.js"></script> +<script> + booleanTests("noreferrer"); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-tokenization-screenx-screeny.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-tokenization-screenx-screeny.html new file mode 100644 index 0000000000..5a53fefc40 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-tokenization-screenx-screeny.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML: window.open `features`: tokenization -- legacy position features `screenx`, `screeny`</title> +<meta name=timeout content=long> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#apis-for-creating-and-navigating-browsing-contexts-by-name"> + +<!-- user agents are not required to support open features other than `noopener` + and on some platforms position and size features don't make sense --> +<meta name="flags" content="may" /> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/PrefixedPostMessage.js"></script> +<script> +var windowURL = 'resources/message-opener.html'; +var width = 'width=401,'; +var height = 'height=402,'; + +[ 'screenx=141', + ' screenx = 141', + 'screenx==141', + '\nscreenx= 141', + ',screenx=141,,', + 'SCREENX=141', + 'screenX=141' +].forEach((features, idx, arr) => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.left, 141); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_screenX=141', '', width + height + features); + }, `${format_value(features)} should set left position of opened window`); +}); + +[ 'screeny=142', + ' screeny = 142', + 'screeny==142', + '\nscreeny= 142', + ',screeny=142,,', + 'SCREENY=142', + 'screenY=142' +].forEach((features, idx, arr) => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.top, 142); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_screenY=142', '', width + height + features); + }, `${format_value(features)} should set top position of opened window`); +}); + +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-tokenization-top-left.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-tokenization-top-left.html new file mode 100644 index 0000000000..842cbcf820 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-tokenization-top-left.html @@ -0,0 +1,69 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML: window.open `features`: tokenization -- position features `top` and `left`</title> +<meta name=timeout content=long> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#apis-for-creating-and-navigating-browsing-contexts-by-name"> + +<!-- user agents are not required to support open features other than `noopener` + and on some platforms position and size features don't make sense --> +<meta name="flags" content="may" /> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/PrefixedPostMessage.js"></script> +<script> +var windowURL = 'resources/message-opener.html'; +var width = 'width=401,'; +var height = 'height=402,'; + +[ 'left=141', + ' left = 141', + 'left==141', + '\nleft= 141', + ',left=141,,', + 'LEFT=141' +].forEach((features, idx, arr) => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.left, 141); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_screenX=141', '', width + height + features); + }, `"${features}" should set left position of opened window`); +}); + +[ 'top=142', + ' top = 142', + 'top==142', + '\ttop= 142', + ',top=142,,', + 'TOP=142' +].forEach((features, idx, arr) => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.top, 142); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_screenY=142', '', width + height + features); + }, `${format_value(features)} should set top position of opened window`); +}); + +[ 'top=152,left=152', + 'top=152,,left=152,', + 'top=152==left=152', + ',,top= 152, left=152' +].forEach((features, idx, arr) => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.top, 152); + assert_equals(data.left, 152); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_screenX=152&expected_screenY=152', '', width + height + features); + }, `${format_value(features)} should set top and left position of opened window`); +}); + +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-tokenization-width-height.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-tokenization-width-height.html new file mode 100644 index 0000000000..ff61199179 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/open-features-tokenization-width-height.html @@ -0,0 +1,71 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML: window.open `features`: tokenization -- size features `width` and `height`</title> +<meta name=timeout content=long> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#apis-for-creating-and-navigating-browsing-contexts-by-name"> + +<!-- user agents are not required to support open features other than `noopener` + and on some platforms position and size features don't make sense --> +<meta name="flags" content="may" /> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/PrefixedPostMessage.js"></script> +<script> +var windowURL = 'resources/message-opener.html'; +var width = 'width=401,'; +var height = 'height=402,'; + +[ 'width=401', + ' width = 401', + 'width==401', + '\nwidth= 401', + ',width=401,,', + 'WIDTH=401' +].forEach((features, idx, arr) => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.width, 401); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_innerWidth=401', '', height + features); + }, `${format_value(features)} should set width of opened window`); +}); + +[ 'height=402', + ' height = 402', + 'height==402', + '\nheight= 402', + ',height=402,,', + 'HEIGHT=402' +].forEach((features, idx, arr) => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.height, 402); + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_innerHeight=402', '', width + features); + }, `${format_value(features)} should set height of opened window`); +}); + +[ 'height=402,width=401', + ' height = 402 , width = 401 ,', + 'height==402 width = 401', + '\nheight= 402,,width=\n401', + ',height=402,,width==401', + 'HEIGHT=402, WIDTH=401' +].forEach((features, idx, arr) => { + async_test(t => { + var prefixedMessage = new PrefixedMessageTest(); + prefixedMessage.onMessage(t.step_func_done((data, e) => { + e.source.close(); + assert_equals(data.height, 402); + assert_equals(data.width, 401) + })); + var win = window.open(prefixedMessage.url(windowURL) + '&expected_innerHeight=402&expected_innerWidth=401', '', features); + }, `${format_value(features)} should set height and width of opened window`); +}); + +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/resources/close-self.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/resources/close-self.html new file mode 100644 index 0000000000..0c0cf9fc49 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/resources/close-self.html @@ -0,0 +1,3 @@ +<script> + window.close(); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/resources/is-popup-barprop.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/resources/is-popup-barprop.html new file mode 100644 index 0000000000..5636e29878 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/resources/is-popup-barprop.html @@ -0,0 +1,15 @@ +<script src="/common/PrefixedPostMessage.js"></script> +<script> +var prefixedMessage = new PrefixedMessageResource(); +function sendBarProps() { + prefixedMessage.postToOpener({ + locationbar: window.locationbar.visible, + menubar: window.menubar.visible, + personalbar: window.personalbar.visible, + scrollbars: window.scrollbars.visible, + statusbar: window.statusbar.visible, + toolbar: window.toolbar.visible, + }); +} +window.addEventListener('load', sendBarProps); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/resources/message-opener.html b/testing/web-platform/tests/html/browsers/the-window-object/open-close/resources/message-opener.html new file mode 100644 index 0000000000..39ad139769 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/resources/message-opener.html @@ -0,0 +1,52 @@ +<script src="/common/PrefixedPostMessage.js"></script> +<script> +var prefixedMessage = new PrefixedMessageResource(); +var max = 150, attempts = 0; + +const urlParams = new URLSearchParams(location.search); +const expected_innerWidth = urlParams.get('expected_innerWidth'); +const expected_innerHeight = urlParams.get('expected_innerHeight'); +const expected_screenX = urlParams.get('expected_screenX'); +const expected_screenY = urlParams.get('expected_screenY'); +let should_wait_until_settled = expected_innerWidth === null && expected_innerHeight === null && expected_screenX === null && expected_screenY === null; + +function sendCoordinates() { + // Certain windowing systems position windows asynchronously. + // As a result, the window may not be positioned yet when the + // load event fires. To accommodate this, allow waiting up to + // 15 seconds for positioning to take place. + if ((!window.screenX && expected_screenX) && + (!window.screenY && expected_screenY) && ++attempts < max) { + setTimeout(sendCoordinates, 100); + return; + } + if (expected_innerWidth && window.innerWidth != expected_innerWidth && ++attempts < max) { + setTimeout(sendCoordinates, 10); + return; + } + if (expected_innerHeight && window.innerHeight != expected_innerHeight && ++attempts < max) { + setTimeout(sendCoordinates, 10); + return; + } + if (expected_screenX && window.screenX != expected_screenX && ++attempts < max) { + setTimeout(sendCoordinates, 10); + return; + } + if (expected_screenY && window.screenY != expected_screenY && ++attempts < max) { + setTimeout(sendCoordinates, 10); + return; + } + if (should_wait_until_settled) { + should_wait_until_settled = false; + setTimeout(sendCoordinates, 300); + return; + } + prefixedMessage.postToOpener({ + left: window.screenX, + top: window.screenY, + width: window.innerWidth, + height: window.innerHeight + }); +} +window.addEventListener('load', sendCoordinates); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/open-close/resources/tokenization-noopener-noreferrer.js b/testing/web-platform/tests/html/browsers/the-window-object/open-close/resources/tokenization-noopener-noreferrer.js new file mode 100644 index 0000000000..a9d42e26de --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/open-close/resources/tokenization-noopener-noreferrer.js @@ -0,0 +1,152 @@ +function booleanTests(feature) { + const windowURL = 'resources/close-self.html'; + // Tests for how windows features are tokenized into 'name', 'value' + // window features separators are ASCII whitespace, '=' and ',' + + const featureUpper = feature.toUpperCase(), + featureSplitBegin = feature.slice(0, 2), + featureSplitEnd = feature.slice(2), + featureMixedCase = featureSplitBegin.toUpperCase() + featureSplitEnd; + featureMixedCase2 = featureSplitBegin + featureSplitEnd.toUpperCase(); + + test (t => { + // Tokenizing `name`: initial window features separators are ignored + // Each of these variants should tokenize to (`${feature}`, '') + [ + ` ${feature}`, + `=${feature}`, + `,,${feature}`, + `,=, ${feature}`, + `\n=${feature}=`, + `\t${feature}`, + `\r,,,=${feature}`, + `\u000C${feature}` + ].forEach(variant => { + const win = window.open(windowURL, "", variant); + assert_equals(win, null, `"${variant}" should activate feature "${feature}"`); + }); + }, `Tokenization of "${feature}" should skip window features separators before feature`); + + test (t => { + // Tokenizing `name`: lowercase conversion + // Each of these variants should tokenize as feature (`${feature}`, '') + // except where indicated + // Note also that `value` is lowercased during tokenization + [ + `${featureUpper}`, + `${featureMixedCase}`, + ` ${featureMixedCase2}`, + `=${featureUpper}`, + `${featureUpper}=1`, + `${featureUpper}=1`, + `${featureUpper}=yes`, + `${feature}=YES`, + ].forEach(variant => { + const win = window.open(windowURL, '', variant); + assert_equals(win, null, `"${variant}" should activate feature "${feature}"`); + }); + }, `Feature "${feature}" should be converted to ASCII lowercase`); + + test (t => { + // After `name` has been collected, ignore any window features separators until '=' + // except ',' OR a non-window-features-separator — break in those cases + // i.e. ignore whitespace until '=' unless a ',' is encountered first + // Each of these variants should tokenize as feature ('noopener', '') + [ + `${feature}`, + ` ${feature}\r`, + `${feature}\n =`, + `${feature},`, + `${feature} =,`, + `, ${feature} =`, + `${feature},=`, + `${feature} foo`, + `foo ${feature}=1`, + `foo=\u000Cbar\u000C${feature}` + ].forEach(variant => { + const win = window.open(windowURL, '', variant); + assert_equals(win, null, `"${variant}" should activate feature "${feature}"`); + }); + }, `After "${feature}", tokenization should skip window features separators that are not "=" or ","`); + + test (t => { + // After initial '=', tokenizing should ignore all separators except ',' + // before collecting `value` + // Each of these variants should tokenize as feature ('noopener', '') + // Except where indicated + [ + `${feature}= yes`, + `${feature}==,`, + `${feature}=\n ,`, + `${feature} = \t ,`, + `${feature}\n=\r 1,`, + `${feature}=,yes`, + `${feature}= yes=,`, + `${feature} = \u000Cyes` + ].forEach(variant => { + const win = window.open(windowURL, '', variant); + assert_equals(win, null, `"${variant}" should activate feature "${feature}"`); + }); + }, `Tokenizing "${feature}" should ignore window feature separators except "," after initial "=" and before value`); + + test (t => { + // Tokenizing `value` should collect any non-separator code points until first separator + [ + `${feature}=1`, + `${feature}=yes`, + `${feature} = yes ,`, + `${feature}=\nyes ,`, + `${feature}=yes yes`, + `${feature}=yes\ts`, + `${feature}==`, + `${feature}=1\n,`, + `==${feature}===`, + `${feature}==\u000C` + ].forEach(variant => { + const win = window.open(windowURL, '', variant); + assert_equals(win, null, `"${variant}" should set "${feature}"`); + }); + }, `Tokenizing "${feature}" should read characters until first window feature separator as \`value\``); + + test (t => { + [ + `${feature}=1`, + `${feature}=2`, + `${feature}=12345`, + `${feature}=1.5`, + `${feature}=-1`, + ].forEach(variant => { + const win = window.open(windowURL, '', variant); + assert_equals(win, null, `"${variant}" should activate feature "${feature}"`); + }); + }, 'Integer values other than 0 should activate the feature'); + + test (t => { + [ + `${feature}=0`, + `${feature}=0.5`, + `${feature}=error`, + ].forEach(variant => { + const win = window.open(windowURL, '', variant); + assert_not_equals(win, null, `"${variant}" should NOT activate feature "${feature}"`); + }); + }, `Integer value of 0 should not activate "${feature}"`); + + test (t => { + [ + `-${feature}`, + `${featureUpper}RRR`, + `${featureMixedCase}R`, + `${featureSplitBegin}_${featureSplitEnd}`, + ` ${featureSplitBegin} ${featureSplitEnd}`, + `${featureSplitBegin}\n${featureSplitEnd}`, + `${featureSplitBegin},${featureSplitEnd}`, + `\0${feature}`, + `${feature}\u0000=yes`, + `foo=\u000C${feature}` + ].forEach(variant => { + const win = window.open(windowURL, '', variant); + assert_not_equals(win, null, `"${variant}" should NOT activate feature "${feature}"`); + }); + }, `Invalid feature names should not tokenize as "${feature}"`); +} diff --git a/testing/web-platform/tests/html/browsers/the-window-object/proxy-getOwnPropertyDescriptor.html b/testing/web-platform/tests/html/browsers/the-window-object/proxy-getOwnPropertyDescriptor.html new file mode 100644 index 0000000000..4ea9504870 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/proxy-getOwnPropertyDescriptor.html @@ -0,0 +1,126 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>getOwnPropertyDescriptor() is correct for Proxy with host object target</title> + <link rel="help" href="https://html.spec.whatwg.org/multipage/#window"> + <link rel="help" href="https://webidl.spec.whatwg.org/#Unforgeable"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> +</head> +<body> +<script> +'use strict'; + +const assert_accessor_descriptor_equals = (actual, expected) => { + assert_equals(actual.get, expected.get, 'get'); + assert_equals(actual.set, expected.set, 'set'); + assert_equals(actual.enumerable, expected.enumerable, 'enumerable'); + assert_equals(actual.configurable, expected.configurable, 'configurable'); +}; + +const assert_data_descriptor_equals = (actual, expected) => { + assert_equals(actual.value, expected.value, 'value'); + assert_equals(actual.writable, expected.writable, 'writable'); + assert_equals(actual.enumerable, expected.enumerable, 'enumerable'); + assert_equals(actual.configurable, expected.configurable, 'configurable'); +}; + +test(() => { + const windowProxy = new Proxy(window, {}); + name = 'old_name'; + const descriptor = Object.getOwnPropertyDescriptor(windowProxy, 'name'); + + assert_equals(descriptor.get.call(window), 'old_name'); + descriptor.set.call(window, 'new_name'); + assert_equals(name, 'new_name'); + assert_true(descriptor.enumerable); + assert_true(descriptor.configurable); +}, 'Window target, no trap, "name" attribute'); + +test(() => { + let trapCalls = 0; + const windowProxy = new Proxy(window, { + getOwnPropertyDescriptor(...args) { + trapCalls++; + return Reflect.getOwnPropertyDescriptor(...args); + }, + }); + + assert_accessor_descriptor_equals( + Object.getOwnPropertyDescriptor(windowProxy, 'document'), + Object.getOwnPropertyDescriptor(window, 'document') + ); + assert_equals(trapCalls, 1); +}, 'Window target, forwarding trap, [LegacyUnforgeable] "document" attribute'); + +test(() => { + const trapResult = {get() {}, set(_val) {}, enumerable: false, configurable: true}; + const windowProxy = new Proxy(new Proxy(window, {}), { + getOwnPropertyDescriptor: () => trapResult, + }); + + assert_accessor_descriptor_equals( + Object.getOwnPropertyDescriptor(windowProxy, 'onclick'), + trapResult + ); +}, 'Window proxy target, custom trap, "onclick" event handler attribute'); + +test(() => { + let trapCalls = 0; + const documentProxy = new Proxy(document, { + getOwnPropertyDescriptor(...args) { + trapCalls++; + return Reflect.getOwnPropertyDescriptor(...args); + }, + }); + + assert_accessor_descriptor_equals( + Object.getOwnPropertyDescriptor(documentProxy, 'location'), + Object.getOwnPropertyDescriptor(document, 'location') + ); + assert_equals(trapCalls, 1); +}, 'Document target, forwarding trap, [LegacyUnforgeable] "location" attribute'); + +test(() => { + const trapResult = {value: 4, writable: false, enumerable: true, configurable: true}; + const documentProxy = new Proxy(new Proxy(document, {}), { + getOwnPropertyDescriptor: () => trapResult, + }); + + assert_data_descriptor_equals( + Object.getOwnPropertyDescriptor(documentProxy, 'foo'), + trapResult + ); +}, 'Document proxy target, custom trap, non-existent value attribute'); + +test(() => { + const locationProxy = new Proxy(location, {}); + location.hash = '#old'; + const descriptor = Object.getOwnPropertyDescriptor(locationProxy, 'hash'); + + assert_equals(descriptor.get.call(location), '#old'); + descriptor.set.call(location, '#new'); + assert_equals(location.hash, '#new'); + assert_true(descriptor.enumerable); + assert_false(descriptor.configurable); +}, 'Location target, no trap, [LegacyUnforgeable] "hash" attribute'); + +test(() => { + let trapCalls = 0; + const locationProxy = new Proxy(new Proxy(location, {}), { + getOwnPropertyDescriptor(...args) { + trapCalls++; + return Reflect.getOwnPropertyDescriptor(...args); + }, + }); + + assert_data_descriptor_equals( + Object.getOwnPropertyDescriptor(locationProxy, 'reload'), + Object.getOwnPropertyDescriptor(location, 'reload') + ); + assert_equals(trapCalls, 1); +}, 'Location proxy target, forwarding trap, [LegacyUnforgeable] "reload" method'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/security-window/window-security.https.html b/testing/web-platform/tests/html/browsers/the-window-object/security-window/window-security.https.html new file mode 100644 index 0000000000..68af5bd90b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/security-window/window-security.https.html @@ -0,0 +1,200 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: Window Security</title> +<link rel="author" title="Intel" href="http://www.intel.com/" /> +<link rel="help" href="https://html.spec.whatwg.org/multipage/multipage/browsers.html#the-window-object" /> +<link rel="help" href="https://html.spec.whatwg.org/multipage/multipage/timers.html#timers" /> +<link rel="help" href="https://html.spec.whatwg.org/multipage/multipage/webappapis.html#atob" /> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#windowsessionstorage" /> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#windowlocalstorage" /> +<link rel="help" href="https://html.spec.whatwg.org/multipage/multipage/browsers.html#window" /> +<link rel="help" href="http://dev.w3.org/csswg/cssom/#extensions-to-the-window-interface" /> +<link rel="help" href="http://dev.w3.org/csswg/cssom-view/#extensions-to-the-window-interface" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<div id="log"></div> +<script> +var t = async_test("Window Security testing"); + +function fr_load() { + fr = document.getElementById("fr"); + + t.step(function () { + //SecurityError should be thrown + [ + //attributes + {name: "devicePixelRatio"}, + {name: "document"}, + {name: "external"}, + {name: "frameElement"}, + {name: "history"}, + {name: "innerWidth"}, + {name: "innerHeight"}, + {name: "locationbar"}, + {name: "localStorage"}, + {name: "menubar"}, + {name: "name"}, + {name: "navigator"}, + {name: "onabort"}, + {name: "onafterprint"}, + {name: "onbeforeprint"}, + {name: "onbeforeunload"}, + {name: "onblur"}, + {name: "oncancel"}, + {name: "oncanplay"}, + {name: "oncanplaythrough"}, + {name: "onchange"}, + {name: "onclick"}, + {name: "onclose"}, + {name: "oncontextmenu"}, + {name: "oncuechange"}, + {name: "ondblclick"}, + {name: "ondrag"}, + {name: "ondragend"}, + {name: "ondragenter"}, + {name: "ondragleave"}, + {name: "ondragover"}, + {name: "ondragstart"}, + {name: "ondrop"}, + {name: "ondurationchange"}, + {name: "onemptied"}, + {name: "onended"}, + {name: "onerror"}, + {name: "onfocus"}, + {name: "onhashchange"}, + {name: "oninput"}, + {name: "oninvalid"}, + {name: "onkeydown"}, + {name: "onkeypress"}, + {name: "onkeyup"}, + {name: "onload"}, + {name: "onloadeddata"}, + {name: "onloadedmetadata"}, + {name: "onloadstart"}, + {name: "onmessage"}, + {name: "onmousedown"}, + {name: "onmousemove"}, + {name: "onmouseout"}, + {name: "onmouseover"}, + {name: "onmouseup"}, + {name: "onmousewheel"}, + {name: "onoffline"}, + {name: "ononline"}, + {name: "onpause"}, + {name: "onplay"}, + {name: "onplaying"}, + {name: "onpagehide"}, + {name: "onpageshow"}, + {name: "onpopstate"}, + {name: "onprogress"}, + {name: "onratechange"}, + {name: "onreset"}, + {name: "onresize"}, + {name: "onscroll"}, + {name: "onseeked"}, + {name: "onseeking"}, + {name: "onselect"}, + {name: "onstalled"}, + {name: "onstorage"}, + {name: "onsubmit"}, + {name: "onsuspend"}, + {name: "ontimeupdate"}, + {name: "onunload"}, + {name: "onvolumechange"}, + {name: "onwaiting"}, + {name: "pageXOffset"}, + {name: "pageYOffset"}, + {name: "personalbar"}, + {name: "screen"}, + {name: "scrollbars"}, + {name: "statusbar"}, + {name: "status"}, + {name: "screenX"}, + {name: "screenY"}, + {name: "sessionStorage"}, + {name: "toolbar"}, + //methods + {name: "alert", isMethod: true}, + {name: "clearInterval", isMethod: true, args:[1]}, + {name: "clearTimeout", isMethod: true, args:[function () {}, 1]}, + {name: "confirm", isMethod: true}, + {name: "getComputedStyle", isMethod: true, args:[document.body, null]}, + {name: "getSelection", isMethod: true}, + {name: "matchMedia", isMethod: true, args:["(min-width:50px)"]}, + {name: "moveBy", isMethod: true, args:[10, 10]}, + {name: "moveTo", isMethod: true, args:[10, 10]}, + {name: "open", isMethod: true}, + {name: "print", isMethod: true}, + {name: "prompt", isMethod: true}, + {name: "resizeTo", isMethod: true, args:[10, 10]}, + {name: "resizeBy", isMethod: true, args:[10, 10]}, + {name: "scroll", isMethod: true, args:[10, 10]}, + {name: "scrollTo", isMethod: true, args:[10, 10]}, + {name: "scrollBy", isMethod: true, args:[10, 10]}, + {name: "setInterval", isMethod: true, args:[function () {}, 1]}, + {name: "setTimeout", isMethod: true, args:[function () {}, 1]}, + {name: "stop", isMethod: true}, + ].forEach(function (item) { + test(function () { + assert_true(item.name in window, "window." + item.name + " should exist."); + assert_throws_dom("SecurityError", function () { + if (item.isMethod) + if (item.args) + fr.contentWindow[item.name](item.args[0], item.args[1]); + else + fr.contentWindow[item.name](); + else + fr.contentWindow[item.name]; + }, "A SecurityError exception should be thrown."); + }, "A SecurityError exception must be thrown when window." + item.name + " is accessed from a different origin."); + }); + + //SecurityError should not be thrown + [ + //attributes + {name: "closed"}, + {name: "frames"}, + {name: "length"}, + {name: "location"}, + {name: "opener"}, + {name: "parent"}, + {name: "self"}, + {name: "top"}, + {name: "window"}, + //methods + {name: "blur", isMethod: true}, + {name: "close", isMethod: true}, + {name: "focus", isMethod: true}, + {name: "postMessage", isMethod: true, args: [{msg: 'foo'}, "*"]} + ].forEach(function (item) { + test(function () { + assert_true(item.name in window, "window." + item.name + " should exist."); + try { + if (item.isMethod) + if (item.args) + fr.contentWindow[item.name](item.args[0], item.args[1]); + else + fr.contentWindow[item.name](); + else + fr.contentWindow[item.name]; + } catch (e) { + assert_unreached("An unexpected exception was thrown."); + } + }, "A SecurityError exception should not be thrown when window." + item.name + " is accessed from a different origin."); + }); + }); + t.done(); +} + +</script> +<script> +onload = function() { + var frame = document.createElement('iframe'); + frame.id = "fr"; + frame.setAttribute("style", "display:none"); + frame.setAttribute('src', get_host_info().HTTPS_REMOTE_ORIGIN + "/"); + frame.setAttribute("onload", "fr_load()"); + document.body.appendChild(frame); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/self-et-al.window.js b/testing/web-platform/tests/html/browsers/the-window-object/self-et-al.window.js new file mode 100644 index 0000000000..1b0fa1211a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/self-et-al.window.js @@ -0,0 +1,43 @@ +function delayed_assert_done(t, w, windowProxySelfReference) { + // Let's make sure nobody is being sneaky + t.step_timeout(() => { + t.step_timeout(() => { + assert_equals(w[windowProxySelfReference], w, `${windowProxySelfReference} got cleared after some time`); + t.done(); + }, 0); + }, 0); +} + +[ + "frames", + "globalThis", + "self", + "window" +].forEach(windowProxySelfReference => { + async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")), + otherW = frame.contentWindow; + assert_equals(otherW[windowProxySelfReference], otherW, `${windowProxySelfReference} is broken`); + frame.remove(); + assert_equals(otherW[windowProxySelfReference], otherW, `${windowProxySelfReference} got cleared after browsing context removal`); + assert_true(otherW.closed); + + delayed_assert_done(t, otherW, windowProxySelfReference); + }, `iframeWindow.${windowProxySelfReference} before and after removal`); + + async_test(t => { + const otherW = window.open(); + assert_equals(otherW[windowProxySelfReference], otherW, `${windowProxySelfReference} is broken`); + otherW.onunload = t.step_func(() => { + assert_equals(otherW[windowProxySelfReference], otherW, `${windowProxySelfReference} got cleared after browsing context unload`); + t.step_timeout(() => { + assert_equals(otherW.opener, null); // Ensure browsing context is discarded + assert_equals(otherW[windowProxySelfReference], otherW, `${windowProxySelfReference} got cleared after browsing context removal`); + delayed_assert_done(t, otherW, windowProxySelfReference); + }, 0); + }); + otherW.close(); + assert_equals(otherW[windowProxySelfReference], otherW, `${windowProxySelfReference} got cleared after browsing context closure`); + assert_true(otherW.closed); + }, `popupWindow.${windowProxySelfReference} before, after closing, and after discarding`) +}); diff --git a/testing/web-platform/tests/html/browsers/the-window-object/support/BarProp-target.html b/testing/web-platform/tests/html/browsers/the-window-object/support/BarProp-target.html new file mode 100644 index 0000000000..9921e7a577 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/support/BarProp-target.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<script> + const barProps = ["locationbar", "menubar", "personalbar", "scrollbars", "statusbar", "toolbar"]; + const barPropsObj = {}; + const channelName = location.search.substr(1); + const channel = new BroadcastChannel(channelName); + for (const prop of barProps) { + barPropsObj[prop] = window[prop].visible; + } + channel.postMessage(barPropsObj); + + // Because messages are not delivered synchronously and because closing a + // browsing context prompts the eventual clearing of all task sources, this + // document should not be closed until the opener document has confirmed + // receipt. + channel.onmessage = () => { window.close() }; +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/support/closed.html b/testing/web-platform/tests/html/browsers/the-window-object/support/closed.html new file mode 100644 index 0000000000..3b70598e34 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/support/closed.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<!-- + There's two URL parameters supported here: + + window: The property name of a getter on the global object that returns the relevant WindowProxy + object to message. + ident: A reasonably unique identifier that will be echoed as the message. +--> +<script> + const params = new URLSearchParams(location.search); + self[params.get("window")].postMessage(params.get("ident"), "*"); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/support/noopener-target.html b/testing/web-platform/tests/html/browsers/the-window-object/support/noopener-target.html new file mode 100644 index 0000000000..41e197a746 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/support/noopener-target.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<script> + var channelName = location.search.substr(1); + var channel = new BroadcastChannel(channelName); + channel.postMessage({ name: window.name, + haveOpener: window.opener !== null }); + + // Because messages are not delivered synchronously and because closing a + // browsing context prompts the eventual clearing of all task sources, this + // document should not be closed until the opener document has confirmed + // receipt. + channel.onmessage = function() { + window.close(); + }; +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/support/noreferrer-target.html b/testing/web-platform/tests/html/browsers/the-window-object/support/noreferrer-target.html new file mode 100644 index 0000000000..c2446c6fe9 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/support/noreferrer-target.html @@ -0,0 +1,13 @@ +<script> + const channelName = location.search.substr(1), + channel = new BroadcastChannel(channelName); + channel.postMessage({ name: window.name, + haveOpener: window.opener !== null, + referrer: document.referrer }); + + // Because messages are not delivered synchronously and because closing a + // browsing context prompts the eventual clearing of all task sources, this + // document should not be closed until the opener document has confirmed + // receipt. + channel.onmessage = () => window.close(); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/support/same-origin-iframe.html b/testing/web-platform/tests/html/browsers/the-window-object/support/same-origin-iframe.html new file mode 100644 index 0000000000..763d9e466b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/support/same-origin-iframe.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html> + <head> + <script> + window.addEventListener('load', () => { window.didLoadFrame = true; }); + </script> + </head> +</html> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/support/sizing-target.html b/testing/web-platform/tests/html/browsers/the-window-object/support/sizing-target.html new file mode 100644 index 0000000000..7cd5348a85 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/support/sizing-target.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<script> + const windowProps = ["innerWidth", "innerHeight"]; + const windowPropsObj = {}; + const channelName = location.search.substr(1); + const channel = new BroadcastChannel(channelName); + for (const prop of windowProps) { + windowPropsObj[prop] = window[prop]; + } + channel.postMessage(windowPropsObj); + + // Because messages are not delivered synchronously and because closing a + // browsing context prompts the eventual clearing of all task sources, this + // document should not be closed until the opener document has confirmed + // receipt. + channel.onmessage = () => { window.close() }; +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/support/window-open-popup-target.html b/testing/web-platform/tests/html/browsers/the-window-object/support/window-open-popup-target.html new file mode 100644 index 0000000000..a0588de829 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/support/window-open-popup-target.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<script> + var channelName = window.name; + var channel = new BroadcastChannel(channelName); + const allBarProps = [ + window.locationbar.visible, + window.menubar.visible, + window.personalbar.visible, + window.scrollbars.visible, + window.statusbar.visible, + window.toolbar.visible + ]; + const allTrue = allBarProps.every(x=>x); + const allFalse = allBarProps.every(x=>!x); + channel.postMessage({isPopup: allFalse, mixedState: !allTrue && !allFalse}); + + // Because messages are not delivered synchronously and because closing a + // browsing context prompts the eventual clearing of all task sources, this + // document should not be closed until the opener document has confirmed + // receipt. + channel.onmessage = function() { + window.close(); + }; +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/the-window-object/support/windowFeature-values-target.html b/testing/web-platform/tests/html/browsers/the-window-object/support/windowFeature-values-target.html new file mode 100644 index 0000000000..3a78ddf660 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/support/windowFeature-values-target.html @@ -0,0 +1,24 @@ +<script> + const channelName = location.search.substr(1), + channel = new BroadcastChannel(channelName); + + const haveOpener = window.opener !== null; + const haveReferrer = document.referrer !== null && document.referrer !== ""; + const allBarProps = [ + window.locationbar.visible, + window.menubar.visible, + window.personalbar.visible, + window.scrollbars.visible, + window.statusbar.visible, + window.toolbar.visible + ]; + const isPopup = allBarProps.every(x=>!x); + + channel.postMessage({haveOpener, haveReferrer, isPopup}); + + // Because messages are not delivered synchronously and because closing a + // browsing context prompts the eventual clearing of all task sources, this + // document should not be closed until the opener document has confirmed + // receipt. + channel.onmessage = () => window.close(); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/window-aliases.html b/testing/web-platform/tests/html/browsers/the-window-object/window-aliases.html new file mode 100644 index 0000000000..135be02a30 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/window-aliases.html @@ -0,0 +1,28 @@ +<!doctype html> +<meta charset=utf-8> +<title>Aliases of the window object</title> +<link rel="author" title="Ms2ger" href="mailto:Ms2ger@gmail.com"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-window"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-frames"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-self"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id=log></div> +<script> +var global = this; + +test(function() { + assert_equals(window, global); + assert_equals(window.window, global); +}, "window should be the global object"); + +test(function() { + assert_equals(frames, global); + assert_equals(window.frames, global); +}, "frames should be the global object"); + +test(function() { + assert_equals(self, global); + assert_equals(window.self, global); +}, "self should be the global object"); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/window-indexed-access-vs-named-access.html b/testing/web-platform/tests/html/browsers/the-window-object/window-indexed-access-vs-named-access.html new file mode 100644 index 0000000000..23b9124ef7 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/window-indexed-access-vs-named-access.html @@ -0,0 +1,58 @@ +<!doctype html> +<meta charset=utf-8> +<title>Interactions between indexed and named access on the Window object</title> +<link rel="author" title="Delan Azabani" href="dazabani@igalia.com"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#accessing-other-browsing-contexts"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#named-access-on-the-window-object"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id=0></div> +<div id=4></div> +<iframe name=3></iframe> +<iframe name=2></iframe> +<iframe name=1></iframe> +<script> +const divs = document.querySelectorAll("div"); +const iframes = document.querySelectorAll("iframe"); +const wp = Object.getPrototypeOf(window); +test(function() { + assert_equals(window[0], iframes[0].contentWindow); + assert_equals(window["0"], iframes[0].contentWindow); +}, "WindowProxy: document-tree child navigable with index 0 (indexed access)"); +test(function() { + assert_equals(window[1], iframes[1].contentWindow); + assert_equals(window["1"], iframes[1].contentWindow); +}, "WindowProxy: document-tree child navigable with index 1 (indexed access)"); +test(function() { + assert_equals(window[2], iframes[2].contentWindow); + assert_equals(window["2"], iframes[2].contentWindow); +}, "WindowProxy: document-tree child navigable with index 2 (indexed access)"); +test(function() { + assert_equals(window[3], iframes[0].contentWindow); + assert_equals(window["3"], iframes[0].contentWindow); +}, "WindowProxy: document-tree child navigable with target name 3 (named access)"); +test(function() { + assert_equals(window[4], divs[1]); + assert_equals(window["4"], divs[1]); +}, "WindowProxy: element with id 4 (named access)"); +test(function() { + assert_equals(wp[0], divs[0]); + assert_equals(wp["0"], divs[0]); +}, "Window prototype: element with id 0 (named access)"); +test(function() { + assert_equals(wp[1], iframes[2].contentWindow); + assert_equals(wp["1"], iframes[2].contentWindow); +}, "Window prototype: document-tree child navigable with target name 1 (named access)"); +test(function() { + assert_equals(wp[2], iframes[1].contentWindow); + assert_equals(wp["2"], iframes[1].contentWindow); +}, "Window prototype: document-tree child navigable with target name 2 (named access)"); +test(function() { + assert_equals(wp[3], iframes[0].contentWindow); + assert_equals(wp["3"], iframes[0].contentWindow); +}, "Window prototype: document-tree child navigable with target name 3 (named access)"); +test(function() { + assert_equals(wp[4], divs[1]); + assert_equals(wp["4"], divs[1]); +}, "Window prototype: element with id 4 (named access)"); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/window-indexed-properties-delete-no-cache.html b/testing/web-platform/tests/html/browsers/the-window-object/window-indexed-properties-delete-no-cache.html new file mode 100644 index 0000000000..22262943aa --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/window-indexed-properties-delete-no-cache.html @@ -0,0 +1,31 @@ +<!doctype html> +<meta charset=utf-8> +<title>Deletion of WindowProxy's indexed properties is not cached</title> +<link rel="help" href="https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-delete"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id=log></div> +<script> +const iframe = document.createElement("iframe"); +iframe.srcdoc = ""; + +test(() => { + assert_equals(window.length, 0); + for (let i = 0; i < 1e5; i++) { + assert_true(delete window[0]); + } + + document.body.append(iframe); + assert_false(delete window[0]); +}, "Absence of index '0' is not cached"); + +test(() => { + assert_equals(window.length, 1); + for (let i = 0; i < 1e5; i++) { + assert_false(delete window[0]); + } + + iframe.remove(); + assert_true(delete window[0]); +}, "Presence of index '0' is not cached"); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/window-indexed-properties-strict.html b/testing/web-platform/tests/html/browsers/the-window-object/window-indexed-properties-strict.html new file mode 100644 index 0000000000..2ee10a7a64 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/window-indexed-properties-strict.html @@ -0,0 +1,75 @@ +<!doctype html> +<meta charset=utf-8> +<title>Indexed properties of the window object (strict mode)</title> +<link rel="author" title="Ms2ger" href="ms2ger@gmail.com"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#window"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-window-item"> +<link rel="help" href="https://webidl.spec.whatwg.org/#getownproperty"> +<link rel="help" href="https://webidl.spec.whatwg.org/#defineownproperty"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id=log></div> +<iframe></iframe> +<script> +test(function() { + "use strict"; + assert_false("-1" in window, "-1 not in window"); + assert_equals(window[-1], undefined); + window[-1] = "foo"; + assert_equals(window[-1], "foo"); +}); +test(function() { + "use strict"; + assert_throws_js(TypeError, function() { + window[0] = "foo"; + }); + assert_throws_js(TypeError, () => Object.defineProperty(window, 0, { value: "bar" })) + assert_throws_js(TypeError, () => Object.defineProperty(window, 0, { get() { return "baz" } })) + assert_throws_js(TypeError, () => Object.defineProperty(window, 0, { set(v) { return "qux" } })) + assert_equals(window[0], + document.getElementsByTagName("iframe")[0].contentWindow); + assert_throws_js(TypeError, () => delete window[0]); +}); +test(function() { + "use strict"; + assert_throws_js(TypeError, function() { + window[1] = "foo"; + }); + assert_throws_js(TypeError, () => Object.defineProperty(window, 1, { value: "bar" })) + assert_throws_js(TypeError, () => Object.defineProperty(window, 1, { get() { return "baz" } })) + assert_throws_js(TypeError, () => Object.defineProperty(window, 1, { set(v) { return "qux" } })) + assert_equals(window[1], undefined); + assert_equals(Object.getOwnPropertyDescriptor(window, 1), undefined); + assert_equals(delete window[1], true); +}); +test(function() { + "use strict"; + assert_throws_js(TypeError, () => { window[4294967294] = 1; }); + assert_false(Reflect.set(window, 4294967294, 2)); + assert_false(Reflect.defineProperty(window, 4294967294, { value: 3 })); + assert_throws_js(TypeError, () => Object.defineProperty(window, 4294967294, { get: () => 4 })); + assert_equals(window[4294967294], undefined); + assert_false(4294967294 in window); + assert_true(delete window[4294967294]); +}, "Borderline numeric key: 2 ** 32 - 2 is an index (strict mode)"); +test(function() { + "use strict"; + window[4294967295] = 1; + assert_equals(window[4294967295], 1); + assert_true(Reflect.set(window, 4294967295, 2)); + assert_equals(window[4294967295], 2); + assert_true(Reflect.defineProperty(window, 4294967295, { value: 3 })); + assert_equals(window[4294967295], 3); + Object.defineProperty(window, 4294967295, { get: () => 4 }); + assert_equals(window[4294967295], 4); + assert_true(delete window[4294967295]); + assert_false(4294967295 in window); +}, "Borderline numeric key: 2 ** 32 - 1 is not an index (strict mode)"); +test(function() { + "use strict"; + var proto = Window.prototype; + [-1, 0, 1].forEach(function(idx) { + assert_false(idx in proto, idx + " in proto"); + }); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/window-indexed-properties.html b/testing/web-platform/tests/html/browsers/the-window-object/window-indexed-properties.html new file mode 100644 index 0000000000..57f18c5944 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/window-indexed-properties.html @@ -0,0 +1,71 @@ +<!doctype html> +<meta charset=utf-8> +<title>Indexed properties of the window object (non-strict mode)</title> +<link rel="author" title="Ms2ger" href="ms2ger@gmail.com"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#window"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-window-item"> +<link rel="help" href="https://webidl.spec.whatwg.org/#getownproperty"> +<link rel="help" href="https://webidl.spec.whatwg.org/#defineownproperty"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id=log></div> +<iframe></iframe> +<script> +test(function() { + assert_false("-1" in window, "-1 not in window"); + assert_equals(window[-1], undefined); + window[-1] = "foo"; + assert_equals(window[-1], "foo"); +}); +test(() => { + const desc = Object.getOwnPropertyDescriptor(window, "0"); + assert_true(desc.configurable); + assert_true(desc.enumerable); + assert_false(desc.writable); +}, "Ensure indexed properties have the correct configuration"); +test(function() { + window[0] = "foo"; + assert_throws_js(TypeError, () => Object.defineProperty(window, 0, { value: "bar" })) + assert_throws_js(TypeError, () => Object.defineProperty(window, 0, { get() { return "baz" } })) + assert_throws_js(TypeError, () => Object.defineProperty(window, 0, { set() { return "quz" } })) + assert_equals(window[0], + document.getElementsByTagName("iframe")[0].contentWindow); + assert_equals(delete window[0], false); +}); +test(function() { + window[1] = "foo"; + assert_throws_js(TypeError, () => Object.defineProperty(window, 1, { value: "bar" })) + assert_throws_js(TypeError, () => Object.defineProperty(window, 1, { get() { return "baz" } })) + assert_throws_js(TypeError, () => Object.defineProperty(window, 1, { set(v) { return "quz" } })) + assert_equals(window[1], undefined); + assert_equals(Object.getOwnPropertyDescriptor(window, 1), undefined); + assert_equals(delete window[1], true); +}); +test(function() { + window[4294967294] = 1; + assert_false(Reflect.set(window, 4294967294, 2)); + assert_false(Reflect.defineProperty(window, 4294967294, { value: 3 })); + assert_throws_js(TypeError, () => Object.defineProperty(window, 4294967294, { get: () => 4 })); + assert_equals(window[4294967294], undefined); + assert_false(4294967294 in window); + assert_true(delete window[4294967294]); +}, "Borderline numeric key: 2 ** 32 - 2 is an index"); +test(function() { + window[4294967295] = 1; + assert_equals(window[4294967295], 1); + assert_true(Reflect.set(window, 4294967295, 2)); + assert_equals(window[4294967295], 2); + assert_true(Reflect.defineProperty(window, 4294967295, { value: 3 })); + assert_equals(window[4294967295], 3); + Object.defineProperty(window, 4294967295, { get: () => 4 }); + assert_equals(window[4294967295], 4); + assert_true(delete window[4294967295]); + assert_false(4294967295 in window); +}, "Borderline numeric key: 2 ** 32 - 1 is not an index"); +test(function() { + var proto = Window.prototype; + [-1, 0, 1].forEach(function(idx) { + assert_false(idx in proto, idx + " in proto"); + }); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/window-open-defaults.window.js b/testing/web-platform/tests/html/browsers/the-window-object/window-open-defaults.window.js new file mode 100644 index 0000000000..1b2d68a462 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/window-open-defaults.window.js @@ -0,0 +1,12 @@ +async_test(t => { + const frame = document.createElement("iframe"); + t.add_cleanup(() => frame.remove()); + frame.name = "foo"; + frame.src = "/common/blank.html"; + frame.onload = t.step_func(() => { + frame.onload = t.unreached_func(); + t.step_timeout(() => t.done(), 500); + assert_equals(window[0], window.open(undefined, "foo")); + }); + document.body.append(frame); +}, "window.open()'s url parameter default"); diff --git a/testing/web-platform/tests/html/browsers/the-window-object/window-open-invalid-url.html b/testing/web-platform/tests/html/browsers/the-window-object/window-open-invalid-url.html new file mode 100644 index 0000000000..cabc143b9d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/window-open-invalid-url.html @@ -0,0 +1,10 @@ +<!doctype html> +<meta charset=utf-8> +<title>window.open() with an invalid URL</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script> +test(() => { + assert_throws_dom("SyntaxError", () => window.open("https://example.com\u0000mozilla.org")); +}, "Window.open should throw SyntaxError when an invalid url is passed as the first parameter"); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/window-open-noopener.html b/testing/web-platform/tests/html/browsers/the-window-object/window-open-noopener.html new file mode 100644 index 0000000000..8d3a95df63 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/window-open-noopener.html @@ -0,0 +1,137 @@ +<!doctype html> +<meta charset=utf-8> +<title>window.open() with "noopener" tests</title> + +<meta name="variant" content="?indexed"> +<meta name="variant" content="?_self"> +<meta name="variant" content="?_parent"> +<meta name="variant" content="?_top"> +<meta name=timeout content=long> + +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script> +var testData = [ + { testDescription: "window.open() with 'noopener' should reuse existing target", + secondWindowFeatureString: "noopener", + shouldReturnWindow: false }, + { testDescription: "noopener=1 means the same as noopener", + secondWindowFeatureString: "noopener=1", + shouldReturnWindow: false }, + { testDescription: "noopener=true means the same as noopener", + secondWindowFeatureString: "noopener=true", + shouldReturnWindow: false }, + { testDescription: "noopener=0 means lack of noopener", + secondWindowFeatureString: "noopener=0", + shouldReturnWindow: true }, + { testDescription: "noopener separated only by spaces should work", + secondWindowFeatureString: "make me noopener", + shouldReturnWindow: false }, + { testDescription: "Trailing noopener should work", + secondWindowFeatureString: "abc def, \n\r noopener", + shouldReturnWindow: false }, + { testDescription: "Leading noopener should work", + secondWindowFeatureString: "noopener \f\t , hey, there", + shouldReturnWindow: false }, + { testDescription: "Interior noopener should work", + secondWindowFeatureString: "and now, noopener , hey, there", + shouldReturnWindow: false }, + { testDescription: "noreferrer should also suppress opener when reusing existing target", + secondWindowFeatureString: "noreferrer", + shouldReturnWindow: false }, +]; + +/** + * Loop over our testData array and kick off an async test for each entry. Each + * async test opens a window using window.open() with some per-test unique name, + * then tries to do a second window.open() call with the same name and the + * test-specific feature string. It then checks whether that second + * window.open() call reuses the existing window, whether the return value of + * the second window.open() call is correct (it should be null in the noopener + * cases and non-null in the cases when the existing window gets reused) and so + * forth. + */ +function indexedTests() { + var tests = []; + for(var i = 0; i < testData.length; ++i) { + var test = testData[i]; + var t = async_test(test.testDescription); + tests.push(t); + t.secondWindowFeatureString = test.secondWindowFeatureString; + t.windowName = "someuniquename" + i; + + if (test.shouldReturnWindow) { + t.step(function() { + var windowName = this.windowName; + + var w1 = window.open("", windowName); + this.add_cleanup(function() { w1.close(); }); + + assert_equals(w1.opener, window); + + var w2 = window.open("", windowName, this.secondWindowFeatureString); + assert_equals(w2, w1); + assert_equals(w2.opener, w1.opener); + assert_equals(w2.opener, window); + this.done(); + }); + } else { + t.step(function() { + var w1; + this.add_cleanup(function() { + w1.close(); + channel.postMessage(null); + }); + + var windowName = this.windowName; + var channel = new BroadcastChannel(windowName); + + channel.onmessage = this.step_func_done(function(e) { + var data = e.data; + assert_equals(data.name, windowName, "Should have the right name"); + assert_equals(data.haveOpener, true, "Should still have opener"); + assert_equals(w1.opener, window); + assert_not_equals(w1.location.href, "about:blank", "Should have navigated"); + }); + + w1 = window.open("", windowName); + assert_equals(w1.opener, window); + + var w2 = window.open("support/noopener-target.html?" + windowName, + windowName, this.secondWindowFeatureString); + assert_equals(w2, null); + + assert_equals(w1.opener, window); + }); + } + } +} + +/** + * Loop over the special targets that ignore noopener and check that doing a + * window.open() with those targets correctly reuses the existing window. + */ +function specialTargetTest(target) { + if (["_self", "_parent", "_top"].includes(target)) { + var t = async_test("noopener window.open targeting " + target); + t.openedWindow = window.open(`javascript:var w2 = window.open("", "${target}", "noopener"); this.checkValues(w2); this.close(); void(0);`); + assert_equals(t.openedWindow.opener, window); + t.openedWindow.checkValues = t.step_func_done(function(win) { + assert_equals(win, this.openedWindow); + }); + } else { + throw 'testError: special target must be one of: _self, _parent, _top' + } +} + +/** + * Parse the Query string, check if it matches keyword 'indexed' to run the indexed tests, + * otherwise test it as a special target + */ +var variant = window.location.href.split("?")[1] +if(variant == "indexed") { + indexedTests(); +} else { + specialTargetTest(variant); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/window-open-noreferrer.html b/testing/web-platform/tests/html/browsers/the-window-object/window-open-noreferrer.html new file mode 100644 index 0000000000..92b72cdb5f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/window-open-noreferrer.html @@ -0,0 +1,20 @@ +<!doctype html> +<meta charset=utf-8> +<title>window.open() with "noreferrer" tests</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script> +async_test(t => { + const channelName = "343243423432", + channel = new BroadcastChannel(channelName); + window.open("support/noreferrer-target.html?" + channelName, "", "noreferrer"); + channel.onmessage = t.step_func_done(e => { + // Send message first so if asserts throw the popup is still closed + channel.postMessage(null); + + assert_equals(e.data.name, ""); + assert_equals(e.data.referrer, ""); + assert_equals(e.data.haveOpener, false); + }); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/window-open-popup-behavior.html b/testing/web-platform/tests/html/browsers/the-window-object/window-open-popup-behavior.html new file mode 100644 index 0000000000..258698d94d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/window-open-popup-behavior.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<meta name="timeout" content="long"> +<title>Window.open popup behavior</title> +<link rel="author" href="masonf@chromium.org"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/window-object.html#window-open-steps"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> +function testOne(windowFeatures, expectPopup) { + const windowName = Math.round(Math.random()*1e12); + const channel = new BroadcastChannel(windowName); + var w; + promise_test(() => { + return new Promise(resolve => { + w = window.open("support/window-open-popup-target.html", windowName, windowFeatures); + channel.addEventListener('message', resolve); + }).then(e => { + // Send message first so if asserts throw the popup is still closed + channel.postMessage(null); + assert_false(e.data.mixedState, "No mixed state"); + assert_equals(e.data.isPopup, expectPopup, "Popup state"); + }); + },`${windowFeatures} (expect ${expectPopup ? "popup" : "tab"})`); +} + +// No windowpreferences at all - tab. +testOne(undefined, /*expectPopup=*/false); + +// Test all permutations of these properties: +const features = ["location","toolbar","menubar","resizable","scrollbars","status"]; +const nProps = features.length; +const skip = 7; // To speed up the test, don't test all values. Skip 7 to pseudo-randomize. +for(let i=0;i<2**nProps;i+=skip) { + const enableVec = Number(i).toString(2).padStart(nProps,'0').split('').map(s => (s==="1")); + let windowFeatures = []; + for(let i=0;i<nProps;++i) { + if (enableVec[i]) + windowFeatures.push(features[i] + "=yes"); + } + windowFeatures = windowFeatures.join(','); + // We get a popup we got windowFeatures, and any of them are false: + const expectPopup = !!windowFeatures.length && (!(enableVec[0] || enableVec[1]) || !enableVec[2] || !enableVec[3] || !enableVec[4] || !enableVec[5]); + testOne(windowFeatures, expectPopup); + testOne(windowFeatures + ",noopener", /*expectPopup=*/false); + testOne(windowFeatures + ",noreferrer", /*expectPopup=*/false); + testOne(windowFeatures + ",popup", /*expectPopup=*/true); // "popup" feature = popup + testOne(windowFeatures + ",noopener,noreferrer,popup", /*expectPopup=*/false); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/window-open-windowfeatures-values.html b/testing/web-platform/tests/html/browsers/the-window-object/window-open-windowfeatures-values.html new file mode 100644 index 0000000000..32551dd8d7 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/window-open-windowfeatures-values.html @@ -0,0 +1,72 @@ +<!doctype html> +<meta charset=utf-8> +<meta name="timeout" content="long"> +<title>window.open() windowFeature value parsing</title> +<link rel="author" href="mailto:masonf@chromium.org"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/window-object.html#concept-window-open-features-parse-boolean"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +function testValueGeneric(val, expectTrue, property, testFn) { + const windowFeatureStr = val === "" ? property : `${property}=${val}`; + async_test(t => { + const windowName = '' + Math.round(Math.random()*1e12); + const channel = new BroadcastChannel(windowName); + channel.onmessage = t.step_func_done(e => { + // Send message first so if asserts throw the popup is still closed + channel.postMessage(null); + testFn(e.data); + }); + window.open("support/windowFeature-values-target.html?" + windowName, windowName, windowFeatureStr); + },`Test ${windowFeatureStr}, expected interpretation is ${expectTrue ? 'true' : 'false'}`); +} + +function testValueForNoReferrer(val, expectTrue) { + testValueGeneric(val, expectTrue, "noreferrer", (data) => { + if (expectTrue) { + assert_false(data.haveReferrer); + assert_false(data.haveOpener); + } else { + assert_true(data.haveReferrer); + assert_true(data.haveOpener); + } + }); +} + +function testValueForNoOpener(val, expectTrue) { + testValueGeneric(val, expectTrue, "noopener", (data) => { + assert_equals(data.haveOpener, !expectTrue); + }); +} + +function testValueForPopup(val, expectTrue) { + testValueGeneric(val, expectTrue, "popup", (data) => { + assert_equals(data.isPopup, expectTrue); + }); +} + +function testValue(val, expectTrue) { + const quotes = val === "" ? [''] : ['','"',"'"]; + let noQuotes = true; + for (const quote of quotes) { + const thisExpectTrue = expectTrue && noQuotes; + const thisVal = quote + val + quote; + testValueForNoReferrer(thisVal, thisExpectTrue); + testValueForNoOpener(thisVal, thisExpectTrue); + testValueForPopup(thisVal, thisExpectTrue); + noQuotes = false; + } +} + +testValue('',true); // Just the parameter means true +testValue('yes',true); // Yes means true +testValue('true',true); // True means true +testValue('foo',false); // If parsing as an integer is an error, false +testValue('0',false); // 0 is false +testValue('00',false); // 0 is false +testValue('1',true); // Non-zero is true +testValue('99999',true); // Non-zero is true +testValue('-1',true); // Non-zero is true +testValue('1foo',true); // This parses to 1 +testValue('0foo',false); // This parses to 0 +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/window-opener-unconfigurable.window.js b/testing/web-platform/tests/html/browsers/the-window-object/window-opener-unconfigurable.window.js new file mode 100644 index 0000000000..2b9bda6792 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/window-opener-unconfigurable.window.js @@ -0,0 +1,17 @@ +test(t => { + let desc = Object.getOwnPropertyDescriptor(self, "opener"); + assert_true(!!desc.get, "Initially {get: function}"); + assert_true(!!desc.set, "Initially {set: function}"); + assert_true(desc.configurable, "Initially {configurable: true}"); + assert_true(desc.enumerable, "Initially {enumerable: true}"); + + Object.defineProperty(self, "opener", {configurable: false}); + + desc = Object.getOwnPropertyDescriptor(self, "opener"); + assert_true(!!desc.get, "Still has {get: function}"); + assert_true(!!desc.set, "Still has {set: function}"); + assert_false(desc.configurable, "Changed to {configurable: false}"); + assert_true(desc.enumerable, "Still has {enumerable: true}"); + + assert_throws_js(TypeError, () => self.opener = "something", "Throws a TypeError due to {configurable: false}"); +}, "Corner case: self.opener is set while it's not configurable"); diff --git a/testing/web-platform/tests/html/browsers/the-window-object/window-properties.https.html b/testing/web-platform/tests/html/browsers/the-window-object/window-properties.https.html new file mode 100644 index 0000000000..c61fb04ae2 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/window-properties.https.html @@ -0,0 +1,358 @@ +<!doctype html> +<meta charset=utf-8> +<title>Properties of the window object</title> +<link rel="author" title="Ms2ger" href="mailto:Ms2ger@gmail.com"> +<link rel="help" href="http://ecma-international.org/ecma-262/5.1/#sec-15.1"> +<link rel="help" href="https://webidl.spec.whatwg.org/#interface-prototype-object"> +<link rel="help" href="https://webidl.spec.whatwg.org/#es-attributes"> +<link rel="help" href="https://webidl.spec.whatwg.org/#es-operations"> +<link rel="help" href="https://dom.spec.whatwg.org/#eventtarget"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#window"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#windowtimers"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#windowbase64"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#windowsessionstorage"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#windowlocalstorage"> +<link rel="help" href="https://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#dom-window-getselection"> +<link rel="help" href="http://dev.w3.org/csswg/cssom/#widl-def-Window"> +<link rel="help" href="http://dev.w3.org/csswg/cssom-view/#widl-def-Window"> +<iframe id="iframe" style="visibility: hidden" src="/resources/blank.html"></iframe> +<iframe id="detachedIframe" style="visibility: hidden" src="/resources/blank.html"></iframe> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id=log></div> +<script> +function assert_data_propdesc(pd, Writable, Enumerable, Configurable) { + assert_equals(typeof pd, "object"); + assert_equals(pd.writable, Writable); + assert_equals(pd.enumerable, Enumerable); + assert_equals(pd.configurable, Configurable); +} +function assert_accessor_propdesc(pd, hasSetter, Enumerable, Configurable) { + assert_equals(typeof pd, "object"); + assert_equals(typeof pd.get, "function"); + assert_true("set" in pd, + "Should always have a setter property on the property descriptor"); + assert_equals(typeof pd.set, hasSetter ? "function" : "undefined"); + assert_equals(pd.enumerable, Enumerable); + assert_equals(pd.configurable, Configurable); +} + +var unforgeableAttributes = [ + "window", + "document", + "location", + "top" +]; + +var replaceableAttributes = [ + "self", + "locationbar", + "menubar", + "personalbar", + "scrollbars", + "statusbar", + "toolbar", + "frames", + "parent", + "external", + "length", + "origin", + + // CSSOM-View + "screen", + "scrollX", + "scrollY", + "pageXOffset", + "pageYOffset", + "innerWidth", + "innerHeight", + "screenLeft", + "screenTop", + "screenX", + "screenY", + "outerWidth", + "outerHeight", + "devicePixelRatio", +]; + +var methods = [ + "close", + "stop", + "focus", + "blur", + "open", + "alert", + "confirm", + "prompt", + "print", + "postMessage", + + // WindowBase64 + "btoa", + "atob", + + // WindowTimers + "setTimeout", + "clearTimeout", + "setInterval", + "clearInterval", + + // Microtask queuing + "queueMicrotask", + + // ImageBitmap + "createImageBitmap", + + // HTML Editing APIs + "getSelection", + + // CSSOM + "getComputedStyle", + + // CSSOM-View + "matchMedia", + "moveBy", + "moveTo", + "resizeBy", + "resizeTo", + "scroll", + "scrollTo", + "scrollBy" +]; + +var readonlyAttributes = [ + "history", + "frameElement", + "navigator", + + // WindowSessionStorage + "sessionStorage", + + // WindowLocalStorage + "localStorage", +]; + +var writableAttributes = [ + "name", + "status", + "opener", + "onabort", + "onafterprint", + "onbeforeprint", + "onbeforeunload", + "onblur", + "oncancel", + "oncanplay", + "oncanplaythrough", + "onchange", + "onclick", + "onclose", + "oncontextmenu", + "oncuechange", + "ondblclick", + "ondrag", + "ondragend", + "ondragenter", + "ondragleave", + "ondragover", + "ondragstart", + "ondrop", + "ondurationchange", + "onemptied", + "onended", + "onerror", + "onfocus", + "onhashchange", + "oninput", + "oninvalid", + "onkeydown", + "onkeypress", + "onkeyup", + "onload", + "onloadeddata", + "onloadedmetadata", + "onloadstart", + "onmessage", + "onmousedown", + "onmousemove", + "onmouseout", + "onmouseover", + "onmouseup", + "onmousewheel", + "onoffline", + "ononline", + "onpause", + "onplay", + "onplaying", + "onpagehide", + "onpageshow", + "onpopstate", + "onprogress", + "onratechange", + "onreset", + "onresize", + "onscroll", + "onseeked", + "onseeking", + "onselect", + "onstalled", + "onstorage", + "onsubmit", + "onsuspend", + "ontimeupdate", + "onunload", + "onvolumechange", + "onwaiting" +]; + +test(function() { + // 15.1.1 Value Properties of the Global Object + ["NaN", "Infinity", "undefined"].forEach(function(id) { + test(function() { + assert_true(id in window, id + " in window"); + assert_data_propdesc(Object.getOwnPropertyDescriptor(window, id), + false, false, false); + }, "Value Property: " + id); + }); +}, "Value Properties of the Global Object"); +test(function() { + // 15.1.2 Function Properties of the Global Object + ["eval", "parseInt", "parseFloat", "isNaN", "isFinite"].forEach(function(id) { + test(function() { + assert_true(id in window, id + " in window"); + assert_data_propdesc(Object.getOwnPropertyDescriptor(window, id), + true, false, true); + }, "Function Property: " + id); + }); +}, "Function Properties of the Global Object"); +test(function() { + // 15.1.3 URI Handling Function Properties + ["decodeURI", "decodeURIComponent", "encodeURI", "encodeURIComponent"].forEach(function(id) { + test(function() { + assert_true(id in window, id + " in window"); + assert_data_propdesc(Object.getOwnPropertyDescriptor(window, id), + true, false, true); + }, "URI Handling Function Property: " + id); + }); +}, "URI Handling Function Properties"); +test(function() { + // 15.1.4 Constructor Properties of the Global Object + ["Object", "Function", "Array", "String", "Boolean", "Number", "Date", + "RegExp", "Error", "EvalError", "RangeError", "ReferenceError", + "SyntaxError", "TypeError", "URIError"].forEach(function(id) { + test(function() { + assert_true(id in window, id + " in window"); + assert_data_propdesc(Object.getOwnPropertyDescriptor(window, id), + true, false, true); + }, "Constructor Property: " + id); + }); +}, "Constructor Properties of the Global Object"); +test(function() { + // 15.1.5 Other Properties of the Global Object + ["Math", "JSON"].forEach(function(id) { + test(function() { + assert_true(id in window, id + " in window"); + assert_data_propdesc(Object.getOwnPropertyDescriptor(window, id), + true, false, true); + }, "Other Property: " + id); + }); +}, "Other Properties of the Global Object"); +test(function() { + // EventTarget interface + ["addEventListener", "removeEventListener", "dispatchEvent"].forEach(function(id) { + test(function() { + var EventTargetProto = EventTarget.prototype; + assert_true(id in window, id + " in window"); + assert_equals(window[id], EventTargetProto[id]); + assert_data_propdesc(Object.getOwnPropertyDescriptor(EventTargetProto, id), + true, true, true); + assert_equals(Object.getOwnPropertyDescriptor(window, id), undefined); + }, "EventTarget method: " + id); + }); +}, "EventTarget interface"); +test(function() { + // Window interface + methods.forEach(function(id) { + test(function() { + var WindowProto = Window.prototype; + assert_true(id in window, id + " in window"); + assert_false(id in WindowProto, id + " in Window.prototype"); + assert_data_propdesc(Object.getOwnPropertyDescriptor(window, id), + true, true, true); + }, "Window method: " + id); + }); + readonlyAttributes.forEach(function(id) { + test(function() { + var WindowProto = Window.prototype; + assert_true(id in window, id + " in window"); + assert_false(id in WindowProto, id + " in Window.prototype"); + assert_accessor_propdesc(Object.getOwnPropertyDescriptor(window, id), + false, true, true); + }, "Window readonly attribute: " + id); + }); + writableAttributes.forEach(function(id) { + test(function() { + var WindowProto = Window.prototype; + assert_true(id in window, id + " in window"); + assert_false(id in WindowProto, id + " in Window.prototype"); + assert_accessor_propdesc(Object.getOwnPropertyDescriptor(window, id), + true, true, true); + }, "Window attribute: " + id); + }); + unforgeableAttributes.forEach(function(id) { + test(function() { + var WindowProto = Window.prototype; + assert_true(id in window, id + " in window"); + assert_false(id in WindowProto, id + " in Window.prototype"); + // location has a [PutForwards] extended attribute. + assert_accessor_propdesc(Object.getOwnPropertyDescriptor(window, id), + id === "location", true, false); + }, "Window unforgeable attribute: " + id); + }); + replaceableAttributes.forEach(function(id) { + test(function() { + var WindowProto = Window.prototype; + assert_true(id in window, id + " in window"); + assert_false(id in WindowProto, id + " in Window.prototype"); + assert_accessor_propdesc(Object.getOwnPropertyDescriptor(window, id), + true, true, true); + }, "Window replaceable attribute: " + id); + }); +}, "Window interface"); +test(function() { + assert_equals(window.constructor, Window); + assert_false(window.hasOwnProperty("constructor"), "window.constructor should not be an own property."); + assert_data_propdesc(Object.getOwnPropertyDescriptor(Window.prototype, "constructor"), + true, false, true); +}, "constructor"); +var selfReferentialAccessors = ['window', 'self', 'frames']; +// Test a variety of access methods +var detached = window.detachedIframe; +var detachedWindow = detached.contentWindow; +detachedIframe.remove(); +selfReferentialAccessors.forEach(function(id) { + test(function() { + assert_equals(eval(`window.${id}`), window); + assert_equals(window[id], window); + assert_equals(Object.getOwnPropertyDescriptor(window, id).get.call(window), window); + assert_equals(Reflect.get(window, id), window); + }, "Window readonly getter: " + id); + + test(function() { + var globalObject = iframe.contentWindow; + var _eval = globalObject.eval; + assert_equals(globalObject.eval(`window.${id}`), globalObject); + assert_equals(globalObject[id], globalObject); + assert_equals(_eval(`Object.getOwnPropertyDescriptor(window, "${id}").get.call(window)`), globalObject); + assert_equals(_eval(`Reflect.get(window, "${id}")`), globalObject); + }, "Window readonly getter in iframe: " + id); + test(function() { + var globalObject = detachedWindow; + var _eval = globalObject.eval; + assert_equals(_eval(`window.${id}`), globalObject); + assert_equals(globalObject[id], globalObject); + assert_equals(_eval(`Object.getOwnPropertyDescriptor(window, "${id}").get.call(window)`), globalObject); + assert_equals(_eval(`Reflect.get(window, "${id}")`), globalObject); + }, "Window readonly getter in detached iframe: " + id); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/window-prototype-chain.html b/testing/web-platform/tests/html/browsers/the-window-object/window-prototype-chain.html new file mode 100644 index 0000000000..14dbca35e8 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/window-prototype-chain.html @@ -0,0 +1,32 @@ +<!doctype html> +<meta charset=utf-8> +<title>Prototype chain of the window object</title> +<link rel="author" title="Ms2ger" href="ms2ger@gmail.com"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#window"> +<link rel="help" href="https://dom.spec.whatwg.org/#eventtarget"> +<link rel="help" href="https://webidl.spec.whatwg.org/#interface-prototype-object"> +<link rel="help" href="https://webidl.spec.whatwg.org/#named-properties-object"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id=log></div> +<script> +test(function() { + assert_class_string(window, "Window"); +}, "window object"); +test(function() { + var proto = Object.getPrototypeOf(window); + assert_equals(proto, Window.prototype); +}, "Window.prototype"); +test(function() { + var gsp = Object.getPrototypeOf(Object.getPrototypeOf(window)); + assert_class_string(gsp, "WindowProperties"); +}, "Global scope polluter"); +test(function() { + var protoproto = Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(window))); + assert_equals(protoproto, EventTarget.prototype); +}, "EventTarget.prototype"); +test(function() { + var protoprotoproto = Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(window)))); + assert_equals(protoprotoproto, Object.prototype); +}, "Object.prototype"); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-window-object/window-reuse-in-nested-browsing-contexts.tentative.html b/testing/web-platform/tests/html/browsers/the-window-object/window-reuse-in-nested-browsing-contexts.tentative.html new file mode 100644 index 0000000000..03c7a68dfd --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-window-object/window-reuse-in-nested-browsing-contexts.tentative.html @@ -0,0 +1,158 @@ +<!doctype html> +<meta charset=utf-8> +<title></title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id=log></div> +<script> +const setupIframe = (t, attrs) => { + const iframe = document.createElement('iframe'); + for (const [key, value] of Object.entries(attrs)) + iframe[key] = value; + const watcher = new EventWatcher(t, iframe, ['load']); + document.body.appendChild(iframe); + return {iframe, watcher}; +}; + +promise_test(async t => { + const {iframe, watcher} = setupIframe(t, {}); + + // Per https://whatwg.org/c/iframe-embed-object.html#process-the-iframe-attributes, + // the task to perform the "iframe load event steps" should still be queued. + // If a browser already fired a load event, the test will fail here since + // EventWatcher will have received an unexpected load event. + + iframe.contentWindow.persistedString = 'Hello world!'; + iframe.src = 'support/same-origin-iframe.html'; + await watcher.wait_for(['load']); + + assert_true(iframe.contentWindow.didLoadFrame); + // The <iframe>'s session history has only one Document, and that Document is + // the initial about:blank Document. The Window object should be reused per + // https://whatwg.org/c/iframe-embed-object.html#process-the-iframe-attributes. + assert_equals(iframe.contentWindow.persistedString, 'Hello world!'); +}, 'synchronously navigate iframe with no initial src.'); + +promise_test(async t => { + const {iframe, watcher} = setupIframe(t, {}); + + // Per https://whatwg.org/c/iframe-embed-object.html#process-the-iframe-attributes, + // the task to perform the "iframe load event steps" should still be queued. + await watcher.wait_for(['load']); + + iframe.contentWindow.persistedString = 'Hello world!'; + iframe.src = 'support/same-origin-iframe.html'; + await watcher.wait_for(['load']); + + assert_true(iframe.contentWindow.didLoadFrame); + // The <iframe>'s session history has only one Document, and that Document is + // the initial about:blank Document. The Window object should be reused per + // https://whatwg.org/c/iframe-embed-object.html#process-the-iframe-attributes. + assert_equals(iframe.contentWindow.persistedString, 'Hello world!'); +}, 'after the first iframe load event, navigate iframe with no initial src.'); + +// Per https://whatwg.org/c/iframe-embed-object.html#otherwise-steps-for-iframe-or-frame-elements, +// setting the <iframe> src to an empty string before inserting the <iframe> +// into the document should begin an attempt to navigate to a resource with +// url == "about:blank". +promise_test(async t => { + const {iframe, watcher} = setupIframe(t, {src: ''}); + + // Per https://whatwg.org/c/browsing-the-web.html#navigate, the "about:blank" + // resource should be obtained "in parallel". If a browser performs this step + // synchronously, the test will fail here since EventWatcher will have + // received an unexpected load event. + + iframe.contentWindow.persistedString = 'Hello world!'; + // An attempt to navigate to "about:blank" already exists but should not have + // matured yet since the resource should be obtained "in parallel". The new + // navigation attempt will cancel the preexisting attempt. + iframe.src = 'support/same-origin-iframe.html'; + await watcher.wait_for(['load']); + + assert_true(iframe.contentWindow.didLoadFrame); + // The navigation attempt to "about:blank" was cancelled, so the <iframe>'s + // session history has only one Document, and that Document is the + // initial about:blank Document. The Window object should be reused per + // https://whatwg.org/c/iframe-embed-object.html#process-the-iframe-attributes. + assert_equals(iframe.contentWindow.persistedString, 'Hello world!'); +}, 'synchronously navigate iframe with initial src == "".'); + +promise_test(async t => { + const {iframe, watcher} = setupIframe(t, {src: ''}); + + // Per https://whatwg.org/c/browsing-the-web.html#navigate, the "about:blank" + // resource should be obtained "in parallel". + await watcher.wait_for(['load']); + + iframe.contentWindow.persistedString = 'Hello world!'; + iframe.src = 'support/same-origin-iframe.html'; + await watcher.wait_for(['load']); + + assert_true(iframe.contentWindow.didLoadFrame); + // A non-initial navigation to about:blank was committed, so the <iframe> + // element is no longer displaying the initial about:blank Document. Per + // https://whatwg.org/c/browsing-the-web.html#initialise-the-document-object, + // the Window object must not be reused. + assert_equals(iframe.contentWindow.persistedString, undefined); +}, 'after the first iframe load event, navigate iframe with initial src == "".'); + +promise_test(async t => { + const {iframe, watcher} = setupIframe(t, {src: 'about:blank'}); + + // Per https://whatwg.org/c/browsing-the-web.html#navigate, the "about:blank" + // resource should be obtained "in parallel". If a browser performs this step + // synchronously, the test will fail here since EventWatcher will have + // received an unexpected load event. + + iframe.contentWindow.persistedString = 'Hello world!'; + // An attempt to navigate to "about:blank" already exists but should not have + // matured yet since the resource should be obtained "in parallel". The new + // navigation attempt will cancel the preexisting attempt. + iframe.src = 'support/same-origin-iframe.html'; + await watcher.wait_for(['load']); + + assert_true(iframe.contentWindow.didLoadFrame); + // The navigation attempt to "about:blank" was cancelled, so the <iframe>'s + // session history has only one Document, and that Document is the + // initial about:blank Document. The Window object should be reused per + // https://whatwg.org/c/iframe-embed-object.html#process-the-iframe-attributes. + assert_equals(iframe.contentWindow.persistedString, 'Hello world!'); +}, 'synchronously navigate iframe with initial src == "about:blank".'); + +promise_test(async t => { + const {iframe, watcher} = setupIframe(t, {src: 'about:blank'}); + + // Per https://whatwg.org/c/browsing-the-web.html#navigate, the "about:blank" + // resource should be obtained "in parallel". + await watcher.wait_for(['load']); + + iframe.contentWindow.persistedString = 'Hello world!'; + iframe.src = 'support/same-origin-iframe.html'; + await watcher.wait_for(['load']); + + assert_true(iframe.contentWindow.didLoadFrame); + // A non-initial navigation to about:blank was committed, so the <iframe> + // element is no longer displaying the initial about:blank Document. Per + // https://whatwg.org/c/browsing-the-web.html#initialise-the-document-object, + // the Window object must not be reused. + assert_equals(iframe.contentWindow.persistedString, undefined); +}, 'after the first iframe load event, navigate iframe with initial src == "about:blank".'); + +// Per https://whatwg.org/c/iframe-embed-object.html#otherwise-steps-for-iframe-or-frame-elements, +// setting <iframe> src before inserting the <iframe> into the document should +// begin an attempt to navigate to the value of the src attribute. +promise_test(async t => { + const {iframe, watcher} = setupIframe(t, {src: 'support/same-origin-iframe.html'}); + + iframe.contentWindow.persistedString = 'Hello world!'; + // Completion of the attempt to navigate happens "in parallel". + await watcher.wait_for(['load']); + + assert_true(iframe.contentWindow.didLoadFrame); + // The <iframe>'s session history has only one Document, and that Document is + // the initial about:blank Document. The Window object should be reused per + // https://whatwg.org/c/iframe-embed-object.html#process-the-iframe-attributes. + assert_equals(iframe.contentWindow.persistedString, 'Hello world!'); +}, 'iframe with initial src == same-origin resource.'); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-windowproxy-exotic-object/document-tree-child-browsing-context-name-property-set.sub.html b/testing/web-platform/tests/html/browsers/the-windowproxy-exotic-object/document-tree-child-browsing-context-name-property-set.sub.html new file mode 100644 index 0000000000..171aa01999 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-windowproxy-exotic-object/document-tree-child-browsing-context-name-property-set.sub.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>document-tree child browsing context name property set</title> +<link rel="help" href="https://html.spec.whatwg.org/C/#document-tree-child-browsing-context-name-property-set"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<iframe src="//{{domains[www]}}:{{ports[http][1]}}/common/window-name-setter.html#spices"></iframe> +<iframe name="spices"></iframe> +<iframe name="fruits"></iframe> + +<script> +"use strict"; +setup({ explicit_done: true }); + +window.onload = () => { + test(() => { + assert_equals(window.spices, undefined); + assert_not_equals(window.fruits, undefined); + assert_equals(window.fruits, window[2]); + }, "Cross origin child window's name shadows the second candidate of a same origin iframe"); + + done(); +}; +</script> diff --git a/testing/web-platform/tests/html/browsers/the-windowproxy-exotic-object/windowproxy-define-own-property-unforgeable-same-origin.html b/testing/web-platform/tests/html/browsers/the-windowproxy-exotic-object/windowproxy-define-own-property-unforgeable-same-origin.html new file mode 100644 index 0000000000..ac4eb458c3 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-windowproxy-exotic-object/windowproxy-define-own-property-unforgeable-same-origin.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>[[DefineOwnProperty]] on a WindowProxy forwards to OrdinaryDefineOwnProperty for same-origin objects</title> +<link rel="help" href="https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-defineownproperty"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> +"use strict"; + +for (const key of ["window", "document", "location", "top"]) { + const { get, set } = Object.getOwnPropertyDescriptor(window, key); + + test(() => { + Object.defineProperty(window, key, {}); + assert_true(Reflect.defineProperty(window, key, { configurable: false }), "[[Configurable]]: false"); + Object.defineProperty(window, key, { enumerable: true }); + + assert_true(Reflect.defineProperty(window, key, { get }), "[[Get]]: unchanged"); + Object.defineProperty(window, key, { set }); + assert_true(Reflect.defineProperty(window, key, { get, set }), "[[Get]]: unchanged, [[Set]]: unchanged"); + + Object.defineProperty(window, key, { get, set, enumerable: true, configurable: false }); + }, `[[DefineOwnProperty]] success: "${key}"`); + + test(() => { + assert_throws_js(TypeError, () => { + Object.defineProperty(window, key, { configurable: true }); + }, "[[Configurable]]: true"); + + assert_false(Reflect.defineProperty(window, key, { enumerable: false }), "[[Enumerable]]: false"); + + assert_throws_js(TypeError, () => { + Object.defineProperty(window, key, { get() {}, set }); + }, "[[Get]]: changed, [[Set]]: unchanged"); + + assert_false(Reflect.defineProperty(window, key, { get, set() {} }), "[[Get]]: unchanged, [[Set]]: changed"); + + assert_throws_js(TypeError, () => { + Object.defineProperty(window, key, { writable: false, configurable: true }); + }, "[[Writable]]: false, [[Configurable]]: true"); + + assert_false(Reflect.defineProperty(window, key, { value: window[key], enumerable: true }), "[[Value]], [[Enumerable]]: true"); + }, `[[DefineOwnProperty]] failure: "${key}"`); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/the-windowproxy-exotic-object/windowproxy-prevent-extensions.html b/testing/web-platform/tests/html/browsers/the-windowproxy-exotic-object/windowproxy-prevent-extensions.html new file mode 100644 index 0000000000..97a156290a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-windowproxy-exotic-object/windowproxy-prevent-extensions.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>[[PreventExtensions]] on a WindowProxy object should return false</title> +<link rel="help" href="https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-preventextensions"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> +"use strict"; + +test(() => { + assert_throws_js(TypeError, () => { + Object.preventExtensions(window); + }); +}, "Object.preventExtensions throws a TypeError"); + +test(() => { + assert_false(Reflect.preventExtensions(window)); +}, "Reflect.preventExtensions returns false"); +</script> diff --git a/testing/web-platform/tests/html/browsers/the-windowproxy-exotic-object/windowproxy-prototype-setting-cross-origin-domain.sub.html b/testing/web-platform/tests/html/browsers/the-windowproxy-exotic-object/windowproxy-prototype-setting-cross-origin-domain.sub.html new file mode 100644 index 0000000000..a5ae78cc87 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-windowproxy-exotic-object/windowproxy-prototype-setting-cross-origin-domain.sub.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>[[SetPrototypeOf]] on a WindowProxy object should not allow changing its value: cross-origin via document.domain</title> +<link rel="help" href="http://html.spec.whatwg.org/multipage/#windowproxy-setprototypeof"> +<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/test-setting-immutable-prototype.js"></script> + +<iframe src="/common/domain-setter.sub.html"></iframe> + +<script> +"use strict"; +// This page does *not* set document.domain, so it's cross-origin with the iframe +setup({ explicit_done: true }); + +window.onload = () => { + const target = frames[0]; + + test(() => { + assert_equals(Object.getPrototypeOf(target), null); + }, "Cross-origin via document.domain: the prototype is null"); + + testSettingImmutablePrototype("Cross-origin via document.domain", target, null, { isSameOriginDomain: false }); + + done(); +}; +</script> diff --git a/testing/web-platform/tests/html/browsers/the-windowproxy-exotic-object/windowproxy-prototype-setting-cross-origin.sub.html b/testing/web-platform/tests/html/browsers/the-windowproxy-exotic-object/windowproxy-prototype-setting-cross-origin.sub.html new file mode 100644 index 0000000000..a5c1a2f102 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-windowproxy-exotic-object/windowproxy-prototype-setting-cross-origin.sub.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>[[SetPrototypeOf]] on a WindowProxy object should not allow changing its value: cross-origin</title> +<link rel="help" href="http://html.spec.whatwg.org/multipage/#windowproxy-setprototypeof"> +<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/test-setting-immutable-prototype.js"></script> + +<iframe src="//{{domains[www]}}:{{ports[http][1]}}/common/blank.html"></iframe> + +<script> +"use strict"; +setup({ explicit_done: true }); + +window.onload = () => { + const target = frames[0]; + + test(() => { + assert_equals(Object.getPrototypeOf(target), null); + }, "Cross-origin: the prototype is null"); + + testSettingImmutablePrototype("Cross-origin", target, null, { isSameOriginDomain: false }); + + done(); +}; +</script> diff --git a/testing/web-platform/tests/html/browsers/the-windowproxy-exotic-object/windowproxy-prototype-setting-goes-cross-origin-domain.sub.html b/testing/web-platform/tests/html/browsers/the-windowproxy-exotic-object/windowproxy-prototype-setting-goes-cross-origin-domain.sub.html new file mode 100644 index 0000000000..239fa6ddf2 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-windowproxy-exotic-object/windowproxy-prototype-setting-goes-cross-origin-domain.sub.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>[[SetPrototypeOf]] on a WindowProxy object should not allow changing its value: cross-origin via document.domain after initially getting the object</title> +<link rel="help" href="http://html.spec.whatwg.org/multipage/#windowproxy-setprototypeof"> +<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/test-setting-immutable-prototype.js"></script> + +<iframe src="/common/blank.html"></iframe> + +<script> +"use strict"; +setup({ explicit_done: true }); + +window.onload = () => { + const target = frames[0]; + const origProto = Object.getPrototypeOf(target); + + test(() => { + assert_not_equals(origProto, null); + }, "Same-origin (for now): the prototype is accessible"); + + document.domain = "{{host}}"; + + test(() => { + assert_equals(Object.getPrototypeOf(target), null); + }, "Became cross-origin via document.domain: the prototype is now null"); + + testSettingImmutablePrototype("Became cross-origin via document.domain", target, null, { isSameOriginDomain: false }); + + testSettingImmutablePrototypeToNewValueOnly( + "Became cross-origin via document.domain", target, origProto, + "the original value from before going cross-origin", { isSameOriginDomain: false }); + + done(); +}; +</script> diff --git a/testing/web-platform/tests/html/browsers/the-windowproxy-exotic-object/windowproxy-prototype-setting-same-origin-domain.sub.html b/testing/web-platform/tests/html/browsers/the-windowproxy-exotic-object/windowproxy-prototype-setting-same-origin-domain.sub.html new file mode 100644 index 0000000000..fb18822ac5 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-windowproxy-exotic-object/windowproxy-prototype-setting-same-origin-domain.sub.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>[[SetPrototypeOf]] on a WindowProxy object should not allow changing its value: cross-origin, but same-origin-domain</title> +<link rel="help" href="http://html.spec.whatwg.org/multipage/#windowproxy-setprototypeof"> +<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/test-setting-immutable-prototype.js"></script> + +<iframe src="//{{domains[www]}}:{{ports[http][1]}}/common/domain-setter.sub.html"></iframe> + +<script> +"use strict"; +document.domain = "{{host}}"; +setup({ explicit_done: true }); + +window.onload = () => { + const target = frames[0]; + const origProto = Object.getPrototypeOf(target); + + test(() => { + assert_not_equals(origProto, null); + }, "Same-origin-domain prerequisite check: the original prototype is accessible"); + + testSettingImmutablePrototype("Same-origin-domain", target, origProto, { isSameOriginDomain: true }, frames[0]); + + done(); +}; +</script> diff --git a/testing/web-platform/tests/html/browsers/the-windowproxy-exotic-object/windowproxy-prototype-setting-same-origin.html b/testing/web-platform/tests/html/browsers/the-windowproxy-exotic-object/windowproxy-prototype-setting-same-origin.html new file mode 100644 index 0000000000..5779199ca3 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/the-windowproxy-exotic-object/windowproxy-prototype-setting-same-origin.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>[[SetPrototypeOf]] on a WindowProxy object should not allow changing its value: same-origin</title> +<link rel="help" href="http://html.spec.whatwg.org/multipage/#windowproxy-setprototypeof"> +<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/test-setting-immutable-prototype.js"></script> + +<script> +"use strict"; + +const origProto = Object.getPrototypeOf(window); + +test(() => { + assert_not_equals(origProto, null); +}, "Same-origin prerequisite check: the original prototype is accessible"); + +testSettingImmutablePrototype("Same-origin", window, origProto, { isSameOriginDomain: true }); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/opener-closed.html b/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/opener-closed.html new file mode 100644 index 0000000000..106c3f97cb --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/opener-closed.html @@ -0,0 +1,31 @@ +<!doctype html> +<meta charset="utf-8"> +<html> + <head> + <title>Auxiliary Browsing Contexts: window.opener when Opener Removed/Closed</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/common/PrefixedLocalStorage.js"></script> + </head> + <body> + <div id="log"></div> + <script> + var prefixedLocalStorage; + setup (() => prefixedLocalStorage = new PrefixedLocalStorageTest()); + async_test(t => { + t.add_cleanup (() => prefixedLocalStorage.cleanup()); + var a = document.createElement('a'); + a.href = prefixedLocalStorage.url('resources/open-closer.html'); + a.target = '_blank'; + prefixedLocalStorage.onSet('openerIsNull', t.step_func_done(e => { + // The window for this auxiliary browsing context's opener + // has been closed and discarded, so the aux browsing context + // should now report `null` for `window.opener` + assert_equals(e.newValue, 'true'); + })); + document.body.append(a); + a.click(); + }, 'An auxiliary browsing context should report `null` for `window.opener` when that browsing context is discarded'); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/opener-multiple.html b/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/opener-multiple.html new file mode 100644 index 0000000000..e71d4dc868 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/opener-multiple.html @@ -0,0 +1,35 @@ +<!doctype html> +<html> + <head> + <title>Auxiliary Browsing Contexts: window.opener, multiple</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/common/PrefixedLocalStorage.js"></script> + </head> + <body> + <div id="log"></div> + <script> + var prefixedLocalStorage; + setup (() => prefixedLocalStorage = new PrefixedLocalStorageTest()); + async_test(t => { + t.add_cleanup (() => prefixedLocalStorage.cleanup()); + var a = document.createElement('a'); + a.href = prefixedLocalStorage.url('resources/multiple-opener.html'); + a.target = 'multipleOpener'; + window.name = 'topOpener'; + document.body.appendChild(a); + window.addEventListener('message', t.step_func_done(e => { + var aux1 = e.data.aux1; // First opened context + var aux2 = e.data.aux2; // Context opened by first-opened context + assert_equals(aux1.name, 'multipleOpener'); + assert_equals(aux1.openerName, window.name); + assert_equals(aux1.isTop, true); + assert_equals(aux2.name, 'multipleOpenee'); + assert_equals(aux2.openerName, aux1.name); + assert_equals(aux2.isTop, true); + })); + a.click(); + }, 'An auxiliary browsing context should be able to open another auxiliary browsing context'); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/opener-noopener.html b/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/opener-noopener.html new file mode 100644 index 0000000000..086a96442d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/opener-noopener.html @@ -0,0 +1,25 @@ +<!doctype html> +<html> + <head> + <title>Auxiliary Browsing Contexts: window.opener noopener</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/common/PrefixedLocalStorage.js"></script> + </head> + <body> + <div id="log"></div> + <script> + var prefixedLocalStorage; + setup(() => prefixedLocalStorage = new PrefixedLocalStorageTest()); + async_test(t => { + t.add_cleanup(() => prefixedLocalStorage.cleanup()); + prefixedLocalStorage.onSet('openerIsNull', t.step_func_done(e => { + assert_equals(e.newValue, 'true'); + })); + window.open(prefixedLocalStorage.url('resources/no-opener.html'), + 'iShouldNotHaveAnOpener', + 'noopener'); + }, 'Auxiliary browsing context created via `window.open` setting `noopener` should report `window.opener` `null`'); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/opener-noreferrer.html b/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/opener-noreferrer.html new file mode 100644 index 0000000000..b8226bd2b9 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/opener-noreferrer.html @@ -0,0 +1,29 @@ +<!doctype html> +<html> + <head> + <title>Auxiliary Browsing Contexts: window.opener noreferrer</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/common/PrefixedLocalStorage.js"></script> + </head> + <body> + <div id="log"></div> + <script> + var prefixedLocalStorage; + setup(() => prefixedLocalStorage = new PrefixedLocalStorageTest()); + async_test(t => { + t.add_cleanup(() => prefixedLocalStorage.cleanup()); + var a = document.createElement('a'); + a.href = prefixedLocalStorage.url('resources/no-opener.html'); + a.target = '_blank'; + a.rel = 'noreferrer'; + window.name = 'topWindow'; + document.body.appendChild(a); + prefixedLocalStorage.onSet('openerIsNull', t.step_func_done(e => { + assert_equals(e.newValue, 'true'); + })); + a.click(); + }, 'Auxiliary browsing context created with `rel="noreferrer"` should report `window.opener` `null`'); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/opener-setter.html b/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/opener-setter.html new file mode 100644 index 0000000000..ac6e47b846 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/opener-setter.html @@ -0,0 +1,33 @@ +<!doctype html> +<html> + <head> + <title>Auxiliary Browsing Contexts: window.opener setter</title> + <meta name=timeout content=long> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/common/PrefixedLocalStorage.js"></script> + </head> + <body> + <div id="log"></div> + <script> + var prefixedLocalStorage; + setup(() => prefixedLocalStorage = new PrefixedLocalStorageTest()); + async_test(t => { + t.add_cleanup(() => prefixedLocalStorage.cleanup()); + prefixedLocalStorage.onSet('openerIsNull', t.step_func_done(e => { + assert_equals(e.newValue, 'true'); + })); + window.open(prefixedLocalStorage.url('resources/opener-setter.html'), + 'iShouldSetOpenerToNull'); + }, 'Auxiliary browsing context created via `window.open` and setting `window.opener` to `null` should report `window.opener` `null`'); + async_test(t => { + t.add_cleanup(() => prefixedLocalStorage.cleanup()); + prefixedLocalStorage.onSet('openerIsTest', t.step_func_done(e => { + assert_equals(e.newValue, 'true'); + })); + window.open(prefixedLocalStorage.url('resources/opener-setter.html'), + 'iShouldSetOpenerToTest'); + }, 'Auxiliary browsing context created via `window.open` and setting `window.opener` to `test` should report `test`'); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/opener-setter.window.js b/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/opener-setter.window.js new file mode 100644 index 0000000000..6d540ce97c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/opener-setter.window.js @@ -0,0 +1,38 @@ +[ + undefined, + 42, + function() { return "hi" }, + "hi", + {}, + [], + Symbol() +].forEach(val => { + test(t => { + const frame = document.body.appendChild(document.createElement("iframe")), + win = frame.contentWindow; + t.add_cleanup(() => frame.remove()); + + assert_own_property(win, "opener"); + assert_equals(win.opener, null); + const beforeDesc = Object.getOwnPropertyDescriptor(win, "opener"), + openerGet = beforeDesc.get, + openerSet = beforeDesc.set; + assert_own_property(beforeDesc, "get"); + assert_own_property(beforeDesc, "set"); + assert_true(beforeDesc.enumerable); + assert_true(beforeDesc.configurable); + + win.opener = val; + assert_equals(win.opener, val); + assert_equals(openerGet(), null); + + const desc = Object.getOwnPropertyDescriptor(win, "opener"); + assert_equals(desc.value, val); + assert_true(desc.writable); + assert_true(desc.enumerable); + assert_true(desc.configurable); + + openerSet("x"); + assert_equals(win.opener, "x"); + }, "Setting window.opener to " + String(val)); // String() needed for symbols +}); diff --git a/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/opener.html b/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/opener.html new file mode 100644 index 0000000000..c43d3bd3bf --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/opener.html @@ -0,0 +1,55 @@ +<!doctype html> +<html> + <head> + <title>Auxiliary Browsing Contexts: window.opener</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/common/PrefixedLocalStorage.js"></script> + </head> + <body> + <div id="log"></div> + <script> + var prefixedLocalStorage; + setup (() => { + window.name = 'topWindow'; + prefixedLocalStorage = new PrefixedLocalStorageTest(); + }); + + function cleanup () { + prefixedLocalStorage.setItem('closeAll', 'true'); + prefixedLocalStorage.clear(); + } + + function testOpener (t, target) { + t.add_cleanup(cleanup); + window.addEventListener('message', t.step_func(e => { + if (e.data.name === target) { + // The opener IDL attribute...must return the WindowProxy object of the + // browsing context from which the current browsing context was created + assert_equals(e.data.openerName, 'topWindow'); + // Auxiliary browsing contexts are always top-level browsing contexts + assert_equals(e.data.isTop, true); + t.done(); + } + })); + } + + async_test(t => { + var target = 'windowOpenerA'; + var a = document.createElement('a'); + a.href = prefixedLocalStorage.url('resources/message-window-opener.html'); + a.target = target; + document.body.appendChild(a); + testOpener(t, target); + a.click(); + }, 'Newly-created auxiliary browsing context should report `window.opener`'); + + async_test(t => { + var target = 'windowOpenerB'; + testOpener(t, target); + window.open(prefixedLocalStorage.url('resources/message-window-opener.html'), + target); + }, 'Browsing context created with `window.open` should report `window.opener`'); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/resources/close-opener.html b/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/resources/close-opener.html new file mode 100644 index 0000000000..f41773ed2c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/resources/close-opener.html @@ -0,0 +1,30 @@ +<!doctype html> +<meta charset="utf-8"> +<html> +<body onload="closeOpener()"> +<p>This window should close its opener.</p> +<script src="/common/PrefixedLocalStorage.js"></script> +<script> +var prefixedLocalStorage = new PrefixedLocalStorageResource({ + close_on_cleanup: true +}); +var prefixedLocalStorage = new PrefixedLocalStorageResource({ + close_on_cleanup: true +}); +function closeOpener () { + if (window.opener) { + window.opener.close(); + + // Give the browsing context a chance to dispose of itself + function waitForContextDiscard () { + if (window.opener === null) { + return prefixedLocalStorage.setItem('openerIsNull', 'true'); + } + return setTimeout(waitForContextDiscard, 0); + } + waitForContextDiscard(); + } +} +</script> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/resources/message-window-opener.html b/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/resources/message-window-opener.html new file mode 100644 index 0000000000..8105650b61 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/resources/message-window-opener.html @@ -0,0 +1,14 @@ +<script src="/common/PrefixedLocalStorage.js"></script> +<script> +var prefixedLocalStorage = new PrefixedLocalStorageResource({ + close_on_cleanup: true +}); + +if (window.opener) { + window.opener.postMessage ({ + name : window.name, + openerName: window.opener.name, + isTop : window.top === window + }, '*'); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/resources/multiple-opener.html b/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/resources/multiple-opener.html new file mode 100644 index 0000000000..2e63b9f4c6 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/resources/multiple-opener.html @@ -0,0 +1,32 @@ +<!doctype html> +<html> +<script src="/common/PrefixedLocalStorage.js"></script> +<body onload="openNested()"> +<script> +var prefixedLocalStorage = new PrefixedLocalStorageResource({ + close_on_cleanup: true +}); +function openNested () { + // Listen for message from opened context and pass through to this + // context's opener + window.addEventListener('message', (e) => { + if (window.opener) { + window.opener.postMessage({ + aux2: e.data, // From multipleOpenee + aux1: { // This context + name : window.name, + openerName : window.opener.name, + isTop : window.top === window + } + }, '*'); + } + }); + var a = document.createElement('a'); + a.target = 'multipleOpenee'; + a.href = prefixedLocalStorage.url('message-window-opener.html'); + document.body.appendChild(a); + a.click(); +} +</script> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/resources/no-opener.html b/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/resources/no-opener.html new file mode 100644 index 0000000000..afd72f948d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/resources/no-opener.html @@ -0,0 +1,16 @@ +<!doctype html> +<meta charset="utf-8"> +<html> +<p>This window should have no opener.</p> +<script src="/common/PrefixedLocalStorage.js"></script> +<script> +var prefixedLocalStorage = new PrefixedLocalStorageResource({ + close_on_cleanup: true +}); +function checkOpener () { + return prefixedLocalStorage.setItem('openerIsNull', window.opener === null); +} +</script> +<body onload="checkOpener()"> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/resources/open-closer.html b/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/resources/open-closer.html new file mode 100644 index 0000000000..7575c7c95f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/resources/open-closer.html @@ -0,0 +1,17 @@ +<!doctype html> +<meta charset="utf-8"> +<html> +<body onload="openAuxiliary()"> +<a rel="opener" target="_blank">Open auxiliary context that will close this window (its opener)</a> +<script src="/common/PrefixedLocalStorage.js"></script> +<script> +function openAuxiliary () { + var prefixedLocalStorage = new PrefixedLocalStorageResource(); + var a = document.body.querySelector('a'); + a.href = prefixedLocalStorage.url('close-opener.html'); + document.body.append(a); + a.click(); +} +</script> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/resources/opener-setter.html b/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/resources/opener-setter.html new file mode 100644 index 0000000000..4112dae0ce --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/auxiliary-browsing-contexts/resources/opener-setter.html @@ -0,0 +1,23 @@ +<!doctype html> +<meta charset="utf-8"> +<html> +<p>This window should set the window.opener attribute</p> +<script src="/common/PrefixedLocalStorage.js"></script> +<script> +var prefixedLocalStorage = new PrefixedLocalStorageResource({ + close_on_cleanup: true +}); +function checkOpener () { + if (window.name == 'iShouldSetOpenerToNull') { + window.opener = null; + return prefixedLocalStorage.setItem('openerIsNull', window.opener === null); + } + if (window.name == 'iShouldSetOpenerToTest') { + window.opener = 'test'; + return prefixedLocalStorage.setItem('openerIsTest', window.opener === "test"); + } +} +</script> +<body onload="checkOpener()"> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_blank-001.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_blank-001.html new file mode 100644 index 0000000000..a1416f2eb8 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_blank-001.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: Browsing context - `_blank` name keyword</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +test(t => { + var window1 = window.open('about:blank', '_blank'); + var window2 = window.open('about:blank', '_blank'); + var window3 = window.open('about:blank', '_blank'); + t.add_cleanup(() => { + window1.close(); + window2.close(); + window3.close(); + }); + assert_not_equals(window1, window2); + assert_not_equals(window2, window3); + assert_not_equals(window1, window3); +}, 'window.open into `_blank` should create a new browsing context each time'); + +test(t => { + var window1 = window.open('about:blank', '_bLAnk'); + var window2 = window.open('about:blank', '_bLAnk'); + var window3 = window.open('about:blank', '_bLAnk'); + t.add_cleanup(() => { + window1.close(); + window2.close(); + window3.close(); + }); + assert_not_equals(window1, window2); + assert_not_equals(window2, window3); + assert_not_equals(window1, window3); +}, '`_blank` should be ASCII case-insensitive'); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_blank-002.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_blank-002.html new file mode 100644 index 0000000000..aba9d52ba0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_blank-002.html @@ -0,0 +1,21 @@ +<!doctype html> +<title>Link with target=_blank, rel=noreferrer</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/PrefixedLocalStorage.js"></script> +<div id="log"></div> +<a href="resources/report-has-opener.html" rel="noreferrer" target="_blank">Link</a> +<script> +var prefixedStorage; +setup (() => prefixedStorage = new PrefixedLocalStorageTest()); + +async_test(t => { + t.add_cleanup(() => prefixedStorage.cleanup()); + var a = document.getElementsByTagName('a')[0]; + a.href = prefixedStorage.url(a.href); + prefixedStorage.onSet('hasOpener', t.step_func_done(e => { + assert_equals(e.newValue, 'false'); + })); + a.click(); +}, 'Context for opened noreferrer link targeted to "_blank" should not have opener reference'); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_blank-003.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_blank-003.html new file mode 100644 index 0000000000..5571344c85 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_blank-003.html @@ -0,0 +1,20 @@ +<!doctype html> +<title>Link with target=_blank, no rel</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/PrefixedLocalStorage.js"></script> +<div id="log"></div> +<a href="resources/report-has-opener.html" target="_blank">Link</a> +<script> +var prefixedStorage; +setup(() => prefixedStorage = new PrefixedLocalStorageTest()); +async_test(t => { + t.add_cleanup(() => prefixedStorage.cleanup()); + prefixedStorage.onSet('hasOpener', t.step_func_done(e => { + assert_equals(e.newValue, 'false'); + })); + var a = document.getElementsByTagName('a')[0]; + a.href = prefixedStorage.url(a.href); + a.click(); +}, 'Context created by link targeting "_blank" should not have opener reference'); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_parent-001.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_parent-001.html new file mode 100644 index 0000000000..35cbc101c7 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_parent-001.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: Choose browsing context - '_parent'</title> +<link rel="author" title="Intel" href="http://www.intel.com/"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +async_test(t => { + window.addEventListener('message', t.step_func_done(e => { + assert_equals(e.data.name, 'parentWin'); + })); +}, 'The parent browsing context must be chosen if the given name is `_parent`'); +</script> +<iframe id="embedded" src="resources/choose-_parent-001-iframe-1.html" name="parentWin" style="display:none"></iframe> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_parent-002.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_parent-002.html new file mode 100644 index 0000000000..7b7d561030 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_parent-002.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: Choose browsing context - '_parent' (nested contexts)</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +async_test(t => { + var topWindow; + t.add_cleanup(() => topWindow.close()); + window.addEventListener('message', t.step_func_done(e => { + assert_equals(e.data.name, 'iframeParent'); + assert_false(e.data.isTop, 'window.parent is not top'); + })); + topWindow = window.open('resources/choose-_parent-002-window.html', '_blank'); +}, 'choosing _parent context: multiple nested contexts'); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_parent-003.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_parent-003.html new file mode 100644 index 0000000000..20dc9b0d2a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_parent-003.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: Choose browsing context - '_parent' (via window.open)</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +async_test(t => { + var topWindow; + t.add_cleanup(() => topWindow.close()); + window.addEventListener('message', t.step_func_done(e => { + assert_equals(e.data.name, 'parentTopReplace'); + assert_equals(e.data.isTop, true); + })); + topWindow = window.open('resources/choose-_parent-003-window.html', 'parentTopReplace'); +}, '_parent should reuse window.parent context'); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_parent-004.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_parent-004.html new file mode 100644 index 0000000000..c79378018a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_parent-004.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: Choose browsing context - '_parent' (case-sensitivity)</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/PrefixedLocalStorage.js"></script> +<body> +<div id="log"></div> + +<script> +var prefixedStorage; +var iframe; +setup(() => prefixedStorage = new PrefixedLocalStorageTest()); + +async_test(t => { + t.add_cleanup(() => prefixedStorage.cleanup()); + var testFunc = (function (t) { + var completed = 0; + var testCount = 2; + return function (actual, expected) { + assert_equals(actual, expected); + if (++completed >= testCount) { + t.done(); + } + } + }(t)); + + prefixedStorage.onSet('isTop', t.step_func(e => { + testFunc(e.newValue, 'false'); + })); + prefixedStorage.onSet('name', t.step_func(e => { + testFunc(e.newValue, 'parentWin'); + })); + iframe = document.createElement('iframe'); + iframe.src = prefixedStorage.url('resources/choose-_parent-004-iframe-1.html'); + iframe.name = 'parentWin'; + document.body.appendChild(iframe); +}, 'choosing _parent context should be case-insensitive'); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_self-001.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_self-001.html new file mode 100644 index 0000000000..ed7666846d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_self-001.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: Choose browsing context - the given name is '_self'</title> +<link rel="author" title="Intel" href="http://www.intel.com/"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<iframe src="resources/choose-_self-001-iframe.html" style="display:none"></iframe> +<script> +async_test(t => { + window.addEventListener('message', t.step_func_done(e => { + assert_equals(e.data.name, 'myownself'); + }), false); +}, 'The current browsing context must be chosen if the given name is "_self"'); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_self-002.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_self-002.html new file mode 100644 index 0000000000..2e798f5493 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_self-002.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: Choose browsing context - '_self' (case-sensitivity)</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/PrefixedLocalStorage.js"></script> +<body> +<div id="log"></div> + +<script> +var prefixedStorage; +setup(() => prefixedStorage = new PrefixedLocalStorageTest()); + +async_test(t => { + var iframe; + + var testFunc = (function (t) { + var completed = 0; + var testCount = 2; + return function (actual, expected) { + assert_equals(actual, expected); + if (++completed >= testCount) { + t.done(); + } + } + }(t)); + + t.add_cleanup(() => prefixedStorage.cleanup()); + + prefixedStorage.onSet('isTop', t.step_func(e => { + testFunc(e.newValue, 'false'); + })); + prefixedStorage.onSet('name', t.step_func(e => { + testFunc(e.newValue, 'testWin'); + })); + + iframe = document.createElement('iframe'); + iframe.name = 'testWin'; + iframe.src = prefixedStorage.url('resources/choose-_self-002-iframe.html'); + document.body.appendChild(iframe); + +}, 'choosing _self context should be case-insensitive'); + +</script> + +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_top-001.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_top-001.html new file mode 100644 index 0000000000..de4c6ad115 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_top-001.html @@ -0,0 +1,34 @@ +<!doctype html> +<title>HTML Test: Browsing context name - _top (current is top)</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/PrefixedLocalStorage.js"></script> +<div id="log"></div> +<script> +var prefixedStorage; +setup (() => prefixedStorage = new PrefixedLocalStorageTest()); + +async_test(t => { + t.add_cleanup(() => prefixedStorage.cleanup()); + + var testFunc = (function (t) { + var completed = 0; + var testCount = 2; + return function (actual, expected) { + assert_equals(actual, expected); + if (++completed >= testCount) { + t.done(); + } + } + }(t)); + + prefixedStorage.onSet('isTop', t.step_func(e => { + testFunc(e.newValue, 'true'); + })); + prefixedStorage.onSet('name', t.step_func(e => { + testFunc(e.newValue, 'topWin1'); + })); + + window.open(prefixedStorage.url('resources/open-in-_top.html'), '_blank'); +}, 'Should choose current browsing context for "_top" if current is top'); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_top-002.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_top-002.html new file mode 100644 index 0000000000..f29da80c6e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_top-002.html @@ -0,0 +1,33 @@ +<!doctype html> +<title>HTML Test: Browsing context name - _top</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/PrefixedLocalStorage.js"></script> +<div id="log"></div> +<script> +var prefixedStorage; +setup (() => prefixedStorage = new PrefixedLocalStorageTest()); + +async_test(t => { + t.add_cleanup(() => prefixedStorage.cleanup()); + + var testFunc = (function (t) { + var completed = 0; + var testCount = 2; + return function (actual, expected) { + assert_equals(actual, expected); + if (++completed >= testCount) { + t.done(); + } + } + }(t)); + + prefixedStorage.onSet('isTop', t.step_func(e => { + testFunc(e.newValue, 'true'); + })); + prefixedStorage.onSet('name', t.step_func(e => { + testFunc(e.newValue, 'topWin2'); + })); + window.open(prefixedStorage.url('resources/choose-_top-002-window.html'), '_blank'); +}, 'Should choose top browsing context for "_top" if current is not top'); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_top-003.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_top-003.html new file mode 100644 index 0000000000..e068f8cc1f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-_top-003.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: Choose browsing context - '_top' (case-sensitivity)</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/PrefixedLocalStorage.js"></script> +<body> +<div id="log"></div> + +<script> +var prefixedStorage; +setup(() => prefixedStorage = new PrefixedLocalStorageTest()); + +async_test(t => { + var testFunc = (function (t) { + var completed = 0; + var testCount = 2; + return function (actual, expected) { + assert_equals(actual, expected); + if (++completed >= testCount) { + t.done(); + } + } + }(t)); + + t.add_cleanup(() => prefixedStorage.cleanup()); + + prefixedStorage.onSet('isTop', t.step_func(e => { + testFunc(e.newValue, 'true'); + })); + prefixedStorage.onSet('name', t.step_func(e => { + testFunc(e.newValue, 'topWin'); + })); + + window.open(prefixedStorage.url('resources/choose-_top-003-iframe-1.html'), '_blank'); +}, 'choosing _top context should be case-insensitive'); + +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-default-001.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-default-001.html new file mode 100644 index 0000000000..c21159e0a6 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-default-001.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: Browsing context - Default name</title> +<link rel="author" title="Intel" href="http://www.intel.com/"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<iframe src="/common/blank.html" style="display:none"></iframe> +<object id="obj" type="text/html" data="about:blank"></object> +<embed id="embedded" type="image/svg+xml" src="/images/green.svg" width="0" height="0" /> +<script> +test(t => { + assert_equals(window.frames[0].name, ""); + assert_equals(document.getElementById("embedded").name, ""); + assert_equals(window["obj"].name, ""); +}, "A embedded browsing context has empty-string default name"); + +test(t => { + var win = window.open("about:blank", "_blank"); + assert_equals(win.name, ""); + win.close(); +}, "A browsing context which is opened by window.open() method with '_blank' parameter has empty-string default name"); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-default-002.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-default-002.html new file mode 100644 index 0000000000..748ee68973 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-default-002.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: Browsing context names - empty string</title> +<link rel="author" title="Intel" href="http://www.intel.com/"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +async_test(t => { + window.addEventListener('message', t.step_func_done(e => { + assert_equals(e.data.isTop, false); + assert_equals(e.data.name, 'hellothere', 'Empty-string browsing context should choose current context'); + }), false); +}, 'The current browsing context must be chosen if the given name is empty string'); +</script> +<iframe name="hellothere" src="resources/choose-default-002-iframe.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-existing-001.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-existing-001.html new file mode 100644 index 0000000000..fdf74b8a79 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/choose-existing-001.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: Choose browsing context - the given name is same as an existing browsing context's name</title> +<link rel="author" title="Intel" href="http://www.intel.com/"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<iframe src="resources/choose-existing-001-iframe.html" style="display:none"></iframe> +<iframe name="iExist" style="display:none"></iframe> +<script> +async_test(t => { + window.addEventListener('message', t.step_func_done(e => { + assert_equals(e.data.name, 'iExist'); + }), false); + +}, 'An existing browsing context must be chosen if the given name is the same as its name'); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_parent-001-iframe-1.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_parent-001-iframe-1.html new file mode 100644 index 0000000000..04dd74f1a5 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_parent-001-iframe-1.html @@ -0,0 +1,5 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: browsing context name - parent</title> +<link rel="author" title="Intel" href="http://www.intel.com/"> +<iframe src="open-in-_parent.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_parent-002-iframe.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_parent-002-iframe.html new file mode 100644 index 0000000000..52da8986b0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_parent-002-iframe.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: browsing context name - parent: nested context</title> +<iframe name="iframeChild" src="open-in-_parent.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_parent-002-window.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_parent-002-window.html new file mode 100644 index 0000000000..558193742f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_parent-002-window.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: browsing context name - parent: top-level context</title> +<iframe name="iframeParent" src="choose-_parent-002-iframe.html"></iframe> +<script> +// Relay a message from child context to opener context +window.addEventListener('message', e => { + if (window.opener) { + window.opener.postMessage(e.data, '*'); + } +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_parent-003-iframe.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_parent-003-iframe.html new file mode 100644 index 0000000000..1412dc2e79 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_parent-003-iframe.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: browsing context name - parent</title> +<script> +window.open("post-to-opener.html", "_parent"); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_parent-003-window.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_parent-003-window.html new file mode 100644 index 0000000000..bd1802aed6 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_parent-003-window.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: browsing context name - parent: top-level context (gets replaced)</title> +<iframe name="iframeOpener" src="choose-_parent-003-iframe.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_parent-004-iframe-1.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_parent-004-iframe-1.html new file mode 100644 index 0000000000..7a8cbecb27 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_parent-004-iframe-1.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: browsing context name - parent</title> +<script src="/common/PrefixedLocalStorage.js"></script> +<body> +<script> +var prefixedStorage = new PrefixedLocalStorageResource({ + close_on_cleanup: true +}); +var iframe = document.createElement('iframe'); +iframe.src = prefixedStorage.url('choose-_parent-004-iframe-2.html'); +document.body.appendChild(iframe); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_parent-004-iframe-2.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_parent-004-iframe-2.html new file mode 100644 index 0000000000..33ead5a538 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_parent-004-iframe-2.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: browsing context name - parent (case-insensitive)</title> +<script src="/common/PrefixedLocalStorage.js"></script> +<script> +var prefixedStorage = new PrefixedLocalStorageResource(); +window.open(prefixedStorage.url('report-is-top.html'), '_pARent'); + +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_self-001-iframe.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_self-001-iframe.html new file mode 100644 index 0000000000..0ad79d6e16 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_self-001-iframe.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: browsing context name - self</title> +<link rel="author" title="Intel" href="http://www.intel.com/"> +<script> +window.name = 'myownself'; +var win = window.open('post-to-top.html', '_self'); +win.close(); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_self-002-iframe.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_self-002-iframe.html new file mode 100644 index 0000000000..9d88305e09 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_self-002-iframe.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: browsing context name - self (case-insensitive)</title> +<link rel="author" title="Intel" href="http://www.intel.com/"> +<script src="/common/PrefixedLocalStorage.js"></script> +<script> +var prefixedStorage = new PrefixedLocalStorageResource({ + close_on_cleanup: true +}); +var win = window.open(prefixedStorage.url('report-is-top.html'), '_sElF'); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_top-002-window.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_top-002-window.html new file mode 100644 index 0000000000..d71384b72f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_top-002-window.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/common/PrefixedLocalStorage.js"></script> +<title>HTML Test: browsing context name - _top</title> +<body> +<script> +var prefixedStorage = new PrefixedLocalStorageResource({ + close_on_cleanup:true +}); +window.name = 'topWin2'; +var iframe = document.createElement('iframe'); +iframe.src = prefixedStorage.url('open-in-_top.html'); +// Append iframe that will open another document into `_top` (this context) +document.body.appendChild(iframe); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_top-003-iframe-1.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_top-003-iframe-1.html new file mode 100644 index 0000000000..aecc2fd88a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_top-003-iframe-1.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: browsing context name - top (case-insensitive)</title> +<script src="/common/PrefixedLocalStorage.js"></script> +<body> +</body> +<script> +var prefixedStorage = new PrefixedLocalStorageResource({ + close_on_cleanup: true +}); +window.name = 'topWin'; +var iframe = document.createElement('iframe'); +iframe.src = prefixedStorage.url('choose-_top-003-iframe-2.html'); +document.body.appendChild(iframe); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_top-003-iframe-2.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_top-003-iframe-2.html new file mode 100644 index 0000000000..a1a7e1dda7 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-_top-003-iframe-2.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: browsing context name - top (case-insensitive)</title> +<script src="/common/PrefixedLocalStorage.js"></script> +<script> +var prefixedStorage = new PrefixedLocalStorageResource({ + close_on_cleanup: true +}); +window.open(prefixedStorage.url("report-is-top.html"), "_ToP"); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-default-002-iframe.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-default-002-iframe.html new file mode 100644 index 0000000000..567e4ea310 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-default-002-iframe.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> +<meta charset="utf-8"> +<title>HTML Test: browsing context name - Empty string</title> +<link rel="author" title="Intel" href="http://www.intel.com/"> +<body onload="followLink()"> +</body> +<script> +function followLink() { + var a = document.createElement('a'); + a.href = 'post-to-top.html'; + a.target = ''; // Target is empty string + document.body.appendChild(a); + a.click(); +} +</script> +</html> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-existing-001-iframe.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-existing-001-iframe.html new file mode 100644 index 0000000000..cb0b554854 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/choose-existing-001-iframe.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>This is a test page</title> +<link rel="author" title="Intel" href="http://www.intel.com/"> +<script> +window.open("post-to-top.html", "iExist"); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/open-in-_parent.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/open-in-_parent.html new file mode 100644 index 0000000000..81d0735c60 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/open-in-_parent.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: browsing context name - parent</title> +<link rel="author" title="Intel" href="http://www.intel.com/"> +<script> + +window.open("post-to-top.html", "_parent"); + +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/open-in-_top.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/open-in-_top.html new file mode 100644 index 0000000000..929d52d15e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/open-in-_top.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/common/PrefixedLocalStorage.js"></script> +<title>HTML Test: browsing context name - _top</title> +<script> +var prefixedStorage = new PrefixedLocalStorageResource(); +window.name = 'topWin1'; +window.open(prefixedStorage.url('report-is-top.html'), '_top'); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/post-to-opener.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/post-to-opener.html new file mode 100644 index 0000000000..3e9b7aaccb --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/post-to-opener.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: post window's name to top browsing context</title> +<link rel="author" title="Intel" href="http://www.intel.com/"> +<script> +if (window.opener) { + window.opener.postMessage({ + name: window.name, + isTop: (window.top === window) + }, "*"); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/post-to-top.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/post-to-top.html new file mode 100644 index 0000000000..aebb57b019 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/post-to-top.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>HTML Test: post window's name to top browsing context</title> +<link rel="author" title="Intel" href="http://www.intel.com/"> +<script> +top.postMessage({ + name: window.name, + isTop: (window.top === window) +}, "*"); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/report-has-opener.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/report-has-opener.html new file mode 100644 index 0000000000..37d8cedc6d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/report-has-opener.html @@ -0,0 +1,8 @@ +<!doctype html> +<script src="/common/PrefixedLocalStorage.js"></script> +<script> +var prefixedStorage = new PrefixedLocalStorageResource({ + close_on_cleanup: true +}); +prefixedStorage.setItem('hasOpener', window.opener !== null); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/report-is-top.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/report-is-top.html new file mode 100644 index 0000000000..8711235982 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-names/resources/report-is-top.html @@ -0,0 +1,10 @@ +<!doctype html> +<meta charset="utf-8"> +<script src="/common/PrefixedLocalStorage.js"></script> +<script> +var prefixedStorage = new PrefixedLocalStorageResource({ + close_on_cleanup: true +}); +prefixedStorage.setItem('isTop', window === window.top); +prefixedStorage.setItem('name', window.name); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context-window.html b/testing/web-platform/tests/html/browsers/windows/browsing-context-window.html new file mode 100644 index 0000000000..f4f3c715ac --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context-window.html @@ -0,0 +1,37 @@ +<!doctype html> +<html> + <head> + <title>HTML Test: Newly-Created browsing context Window and `this`</title> + <link rel="author" title="Intel" href="http://www.intel.com/" /> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/common/get-host-info.sub.js"></script> + </head> + <body> + <div id="log"></div> + <script> + + /** + * Test for creating a new browsing context, [Browsing Contexts](#windows). + * This test is separate from the rest of the browsing-content-creation tests + * because the `iframe` has a src and thus its document won't be `about:blank`. + */ + var doc, iframe; + + setup(function () { + iframe = document.createElement("iframe"); + iframe.src = get_host_info().HTTP_REMOTE_ORIGIN + "/html/browsers/windows/resources/browsing-context-window.html"; + document.body.appendChild(iframe); + doc = iframe.contentDocument; + }); + + async_test(function (t) { + window.onmessage = t.step_func(function (e) { + assert_equals(e.data.thisWindowEquivalency, true, "The global `this` for the created browsing context should be a reference to Window through WindowProxy"); + t.done(); + }); + }); + + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/windows/browsing-context.html b/testing/web-platform/tests/html/browsers/windows/browsing-context.html new file mode 100644 index 0000000000..59367b0428 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/browsing-context.html @@ -0,0 +1,48 @@ +<!doctype html> +<html> + <head> + <title>HTML Test: Browsing context is first created</title> + <link rel="author" title="Intel" href="http://www.intel.com/" /> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id="log"></div> + <script> + var doc, iframe; + + setup(function () { + // Create new browsing context via iframe + iframe = document.createElement("iframe"); + document.body.appendChild(iframe); + doc = iframe.contentDocument; + }); + + test(function () { + assert_equals(doc.compatMode, "BackCompat", "The compatMode of a document without a document type declaration should be 'BackCompat'."); // Quirksmode + assert_equals(doc.contentType, "text/html", "The document should be an HTML document."); + assert_equals(doc.readyState, "complete", "The readyState attribute should be 'complete'."); + // Document metadata... + assert_equals(doc.documentURI, "about:blank", "The document's address should be 'about:blank'."); + assert_equals(doc.URL, "about:blank", "The document's address should be 'about:blank'."); + assert_equals(doc.doctype, null, "The docType of a document without a document type declaration should be null."); + assert_equals(doc.characterSet, "UTF-8", "The document's encoding should be 'UTF-8'."); + }, "Check that browsing context has new, ready HTML document"); + + test(function () { + assert_equals(doc.childNodes.length, 1, "The document must have only one child."); + assert_equals(doc.documentElement.tagName, "HTML"); + assert_equals(doc.documentElement.childNodes.length, 2, "The HTML element should have 2 children."); + assert_equals(doc.documentElement.childNodes[0].tagName, "HEAD", "The first child of HTML element should be a HEAD element."); + assert_false(doc.documentElement.childNodes[0].hasChildNodes(), "The HEAD element should not have children."); + assert_equals(doc.documentElement.childNodes[1].tagName, "BODY", "The second child of HTML element should be a BODY element."); + assert_false(doc.documentElement.childNodes[1].hasChildNodes(), "The BODY element should not have children."); + }, "Check that new document nodes extant, empty"); + + test(function () { + assert_equals(doc.referrer, document.URL, "The document's referrer should be its creator document's URL."); + assert_equals(iframe.contentWindow.parent.document, document); + }, "Check the document properties corresponding to the creator browsing context"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/windows/clear-window-name.https.html b/testing/web-platform/tests/html/browsers/windows/clear-window-name.https.html new file mode 100644 index 0000000000..698de8a1ca --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/clear-window-name.https.html @@ -0,0 +1,122 @@ +<!doctype html> +<html> +<head> + <title>Clear window.name when cross-origin</title> + <meta name="timeout" content="long"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/common/utils.js"></script> +</head> +<body> + <script> + +function anchorClick(n) { + const hyperlink = document.body.appendChild(document.createElement("a")) + hyperlink.rel = "noopener"; + hyperlink.target = "_blank"; + hyperlink.href = n; + hyperlink.click(); +} + +async function pollResultAndCheck(t, id, expected) { + const stashURL = new URL("resources/window-name-stash.py", location); + stashURL.searchParams.set('id', id); + + let res = "NONE"; + while (res == "NONE") { + await new Promise(resolve => { t.step_timeout(resolve, 100); }); + + const response = await fetch(stashURL); + res = await response.text(); + } + if (res !== expected) { + assert_unreached('Stash result does not equal expected result.') + } +} + +promise_test(async t => { + const id = token(); + + window.open(`resources/window-name.sub.html?report=${id}|close`, id); + await pollResultAndCheck(t, id, id); +}, "Window.name is not reset when there is an opener around"); + +promise_test(async t => { + const id = token(); + + window.open(`resources/window-name.sub.html?cross|same|report=${id}|close`, id); + await pollResultAndCheck(t, id, id); +}, "Window.name is not reset when there is an opener around (cross-origin)"); + +promise_test(async t => { + const id = token(); + + window.open(`resources/window-name.sub.html?report=${id}|close`, id, "noopener"); + await pollResultAndCheck(t, id, id); +}, "Window.name is not reset at the first navigation away from initial about:blank with noopener"); + +promise_test(async t => { + const id = token(); + + window.open(`resources/window-name.sub.html?cross|same|report=${id}|close`, id, "noopener"); + await pollResultAndCheck(t, id, ""); +}, "Window.name is reset at the first cross-origin navigation with noopener"); + +promise_test(async t => { + const id = token(); + + let win = window.open(`resources/window-name.sub.html?report=${id}|close`, id); + win.opener = null; + await pollResultAndCheck(t, id, id); +}, "Window.name is not reset at the first navigation away from initial about:blank with window.opener set to null"); + +promise_test(async t => { + const id = token(); + + let win = window.open(`resources/window-name.sub.html?same|report=${id}|close`, id); + win.opener = null; + await pollResultAndCheck(t, id, id); +}, "Window.name is not reset at the same-origin navigation with window.opener set to null"); + +promise_test(async t => { + const id = token(); + + let win = window.open(`resources/window-name.sub.html?cross|same|report=${id}|close`, id); + win.opener = null; + await pollResultAndCheck(t, id, ""); +}, "Window.name is reset at the first first cross-origin navigation with window.opener set to null"); + +promise_test(async t => { + const id = token(); + + anchorClick(`resources/window-name.sub.html?set=${id}|report=${id}|close`); + await pollResultAndCheck(t, id, id); +}, "Window.name is set by the window"); + +promise_test(async t => { + const id = token(); + + anchorClick(`resources/window-name.sub.html?set=${id}|cross|same|report=${id}|close`); + await pollResultAndCheck(t, id, ""); +}, "Window.name is reset at the first cross-origin navigation"); + +promise_test(async t => { + const id = token(); + + window.open(`resources/window-name.sub.html?open|navOpener=about:blank|reportOpener=${id}|closeOpener|close`, id, "noopener"); + await pollResultAndCheck(t, id, id); +}, "window.name is not reset after navigating to an about:blank page from a non-about:blank page"); + + +promise_test(async t => { + const id = token(); + const domain = window.location.host; + + anchorClick(`resources/window-name.sub.html?sub|set=${id}|setDomain=${domain}|sub|report=${id}|close`); + await pollResultAndCheck(t, id, id); +}, "Window.name is not reset if the document.domain is set to the parent domain"); + + + </script> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/windows/document-domain-nested-navigate.window.js b/testing/web-platform/tests/html/browsers/windows/document-domain-nested-navigate.window.js new file mode 100644 index 0000000000..f51eed5ca9 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/document-domain-nested-navigate.window.js @@ -0,0 +1,16 @@ +async_test(t => { + // Setting document.domain makes this document cross-origin with that of the frame. However, + // about:blank will end up reusing the origin of this document, at which point the frame's + // document is no longer cross-origin. + const frame = document.body.appendChild(document.createElement('iframe')); + document.domain = document.domain; + frame.src = "/common/blank.html"; + frame.onload = t.step_func(() => { + assert_throws_dom("SecurityError", () => window[0].document); + frame.src = "about:blank"; + frame.onload = t.step_func_done(() => { + // Ensure we can access the child browsing context after navigation to non-initial about:blank + assert_equals(window[0].document, frame.contentDocument); + }); + }); +}, "Navigated frame to about:blank and document.domain"); diff --git a/testing/web-platform/tests/html/browsers/windows/document-domain-nested-set.window.js b/testing/web-platform/tests/html/browsers/windows/document-domain-nested-set.window.js new file mode 100644 index 0000000000..13c0d50ba0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/document-domain-nested-set.window.js @@ -0,0 +1,10 @@ +test(() => { + // As the initial:about frame document reuses the origin of this document, setting document.domain + // from the frame, resulting in a origin mutation, has no effect on these documents being able to + // reach each other, as they share the same "physical" origin. + document.body.appendChild(document.createElement('iframe')); + const script = document.createElement("script"); + script.text = "document.domain = document.domain"; + window[0].document.body.appendChild(script); + assert_equals(window[0].document.body.localName, "body"); +}, "Initial about:blank frame and document.domain in the frame"); diff --git a/testing/web-platform/tests/html/browsers/windows/document-domain-nested.window.js b/testing/web-platform/tests/html/browsers/windows/document-domain-nested.window.js new file mode 100644 index 0000000000..2e39d05f03 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/document-domain-nested.window.js @@ -0,0 +1,9 @@ +test(() => { + // As the initial:about frame document reuses the origin of this document, setting document.domain + // from this document, resulting in a origin mutation, has no effect on these documents being able + // to reach each other, as they share the same "physical" origin. + document.body.appendChild(document.createElement('iframe')); + document.domain = document.domain; + // Ensure we can still access the child browsing context + assert_equals(window[0].document.body.localName, "body"); +}, "Initial about:blank frame and document.domain"); diff --git a/testing/web-platform/tests/html/browsers/windows/document-domain-removed-iframe.html b/testing/web-platform/tests/html/browsers/windows/document-domain-removed-iframe.html new file mode 100644 index 0000000000..7403ebfaad --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/document-domain-removed-iframe.html @@ -0,0 +1,72 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>document.domain and removed iframe interaction</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- This is a test for https://crbug.com/1095145 where window + properties become undefined for document.domain-using removed + iframes --> + +<div id="log"></div> + +<script> +"use strict"; + +promise_test(t => { + return new Promise(resolve => { + const iframe = document.createElement("iframe"); + iframe.onload = t.step_func(() => { + const { contentWindow } = iframe; + assert_equals(contentWindow.status, ""); + resolve(); + }); + iframe.src = "/common/blank.html"; + document.body.append(iframe); + }); +}, "No removal, no document.domain"); + +promise_test(t => { + return new Promise(resolve => { + const iframe = document.createElement("iframe"); + iframe.onload = t.step_func(() => { + const { contentWindow } = iframe; + iframe.remove(); + assert_equals(contentWindow.status, ""); + resolve(); + }); + iframe.src = "/common/blank.html"; + document.body.append(iframe); + }); +}, "Removal, no document.domain"); + +promise_test(t => { + return new Promise(resolve => { + const iframe = document.createElement("iframe"); + iframe.onload = t.step_func(() => { + document.domain = document.domain; + const { contentWindow } = iframe; + assert_equals(contentWindow.status, ""); + resolve(); + }); + iframe.src = "resources/document-domain-setter.html"; + document.body.append(iframe); + }); +}, "No removal, document.domain"); + +promise_test(t => { + return new Promise(resolve => { + const iframe = document.createElement("iframe"); + iframe.onload = t.step_func(() => { + document.domain = document.domain; // technically we already did this above + const { contentWindow } = iframe; + iframe.remove(); + assert_equals(contentWindow.status, ""); + resolve(); + }); + iframe.src = "resources/document-domain-setter.html"; + document.body.append(iframe); + }); +}, "Removal, document.domain"); + +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/embedded-opener-a-form.html b/testing/web-platform/tests/html/browsers/windows/embedded-opener-a-form.html new file mode 100644 index 0000000000..e1ec760b92 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/embedded-opener-a-form.html @@ -0,0 +1,30 @@ +<!doctype html> +<title>opener and embedded documents; using a and form</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<div id=log></div> +<iframe name=matchesastring></iframe> +<a href=/common/blank.html target=matchesastring><a></a> +<form action=/common/blank.html target=matchesastring><input type=submit value="<form>"></form> +<script> +async_test(t => { + const frame = document.querySelector("iframe"); + let counter = 0; + frame.onload = t.step_func(() => { + // Firefox and Chrome/Safari load differently + if (frame.contentWindow.location.href === "about:blank") { + return; + } + + // Test bits + assert_equals(frame.contentWindow.opener, null); + if (counter === 0) { + document.querySelector("input").click(); + } else { + t.done(); + } + counter++; + }); + document.querySelector("a").click(); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/embedded-opener-remove-frame.html b/testing/web-platform/tests/html/browsers/windows/embedded-opener-remove-frame.html new file mode 100644 index 0000000000..a66f52e5f6 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/embedded-opener-remove-frame.html @@ -0,0 +1,66 @@ +<!doctype html> +<title>opener and discarded browsing contexts</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<div id=log></div> +<iframe name=matchesastring></iframe> +<script> +function testOpener(t, otherW, thisW, discardOtherBC, isDiscardedFromATask) { + assert_equals(otherW.opener, thisW, "opener before removal"); + + const openerDesc = Object.getOwnPropertyDescriptor(otherW, "opener"), + openerGet = openerDesc.get; + + assert_equals(openerGet(), thisW, "opener before removal via directly invoking the getter"); + discardOtherBC(); + if (isDiscardedFromATask) { + t.step_timeout(() => { + testOpenerRemainder(t, otherW, openerDesc, openerGet); + }, 250); + } else { + testOpenerRemainder(t, otherW, openerDesc, openerGet); + } +} + +function testOpenerRemainder(t, otherW, openerDesc, openerGet) { + assert_equals(otherW.opener, null, "opener after removal"); + assert_equals(openerGet(), null, "opener after removal via directly invoking the getter"); + + otherW.opener = null; + assert_equals(openerGet(), null, "opener after setting it null via directly invoking the getter"); + const openerDescNull = Object.getOwnPropertyDescriptor(otherW, "opener"); + assert_not_equals(openerDescNull, openerDesc); + assert_object_equals(openerDescNull, openerDesc); + + otherW.opener = "immaterial"; + assert_equals(openerGet(), null, "opener after setting it \"immaterial\" via directly invoking the getter"); + const openerDescImmaterial = Object.getOwnPropertyDescriptor(otherW, "opener"); + assert_equals(openerDescImmaterial.value, "immaterial"); + assert_true(openerDescImmaterial.writable); + assert_true(openerDescImmaterial.enumerable); + assert_true(openerDescImmaterial.configurable); + + t.done(); +} + +async_test(t => { + const frame = document.querySelector("iframe"), + frameW = frame.contentWindow; + frame.onload = t.step_func(() => { + // Firefox and Chrome/Safari load differently + if (frame.contentWindow.location.href === "about:blank") { + return; + } + + testOpener(t, frameW, window, () => frame.remove(), false); + }); + window.open("/common/blank.html", "matchesastring"); +}, "opener of discarded nested browsing context"); + +async_test(t => { + const popupW = window.open("/common/blank.html"); + popupW.onload = t.step_func(() => { + testOpener(t, popupW, window, () => popupW.close(), true); + }); +}, "opener of discarded auxiliary browsing context"); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/embedded-opener.html b/testing/web-platform/tests/html/browsers/windows/embedded-opener.html new file mode 100644 index 0000000000..8e02664342 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/embedded-opener.html @@ -0,0 +1,32 @@ +<!doctype html> +<title>opener and embedded documents; using window.open()</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<div id=log></div> +<iframe name=matchesastring></iframe> +<script> +async_test(t => { + const frame = document.querySelector("iframe"); + frame.onload = t.step_func(() => { + // Firefox and Chrome/Safari load differently + if (frame.contentWindow.location.href === "about:blank") { + return; + } + + // Test bits + assert_equals(frame.contentWindow.opener, window, "opener before setting it to null"); + + const openerDesc = Object.getOwnPropertyDescriptor(frame.contentWindow, "opener"), + openerGet = openerDesc.get; + + assert_equals(openerGet(), window, "opener before setting it to null via directly invoking the getter"); + frame.contentWindow.opener = null; + frame.contentWindow.opener = "immaterial"; + assert_equals(openerGet(), null, "opener after setting it to null via directly invoking the getter"); + assert_equals(frame.contentWindow.opener, "immaterial"); + + t.done(); + }); + window.open("/common/blank.html", "matchesastring"); +}); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/iframe-cross-origin-print.sub.html b/testing/web-platform/tests/html/browsers/windows/iframe-cross-origin-print.sub.html new file mode 100644 index 0000000000..d07db13776 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/iframe-cross-origin-print.sub.html @@ -0,0 +1,6 @@ +<!doctype html> +<link rel=match href="iframe-nested-print-ref.html"> +<style> + body { margin: 0 } +</style> +<iframe frameborder=0 scrolling=no src="//{{hosts[alt][www]}}:{{ports[http][0]}}{{location[path]}}/../resources/iframe-nested-cross-origin.html"></iframe> diff --git a/testing/web-platform/tests/html/browsers/windows/iframe-cross-origin-scaled-print.sub.html b/testing/web-platform/tests/html/browsers/windows/iframe-cross-origin-scaled-print.sub.html new file mode 100644 index 0000000000..2442563082 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/iframe-cross-origin-scaled-print.sub.html @@ -0,0 +1,17 @@ +<!doctype html> +<link rel=match href="iframe-nested-scaled-print-ref.html"> +<style> + body { margin: 0 } + div { + transform-origin: top left; + transform: scale(2); + overflow: hidden; + } + iframe { + width: 100px; + height: 50px; + } +</style> +<div> +<iframe frameborder=0 scrolling=no src="//{{hosts[alt][www]}}:{{ports[http][0]}}{{location[path]}}/../resources/iframe-nested-printing-pass.html"></iframe> +</div> diff --git a/testing/web-platform/tests/html/browsers/windows/iframe-nested-print-ref.html b/testing/web-platform/tests/html/browsers/windows/iframe-nested-print-ref.html new file mode 100644 index 0000000000..c36c459881 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/iframe-nested-print-ref.html @@ -0,0 +1,9 @@ +<!doctype html> +<style> + body { margin: 0 } + p { + /* 2 times the default margin of <body>, to account for the doubly-nested document */ + margin: 16px; + } +</style> +<p>PASS diff --git a/testing/web-platform/tests/html/browsers/windows/iframe-nested-print.html b/testing/web-platform/tests/html/browsers/windows/iframe-nested-print.html new file mode 100644 index 0000000000..844f6ba373 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/iframe-nested-print.html @@ -0,0 +1,6 @@ +<!doctype html> +<link rel=match href="iframe-nested-print-ref.html"> +<style> + body { margin: 0 } +</style> +<iframe frameborder=0 scrolling=no srcdoc="<iframe scrolling=no frameborder=0 srcdoc='PASS'></iframe>"></iframe> diff --git a/testing/web-platform/tests/html/browsers/windows/iframe-nested-scaled-print-ref.html b/testing/web-platform/tests/html/browsers/windows/iframe-nested-scaled-print-ref.html new file mode 100644 index 0000000000..fceaf1e52c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/iframe-nested-scaled-print-ref.html @@ -0,0 +1,15 @@ +<!doctype html> +<style> + body { margin: 0 } + div { + transform-origin: top left; + transform: scale(2); + } + iframe { + width: 100px; + height: 50px; + } +</style> +<div> +<iframe frameborder=0 scrolling=no src="resources/iframe-nested-printing-pass.html"></iframe> +</div> diff --git a/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/frameElement-siblings.sub.html b/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/frameElement-siblings.sub.html new file mode 100644 index 0000000000..0c814d26d9 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/frameElement-siblings.sub.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>window.frameElement access to a same-origin-domain sibling</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<iframe src="//{{hosts[][]}}:{{ports[http][0]}}/html/browsers/windows/nested-browsing-contexts/resources/frameElement-sibling-accessed.html"></iframe> +<iframe src="//{{hosts[][www]}}:{{ports[http][0]}}/html/browsers/windows/nested-browsing-contexts/resources/frameElement-sibling-accessor.html"></iframe> + +<script> +"use strict"; +setup({ explicit_done: true }); + +window.onload = () => { + promise_test(async () => { + frames[1].postMessage({}, "*"); + const result = await waitForMessage(); + + assert_equals(result, "SecurityError"); + }, "it must give a \"SecurityError\" DOMException if the pages are different-origin domain"); + + promise_test(async () => { + document.domain = document.domain; + + frames[0].postMessage({ newDocumentDomain: document.domain }, "*"); + assert_equals(await waitForMessage(), "done"); + + frames[1].postMessage({ newDocumentDomain: document.domain }, "*"); + const result = await waitForMessage(); + + assert_equals(result, "HTMLIFrameElement"); + }, "it must return the iframe element if the pages are same-origin domain"); + + done(); +}; + +function waitForMessage() { + return new Promise(resolve => { + window.addEventListener("message", e => resolve(e.data), { once: true }); + }); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/frameElement.sub.html b/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/frameElement.sub.html new file mode 100644 index 0000000000..7ea1182081 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/frameElement.sub.html @@ -0,0 +1,66 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"/> + <title>HTML Test: window.frameElement</title> + <link rel="author" title="Intel" href="http://www.intel.com/" /> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <!-- t1 (same-origin)--> + <iframe id="iframe_0"></iframe> + <iframe id="iframe_1" src="./resources/frameElement-nested-frame.html"></iframe> + <object id="object_id" name="object_name" type="text/html" data="about:blank"></object> + <embed id="embed_id" name="embed_name" type="image/svg+xml" src="/images/green.svg" /> + + <!-- t2 (cross-origin) --> + <iframe name="iframe_2" src="http://{{hosts[alt][]}}:{{ports[http][0]}}/html/browsers/windows/nested-browsing-contexts/resources/frameElement-nested-frame.html"></iframe> + + <!-- t3 (cross-origin) --> + <iframe id="iframe_3" src="http://{{hosts[alt][]}}:{{ports[http][0]}}/html/browsers/windows/nested-browsing-contexts/resources/frameElement-window-post.html"></iframe> + + <script> + test(function() { + assert_equals(window.frameElement, null, + "The frameElement attribute should be null."); + }, "The window's frameElement attribute must return null if it is not a nested browsing context"); + + var t1 = async_test("The window's frameElement attribute must return its container element if it is a nested browsing context"); + window.addEventListener("load", t1.step_func_done(function() { + assert_equals(frames[0].frameElement, document.getElementById("iframe_0"), + "The frameElement attribute should be the first iframe element."); + assert_equals(window["object_name"].frameElement, document.getElementById("object_id"), + "The frameElement attribute should be the object element."); + assert_equals(window["embed_name"].frameElement, document.getElementById("embed_id"), + "The frameElement attribute should be the embed element."); + assert_equals(document.getElementById("iframe_1").contentWindow[0].frameElement, + document.getElementById("iframe_1").contentDocument.getElementById("f1"), + "The frameElement attribute should be the frame element in 'resources/frameElement-nested-frame.html'."); + })); + + var t2 = async_test("The SecurityError must be thrown if the window accesses to frameElement attribute of a Window which does not have the same effective script origin"); + window.addEventListener("load", t2.step_func_done(function() { + assert_throws_dom("SecurityError", function() { + frames["iframe_2"].frameElement; + }, + "The SecurityError exception should be thrown."); + })); + + var t3 = async_test("The window's frameElement attribute must return null if the container's document does not have the same effective script origin"); + window.addEventListener("load", function() { + window.addEventListener("message", function(event) { + var data = JSON.parse(event.data); + if (data.name == "testcase3") { + t3.step(function() { + assert_equals(data.result, "window.frameElement = null", + "The frameElement attribute should be null."); + t3.done(); + }); + } + }, false); + document.getElementById("iframe_3").contentWindow.postMessage(null, "*"); + }) + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/name-attribute.window.js b/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/name-attribute.window.js new file mode 100644 index 0000000000..69908af71b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/name-attribute.window.js @@ -0,0 +1,58 @@ +// META: script=/common/get-host-info.sub.js + +[ + "frame", // This works without <frameset>, so great + "iframe", + "object", + "embed", +].forEach(element => { + [ + null, + "", + "initialvalue" + ].forEach(initialNameValue => { + [ + "same-origin", + "cross-origin" + ].forEach(originType => { + async_test(t => { + const ident = element + initialNameValue + originType, + file = `${new URL("resources/post-to-parent.html", location.href).pathname}?ident=${ident}`, + child = originType === "same-origin" ? file : `${get_host_info().HTTP_REMOTE_ORIGIN}${file}`, + frame = document.createElement(element), + expectedNameValue = initialNameValue || ""; + let state = "set"; + const listener = t.step_func(e => { + if (e.data.ident === ident) { + assert_equals(e.data.name, expectedNameValue); // This check is always the same + if (state === "set") { + frame.setAttribute("name", "meh"); + state = "remove" + e.source.postMessage(null, "*"); + return; + } + if (state === "remove") { + frame.removeAttribute("name"); + state = "done"; + e.source.postMessage(null, "*"); + return; + } + if (state === "done") { + t.done(); + } + } + }); + frame.setAttribute(element === "object" ? "data" : "src", child); + if (initialNameValue !== null) { + frame.setAttribute("name", initialNameValue); + } + t.add_cleanup(() => { + self.removeEventListener("message", listener); + frame.remove(); + }); + self.addEventListener("message", listener); + document.body.append(frame); + }, `${originType} <${element}${initialNameValue !== null ? ' name=' + initialNameValue : ''}>`); + }); + }); +}); diff --git a/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/resources/frameElement-nested-frame.html b/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/resources/frameElement-nested-frame.html new file mode 100644 index 0000000000..d066b8d4cb --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/resources/frameElement-nested-frame.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<meta charset="utf-8"/> +<title>HTML Test: child browsing context created by the frame element</title> +<link rel="author" title="Intel" href="http://www.intel.com/" /> +<frameset> + <frame id="f1" name="frame"> +</frameset> diff --git a/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/resources/frameElement-sibling-accessed.html b/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/resources/frameElement-sibling-accessed.html new file mode 100644 index 0000000000..15245981ce --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/resources/frameElement-sibling-accessed.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>This page will set its document.domain on request so that its sibling can access it</title> + +<h1>I did get loaded</h1> + +<script> +"use strict"; + +window.onmessage = e => { + const { newDocumentDomain } = e.data; + document.domain = newDocumentDomain; + + parent.postMessage("done", "*"); +}; +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/resources/frameElement-sibling-accessor.html b/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/resources/frameElement-sibling-accessor.html new file mode 100644 index 0000000000..4b4c7a87bb --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/resources/frameElement-sibling-accessor.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>This page will attempt to access the frameElement of its sibling and report the results on request</title> + +<h1>I did get loaded</h1> + +<script> +"use strict"; + +window.onmessage = e => { + const { newDocumentDomain } = e.data; + if (newDocumentDomain) { + document.domain = newDocumentDomain; + } + + const siblingWindow = parent.frames[0]; + + try { + const { frameElement } = siblingWindow; + + let result = "something wierd happened"; + if (frameElement === null) { + result = "null"; + } else if (frameElement.constructor) { + result = frameElement.constructor.name; + } + + parent.postMessage(result, "*"); + } catch (e) { + parent.postMessage(e.name, "*"); + } +}; +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/resources/frameElement-window-post.html b/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/resources/frameElement-window-post.html new file mode 100644 index 0000000000..d67bde26f4 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/resources/frameElement-window-post.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<meta charset="utf-8"/> +<title>Testcase 3: frameElement attribute must return null if the container\'s document does not have the same effective script origin</title> +<script> +window.addEventListener("message", function (event) { + try { + var result = "window.frameElement = " + window.frameElement; + } catch (e) { + result = e.message; + } + event.source.postMessage(JSON.stringify({name: "testcase3", result: result}), + "*"); +}, false); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/resources/post-to-opener.html b/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/resources/post-to-opener.html new file mode 100644 index 0000000000..65a825f573 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/resources/post-to-opener.html @@ -0,0 +1,7 @@ +<script> + if (window.opener) { + window.opener.postMessage({ + "parent_isTop": (window.parent === window) + }, "*"); + } +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/resources/post-to-parent.html b/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/resources/post-to-parent.html new file mode 100644 index 0000000000..302e9d9cc1 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/resources/post-to-parent.html @@ -0,0 +1,6 @@ +<script> +const ident = new URL(location).searchParams.get("ident"), + post = () => parent.postMessage({ name: window.name, ident }, "*"); +onmessage = () => post(); +post(); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/window-parent-null.html b/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/window-parent-null.html new file mode 100644 index 0000000000..428312086e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/window-parent-null.html @@ -0,0 +1,66 @@ +<!doctype html> +<meta charset="utf-8"> +<title>window.parent: `null`</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +async_test(t => { + var iframe = document.createElement('iframe'); + iframe.onload = t.step_func_done(() => { + var iWindow = iframe.contentWindow; + assert_equals(iWindow.parent, window); + document.body.removeChild(iframe); + assert_equals(iWindow.parent, null); + }); + + document.body.appendChild(iframe); +}, '`window.parent` is null when browsing context container element removed'); + +async_test(t => { + var iframe = document.createElement('iframe'); + var iframe2, removedEl; + + var testFunc = t.step_func(() => { + var frameWindow = iframe.contentWindow; + var frame2Window = iframe2.contentWindow; + + assert_equals(frameWindow.parent, window); + assert_equals(frame2Window.parent, frameWindow); + + iframe.removeEventListener('load', nestFrame); + iframe2.removeEventListener('load', testFunc); + removedEl = document.body.removeChild(iframe); + + assert_equals(frameWindow.parent, null); + assert_equals(frame2Window.parent, null); + + removedEl.addEventListener('load', t.step_func_done(() => { + // reattached iframe's browsing context will report window.parent again + assert_equals(removedEl.contentWindow.parent, window); + // The following window s are no longer referenced by active browsing contexts + assert_equals(frameWindow.parent, null); + assert_equals(frame2Window.parent, null); + // Per #the-iframe-element, reattaching a removed iframe will result in the + // browser creating a new browsing context once again, with a fresh + // document in our case, so the second iframe or any other elements + // previously added to iframe.contentDocument will be gone + assert_equals(removedEl.contentDocument.querySelector('iframe'), null); + assert_equals(removedEl.contentDocument.querySelector('hr'), null); + })); + document.body.appendChild(removedEl); + }); + + var nestFrame = function () { + iframe.contentDocument.body.appendChild(document.createElement('hr')); + iframe2 = iframe.contentDocument.createElement('iframe'); + // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1229707 + iframe2.src = '/common/blank.html'; + iframe2.addEventListener('load', testFunc); + iframe.contentDocument.body.appendChild(iframe2); + }; + + iframe.addEventListener('load', nestFrame); + document.body.appendChild(iframe); +}, '`window.parent` null when parent browsing context container removed'); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/window-parent.html b/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/window-parent.html new file mode 100644 index 0000000000..ef662dca5e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/window-parent.html @@ -0,0 +1,44 @@ +<!doctype html> +<meta charset="utf-8"> +<title>window.parent</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> + test(function() { + assert_equals(window, parent) + }, '`window.parent` for top-level browsing context'); + +async_test(t => { + var iframe = document.createElement('iframe'); + iframe.onload = t.step_func_done(function () { + var iWindow = iframe.contentWindow; + assert_equals(iWindow.parent, window); + }); + document.body.appendChild(iframe); +}, '`window.parent` on single nested browsing context'); + +async_test(t => { + var iframe = document.createElement('iframe'); + var iframe2; + + var testFunc = t.step_func_done(function () { + var frameWindow = iframe.contentWindow; + var frame2Window = iframe2.contentWindow; + assert_equals(frameWindow.parent, window); + assert_equals(frame2Window.parent, frameWindow); + assert_not_equals(frame2Window.parent, window); + }); + + var nestFrame = function () { + iframe2 = iframe.contentDocument.createElement('iframe'); + // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1229707 + iframe2.src = '/common/blank.html'; + iframe2.addEventListener('load', testFunc); + iframe.contentDocument.body.appendChild(iframe2); + }; + + iframe.addEventListener('load', nestFrame); + document.body.appendChild(iframe); +}, '`window.parent` for multiple nested browsing contexts'); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/window-top-null.html b/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/window-top-null.html new file mode 100644 index 0000000000..cf7fcf2ae1 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/window-top-null.html @@ -0,0 +1,66 @@ +<!doctype html> +<meta charset="utf-8"> +<title>window.top: `null`</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +async_test(function (t) { + var iframe = document.createElement('iframe'); + iframe.onload = t.step_func_done(() => { + var iWindow = iframe.contentWindow; + /** + * `top` should return the top-level browsing context but will return + * `null` if none exists, such as when any of the BC's ancestor browsing + * context container's document elements are disconnected/removed. + */ + assert_equals(iWindow.top, window); + document.body.removeChild(iframe); + assert_equals(iWindow.top, null); + }); + + document.body.appendChild(iframe); +}, '`window.top` is null when browsing context container element removed'); + +async_test(t => { + var iframe = document.createElement('iframe'); + var iframe2, removedEl; + + var testFunc = t.step_func(() => { + var frameWindow = iframe.contentWindow; + var frame2Window = iframe2.contentWindow; + + assert_equals(frameWindow.top, window); + assert_equals(frame2Window.top, window); + + iframe.removeEventListener('load', nestFrame); + iframe2.removeEventListener('load', testFunc); + + removedEl = document.body.removeChild(iframe); + + assert_equals(frameWindow.top, null); + assert_equals(frame2Window.top, null); + + removedEl.addEventListener('load', t.step_func_done(() => { + // reattached iframe's browsing context will report window.top + assert_equals(removedEl.contentWindow.top, window); + // removed/re-added iframes will have new browsing context / window + assert_equals(frameWindow.top, null); + assert_equals(frame2Window.top, null); + })); + + document.body.appendChild(removedEl); + }); + + var nestFrame = function () { + iframe2 = iframe.contentDocument.createElement('iframe'); + // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1229707 + iframe2.src = '/common/blank.html'; + iframe2.addEventListener('load', testFunc); + iframe.contentDocument.body.appendChild(iframe2); + }; + + iframe.addEventListener('load', nestFrame); + document.body.appendChild(iframe); +}, '`window.top`null when any ancestor browsing context container removed'); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/window-top.html b/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/window-top.html new file mode 100644 index 0000000000..e5590adeb8 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/nested-browsing-contexts/window-top.html @@ -0,0 +1,65 @@ +<!doctype html> +<meta charset="utf-8"> +<title>window.top</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +test(function() { + assert_equals(window, top) +}, "Top level browsing context"); + +function step_func(test) { + return function (top_pointer) { + test.step(function() {assert_equals(top_pointer, window);}) + test.done(); + } +} + +var t1 = async_test("One nested iframe"); +t1.step(function() { + var iframe = document.createElement("iframe"); + //iframe.src = "data:text/html," + + iframe.onload = t1.step_func( + function() { + var doc = iframe.contentDocument; + iframe.contentWindow.test_func = step_func(t1); + + var script = doc.createElement("script") + script.textContent = "test_func(top);" + doc.body.appendChild(script); + }); + document.body.appendChild(iframe); +}); + +var t2 = async_test("Two nested iframes"); +t2.step(function() { + var iframe = document.createElement("iframe"); + //iframe.src = "data:text/html," + + iframe.onload = t2.step_func( + function() { + var doc = iframe.contentDocument; + iframe2 = document.createElement("iframe"); + //iframe2.src = "data:text/html," + // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1229707 + iframe2.src = '/common/blank.html'; + + iframe2.onload = t2.step_func( + function() { + var doc2 = iframe2.contentDocument; + + iframe2.contentWindow.test_func = step_func(t2); + + var script = doc2.createElement("script") + script.textContent = "test_func(top);" + doc2.body.appendChild(script); + }); + doc.body.appendChild(iframe2); + }); + + document.body.appendChild(iframe); +}); + +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/noreferrer-cross-origin-close-manual.sub.html b/testing/web-platform/tests/html/browsers/windows/noreferrer-cross-origin-close-manual.sub.html new file mode 100644 index 0000000000..8e2740c268 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/noreferrer-cross-origin-close-manual.sub.html @@ -0,0 +1,3 @@ +<meta charset=utf-8> +<p>Follow this link to open a new browsing context and then confirm it can be closed: +<a rel=noreferrer target=reallydoesnotmatter href="//天気の良い日.{{location[host]}}/html/browsers/windows/resources/window-close-button.html">link</a>. diff --git a/testing/web-platform/tests/html/browsers/windows/noreferrer-cross-origin-manual.html b/testing/web-platform/tests/html/browsers/windows/noreferrer-cross-origin-manual.html new file mode 100644 index 0000000000..f5879ee6d7 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/noreferrer-cross-origin-manual.html @@ -0,0 +1,10 @@ +<ol> + <li><p>After clicking these two links in order a single browsing context should be open showing + <code>example.org</code>: + <a target=doesnotmatter href="http://example.com/">one</a>, + <a target=doesnotmatter href="http://example.org/">two</a>. + + <li><p>After clicking these two links two browsing contexts should have been opened: + <a rel=noreferrer target=reallydoesnotmatter href="http://example.com/">one</a>, + <a rel=noreferrer target=reallydoesnotmatter href="http://example.com/">two</a>. +</ol> diff --git a/testing/web-platform/tests/html/browsers/windows/noreferrer-cross-origin-window-name-manual.sub.html b/testing/web-platform/tests/html/browsers/windows/noreferrer-cross-origin-window-name-manual.sub.html new file mode 100644 index 0000000000..c598ffbfaa --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/noreferrer-cross-origin-window-name-manual.sub.html @@ -0,0 +1,3 @@ +<meta charset=utf-8> +<p>Follow this link to open a new browsing context and then confirm it says "idonteven": +<a rel=noreferrer target=idonteven href="//天気の良い日.{{location[host]}}/html/browsers/windows/resources/echo-window-name.html">link</a>. diff --git a/testing/web-platform/tests/html/browsers/windows/noreferrer-null-opener.html b/testing/web-platform/tests/html/browsers/windows/noreferrer-null-opener.html new file mode 100644 index 0000000000..f308abc05e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/noreferrer-null-opener.html @@ -0,0 +1,25 @@ +<!doctype html> +<title>rel=noreferrer nullifies window.opener</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> + async_test(function(t) { + localStorage.clear() + + var hyperlink = document.body.appendChild(document.createElement("a")) + hyperlink.rel = "noreferrer" + hyperlink.target = "_blank" + hyperlink.href = "resources/window-opener.html" + hyperlink.click() + document.body.removeChild(hyperlink) + + addEventListener("storage", function(e) { + t.step(function() { + assert_equals(e.newValue, "null") + localStorage.clear() + t.done() + }) + }) + }) +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/noreferrer-window-name.html b/testing/web-platform/tests/html/browsers/windows/noreferrer-window-name.html new file mode 100644 index 0000000000..5fe649d172 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/noreferrer-window-name.html @@ -0,0 +1,86 @@ +<!doctype html> +<title>rel=noreferrer and reuse of names</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> + async_test(function(t) { + localStorage.clear() + + function makeHyperlink(n) { + var hyperlink = document.body.appendChild(document.createElement("a")) + hyperlink.rel = "noreferrer" + hyperlink.target = "sufficientlyrandomwindownameamiright" + hyperlink.href = "resources/noreferrer-window-name.html#" + n + return hyperlink + } + + var hyperlink1 = makeHyperlink(1), + hyperlink2 = makeHyperlink(2) + + t.add_cleanup(function() { + localStorage.setItem("x", "close") + localStorage.clear() + document.body.removeChild(hyperlink1) + document.body.removeChild(hyperlink2) + }) + + addEventListener("storage", function(e) { + t.step(function() { + if(localStorage.getItem("window1") && localStorage.getItem("window2")) { + localStorage.setItem("x", "close") + t.done() + } + }) + }) + + hyperlink1.click() + hyperlink2.click() + }, "Following a noreferrer link with a named target should not cause creation of a window that can be targeted by another noreferrer link with the same named target"); + + async_test(function(t) { + var ifr = document.createElement("iframe"); + ifr.name = "sufficientlyrandomwindownameamiright2"; + ifr.onload = t.step_func(function() { + var hyperlink = document.body.appendChild(document.createElement("a")); + t.add_cleanup(function() { + hyperlink.remove(); + }); + hyperlink.rel = "noreferrer"; + hyperlink.href = URL.createObjectURL(new Blob(["hello subframe"], + { type: "text/html"})); + hyperlink.target = "sufficientlyrandomwindownameamiright2"; + ifr.onload = t.step_func_done(function() { + assert_equals(ifr.contentDocument.documentElement.textContent, + "hello subframe"); + }); + hyperlink.click(); + }); + document.body.appendChild(ifr); + t.add_cleanup(function() { + ifr.remove(); + }); + }, "Targeting a rel=noreferrer link at an existing named subframe should work"); + + async_test(function(t) { + var win = window.open("", "sufficientlyrandomwindownameamiright3"); + t.add_cleanup(function() { + win.close(); + }); + + var hyperlink = document.body.appendChild(document.createElement("a")); + t.add_cleanup(function() { + hyperlink.remove(); + }); + hyperlink.rel = "noreferrer"; + hyperlink.href = URL.createObjectURL(new Blob(["hello window"], + { type: "text/html"})); + hyperlink.target = "sufficientlyrandomwindownameamiright3"; + win.onload = t.step_func_done(function() { + assert_equals(win.document.documentElement.textContent, + "hello window"); + }); + hyperlink.click(); + }, "Targeting a rel=noreferrer link at an existing named window should work"); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/opener-cross-origin-manual.sub.html b/testing/web-platform/tests/html/browsers/windows/opener-cross-origin-manual.sub.html new file mode 100644 index 0000000000..0c018663a2 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/opener-cross-origin-manual.sub.html @@ -0,0 +1,10 @@ +<ol> + <li><p>Clicking this link must navigate this page to a resource that contains "THE END": + <a href=//{{domains[www1]}}:{{location[port]}}/html/browsers/windows/resources/opener-cross-origin.html target=_blank>test</a> + + <li><p>Clicking this link must open a new browsing context that is empty: + <a rel=noreferrer href=//{{domains[www1]}}:{{location[port]}}/html/browsers/windows/resources/opener-cross-origin.html target=_blank>test</a> + + <li><p>Clicking this link must navigate this page to a resource that contains "THE END": + <a href=//{{domains[www1]}}:{{location[port]}}/html/browsers/windows/resources/opener-cross-origin-embed.sub.html target=_blank>test</a> +</ol> diff --git a/testing/web-platform/tests/html/browsers/windows/opener-string.window.js b/testing/web-platform/tests/html/browsers/windows/opener-string.window.js new file mode 100644 index 0000000000..a39c584d20 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/opener-string.window.js @@ -0,0 +1,14 @@ +test(t => { + const popup = window.open(); + t.add_cleanup(() => popup.close()); + assert_equals(popup.opener, self, "The opener of the popup is me"); + assert_equals(Object.getOwnPropertyDescriptor(popup, "opener").writable, undefined); + + popup.opener = "blah"; + assert_equals(popup.opener, "blah", "The popup's opener is now a string"); + assert_equals(Object.getOwnPropertyDescriptor(popup, "opener").writable, true); + + const openerGetter = Object.getOwnPropertyDescriptor(self, "opener").get; + const popupOpener = openerGetter.call(popup); + assert_equals(popupOpener, self, "The underlying opener of the popup is still me"); +}, "Setting popup.opener to a string"); diff --git a/testing/web-platform/tests/html/browsers/windows/post-message/first-party-to-first-party-cross-partition.sub.html b/testing/web-platform/tests/html/browsers/windows/post-message/first-party-to-first-party-cross-partition.sub.html new file mode 100644 index 0000000000..f91d9403ea --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/post-message/first-party-to-first-party-cross-partition.sub.html @@ -0,0 +1,26 @@ +<!doctype html> +<meta charset=utf-8> +<title>postMessage: First-Party to First-Party, Cross-Partition</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +// Here's the set-up for this test: +// Step 1. (Site 1 Window) set up listener and open Site 2 Window. +// Step 2. (Site 2 Window) send "Site 2 Window" message to Site 1 Window. +// Step 3. (Site 1 Window) receive "Site 2 Window" message and exit. + +async_test(t => { + // Step 3 + const listener = t.step_func(e => { + if (e.data === "Site 2 Window") { + t.done(); + } + }); + // Step 1 + window.addEventListener("message", listener); + const site2Window = window.open("http://{{hosts[alt][]}}:{{ports[http][0]}}/html/browsers/windows/post-message/resources/first-party-to-first-party-cross-partition-window.html", "", "noopener=false"); + t.add_cleanup(() => site2Window.close()); +}, "postMessage: First-Party to First-Party, Cross-Partition"); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/post-message/first-party-to-first-party-same-partition.html b/testing/web-platform/tests/html/browsers/windows/post-message/first-party-to-first-party-same-partition.html new file mode 100644 index 0000000000..6fc915298b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/post-message/first-party-to-first-party-same-partition.html @@ -0,0 +1,26 @@ +<!doctype html> +<meta charset=utf-8> +<title>postMessage: First-Party to First-Party, Same-Partition</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +// Here's the set-up for this test: +// Step 1. (Site 1 Window A) set up listener and open Site 1 Window B. +// Step 2. (Site 1 Window B) send "Site 1 Window B" message to Site 1 Window A. +// Step 3. (Site 1 Window A) receive "Site 1 Window B" message and exit. + +async_test(t => { + // Step 3 + const listener = t.step_func(e => { + if (e.data === "Site 1 Window B") { + t.done(); + } + }); + // Step 1 + window.addEventListener("message", listener); + const site1WindowB = window.open("/html/browsers/windows/post-message/resources/first-party-to-first-party-same-partition-window.html", "", "noopener=false"); + t.add_cleanup(() => site1WindowB.close()); +}, "postMessage: First-Party to First-Party, Same-Partition"); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-cross-origin.sub.html b/testing/web-platform/tests/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-cross-origin.sub.html new file mode 100644 index 0000000000..de776f8381 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-cross-origin.sub.html @@ -0,0 +1,27 @@ +<!doctype html> +<meta charset=utf-8> +<title>postMessage: First-Party to Third-Party, Cross-Partition, Cross-Origin</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +// Here's the set-up for this test: +// Step 1. (Site 1 Window) set up listener and open Site 2 Window. +// Step 2. (Site 2 Window) open Site 3 Frame. +// Step 3. (Site 3 Frame) send "Site 3 Frame" message to Site 1 Window. +// Step 4. (Site 1 Window) receive "Site 1 Frame" message and exit. + +async_test(t => { + // Step 4 + const listener = t.step_func(e => { + if (e.data === "Site 3 Frame") { + t.done(); + } + }); + // Step 1 + window.addEventListener("message", listener); + const site2Window = window.open("https://{{hosts[alt][]}}:{{ports[https][0]}}/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-cross-origin-window.sub.https.html", "", "noopener=false"); + t.add_cleanup(() => site2Window.close()); +}, "postMessage: First-Party to Third-Party, Cross-Partition, Cross-Origin"); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-same-origin.sub.html b/testing/web-platform/tests/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-same-origin.sub.html new file mode 100644 index 0000000000..490b647fa4 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-same-origin.sub.html @@ -0,0 +1,27 @@ +<!doctype html> +<meta charset=utf-8> +<title>postMessage: First-Party to Third-Party, Cross-Partition, Same-Origin</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +// Here's the set-up for this test: +// Step 1. (Site 1 Window) set up listener and open Site 2 Window. +// Step 2. (Site 2 Window) open Site 1 Frame. +// Step 3. (Site 1 Frame) send "Site 1 Frame" message to Site 1 Window. +// Step 4. (Site 1 Window) receive "Site 1 Frame" message and exit. + +async_test(t => { + // Step 4 + const listener = t.step_func(e => { + if (e.data === "Site 1 Frame") { + t.done(); + } + }); + // Step 1 + window.addEventListener("message", listener); + const site2Window = window.open("http://{{hosts[alt][]}}:{{ports[http][0]}}/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-same-origin-window.sub.html", "", "noopener=false"); + t.add_cleanup(() => site2Window.close()); +}, "postMessage: First-Party to Third-Party, Cross-Partition, Same-Origin"); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/post-message/resources/first-party-to-first-party-cross-partition-window.html b/testing/web-platform/tests/html/browsers/windows/post-message/resources/first-party-to-first-party-cross-partition-window.html new file mode 100644 index 0000000000..cdfa9d95ca --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/post-message/resources/first-party-to-first-party-cross-partition-window.html @@ -0,0 +1,8 @@ +<!doctype html> +<meta charset=utf-8> +<body> +<script> +// Step 2 (html/browsers/windows/post-message/first-party-to-first-party-cross-partition.sub.html) +window.opener.postMessage("Site 2 Window", "*"); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/post-message/resources/first-party-to-first-party-same-partition-window.html b/testing/web-platform/tests/html/browsers/windows/post-message/resources/first-party-to-first-party-same-partition-window.html new file mode 100644 index 0000000000..4baf45d3c8 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/post-message/resources/first-party-to-first-party-same-partition-window.html @@ -0,0 +1,8 @@ +<!doctype html> +<meta charset=utf-8> +<body> +<script> +// Step 2 (html/browsers/windows/post-message/first-party-to-first-party-same-partition.html) +window.opener.postMessage("Site 1 Window B", window.opener.origin); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-cross-origin-iframe.https.html b/testing/web-platform/tests/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-cross-origin-iframe.https.html new file mode 100644 index 0000000000..eef594831a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-cross-origin-iframe.https.html @@ -0,0 +1,8 @@ +<!doctype html> +<meta charset=utf-8> +<body> +<script> +// Step 3 (html/browsers/windows/post-message/first-party-to-third-party-cross-partition-cross-origin.sub.html) +window.top.opener.postMessage("Site 3 Frame", "*"); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-cross-origin-window.sub.https.html b/testing/web-platform/tests/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-cross-origin-window.sub.https.html new file mode 100644 index 0000000000..7edfbd9328 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-cross-origin-window.sub.https.html @@ -0,0 +1,6 @@ +<!doctype html> +<meta charset=utf-8> +<body> +<!--Step 2 (html/browsers/windows/post-message/first-party-to-third-party-cross-partition-cross-origin.sub.html)--> +<iframe src="https://{{host}}:{{ports[https][0]}}/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-cross-origin-iframe.https.html"></iframe> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-same-origin-iframe.html b/testing/web-platform/tests/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-same-origin-iframe.html new file mode 100644 index 0000000000..eb17036c8c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-same-origin-iframe.html @@ -0,0 +1,8 @@ +<!doctype html> +<meta charset=utf-8> +<body> +<script> +// Step 3 (html/browsers/windows/post-message/first-party-to-third-party-cross-partition-same-origin.sub.html) +window.top.opener.postMessage("Site 1 Frame", window.top.opener.origin); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-same-origin-window.sub.html b/testing/web-platform/tests/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-same-origin-window.sub.html new file mode 100644 index 0000000000..f99b96c466 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-same-origin-window.sub.html @@ -0,0 +1,6 @@ +<!doctype html> +<meta charset=utf-8> +<body> +<!--Step 2 (html/browsers/windows/post-message/first-party-to-third-party-cross-partition-same-origin.sub.html)--> +<iframe src="http://{{host}}:{{ports[http][0]}}/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-same-origin-iframe.html"></iframe> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-cross-origin-iframe.sub.html b/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-cross-origin-iframe.sub.html new file mode 100644 index 0000000000..348efb3f9c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-cross-origin-iframe.sub.html @@ -0,0 +1,17 @@ +<!doctype html> +<meta charset=utf-8> +<body> +<script> +// Step 4 (html/browsers/windows/post-message/third-party-to-first-party-cross-partition-cross-origin.sub.html) +let site2Window; +const listener = e => { + if (e.data === "Site 3 Window") { + site2Window.close(); + window.top.postMessage("Site 2 Frame", "*"); + } +}; +// Step 2 (html/browsers/windows/post-message/third-party-to-first-party-cross-partition-cross-origin.sub.html) +window.addEventListener("message", listener); +site2Window = window.open("https://{{hosts[alt][]}}:{{ports[https][0]}}/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-cross-origin-window.https.html", "", "noopener=false"); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-cross-origin-window.https.html b/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-cross-origin-window.https.html new file mode 100644 index 0000000000..0045909494 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-cross-origin-window.https.html @@ -0,0 +1,8 @@ +<!doctype html> +<meta charset=utf-8> +<body> +<script> +// Step 3 (html/browsers/windows/post-message/third-party-to-first-party-cross-partition-cross-origin.sub.html) +window.opener.postMessage("Site 3 Window", "*"); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-same-origin-iframe.sub.html b/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-same-origin-iframe.sub.html new file mode 100644 index 0000000000..405b1053d3 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-same-origin-iframe.sub.html @@ -0,0 +1,17 @@ +<!doctype html> +<meta charset=utf-8> +<body> +<script> +// Step 4 (html/browsers/windows/post-message/third-party-to-first-party-cross-partition-same-origin.sub.html) +let site2Window; +const listener = e => { + if (e.data === "Site 2 Window") { + site2Window.close(); + window.top.postMessage("Site 2 Frame", "*"); + } +}; +// Step 2 (html/browsers/windows/post-message/third-party-to-first-party-cross-partition-same-origin.sub.html) +window.addEventListener("message", listener); +site2Window = window.open("http://{{hosts[alt][]}}:{{ports[http][0]}}/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-same-origin-window.html", "", "noopener=false"); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-same-origin-window.html b/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-same-origin-window.html new file mode 100644 index 0000000000..0ae51e3fc1 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-same-origin-window.html @@ -0,0 +1,8 @@ +<!doctype html> +<meta charset=utf-8> +<body> +<script> +// Step 3 (html/browsers/windows/post-message/third-party-to-first-party-cross-partition-same-origin.sub.html) +window.opener.postMessage("Site 2 Window", window.opener.origin); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-cross-origin-iframe-a.sub.html b/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-cross-origin-iframe-a.sub.html new file mode 100644 index 0000000000..a11f3640e8 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-cross-origin-iframe-a.sub.html @@ -0,0 +1,17 @@ +<!doctype html> +<meta charset=utf-8> +<body> +<script> +// Step 5 (html/browsers/windows/post-message/third-party-to-third-party-cross-partition-cross-origin.sub.html) +let site3Window; +const listener = e => { + if (e.data === "Site 4 Frame") { + site3Window.close(); + window.top.postMessage("Site 2 Frame", "*"); + } +}; +// Step 2 (html/browsers/windows/post-message/third-party-to-third-party-cross-partition-cross-origin.sub.html) +window.addEventListener("message", listener); +site3Window = window.open("https://{{host}}:{{ports[https][0]}}/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-cross-origin-window.sub.https.html", "", "noopener=false"); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-cross-origin-iframe-b.https.html b/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-cross-origin-iframe-b.https.html new file mode 100644 index 0000000000..f56e2b35d8 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-cross-origin-iframe-b.https.html @@ -0,0 +1,8 @@ +<!doctype html> +<meta charset=utf-8> +<body> +<script> +// Step 4 (html/browsers/windows/post-message/third-party-to-third-party-cross-partition-cross-origin.sub.html) +window.top.opener.postMessage("Site 4 Frame", "*"); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-cross-origin-window.sub.https.html b/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-cross-origin-window.sub.https.html new file mode 100644 index 0000000000..1307287587 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-cross-origin-window.sub.https.html @@ -0,0 +1,6 @@ +<!doctype html> +<meta charset=utf-8> +<body> +<!--Step 3 (html/browsers/windows/post-message/third-party-to-third-party-cross-partition-cross-origin.sub.html)--> +<iframe src="https://{{hosts[alt][]}}:{{ports[https][0]}}/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-cross-origin-iframe-b.https.html"></iframe> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-same-origin-iframe-a.sub.https.html b/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-same-origin-iframe-a.sub.https.html new file mode 100644 index 0000000000..ec551bfdec --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-same-origin-iframe-a.sub.https.html @@ -0,0 +1,17 @@ +<!doctype html> +<meta charset=utf-8> +<body> +<script> +// Step 5 (html/browsers/windows/post-message/third-party-to-third-party-cross-partition-same-origin.sub.html) +let site3Window; +const listener = e => { + if (e.data === "Site 2 Frame B") { + site3Window.close(); + window.top.postMessage("Site 2 Frame A", "*"); + } +}; +// Step 2 (html/browsers/windows/post-message/third-party-to-third-party-cross-partition-same-origin.sub.html) +window.addEventListener("message", listener); +site3Window = window.open("https://{{host}}:{{ports[https][0]}}/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-same-origin-window.sub.https.html", "", "noopener=false"); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-same-origin-iframe-b.https.html b/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-same-origin-iframe-b.https.html new file mode 100644 index 0000000000..fc15b1f125 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-same-origin-iframe-b.https.html @@ -0,0 +1,8 @@ +<!doctype html> +<meta charset=utf-8> +<body> +<script> +// Step 4 (html/browsers/windows/post-message/third-party-to-third-party-cross-partition-same-origin.sub.html) +window.top.opener.postMessage("Site 2 Frame B", window.top.opener.origin); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-same-origin-window.sub.https.html b/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-same-origin-window.sub.https.html new file mode 100644 index 0000000000..f26f709db3 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-same-origin-window.sub.https.html @@ -0,0 +1,6 @@ +<!doctype html> +<meta charset=utf-8> +<body> +<!--Step 3 (html/browsers/windows/post-message/third-party-to-third-party-cross-partition-same-origin.sub.html)--> +<iframe src="https://{{hosts[alt][]}}:{{ports[https][0]}}/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-same-origin-iframe-b.https.html"></iframe> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-same-partition-iframe-a.sub.html b/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-same-partition-iframe-a.sub.html new file mode 100644 index 0000000000..339dc9f06e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-same-partition-iframe-a.sub.html @@ -0,0 +1,17 @@ +<!doctype html> +<meta charset=utf-8> +<body> +<script> +// Step 5 (html/browsers/windows/post-message/third-party-to-third-party-same-partition.sub.html) +let site2WindowB; +const listener = e => { + if (e.data === "Site 2 Frame B") { + site2WindowB.close(); + window.top.postMessage("Site 2 Frame A", "*"); + } +}; +// Step 2 (html/browsers/windows/post-message/third-party-to-third-party-same-partition.sub.html) +window.addEventListener("message", listener); +site2WindowB = window.open("http://{{host}}:{{ports[http][0]}}/html/browsers/windows/post-message/resources/third-party-to-third-party-same-partition-window.sub.html", "", "noopener=false"); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-same-partition-iframe-b.html b/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-same-partition-iframe-b.html new file mode 100644 index 0000000000..daaf236bfc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-same-partition-iframe-b.html @@ -0,0 +1,8 @@ +<!doctype html> +<meta charset=utf-8> +<body> +<script> +// Step 4 (html/browsers/windows/post-message/third-party-to-third-party-same-partition.sub.html) +window.top.opener.postMessage("Site 2 Frame B", window.top.opener.origin); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-same-partition-window.sub.html b/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-same-partition-window.sub.html new file mode 100644 index 0000000000..7cea58f235 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-same-partition-window.sub.html @@ -0,0 +1,6 @@ +<!doctype html> +<meta charset=utf-8> +<body> +<!--Step 3 (html/browsers/windows/post-message/third-party-to-third-party-same-partition.sub.html)--> +<iframe src="http://{{hosts[alt][]}}:{{ports[http][0]}}/html/browsers/windows/post-message/resources/third-party-to-third-party-same-partition-iframe-b.html"></iframe> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/post-message/third-party-to-first-party-cross-partition-cross-origin.sub.html b/testing/web-platform/tests/html/browsers/windows/post-message/third-party-to-first-party-cross-partition-cross-origin.sub.html new file mode 100644 index 0000000000..2618e966de --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/post-message/third-party-to-first-party-cross-partition-cross-origin.sub.html @@ -0,0 +1,29 @@ +<!doctype html> +<meta charset=utf-8> +<title>postMessage: Third-Party to First-Party, Cross-Partition, Cross-Origin</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +// Here's the set-up for this test: +// Step 1. (Site 1 Window) set up listener and open Site 2 Frame. +// Step 2. (Site 2 Frame) set up listener and open Site 3 Window. +// Step 3. (Site 3 Window) send "Site 3 Window" message to Site 2 Frame. +// Step 4. (Site 2 Frame) receive "Site 3 Window" message and send "Site 2 Frame" message to Site 1 Window. +// Step 5. (Site 1 Window) receive "Site 2 Frame" message and exit. + +async_test(t => { + // Step 5 + const listener = t.step_func(e => { + if (e.data === "Site 2 Frame") { + t.done(); + } + }); + // Step 1 + window.addEventListener("message", listener); + const site2Frame = document.createElement("iframe"); + site2Frame.src = "http://{{hosts[alt][]}}:{{ports[http][0]}}/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-cross-origin-iframe.sub.html"; + document.body.appendChild(site2Frame); +}, "postMessage: Third-Party to First-Party, Cross-Partition, Cross-Origin"); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/post-message/third-party-to-first-party-cross-partition-same-origin.sub.html b/testing/web-platform/tests/html/browsers/windows/post-message/third-party-to-first-party-cross-partition-same-origin.sub.html new file mode 100644 index 0000000000..735b458841 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/post-message/third-party-to-first-party-cross-partition-same-origin.sub.html @@ -0,0 +1,29 @@ +<!doctype html> +<meta charset=utf-8> +<title>postMessage: Third-Party to First-Party, Cross-Partition, Same-Origin</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +// Here's the set-up for this test: +// Step 1. (Site 1 Window) set up listener and open Site 2 Frame. +// Step 2. (Site 2 Frame) set up listener and open Site 2 Window. +// Step 3. (Site 2 Window) send "Site 2 Window" message to Site 2 Frame. +// Step 4. (Site 2 Frame) receive "Site 2 Window" message and send "Site 2 Frame" message to Site 1 Window. +// Step 5. (Site 1 Window) receive "Site 2 Frame" message and exit. + +async_test(t => { + // Step 5 + const listener = t.step_func(e => { + if (e.data === "Site 2 Frame") { + t.done(); + } + }); + // Step 1 + window.addEventListener("message", listener); + const site2Frame = document.createElement("iframe"); + site2Frame.src = "http://{{hosts[alt][]}}:{{ports[http][0]}}/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-same-origin-iframe.sub.html"; + document.body.appendChild(site2Frame); +}, "postMessage: Third-Party to First-Party, Cross-Partition, Same-Origin"); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-cross-origin.sub.html b/testing/web-platform/tests/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-cross-origin.sub.html new file mode 100644 index 0000000000..1083071f7d --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-cross-origin.sub.html @@ -0,0 +1,30 @@ +<!doctype html> +<meta charset=utf-8> +<title>postMessage: Third-Party to Third-Party, Cross-Partition, Cross-Origin</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +// Here's the set-up for this test: +// Step 1. (Site 1 Window) set up listener and open Site 2 Frame. +// Step 2. (Site 2 Frame) set up listener and open Site 3 Window. +// Step 3. (Site 3 Window) open Site 4 Frame. +// Step 4. (Site 4 Frame) send "Site 4 Frame" message to Site 2 Frame. +// Step 5. (Site 2 Frame) receive "Site 4 Frame" message and send "Site 2 Frame" message to Site 1 Window. +// Step 6. (Site 1 Window) receive "Site 2 Frame" message and exit. + +async_test(t => { + // Step 6 + const listener = t.step_func(e => { + if (e.data === "Site 2 Frame") { + t.done(); + } + }); + // Step 1 + window.addEventListener("message", listener); + const site2FrameA = document.createElement("iframe"); + site2FrameA.src = "http://{{hosts[alt][]}}:{{ports[http][0]}}/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-cross-origin-iframe-a.sub.html"; + document.body.appendChild(site2FrameA); +}, "postMessage: Third-Party to Third-Party, Cross-Partition, Cross-Origin"); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-same-origin.sub.html b/testing/web-platform/tests/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-same-origin.sub.html new file mode 100644 index 0000000000..9caf6c11e4 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-same-origin.sub.html @@ -0,0 +1,30 @@ +<!doctype html> +<meta charset=utf-8> +<title>postMessage: Third-Party to Third-Party, Cross-Partition, Same-Origin</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +// Here's the set-up for this test: +// Step 1. (Site 1 Window) set up listener and open Site 2 Frame A. +// Step 2. (Site 2 Frame A) set up listener and open Site 3 Window. +// Step 3. (Site 3 Window) open Site 2 Frame B. +// Step 4. (Site 2 Frame B) send "Site 2 Frame B" message to Site 2 Frame A. +// Step 5. (Site 2 Frame A) receive "Site 2 Frame B" message and send "Site 2 Frame A" message to Site 1 Window. +// Step 6. (Site 1 Window) receive "Site 2 Frame A" message and exit. + +async_test(t => { + // Step 6 + const listener = t.step_func(e => { + if (e.data === "Site 2 Frame A") { + t.done(); + } + }); + // Step 1 + window.addEventListener("message", listener); + const site2FrameA = document.createElement("iframe"); + site2FrameA.src = "https://{{hosts[alt][]}}:{{ports[https][0]}}/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-same-origin-iframe-a.sub.https.html"; + document.body.appendChild(site2FrameA); +}, "postMessage: Third-Party to Third-Party, Cross-Partition, Same-Origin"); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/post-message/third-party-to-third-party-same-partition.sub.html b/testing/web-platform/tests/html/browsers/windows/post-message/third-party-to-third-party-same-partition.sub.html new file mode 100644 index 0000000000..c90a055268 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/post-message/third-party-to-third-party-same-partition.sub.html @@ -0,0 +1,30 @@ +<!doctype html> +<meta charset=utf-8> +<title>postMessage: Third-Party to Third-Party, Same-Partition</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +// Here's the set-up for this test: +// Step 1. (Site 1 Window A) set up listener and open Site 2 Frame A. +// Step 2. (Site 2 Frame A) set up listener and open Site 1 Window B. +// Step 3. (Site 1 Window B) open Site 2 Frame B. +// Step 4. (Site 2 Frame B) send "Site 2 Frame B" message to Site 2 Frame A. +// Step 5. (Site 2 Frame A) receive "Site 2 Frame B" message and send "Site 2 Frame A" message to Site 1 Window A. +// Step 6. (Site 1 Window A) receive "Site 2 Frame A" message and exit. + +async_test(t => { + // Step 6 + const listener = t.step_func(e => { + if (e.data === "Site 2 Frame A") { + t.done(); + } + }); + // Step 1 + window.addEventListener("message", listener); + const site2FrameA = document.createElement("iframe"); + site2FrameA.src = "http://{{hosts[alt][]}}:{{ports[http][0]}}/html/browsers/windows/post-message/resources/third-party-to-third-party-same-partition-iframe-a.sub.html"; + document.body.appendChild(site2FrameA); +}, "postMessage: Third-Party to Third-Party, Same-Partition"); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/resources/browsing-context-window.html b/testing/web-platform/tests/html/browsers/windows/resources/browsing-context-window.html new file mode 100644 index 0000000000..c1594f637e --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/resources/browsing-context-window.html @@ -0,0 +1,7 @@ +<script> +if (window.parent) { + window.parent.postMessage({ + "thisWindowEquivalency": (window === this) + }, "*"); +} +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/resources/document-domain-setter.html b/testing/web-platform/tests/html/browsers/windows/resources/document-domain-setter.html new file mode 100644 index 0000000000..3b14255571 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/resources/document-domain-setter.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Helper page that sets document.domain</title> +<script> +"use strict"; +document.domain = document.domain; +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/resources/echo-window-name.html b/testing/web-platform/tests/html/browsers/windows/resources/echo-window-name.html new file mode 100644 index 0000000000..a437fecb2c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/resources/echo-window-name.html @@ -0,0 +1 @@ +<script>document.write(name)</script> diff --git a/testing/web-platform/tests/html/browsers/windows/resources/iframe-nested-cross-origin.html b/testing/web-platform/tests/html/browsers/windows/resources/iframe-nested-cross-origin.html new file mode 100644 index 0000000000..a1a66ed842 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/resources/iframe-nested-cross-origin.html @@ -0,0 +1,2 @@ +<!doctype html> +<iframe scrolling=no frameborder=0 srcdoc='PASS'></iframe> diff --git a/testing/web-platform/tests/html/browsers/windows/resources/iframe-nested-printing-pass.html b/testing/web-platform/tests/html/browsers/windows/resources/iframe-nested-printing-pass.html new file mode 100644 index 0000000000..c2dca9185f --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/resources/iframe-nested-printing-pass.html @@ -0,0 +1,9 @@ +<!doctype html> +<style> +body { + margin: calc(0.5px * 2 / 3); +} +</style> +<body> +PASS +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/resources/message-parent.html b/testing/web-platform/tests/html/browsers/windows/resources/message-parent.html new file mode 100644 index 0000000000..ba60618ad4 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/resources/message-parent.html @@ -0,0 +1,5 @@ +<script> + window.parent.postMessage({ + "name": window.name, + }, "*"); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/resources/nested-post-to-opener.html b/testing/web-platform/tests/html/browsers/windows/resources/nested-post-to-opener.html new file mode 100644 index 0000000000..e92b69d7e7 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/resources/nested-post-to-opener.html @@ -0,0 +1,12 @@ +<body> +<script> + var i = document.createElement("iframe"); + i.name = "nested1"; + document.body.appendChild(i); + + window.opener.postMessage({ + "name": window.name, + "isTop": window.top === window + }, "*"); +</script> +</body> diff --git a/testing/web-platform/tests/html/browsers/windows/resources/noreferrer-window-name.html b/testing/web-platform/tests/html/browsers/windows/resources/noreferrer-window-name.html new file mode 100644 index 0000000000..8c106ca886 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/resources/noreferrer-window-name.html @@ -0,0 +1,8 @@ +<script> + addEventListener("storage", function(e) { + if(e.newValue === "close") { + close() + } + }) + localStorage.setItem("window" + location.hash.slice(1), "tralala") +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/resources/opener-cross-origin-embed.sub.html b/testing/web-platform/tests/html/browsers/windows/resources/opener-cross-origin-embed.sub.html new file mode 100644 index 0000000000..db0c003141 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/resources/opener-cross-origin-embed.sub.html @@ -0,0 +1,2 @@ +<meta charset=utf-8> +<iframe src=//{{domains[élève]}}:{{location[port]}}/html/browsers/windows/resources/opener-cross-origin.html></iframe> diff --git a/testing/web-platform/tests/html/browsers/windows/resources/opener-cross-origin-end.txt b/testing/web-platform/tests/html/browsers/windows/resources/opener-cross-origin-end.txt new file mode 100644 index 0000000000..8b74fbab8a --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/resources/opener-cross-origin-end.txt @@ -0,0 +1 @@ +THE END diff --git a/testing/web-platform/tests/html/browsers/windows/resources/opener-cross-origin.html b/testing/web-platform/tests/html/browsers/windows/resources/opener-cross-origin.html new file mode 100644 index 0000000000..7c536b9213 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/resources/opener-cross-origin.html @@ -0,0 +1,4 @@ +<script> + parent.opener.location.href = "./opener-cross-origin-end.txt" + parent.window.close() +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/resources/post-to-opener.html b/testing/web-platform/tests/html/browsers/windows/resources/post-to-opener.html new file mode 100644 index 0000000000..453fec97a7 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/resources/post-to-opener.html @@ -0,0 +1,8 @@ +<script> + if (window.opener) { + window.opener.postMessage({ + "name": window.name, + "isTop": window.top === window + }, "*"); + } +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/resources/restore-window-name-back.sub.html b/testing/web-platform/tests/html/browsers/windows/resources/restore-window-name-back.sub.html new file mode 100644 index 0000000000..ea2c3a92b4 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/resources/restore-window-name-back.sub.html @@ -0,0 +1,7 @@ +<!doctype html> +<script> + onload = () => { + window.name = "clear"; + history.back(); + }; +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/browsers/windows/resources/restore-window-name.sub.html b/testing/web-platform/tests/html/browsers/windows/resources/restore-window-name.sub.html new file mode 100644 index 0000000000..a1e573a7c0 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/resources/restore-window-name.sub.html @@ -0,0 +1,43 @@ +<!doctype html> +<html> +<head> +<title>popup helper for restore window.name test</title> +<script> + +const search = window.location.search.replace("?", ""); +const name = search.split("=")[1]; + +if (!search.startsWith("name=")) { + throw new Error("Unsupported test!"); +} + +function startTest() { + if (window.name === "start") { + window.name = name; + const url = new URL(window.location); + url.host = "{{hosts[alt][]}}:{{ports[https][0]}}"; + url.pathname = url.pathname.slice(0, url.pathname.lastIndexOf("/") + 1); + url.pathname += "restore-window-name-back.sub.html"; + window.location = url.href; + } +} + +function verifyResult() { + const result = document.getElementById("result"); + if (window.name === "start") { + result.textContent = "Please first click the above 'start test' link."; + return; + } + + result.textContent = window.name === name ? "PASS" : "FAIL"; +} + +</script> +</head> +<body> + <p>Please first click the 'start test' link. And then, click the 'Verify Result' button to verify the result. You should get a PASS after clicking the button</p> + <a id="navigate" href="javascript:void(0)" onclick="startTest()">start test</a><br> + <button onclick="verifyResult()">Verify Result</button> + Result: <a id="result"></a> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/windows/resources/target-cross-origin.sub.html b/testing/web-platform/tests/html/browsers/windows/resources/target-cross-origin.sub.html new file mode 100644 index 0000000000..8472f96b90 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/resources/target-cross-origin.sub.html @@ -0,0 +1,3 @@ +<meta charset=utf-8> +<p>Follow this link to open a new browsing context in a separate origin. +<a target=second href="{{location[scheme]}}://{{domains[www2]}}:{{location[port]}}/html/browsers/windows/resources/target-cross-origin.sub.html">link</a>. diff --git a/testing/web-platform/tests/html/browsers/windows/resources/window-close-button.html b/testing/web-platform/tests/html/browsers/windows/resources/window-close-button.html new file mode 100644 index 0000000000..38ec2aef5c --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/resources/window-close-button.html @@ -0,0 +1 @@ +<p>Clicking this button should close this browsing context: <button onclick=window.close()>button</button> diff --git a/testing/web-platform/tests/html/browsers/windows/resources/window-name-stash.py b/testing/web-platform/tests/html/browsers/windows/resources/window-name-stash.py new file mode 100644 index 0000000000..411a4587bc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/resources/window-name-stash.py @@ -0,0 +1,13 @@ +def main(request, response): + key = request.GET.first(b"id") + if request.method == "POST": + value = request.GET.first(b"value") + request.server.stash.take(key) + request.server.stash.put(key, value) + return b"OK" + else: + value = request.server.stash.take(key) + if value is not None: + return value + else: + return b"NONE" diff --git a/testing/web-platform/tests/html/browsers/windows/resources/window-name.sub.html b/testing/web-platform/tests/html/browsers/windows/resources/window-name.sub.html new file mode 100644 index 0000000000..55fb8ebbbc --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/resources/window-name.sub.html @@ -0,0 +1,102 @@ +<!doctype html> +<title>popup helper</title> +<script> + +const search = window.location.search.replace("?", ""); +const steps = search.split("|"); + +async function proceedTest() { + while (steps.length) { + const step = steps.shift(); + + if (step.startsWith("report=")) { + const id = step.split("=")[1]; + const stashURL = new URL("window-name-stash.py", location); + stashURL.searchParams.set('id', id); + stashURL.searchParams.set('value', window.name); + + await fetch(stashURL, { method: "POST" }); + continue; + } + + if (step === "close") { + window.close(); + break; + } + + if (step === "cross") { + const url = new URL(window.location); + url.host = "{{hosts[alt][]}}:{{ports[https][0]}}"; + url.search = "?" + steps.join("|"); + window.location = url.href; + break; + } + + if (step === "same") { + const url = new URL(window.location); + url.host = "{{host}}:{{ports[https][0]}}"; + url.search = "?" + steps.join("|"); + window.location = url.href; + break; + } + + if (step === "sub") { + const url = new URL(window.location); + url.host = "{{hosts[][www]}}:{{ports[https][0]}}"; + url.search = "?" + steps.join("|"); + window.location = url.href; + break; + } + + if (step === "closeOpener") { + if (window.opener) { + window.opener.close(); + } + continue; + } + + if (step.startsWith("navOpener=")) { + if (!window.opener) { + continue; + } + + let url = step.split("=")[1]; + window.opener.location.href = url; + + continue; + } + + if (step === "open") { + const url = new URL(window.location); + url.host = "{{host}}:{{ports[https][0]}}"; + url.search = "?" + steps.join("|"); + window.open(url); + break; + } + + if (step.startsWith("reportOpener=")) { + const id = step.split("=")[1]; + const stashURL = new URL("window-name-stash.py", location); + stashURL.searchParams.set('id', id); + stashURL.searchParams.set('value', window.opener.name); + + await fetch(stashURL, { method: "POST" }); + continue; + } + + if (step.startsWith("set=")) { + window.name = step.split("=")[1]; + continue; + } + + if (step.startsWith("setDomain=")) { + document.domain = step.split("=")[1]; + continue; + } + + throw new Error("Unsupported step!"); + } +} + +proceedTest(); +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/resources/window-opener.html b/testing/web-platform/tests/html/browsers/windows/resources/window-opener.html new file mode 100644 index 0000000000..c734eb3056 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/resources/window-opener.html @@ -0,0 +1,4 @@ +<script> + localStorage.setItem("opener", window.opener) + window.close() +</script> diff --git a/testing/web-platform/tests/html/browsers/windows/restore-window-name-manual.https.html b/testing/web-platform/tests/html/browsers/windows/restore-window-name-manual.https.html new file mode 100644 index 0000000000..13e8381e31 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/restore-window-name-manual.https.html @@ -0,0 +1,20 @@ +<!doctype html> +<html> +<head> + <title>Restore window.name when navigating back from a cross-origin</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/common/utils.js"></script> +</head> +<body> + <script> + function runTest() { + const name = token(); + window.open(`resources/restore-window-name.sub.html?name=${name}`, "start"); + } + + </script> + <p>Please click the following link and follow the instructions in the page for testing</p> + <a target="start" href="javascript:void(0)" onclick="runTest()">run test</a> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/windows/targeting-cross-origin-nested-browsing-contexts.html b/testing/web-platform/tests/html/browsers/windows/targeting-cross-origin-nested-browsing-contexts.html new file mode 100644 index 0000000000..44d4fbad6b --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/targeting-cross-origin-nested-browsing-contexts.html @@ -0,0 +1,39 @@ +<!doctype html> +<html> +<head> + <meta charset="utf-8"> + <title>Targeting nested browsing contexts</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> +</head> +<body> + <script src="/common/get-host-info.sub.js"></script> + <script> + async_test(function (t) { + var windowsToClose = []; + window.onmessage = t.step_func(function (e) { + if (e.data.name == "openee") { + var a = document.body.appendChild(document.createElement('a')); + a.target = "nested1"; + a.href = "resources/post-to-opener.html"; + a.click(); + windowsToClose.push(e.source); + } else { + assert_equals(e.data.name, "nested1"); + assert_equals(e.data.isTop, true); + windowsToClose.push(e.source); + windowsToClose.forEach(function (w) { + w.close(); + }); + t.done(); + } + }); + + var a = document.body.appendChild(document.createElement('a')); + a.target = "openee"; + a.href = get_host_info().HTTP_REMOTE_ORIGIN + "/html/browsers/windows/resources/nested-post-to-opener.html"; + a.click(); + }); + </script> +</body> +</html> diff --git a/testing/web-platform/tests/html/browsers/windows/targeting-multiple-cross-origin-manual.sub.html b/testing/web-platform/tests/html/browsers/windows/targeting-multiple-cross-origin-manual.sub.html new file mode 100644 index 0000000000..c8f8cfbe29 --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/targeting-multiple-cross-origin-manual.sub.html @@ -0,0 +1,9 @@ +<meta charset=utf-8> +<p>Follow this link to open a new browsing context in a separate origin. Follow the instructions +in that new window, and then come back to this window. +<a target=first href="{{location[scheme]}}://{{domains[天気の良い日]}}:{{location[port]}}/html/browsers/windows/resources/target-cross-origin.sub.html">link</a>. + +<p>Once you come back to this page, follow this link. +<a target=second href="resources/echo-window-name.html">link</a>. + +<p>After clicking that link, you should have three additional windows open. diff --git a/testing/web-platform/tests/html/browsers/windows/targeting-with-embedded-null-in-target.html b/testing/web-platform/tests/html/browsers/windows/targeting-with-embedded-null-in-target.html new file mode 100644 index 0000000000..7407248ffe --- /dev/null +++ b/testing/web-platform/tests/html/browsers/windows/targeting-with-embedded-null-in-target.html @@ -0,0 +1,34 @@ +<!doctype html> +<html> +<head> + <meta charset="utf-8"> + <title>Targeting with embedded null in target</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> +</head> +<body> + <iframe name="abc"> + </iframe> + <a href="resources/message-parent.html" target="abc">Click me</a> + <script> + var t = async_test(); + var target_name = "abc\u0000def"; + + onmessage = t.step_func_done(function (e) { + assert_equals(e.data.name, target_name, + "Should come from a window with the right name"); + assert_equals(e.source, frames[1], + "Should come frome the right window"); + }); + + t.step(function() { + var iframe = document.createElement("iframe"); + iframe.setAttribute("name", target_name); + document.body.appendChild(iframe); + var a = document.querySelector("a"); + a.target = target_name; + a.click(); + }); + </script> +</body> +</html> |