tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 9812cb264720210ab5728332161938cbc8be5eba
parent 967fc6d445e4e7bfc2d8df824b2e47c59b47e8f8
Author: Vincent Hilla <vhilla@mozilla.com>
Date:   Mon, 24 Nov 2025 05:32:05 +0000

Bug 543435 - Only reuse inner window for uncommitted initial documents. r=smaug,asuth,credential-management-reviewers,webrtc-reviewers,pehrsons,hsivonen,dimi

The conceptual change is small but it requires several test changes.

In principle, tests cannot open a new empty window, modify the global, then
load their target document and expect their modifications to stick. Only if the
target URI is opened directly, the synchronously available window is reused. So
either tests need to do that, or rely on messages.

Differential Revision: https://phabricator.services.mozilla.com/D265268

Diffstat:
Mdocshell/base/nsDocShell.cpp | 3++-
Mdocshell/test/chrome/test_bug608669.xhtml | 7++++---
Mdocshell/test/mochitest/test_bug1729662.html | 81+++++++++++++++++++++++++++++++++----------------------------------------------
Mdom/base/nsGlobalWindowInner.cpp | 2+-
Mdom/base/nsGlobalWindowOuter.cpp | 6++++--
Adom/base/test/file_bug1126851_post_unload.html | 5+++++
Mdom/base/test/mochitest.toml | 1+
Mdom/base/test/test_bug1126851.html | 12++++++++----
Mdom/base/test/test_window_focus_by_close_and_open.html | 6++----
Mdom/encoding/test/test_submit_euckr.html | 23++++++++++-------------
Mdom/events/test/test_bug322588.html | 3+--
Mdom/media/tests/crashtests/1429507_1.html | 8+++++++-
Mdom/media/tests/crashtests/1429507_2.html | 8+++++++-
Mdom/tests/mochitest/bugs/bug346659-echoer.html | 3++-
Ddom/tests/mochitest/bugs/bug346659-opener-echoer.html | 6------
Ddom/tests/mochitest/bugs/bug346659-opener.html | 9---------
Ddom/tests/mochitest/bugs/bug346659-parent-echoer.html | 5-----
Ddom/tests/mochitest/bugs/bug346659-parent.html | 11-----------
Adom/tests/mochitest/bugs/file_bug346659.html | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdom/tests/mochitest/bugs/mochitest.toml | 5+----
Mdom/tests/mochitest/bugs/test_bug346659.html | 272++++++++++++++++++++++++++++++-------------------------------------------------
Meditor/composer/test/test_bug519928.html | 122+++++++++++++++++++++++++++++++++++--------------------------------------------
Mgfx/layers/apz/test/mochitest/apz_test_utils.js | 9++++-----
Atesting/web-platform/meta/custom-elements/registries/per-global.html.ini | 3+++
Mtesting/web-platform/meta/fetch/security/dangling-markup/dangling-markup-mitigation.tentative.https.html.ini | 3+--
Mtesting/web-platform/meta/html/browsers/history/the-history-interface/history-associated-with-document.window.js.ini | 3---
Mtesting/web-platform/meta/html/browsers/history/the-location-interface/per-global.window.js.ini | 2++
Mtesting/web-platform/meta/html/webappapis/system-state-and-capabilities/the-navigator-object/per-global.window.js.ini | 6++++++
Mtesting/web-platform/meta/webdriver/tests/bidi/script/realm_destroyed/realm_destroyed.py.ini | 3---
Mtesting/web-platform/tests/html/browsers/the-window-object/window-reuse-in-nested-browsing-contexts.tentative.html | 3+++
Atesting/web-platform/tests/html/browsers/the-window-object/window-reuse.tentative.html | 34++++++++++++++++++++++++++++++++++
Mtesting/web-platform/tests/html/browsers/windows/noreferrer-window-name.html | 31+++++++++++++++++++++----------
Mtesting/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_javascript_url_xhr.html | 7+------
Atoolkit/components/passwordmgr/test/mochitest/file_postmessage_channel.html | 5+++++
Mtoolkit/components/passwordmgr/test/mochitest/mochitest.toml | 1+
Mtoolkit/components/passwordmgr/test/mochitest/pwmgr_common.js | 19+++++++------------
Mtoolkit/components/passwordmgr/test/mochitest/test_DOMInputPasswordAdded_fired_between_DOMContentLoaded_and_load_events.html | 18++++++------------
37 files changed, 408 insertions(+), 406 deletions(-)

diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp @@ -2527,7 +2527,8 @@ Maybe<ClientInfo> nsDocShell::GetInitialClientInfo() const { mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindow() : nullptr; Document* doc = innerWindow ? innerWindow->GetExtantDoc() : nullptr; - if (!doc || !doc->IsInitialDocument()) { + if (!doc || !doc->IsUncommittedInitialDocument()) { + // We won't reuse the inner window so let the channel determine one return Maybe<ClientInfo>(); } diff --git a/docshell/test/chrome/test_bug608669.xhtml b/docshell/test/chrome/test_bug608669.xhtml @@ -49,12 +49,13 @@ function* doTest() { is(notificationCount, 0, "initial count"); // create a new window - var testWin = chromeWindow.open("", "bug 608669", "chrome,width=600,height=600"); + var testWin = chromeWindow.open("bug608669.xhtml", "bug 608669", "chrome,width=600,height=600"); + // Synchronously modify the transient about:blank window testWin.x = "y"; + is(testWin.location.href, 'about:blank'); is(notificationCount, 0, "after created window"); - // Try loading in the window - testWin.location = "bug608669.xhtml"; + // Wait for the window to load chromeWindow.onmessage = nextTest; yield undefined; is(notificationCount, 1, "after first load"); diff --git a/docshell/test/mochitest/test_bug1729662.html b/docshell/test/mochitest/test_bug1729662.html @@ -9,59 +9,46 @@ SimpleTest.waitForExplicitFinish(); SimpleTest.requestFlakyTimeout("Need to wait to make sure an event does not fire"); - async function runTest() { - let win = window.open(); - let goneBackAndForwardOnce = new Promise((resolve) => { - let timeoutID; - - // We should only get one load event in win. - let bc = new BroadcastChannel("bug1729662"); - bc.addEventListener("message", () => { - bc.addEventListener("message", () => { - clearTimeout(timeoutID); - resolve(false); - }); - }, { once: true }); - - let goneBack = false, goneForward = false; - win.addEventListener("popstate", ({ state }) => { - // We should only go back and forward once, if we get another - // popstate after that then we should fall through to the - // failure case below. - if (!(goneBack && goneForward)) { - // Check if this is the popstate for the forward (the one for - // back will have state == undefined). - if (state == 1) { - ok(goneBack, "We should have gone back before going forward"); - - goneForward = true; - - // Wait a bit to make sure there are no more popstate events. - // eslint-disable-next-line mozilla/no-arbitrary-setTimeout - timeoutID = setTimeout(resolve, 1000, true); - - return; - } + function waitForEvent(target, event) { + return new Promise(resolve => + target.addEventListener(event, resolve, { once: true })); + } - // Check if we've gone back once before, if we get another - // popstate after that then we should fall through to the - // failure case below. - if (!goneBack) { - goneBack = true; + async function runTest() { + // Target page needs to be the initial load so that the synchronously + // available window is reused and we can attach popstate listeners. + const win = window.open('file_bug1729662.html'); + + let timeoutID; + let loadCount = 0; + let bc = new BroadcastChannel("bug1729662"); + bc.onmessage = ({ data }) => { + if (data != 'load') return; + if (++loadCount > 1) { + // We should only get one load event in win + clearTimeout(timeoutID); + } + }; - return; - } - } + // We should only go back and forward once + // The popstate for back will have state == null, see file_bug1729662.html + const state1 = await waitForEvent(win, 'popstate'); + is(state1.state, null, 'got expected state for history.back'); - clearTimeout(timeoutID); - resolve(false); - }); - }); + const state2 = await waitForEvent(win, 'popstate'); + is(state2.state, 1, 'got expected state for history.forward'); - win.location = "file_bug1729662.html"; + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + const timeout = new Promise(resolve => timeoutID = setTimeout(resolve, 1000, 'timeout')); + const result = await Promise.race([ + waitForEvent(win, 'popstate'), + timeout + ]); + is(result, 'timeout', 'Got no more popstate'); - ok(await goneBackAndForwardOnce, "Stopped navigating history"); + is(loadCount, isXOrigin ? 0 : 1, 'Got exactly one (zero for xorigin) load events in win'); + clearTimeout(timeoutID); win.close(); SimpleTest.finish(); diff --git a/dom/base/nsGlobalWindowInner.cpp b/dom/base/nsGlobalWindowInner.cpp @@ -1916,7 +1916,7 @@ nsresult nsGlobalWindowInner::EnsureClientSource() { // Try to get the reserved client from the LoadInfo. A Client is // reserved at the start of the channel load if there is not an - // initial about:blank document that will be reused. It is also + // initial uncommitted about:blank whose window will be reused. It is also // created if the channel load encounters a cross-origin redirect. if (loadInfo) { UniquePtr<ClientSource> reservedClient = diff --git a/dom/base/nsGlobalWindowOuter.cpp b/dom/base/nsGlobalWindowOuter.cpp @@ -1716,7 +1716,9 @@ nsIScriptContext* nsGlobalWindowOuter::GetScriptContext() { return mContext; } bool nsGlobalWindowOuter::WouldReuseInnerWindow(Document* aNewDocument) { // We reuse the inner window when: - // a. We are currently at our original document. + // a. The current document is transient, i.e. a temporary placeholder while + // an async load is ongoing. This is equivalent to the uncommitted initial + // document. // b. At least one of the following conditions are true: // -- The new document is the same as the old document. This means that we're // getting called from document.open(). @@ -1726,7 +1728,7 @@ bool nsGlobalWindowOuter::WouldReuseInnerWindow(Document* aNewDocument) { return false; } - if (!mDoc->IsInitialDocument()) { + if (!mDoc->IsUncommittedInitialDocument()) { return false; } diff --git a/dom/base/test/file_bug1126851_post_unload.html b/dom/base/test/file_bug1126851_post_unload.html @@ -0,0 +1,5 @@ +<script> +addEventListener('unload', () => { + window.opener.postMessage('unload', '*'); +}); +</script> diff --git a/dom/base/test/mochitest.toml b/dom/base/test/mochitest.toml @@ -175,6 +175,7 @@ support-files = [ "referrer_change_server.sjs", "file_change_policy_redirect.html", "file_bug1198095.js", + "file_bug1126851_post_unload.html", "file_bug1250148.sjs", "file_bug1268962.sjs", "iframe_meta_refresh.sjs", diff --git a/dom/base/test/test_bug1126851.html b/dom/base/test/test_bug1126851.html @@ -17,8 +17,10 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1126851 let gotUnload = false; function runTest() { - win = window.open("about:blank", ""); - win.onunload = function() { + win = window.open("file_bug1126851_post_unload.html", ""); + // Reload replaces the inner window so we cannot wait for win.onunload + // and instead must rely on messages from the popup. + window.onmessage = function() { if (gotUnload) { SimpleTest.finish(); } else { @@ -36,8 +38,10 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1126851 win.location.reload(); } } - evt = new win.Event("resize", { bubbles: true }); - win.document.dispatchEvent(evt); + win.onload = function() { + evt = new win.Event("resize", { bubbles: true }); + win.document.dispatchEvent(evt); + } } diff --git a/dom/base/test/test_window_focus_by_close_and_open.html b/dom/base/test/test_window_focus_by_close_and_open.html @@ -9,19 +9,17 @@ SimpleTest.waitForExplicitFinish(); function start() { - var w = window.open("", "_blank"); + var w = window.open("file_window_focus_by_close_and_open.html", "_blank"); w.finished = function() { ok(true, "1st new window had focus"); w.close(); - w = window.open("", "_blank"); + w = window.open("file_window_focus_by_close_and_open.html", "_blank"); w.finished = function() { ok(true, "2nd new window had focus"); w.close(); SimpleTest.finish(); }; - w.location = "file_window_focus_by_close_and_open.html"; }; - w.location = "file_window_focus_by_close_and_open.html"; } </script> </head> diff --git a/dom/encoding/test/test_submit_euckr.html b/dom/encoding/test/test_submit_euckr.html @@ -14,20 +14,17 @@ </form> <script> -runTest(); +async_test((t) => { + const form = document.forms[0]; + const iframe = document.querySelector('iframe'); -function runTest() { - var t = async_test("Test for euc-kr encoded form submission"); - var f = document.forms[0]; - var ifr = frames.ifr; - ifr.onload = function() { - t.step(function() { - assert_equals("".split.call(ifr.location, "?")[1], "a=%81A"); - }); - t.done(); - }; - f.submit(); -} + // note: listener must be on the iframe, not the window that will be replaced + iframe.onload = t.step_func_done(() => + assert_equals("".split.call(iframe.contentWindow.location, "?")[1], "a=%81A") + ); + + form.submit(); +}, "Test for euc-kr encoded form submission"); </script> </body> diff --git a/dom/events/test/test_bug322588.html b/dom/events/test/test_bug322588.html @@ -27,11 +27,10 @@ var result = ""; var w; function pop350d(url) { - w = window.open(); + w = window.open(url); w.addEventListener("unload", function () { result += " unload";}); w.addEventListener("load", function () { result += " load"; setTimeout(done, 1000);}); w.addEventListener("blur", function () { result += " blur";}); - w.location = url; } function doTest() { diff --git a/dom/media/tests/crashtests/1429507_1.html b/dom/media/tests/crashtests/1429507_1.html @@ -4,7 +4,9 @@ let o1; let p1, p2; window.addEventListener("MozReftestInvalidate", finish); - try { o1 = window.open("") } catch (e) {} + try { o1 = window.open("/") } catch (e) {} + o1.foo = 1 + // We require inner window reuse, so reload while an async load is ongoing try { o1.location.reload() } catch (e) {} try { o2 = new RTCPeerConnection({iceServers: [{"url": "stun:d"}]}) } catch (e) {} try { p1 = o1.navigator.mediaDevices.getUserMedia({video: true, fake: true}).catch((error) => {}) } catch (e) {} @@ -19,6 +21,10 @@ try { await p2; } catch (e) {} + if (o1.foo != 1) { + // fail if no window reuse + return; + } try { o1.close(); } catch (e) {} diff --git a/dom/media/tests/crashtests/1429507_2.html b/dom/media/tests/crashtests/1429507_2.html @@ -4,7 +4,9 @@ let o1; let p1, p2; window.addEventListener("MozReftestInvalidate", finish); - try { o1 = window.open("") } catch(e) { } + try { o1 = window.open("/") } catch(e) { } + o1.foo = 1 + // We require inner window reuse, so reload while an async load is ongoing try { o1.location.reload() } catch(e) { } try { o2 = o1.navigator } catch(e) { } try { o3 = o2.mediaDevices } catch(e) { } @@ -21,6 +23,10 @@ try { await p2; } catch (e) {} + if (o1.foo != 1) { + // fail if no window reuse + return; + } try { o1.close(); } catch (e) {} diff --git a/dom/tests/mochitest/bugs/bug346659-echoer.html b/dom/tests/mochitest/bugs/bug346659-echoer.html @@ -1,4 +1,5 @@ <script> - window.opener.postMessage("1 - " + window.x, "http://mochi.test:8888"); + const testNum = decodeURIComponent(window.location.search.substring(1)); + (window.opener || window.parent).postMessage(testNum + " - " + window.x, "*"); window.close(); </script> diff --git a/dom/tests/mochitest/bugs/bug346659-opener-echoer.html b/dom/tests/mochitest/bugs/bug346659-opener-echoer.html @@ -1,6 +0,0 @@ -<script> - var testNum = decodeURIComponent(window.location.search.substring(1)); - window.opener.opener.postMessage(testNum + " - " + window.x, "http://mochi.test:8888"); - window.opener.close(); - window.close(); -</script> diff --git a/dom/tests/mochitest/bugs/bug346659-opener.html b/dom/tests/mochitest/bugs/bug346659-opener.html @@ -1,9 +0,0 @@ -<body onload="postBack();"> - <script> - function postBack() { - var s = decodeURIComponent(window.location.search.substring(1)); - window.opener.postMessage(s, "http://mochi.test:8888"); - } - var childWin = window.open(); - </script> -</body> diff --git a/dom/tests/mochitest/bugs/bug346659-parent-echoer.html b/dom/tests/mochitest/bugs/bug346659-parent-echoer.html @@ -1,5 +0,0 @@ -<script> - var testNum = decodeURIComponent(window.location.search.substring(1)); - window.parent.opener.postMessage(testNum + " - " + window.x, "http://mochi.test:8888"); - window.parent.close(); -</script> diff --git a/dom/tests/mochitest/bugs/bug346659-parent.html b/dom/tests/mochitest/bugs/bug346659-parent.html @@ -1,11 +0,0 @@ -<body onload="postBack();"> - <script> - var childWin; - function postBack() { - childWin = window.frames[0]; - var s = decodeURIComponent(window.location.search.substring(1)); - window.opener.postMessage(s, "http://mochi.test:8888"); - } - </script> - <iframe></iframe> -</body> diff --git a/dom/tests/mochitest/bugs/file_bug346659.html b/dom/tests/mochitest/bugs/file_bug346659.html @@ -0,0 +1,69 @@ +<script> +function assert(cond, msg) { + console.assert(cond, msg); + if (!cond) { + (window.opener || window.parent).postMessage({ type: 'error', msg }, '*'); + throw new Error(msg); + } +} + +function assert_if(when, cond, msg) { + if (when) assert(cond, msg); +} + +addEventListener("load", () => { + const params = new URLSearchParams(location.search); + const testId = params.get("testId"); + const type = params.get("type"); + const method = params.get("method") || 'load'; + const childHost = params.get("childHost") || location.host; + + assert(type == 'popup' || type == 'iframe', 'type must be popup or iframe'); + assert(method == 'write' || method == 'load', 'method must be write or load'); + assert(testId != null && !isNaN(testId), 'must provide testId'); + assert_if(method == 'write', !params.get("childHost"), 'childHost does not apply for method=write'); + + let childURL = new URL(`bug346659-echoer.html?${testId}`, document.baseURI); + childURL.host = childHost; + + if (method == 'write') { + childURL = new URL('about:blank'); + } + + let childWindow; + if (type == 'popup') { + childWindow = window.open(childURL); + } else if (type == 'iframe') { + const iframe = document.createElement("iframe"); + iframe.src = childURL; + document.body.append(iframe); + childWindow = iframe.contentWindow; + } + childWindow.x = testId; + + if (method == 'write') { + try { + childWindow.document.write(` + <script> + const testNum = decodeURIComponent(window.location.search.substring(1)); + (window.opener || window.parent).postMessage("${testId} - " + window.x, "*"); + window.close(); + </scr`+`ipt> + `) + } catch (e) { + if (e.name != "SecurityError" || e.code != 18) { + assert(false, `Unknown error ${e.name}: ${e.message}`); + } + // Security error on cross-site write() is fine + childWindow.close(); + } + } +}) + +addEventListener("message", ({ data }) => { + // forward further to test + (window.opener || window.parent).postMessage(data, "*"); + // We expect to be done once we received a message + window.close(); +}) +</script> diff --git a/dom/tests/mochitest/bugs/mochitest.toml b/dom/tests/mochitest/bugs/mochitest.toml @@ -3,14 +3,11 @@ tags = "condprof" support-files = [ "bug289714.sjs", "bug346659-echoer.html", - "bug346659-opener-echoer.html", - "bug346659-opener.html", - "bug346659-parent-echoer.html", - "bug346659-parent.html", "bug458091_child.html", "child_bug260264.html", "devicemotion_inner.html", "devicemotion_outer.html", + "file_bug346659.html", "file_bug593174_1.html", "file_bug593174_2.html", "file_bug809290_b1.html", diff --git a/dom/tests/mochitest/bugs/test_bug346659.html b/dom/tests/mochitest/bugs/test_bug346659.html @@ -18,189 +18,121 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=346659 <script type="application/javascript"> /** Test for Bug 346659 */ -var numTests = 10; +var numTests = 11; SimpleTest.requestLongerTimeout(2); // test takes a long time on android and b2g emulators SimpleTest.waitForExplicitFinish(); -var wins = []; - -function r(base, tail) { - return base.replace(/\/[^\/]*$/, "/" + tail); -} - -/** - * This function sets up the test according to the data it receives. If the data - * is a JSON string, it will use the object parsed from that to determine how to - * set up the test. - */ -async function handleCmd(evt) { - var cmd; - try { - cmd = JSON.parse(evt.data); - } catch (e) { - // Not json, so it should be a test result. We don't need to set up test. - return false; - } - - if ("load" in cmd) { - var testNum = cmd.load; - - // Set up the testing window property and get necessary information from it. - // We use SpecialPowers.spawn() here since the testing window could be cross - // origin. - var { isOpenerTest, location } = - await SpecialPowers.spawn(wins[testNum], [testNum], testNum => { - var win = content.wrappedJSObject; - win.childWin.x = testNum; +const xorigBase = window.location.href.replace(location.host, "example.com"); +const makeXOrig = tail => xorigBase.replace(/\/[^\/]*$/, "/" + tail); + +const tests = [ + { + func: testId => { + const win = window.open(`bug346659-echoer.html?${testId}`); + win.x = testId; + }, + expectPreserveProp: true, + descr: 'Props on new window should be preserved when loading' + }, + { + func: testId => { + const win = window.open(""); + win.x = testId; + win.document.write(`<script> window.opener.postMessage("${testId} - " + window.x, '*'); window.close(); </scr`+`ipt>`); + }, + expectPreserveProp: true, + descr: 'Props on new window should be preserved when writing' + }, + { + func: testId => window.open(`file_bug346659.html?testId=${testId}&type=popup`), + expectPreserveProp: true, + descr: 'Props on window opened from new window should be preserved when loading' + }, + { + func: testId => window.open(`file_bug346659.html?testId=${testId}&type=popup&method=write`), + expectPreserveProp: true, + descr: 'Props on window opened from new window should be preserved when writing' + }, + { + func: testId => window.open(`file_bug346659.html?testId=${testId}&type=iframe`), + expectPreserveProp: true, + descr: "Props on new window's child should be preserved when loading" + }, + { + func: testId => window.open(`file_bug346659.html?testId=${testId}&type=iframe&method=write`), + expectPreserveProp: true, + descr: "Props on new window's child should not go away when writing" + }, + { + func: testId => window.open(makeXOrig(`file_bug346659.html?testId=${testId}&type=popup`)), + expectPreserveProp: true, + descr: 'Props on different-domain window opened from different-domain new window can stay' + }, + { + func: testId => window.open(makeXOrig(`file_bug346659.html?testId=${testId}&type=iframe`)), + expectPreserveProp: true, + descr: "Props on different-domain new window's child should be preserved when loading" + }, + { + func: testId => window.open(makeXOrig(`file_bug346659.html?testId=${testId}&type=popup&childHost=${location.host}`)), + expectPreserveProp: false, + descr: "Props on same-domain window opened from different-domain new window should go away when loading" + }, + { + func: testId => window.open(makeXOrig(`file_bug346659.html?testId=${testId}&type=iframe&childHost=${location.host}`)), + expectPreserveProp: false, + descr: "Props on different-domain new window's same-domain child should go away when loading" + }, + { + func: testId => { + win = window.open("about:blank"); + win.x = testId; + win.location = new URL(`bug346659-echoer.html?${testId}`, document.baseURI); + }, + expectPreserveProp: false, + descr: 'Props should go away when replacing the initial about:blank' + }, +]; + +function runTests() { + const remainingTests = new Map(); + + window.addEventListener("message", ({ data }) => { + if (data.type == 'error') { + throw new Error(evt.data.msg); + } - return { - isOpenerTest: win.childWin.opener == win, - location: content.location.href, - }; - }); + // evt.data has the format 'testNumber - testResult' + const testId = parseInt(data); + const property = data.substring(3 + Math.floor(Math.log(testId) * Math.LOG10E + 1)); - // Get the test location according to the test. - if (isOpenerTest) { - if ("xsite" in cmd) { - var loc = r(window.location.href, "bug346659-opener-echoer.html?" + testNum); - } else { - var loc = r(location, "bug346659-opener-echoer.html?" + testNum); - } + const test = remainingTests.get(testId); + ok(test, `Test number ${testId} is known and still remaining`); + if (test.expectPreserveProp) { + is(property, `${testId}`, `${testId}: ${test.descr}`); } else { - if ("xsite" in cmd) { - var loc = r(window.location.href, "bug346659-parent-echoer.html?" + testNum); - } else { - var loc = r(location, "bug346659-parent-echoer.html?" + testNum); - } + is(property, 'undefined', `${testId}: ${test.descr}`); } - // Trigger the loading on the child window of the testing window. - await SpecialPowers.spawn(wins[testNum], [loc], loc => { - content.wrappedJSObject.childWin.location.href = loc; - }); - wins[testNum] = null; - } else if ("write" in cmd) { - var testNum = cmd.write; - - try { - // Set up the test on the testing window. - await SpecialPowers.spawn(wins[testNum], [testNum], testNum => { - var win = content.wrappedJSObject; - win.childWin.x = testNum; - - // Test document.write(). - if (win.childWin.opener == win) { - win.childWin.document.write(` - <script> - window.opener.opener.postMessage("${testNum} - " + window.x, "http://mochi.test:8888/"); - window.opener.close(); - window.close(); - <` + '/script>'); - } else { - win.childWin.document.write(` - <script> - window.parent.opener.postMessage("${testNum} - " + window.x, "http://mochi.test:8888/"); - window.parent.close(); - <` + '/script>'); - } - }); - } catch (e) { - if (e.name != "SecurityError" || e.code != 18) { - throw e; - } - // Security error on cross-site write() is fine - await SpecialPowers.spawn(wins[testNum], [], () => { - var win = content.wrappedJSObject; - if (win.childWin.opener == win) { - win.childWin.close(); - } - }); - - handleTestEnd(); + remainingTests.delete(testId); + if (remainingTests.size == 0) { + SimpleTest.finish(); } - wins[testNum] = null; - } - return true; -} - -async function messageReceiver(evt) { - // First try to detect a load/write command - if (await handleCmd(evt)) { - return; - } - - var testNumber = parseInt(evt.data); - var testResult = evt.data.substring(3 + Math.floor(Math.log(testNumber) * Math.LOG10E + 1)); - - switch (testNumber) { - case 1: - is(testResult, "1", "Props on new window should be preserved when loading"); - break; - case 2: - is(testResult, "2", "Props on new window should be preserved when writing"); - break; - case 3: - is(testResult, "3", "Props on window opened from new window should be preserved when loading"); - break; - case 4: - is(testResult, "4", "Props on window opened from new window should be preserved when writing"); - break; - case 5: - is(testResult, "5", "Props on new window's child should be preserved when loading"); - break; - case 6: - is(testResult, "6", "Props on new window's child should not go away when writing"); - break; - case 7: - is(testResult, "7", "Props on different-domain window opened from different-domain new window can stay"); - break; - case 9: - is(testResult, "9", "Props on different-domain new window's child should be preserved when loading"); - break; - case 11: - is(testResult, "undefined", "Props on same-domain window opened from different-domain new window should go away when loading"); - break; - case 12: - is(testResult, "undefined", "Props on different-domain new window's same-domain child should go away when loading"); - break; - default: - ok(0, "unexpected test number (" + testNumber + ") when data is " + evt.data); - } + }); - handleTestEnd(); -} - -function handleTestEnd() { - if (!--numTests) { - SimpleTest.finish(); + let testId = 1; + for (const test of tests) { + if (!test.func || !test.descr || test.expectPreserveProp == 'undefined') { + throw new Error('Invalid test'); + } + test.func(testId); + remainingTests.set(testId, test); + ++testId; } } -window.addEventListener("message", messageReceiver); - -var win = window.open(""); -win.x = 1; -win.location.href = "bug346659-echoer.html"; - -win = window.open(""); -win.x = 2; -win.document.write('<script> window.opener.postMessage("2 - " + window.x, window.location.href); window.close(); </' + 'script>'); - -wins[3] = window.open('bug346659-opener.html?{"load":3}'); -wins[4] = window.open('bug346659-opener.html?{"write":4}'); -wins[5] = window.open('bug346659-parent.html?{"load":5}'); -wins[6] = window.open('bug346659-parent.html?{"write":6}'); - -is(location.host, "mochi.test:8888", "Unexpected host"); - -SpecialPowers.pushPrefEnv({"set": [["dom.security.https_first", false]]}, function() { - var baseurl = window.location.href.replace(/mochi\.test:8888/, "example.com"); - wins[7] = window.open(r(baseurl, 'bug346659-opener.html?{"load":7}')); - wins[9] = window.open(r(baseurl, 'bug346659-parent.html?{"load":9}')); - - wins[11] = window.open(r(baseurl, 'bug346659-opener.html?{"load":11,"xsite":true}')); - wins[12] = window.open(r(baseurl, 'bug346659-parent.html?{"load":12,"xsite":true}')); -}); +// The xorigin tests load http://example.com and from it load http://mochi.test +SpecialPowers.pushPrefEnv({"set": [["dom.security.https_first", false]]}, runTests); </script> </pre> </body> diff --git a/editor/composer/test/test_bug519928.html b/editor/composer/test/test_bug519928.html @@ -25,70 +25,27 @@ function allowJS(allow, frame) { SpecialPowers.wrap(frame.contentWindow).windowGlobalChild.windowContext.allowJavascript = allow; } -function expectJSAllowed(allowed, testCondition, callback) { +async function expectJSAllowed(allowed, testCondition) { window.ICanRunMyJS = false; - var self_ = window; + const self_ = window; testCondition(); - var doc = iframe.contentDocument; - doc.body.innerHTML = "<iframe></iframe>"; - var innerFrame = doc.querySelector("iframe"); - innerFrame.addEventListener("load", function() { - var msg = "The inner iframe should" + (allowed ? "" : " not") + " be able to run Javascript"; - is(self_.ICanRunMyJS, allowed, msg); - callback(); - }, {once: true}); + const doc = iframe.contentDocument; + const innerFrame = doc.createElement("iframe"); // eslint-disable-next-line no-useless-concat - var iframeSrc = "<script>parent.parent.ICanRunMyJS = true;</scr" + "ipt>"; - innerFrame.srcdoc = iframeSrc; -} + innerFrame.srcdoc = "<script>parent.parent.ICanRunMyJS = true;</scr" + "ipt>"; + const loaded = new Promise(resolve => innerFrame.onload = resolve); + doc.body.append(innerFrame); -SimpleTest.waitForExplicitFinish(); -/* eslint-disable max-nested-callbacks */ -addLoadEvent(function() { - var enterDesignMode = function() { document.designMode = "on"; }; - var leaveDesignMode = function() { document.designMode = "off"; }; - expectJSAllowed(false, disableJS, function() { - expectJSAllowed(true, enableJS, function() { - expectJSAllowed(true, enterDesignMode, function() { - expectJSAllowed(true, leaveDesignMode, function() { - expectJSAllowed(false, disableJS, function() { - expectJSAllowed(false, enterDesignMode, function() { - expectJSAllowed(false, leaveDesignMode, function() { - expectJSAllowed(true, enableJS, function() { - enterDesignMode = function() { iframe.contentDocument.designMode = "on"; }; - leaveDesignMode = function() { iframe.contentDocument.designMode = "off"; }; - expectJSAllowed(false, disableJS, function() { - expectJSAllowed(true, enableJS, function() { - expectJSAllowed(true, enterDesignMode, function() { - expectJSAllowed(true, leaveDesignMode, function() { - expectJSAllowed(false, disableJS, function() { - expectJSAllowed(false, enterDesignMode, function() { - expectJSAllowed(false, leaveDesignMode, function() { - expectJSAllowed(true, enableJS, function() { - testDocumentDisabledJS(); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); -}); -/* eslint-enable max-nested-callbacks */ + await loaded; + is(self_.ICanRunMyJS, allowed, "The inner iframe should" + (allowed ? "" : " not") + " be able to run Javascript"); -function testDocumentDisabledJS() { + innerFrame.remove(); +} + +async function testDocumentDisabledJS() { window.ICanRunMyJS = false; - var self_ = window; + const self_ = window; // Ensure design modes are disabled document.designMode = "off"; iframe.contentDocument.designMode = "off"; @@ -96,23 +53,52 @@ function testDocumentDisabledJS() { // Javascript enabled on the main iframe enableJS(); - var doc = iframe.contentDocument; - doc.body.innerHTML = "<iframe></iframe>"; - var innerFrame = doc.querySelector("iframe"); + const doc = iframe.contentDocument; + const innerFrame = doc.createElement("iframe"); + // eslint-disable-next-line no-useless-concat + innerFrame.srcdoc = "<script>parent.parent.ICanRunMyJS = true;</scr" + "ipt>"; + const loaded = new Promise(resolve => innerFrame.onload = resolve); + doc.body.append(innerFrame); // Javascript disabled on the innerFrame. allowJS(false, innerFrame); - innerFrame.addEventListener("load", function() { - var msg = "The inner iframe should not be able to run Javascript"; - is(self_.ICanRunMyJS, false, msg); - SimpleTest.finish(); - }, {once: true}); - // eslint-disable-next-line no-useless-concat - var iframeSrc = "<script>parent.parent.ICanRunMyJS = true;</scr" + "ipt>"; - innerFrame.srcdoc = iframeSrc; + await loaded; + is(self_.ICanRunMyJS, false, "The inner iframe should not be able to run Javascript"); + + innerFrame.remove(); } +SimpleTest.waitForExplicitFinish(); +addLoadEvent(async function() { + var enterDesignMode = function() { document.designMode = "on"; }; + var leaveDesignMode = function() { document.designMode = "off"; }; + + await expectJSAllowed(false, disableJS); + await expectJSAllowed(true, enableJS); + await expectJSAllowed(true, enterDesignMode); + await expectJSAllowed(true, leaveDesignMode); + await expectJSAllowed(false, disableJS); + await expectJSAllowed(false, enterDesignMode); + await expectJSAllowed(false, leaveDesignMode); + await expectJSAllowed(true, enableJS); + + enterDesignMode = function() { iframe.contentDocument.designMode = "on"; }; + leaveDesignMode = function() { iframe.contentDocument.designMode = "off"; }; + + await expectJSAllowed(false, disableJS); + await expectJSAllowed(true, enableJS); + await expectJSAllowed(true, enterDesignMode); + await expectJSAllowed(true, leaveDesignMode); + await expectJSAllowed(false, disableJS); + await expectJSAllowed(false, enterDesignMode); + await expectJSAllowed(false, leaveDesignMode); + await expectJSAllowed(true, enableJS); + + await testDocumentDisabledJS(); + + SimpleTest.finish(); +}); </script> </pre> </body> diff --git a/gfx/layers/apz/test/mochitest/apz_test_utils.js b/gfx/layers/apz/test/mochitest/apz_test_utils.js @@ -513,8 +513,11 @@ function runSubtestsSeriallyInFreshWindows(aSubtests) { } function spawnTest(aFile) { + var subtestUrl = + location.href.substring(0, location.href.lastIndexOf("/") + 1) + + aFile; w = window.open( - "", + subtestUrl, "_blank", test.windowFeatures ? test.windowFeatures : "" ); @@ -558,9 +561,6 @@ function runSubtestsSeriallyInFreshWindows(aSubtests) { { once: true } ); } - var subtestUrl = - location.href.substring(0, location.href.lastIndexOf("/") + 1) + - aFile; function urlResolves(url) { var request = new XMLHttpRequest(); request.open("GET", url, false); @@ -578,7 +578,6 @@ function runSubtestsSeriallyInFreshWindows(aSubtests) { reject(); return undefined; } - w.location = subtestUrl; return w; } diff --git a/testing/web-platform/meta/custom-elements/registries/per-global.html.ini b/testing/web-platform/meta/custom-elements/registries/per-global.html.ini @@ -0,0 +1,3 @@ +[per-global.html] + [Navigating from the initial about:blank must not replace window.customElements] + expected: FAIL diff --git a/testing/web-platform/meta/fetch/security/dangling-markup/dangling-markup-mitigation.tentative.https.html.ini b/testing/web-platform/meta/fetch/security/dangling-markup/dangling-markup-mitigation.tentative.https.html.ini @@ -1,6 +1,5 @@ [dangling-markup-mitigation.tentative.https.html] expected: - if tsan: [ERROR, OK, TIMEOUT] - [ERROR, OK] + [ERROR, OK, TIMEOUT] [Only blocks dangling markup requests] expected: [TIMEOUT, PASS] diff --git a/testing/web-platform/meta/html/browsers/history/the-history-interface/history-associated-with-document.window.js.ini b/testing/web-platform/meta/html/browsers/history/the-history-interface/history-associated-with-document.window.js.ini @@ -1,8 +1,5 @@ [history-associated-with-document.window.html] expected: if (os == "android") and fission: [OK, TIMEOUT] - [Navigating from the initial about:blank must replace window.history] - expected: FAIL - [Discarding the browsing context must not change window.history] expected: FAIL diff --git a/testing/web-platform/meta/html/browsers/history/the-location-interface/per-global.window.js.ini b/testing/web-platform/meta/html/browsers/history/the-location-interface/per-global.window.js.ini @@ -1,3 +1,5 @@ [per-global.window.html] expected: if (os == "android") and fission: [OK, TIMEOUT] + [Navigating from the initial about:blank must not replace window.location] + expected: FAIL diff --git a/testing/web-platform/meta/html/webappapis/system-state-and-capabilities/the-navigator-object/per-global.window.js.ini b/testing/web-platform/meta/html/webappapis/system-state-and-capabilities/the-navigator-object/per-global.window.js.ini @@ -6,3 +6,9 @@ [Discarding the browsing context must not change window.clientInformation] expected: FAIL + + [Navigating from the initial about:blank must not replace window.navigator] + expected: FAIL + + [Navigating from the initial about:blank must not replace window.clientInformation] + expected: FAIL diff --git a/testing/web-platform/meta/webdriver/tests/bidi/script/realm_destroyed/realm_destroyed.py.ini b/testing/web-platform/meta/webdriver/tests/bidi/script/realm_destroyed/realm_destroyed.py.ini @@ -4,6 +4,3 @@ [test_shared_worker] expected: FAIL - - [test_reload_context] - expected: FAIL # this is fixed in the window reuse patch of the stack 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 @@ -14,6 +14,9 @@ const setupIframe = (t, attrs) => { return {iframe, watcher}; }; +// This test is outdated, see +// https://github.com/whatwg/html/issues/3267#issuecomment-2788132131 + promise_test(async t => { const {iframe, watcher} = setupIframe(t, {}); diff --git a/testing/web-platform/tests/html/browsers/the-window-object/window-reuse.tentative.html b/testing/web-platform/tests/html/browsers/the-window-object/window-reuse.tentative.html @@ -0,0 +1,34 @@ +<!doctype html> +<meta charset=utf-8> +<title>Test window reuse on initial navigation away from about:blank</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id=log></div> +<script> +promise_test(async t => { + const w = window.open("/common/blank.html"); + t.add_cleanup(() => w.close()); + + assert_equals(w.location.href, 'about:blank', 'window initially on a transient about:blank'); + w.foo = 'bar'; + + await new Promise(res => w.onload = res); + assert_true(w.location.href.endsWith('/common/blank.html'), 'loaded initial navigation target'); + assert_equals(w.foo, 'bar', 'did reuse window'); +}, 'Initially navigating window to non-about:blank page should reuse synchonously available window'); + +promise_test(async t => { + const name = 'unique-name-11sep25'; + const channel = new BroadcastChannel(name); + const w = window.open("about:blank", name); + t.add_cleanup(() => w.close()); + + assert_equals(w.location.href, 'about:blank', 'loaded about:blank'); + w.foo = 'bar'; + + w.location.href = 'support/window-open-popup-target.html'; + await new Promise(res => channel.onmessage = res); + assert_true(true, 'completed non-initial load onto initial about:blank'); + assert_equals(w.foo, undefined, 'did reuse window'); +}, 'synchronously navigating window away from initial about:blank should not reuse window'); +</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 @@ -65,22 +65,33 @@ async_test(function(t) { var win = window.open("", "sufficientlyrandomwindownameamiright3"); - t.add_cleanup(function() { - win.close(); - }); + t.add_cleanup(() => win.close()); + + const channel = new BroadcastChannel('sufficientlyrandomchannelnameamiright3'); + t.add_cleanup(() => channel.close()); + + const targetHtml = ` + <script> + const channel = new BroadcastChannel('sufficientlyrandomchannelnameamiright3'); + channel.postMessage({ name: window.name, hasOpener: window.opener === null }); + </scr`+`ipt> + `; var hyperlink = document.body.appendChild(document.createElement("a")); - t.add_cleanup(function() { - hyperlink.remove(); - }); + t.add_cleanup(() => hyperlink.remove()); hyperlink.rel = "noreferrer"; - hyperlink.href = URL.createObjectURL(new Blob(["hello window"], + hyperlink.href = URL.createObjectURL(new Blob([targetHtml], { type: "text/html"})); hyperlink.target = "sufficientlyrandomwindownameamiright3"; - win.onload = t.step_func_done(function() { - assert_equals(win.document.documentElement.textContent, - "hello window"); + + // win already loaded about:blank, the next load won't reuse the window. So we cannot + // add a load listener and rather need to use a channel. + channel.onmessage = t.step_func_done(function({ data }) { + assert_equals(data.name, 'sufficientlyrandomwindownameamiright3'); + assert_equals(data.hasOpener, false); + assert_equals(win.location.href, hyperlink.href); }); + hyperlink.click(); }, "Targeting a rel=noreferrer link at an existing named window should work"); </script> diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_javascript_url_xhr.html b/testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/iframe_javascript_url_xhr.html @@ -24,20 +24,15 @@ window.javascriptURLXhr = function() { }; promise_test(async t => { - iframeLoaded = false; + assert_true(iframeLoaded, "iframeLoaded", "Initial iframe load occured synchronously"); javascriptUrlRan = false; - await new Promise(resolve => { - window.addEventListener("load", resolve, { once: true }); - }); - let scriptExecutePromise = new Promise(resolve => { document.addEventListener("scriptExecuted", function() { resolve(); }, { once: true }); }); - assert_true(iframeLoaded, "iframeLoaded"); const iframe = document.querySelector('iframe'); // Load image on iframe. diff --git a/toolkit/components/passwordmgr/test/mochitest/file_postmessage_channel.html b/toolkit/components/passwordmgr/test/mochitest/file_postmessage_channel.html @@ -0,0 +1,5 @@ +<script> + const params = new URLSearchParams(window.location.search); + const bc = new BroadcastChannel(params.get("token")); + bc.postMessage({ msg: "ready" }); +</script> diff --git a/toolkit/components/passwordmgr/test/mochitest/mochitest.toml b/toolkit/components/passwordmgr/test/mochitest/mochitest.toml @@ -30,6 +30,7 @@ support-files = [ "../browser/form_same_origin_action.html", "auth2/authenticate.sjs", "file_history_back.html", + "file_postmessage_channel.html", "form_basic_shadow_DOM_both_fields_together_in_a_shadow_root.html", "form_basic_shadow_DOM_each_field_in_its_own_shadow_root.html", "form_basic_shadow_DOM_form_and_fields_together_in_a_shadow_root.html", diff --git a/toolkit/components/passwordmgr/test/mochitest/pwmgr_common.js b/toolkit/components/passwordmgr/test/mochitest/pwmgr_common.js @@ -749,23 +749,18 @@ async function promiseFormsProcessed(expectedCount = 1) { } async function loadFormIntoWindow(origin, html, win, expectedCount = 1, task) { - let loadedPromise = new Promise(resolve => { - win.addEventListener( - "load", - function (event) { - if (event.target.location.href.endsWith("blank.html")) { - resolve(); - } - }, - { once: true } - ); - }); + const token = `channel${Math.random().toString().slice(2)}`; + const bc = new BroadcastChannel(token); + const loadedPromise = new Promise(resolve => (bc.onmessage = resolve)); let processedPromise = promiseFormsProcessed(expectedCount); win.location = - origin + "/tests/toolkit/components/passwordmgr/test/mochitest/blank.html"; + origin + + `/tests/toolkit/components/passwordmgr/test/mochitest` + + `/file_postmessage_channel.html?token=${token}`; info(`Waiting for window to load for origin: ${origin}`); await loadedPromise; + bc.close(); await SpecialPowers.spawn( win, diff --git a/toolkit/components/passwordmgr/test/mochitest/test_DOMInputPasswordAdded_fired_between_DOMContentLoaded_and_load_events.html b/toolkit/components/passwordmgr/test/mochitest/test_DOMInputPasswordAdded_fired_between_DOMContentLoaded_and_load_events.html @@ -19,16 +19,6 @@ let DEFAULT_ORIGIN = window.location.origin; let FILE_PATH = "/tests/toolkit/components/passwordmgr/test/mochitest/slow_image.html"; -async function openDocumentInWindow(win) { - let DOMContentLoadedPromise = new Promise((resolve) => { - win.addEventListener("DOMContentLoaded", function() { - resolve(); - }, {once: true}); - }); - win.location = DEFAULT_ORIGIN + FILE_PATH; - await DOMContentLoadedPromise; -} - add_setup(async () => { await setStoredLoginsAsync([DEFAULT_ORIGIN, DEFAULT_ORIGIN, null, "user", "omgsecret!"]); }); @@ -37,9 +27,13 @@ add_task(async function test_password_autofilled() { let numLogins = await LoginManager.countLogins(DEFAULT_ORIGIN, DEFAULT_ORIGIN, null); is(numLogins, 1, "Correct number of logins"); - let win = window.open("about:blank"); + let win = window.open(DEFAULT_ORIGIN + FILE_PATH); SimpleTest.registerCleanupFunction(() => win.close()); - await openDocumentInWindow(win); + await new Promise((resolve) => { + win.addEventListener("DOMContentLoaded", function() { + resolve(); + }, {once: true}); + }); let processedPromise = promiseFormsProcessed(); await SpecialPowers.spawn(win, [], function() { let doc = this.content.document;