commit 2fe2da3e4b00a6f242e68aa97c519034c4f304c0
parent dbad5d1e6bb41cfe17061a0258a872ac7a642a8c
Author: Vincent Hilla <vhilla@mozilla.com>
Date: Tue, 9 Dec 2025 15:18:38 +0000
Bug 2004407 - Propagate base URI to initial top level about:blank. r=smaug
Differential Revision: https://phabricator.services.mozilla.com/D275446
Diffstat:
10 files changed, 118 insertions(+), 36 deletions(-)
diff --git a/dom/base/nsGlobalWindowOuter.cpp b/dom/base/nsGlobalWindowOuter.cpp
@@ -1759,8 +1759,7 @@ bool nsGlobalWindowOuter::WouldReuseInnerWindow(Document* aNewDocument) {
}
void nsGlobalWindowOuter::SetInitialPrincipal(
- nsIPrincipal* aNewWindowPrincipal, nsIPolicyContainer* aPolicyContainer,
- const Maybe<nsILoadInfo::CrossOriginEmbedderPolicy>& aCOEP) {
+ nsIPrincipal* aNewWindowPrincipal) {
// We should never create windows with an expanded principal.
// If we have a system principal, make sure we're not using it for a content
// docshell.
@@ -1772,30 +1771,28 @@ void nsGlobalWindowOuter::SetInitialPrincipal(
aNewWindowPrincipal = nullptr;
}
- // If there's an existing document, bail if it either:
- if (mDoc) {
- // (a) is not an initial about:blank document, or
- if (!mDoc->IsInitialDocument()) return;
- // (b) already has the correct principal.
- if (mDoc->NodePrincipal() == aNewWindowPrincipal) return;
+ MOZ_ASSERT(mDoc, "Some document should've been eagerly created");
+
+ // Bail if the existing document is (a) not initial
+ if (!mDoc->IsUncommittedInitialDocument()) return;
+ // or (b) already has the correct principal.
+ if (mDoc->NodePrincipal() == aNewWindowPrincipal) return;
#ifdef DEBUG
- // If we have a document loaded at this point, it had better be about:blank.
- // Otherwise, something is really weird. An about:blank page has a
- // NullPrincipal.
- bool isNullPrincipal;
- MOZ_ASSERT(NS_SUCCEEDED(mDoc->NodePrincipal()->GetIsNullPrincipal(
- &isNullPrincipal)) &&
- isNullPrincipal);
+ // The current document should be a dummy and therefore have a null principal
+ bool isNullPrincipal;
+ MOZ_ASSERT(NS_SUCCEEDED(
+ mDoc->NodePrincipal()->GetIsNullPrincipal(&isNullPrincipal)) &&
+ isNullPrincipal);
#endif
- }
// Use the subject (or system) principal as the storage principal too until
// the new window finishes navigating and gets a real storage principal.
nsDocShell::Cast(GetDocShell())
- ->CreateAboutBlankDocumentViewer(aNewWindowPrincipal, aNewWindowPrincipal,
- aPolicyContainer, nullptr,
- /* aIsInitialDocument */ true, aCOEP);
+ ->CreateAboutBlankDocumentViewer(
+ aNewWindowPrincipal, aNewWindowPrincipal, mDoc->GetPolicyContainer(),
+ mDoc->GetDocBaseURI(),
+ /* aIsInitialDocument */ true, mDoc->GetEmbedderPolicy());
if (mDoc) {
MOZ_ASSERT(mDoc->IsInitialDocument(),
diff --git a/dom/base/nsGlobalWindowOuter.h b/dom/base/nsGlobalWindowOuter.h
@@ -284,10 +284,7 @@ class nsGlobalWindowOuter final : public mozilla::dom::EventTarget,
mozilla::dom::EventTarget* aChromeEventHandler) override;
// Outer windows only.
- virtual void SetInitialPrincipal(
- nsIPrincipal* aNewWindowPrincipal, nsIPolicyContainer* aPolicyContainer,
- const mozilla::Maybe<nsILoadInfo::CrossOriginEmbedderPolicy>& aCoep)
- override;
+ virtual void SetInitialPrincipal(nsIPrincipal* aNewWindowPrincipal) override;
virtual already_AddRefed<nsISupports> SaveWindowState() override;
MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual nsresult RestoreWindowState(
diff --git a/dom/base/nsPIDOMWindow.h b/dom/base/nsPIDOMWindow.h
@@ -875,11 +875,10 @@ class nsPIDOMWindowOuter : public mozIDOMWindowProxy {
return mDoc;
}
- // Set the window up with an about:blank document with the given principal and
- // potentially a policyContainer and a COEP.
- virtual void SetInitialPrincipal(
- nsIPrincipal* aNewWindowPrincipal, nsIPolicyContainer* aPolicyContainer,
- const mozilla::Maybe<nsILoadInfo::CrossOriginEmbedderPolicy>& aCoep) = 0;
+ // Set the window up with an about:blank document with the given principal.
+ // Base URI, COEP and PolicyContainer of the current document will be
+ // retained.
+ virtual void SetInitialPrincipal(nsIPrincipal* aNewWindowPrincipal) = 0;
// Returns an object containing the window's state. This also suspends
// all running timeouts in the window.
diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp
@@ -1170,9 +1170,7 @@ nsresult ContentChild::ProvideWindowCommon(
// This creates a new document and the timing is quite fragile.
NS_ENSURE_TRUE(browsingContext->GetDOMWindow(), NS_ERROR_ABORT);
browsingContext->GetDOMWindow()->SetInitialPrincipal(
- aOpenWindowInfo->PrincipalToInheritForAboutBlank(),
- aOpenWindowInfo->PolicyContainerToInheritForAboutBlank(),
- aOpenWindowInfo->CoepToInheritForAboutBlank());
+ aOpenWindowInfo->PrincipalToInheritForAboutBlank());
// Set to true when we're ready to return from this function.
bool ready = false;
diff --git a/dom/tests/browser/browser_alert_from_about_blank.js b/dom/tests/browser/browser_alert_from_about_blank.js
@@ -25,7 +25,7 @@ add_task(async function test_check_alert_from_blank() {
// Start an async navigation that will close the alert
// Previously this wasn't needed as the initial about:blank
// was followed by an async about:blank load. See Bug 543435
- newWin.location = "/blank";
+ newWin.location = "about:blank?0";
newWin.alert("Alert from the popup.");
info("Button onclick: finished");
});
diff --git a/testing/web-platform/meta/html/infrastructure/urls/base-url/document-base-url-window-open-about-blank.https.window.js.ini b/testing/web-platform/meta/html/infrastructure/urls/base-url/document-base-url-window-open-about-blank.https.window.js.ini
@@ -1,3 +0,0 @@
-[document-base-url-window-open-about-blank.https.window.html]
- [window.open() gets base url from initiator.]
- expected: FAIL
diff --git a/testing/web-platform/meta/service-workers/service-worker/register-closed-window.https.html.ini b/testing/web-platform/meta/service-workers/service-worker/register-closed-window.https.html.ini
@@ -1,3 +1,6 @@
[register-closed-window.https.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
+ TIMEOUT
+ [Call register() on ServiceWorkerContainer owned by closed window.]
+ expected: TIMEOUT
diff --git a/testing/web-platform/tests/html/infrastructure/urls/base-url/initial-about-blank-baseURI.window.js b/testing/web-platform/tests/html/infrastructure/urls/base-url/initial-about-blank-baseURI.window.js
@@ -0,0 +1,85 @@
+// META: script=/common/get-host-info.sub.js
+// Test that the initial about:blank gets the about base URL
+// from the entry global object.
+
+async function withIframe(src, cb) {
+ const ifr = document.createElement("iframe");
+ ifr.src = src;
+ document.body.append(ifr);
+ cb(ifr);
+ ifr.remove();
+}
+
+async function withWindow(src, cb) {
+ const w = window.open(src);
+ cb(w);
+ w.close();
+}
+
+// Need a trailing '/' for equality checks
+const REMOTE_ORIGIN = new URL("/", get_host_info().REMOTE_ORIGIN).href;
+
+async function withWindowOpenerNotInitiator(src, cb) {
+ window.deferredIframeWindow = Promise.withResolvers();
+
+ // Create an iframe with a different base URL.
+ // If it opens a window with window.top being the opener,
+ // the base URL should come from the initiator, i.e. this iframe.
+ const ifr = document.createElement("iframe");
+ ifr.srcdoc = `
+ <head>
+ <base href='${REMOTE_ORIGIN}'>
+ <script>
+ const w = window.top.open('${src}');
+ window.top.deferredIframeWindow.resolve(w);
+ </scr` + `ipt>
+ </head>
+ <body></body>
+ `;
+ document.body.append(ifr);
+
+ const w = await window.deferredIframeWindow.promise;
+
+ cb(w);
+
+ w.close();
+ ifr.remove();
+}
+
+promise_test(async t => {
+ await withIframe("", ifr => {
+ assert_equals(ifr.contentDocument.baseURI, document.baseURI, "about:blank has creator's base URI");
+ })
+}, "Initial iframe about:blank gets base url from creator");
+
+promise_test(async t => {
+ await withIframe("/arbitrary-sameorigin-src", ifr => {
+ assert_equals(ifr.contentDocument.baseURI, document.baseURI, "about:blank has creator's base URI");
+ })
+}, "Transient iframe about:blank gets base url from creator");
+
+promise_test(async t => {
+ await withWindow("", w => {
+ assert_equals(w.document.baseURI, document.baseURI, "about:blank has creator's base URI");
+ })
+}, "Initial top-level about:blank gets base url from creator = opener");
+
+promise_test(async t => {
+ await withWindow("/arbitrary-sameorigin-src", w => {
+ assert_equals(w.document.baseURI, document.baseURI, "about:blank has creator's base URI");
+ })
+}, "Transient top-level about:blank gets base url from creator = opener");
+
+promise_test(async t => {
+ await withWindowOpenerNotInitiator("", w => {
+ assert_not_equals(REMOTE_ORIGIN, document.baseURI, "These need to be different");
+ assert_equals(w.document.baseURI, REMOTE_ORIGIN, "about:blank has creator's base URI");
+ })
+}, "Initial top-level about:blank gets base url from creator != opener");
+
+promise_test(async t => {
+ await withWindowOpenerNotInitiator("/arbitrary-sameorigin-src", w => {
+ assert_not_equals(REMOTE_ORIGIN, document.baseURI, "These need to be different");
+ assert_equals(w.document.baseURI, REMOTE_ORIGIN, "about:blank has creator's base URI");
+ })
+}, "Transient top-level about:blank gets base url from creator != opener");
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/register-closed-window-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/register-closed-window-iframe.html
@@ -7,8 +7,12 @@ window.addEventListener('message', async function(evt) {
var sw = w.navigator.serviceWorker;
w.close();
w = null;
+
+ // Ensure sw.register receives a valid URL. The popup might have
+ // about:blank as base so sw.register('doestnmatter.js') would be invalid.
+ const swUrl = new URL('doestnmatter.js', document.baseURI).href;
try {
- await sw.register('doesntmatter.js');
+ await sw.register(swUrl);
} finally {
parent.postMessage('OK', '*');
}
diff --git a/toolkit/components/windowwatcher/nsWindowWatcher.cpp b/toolkit/components/windowwatcher/nsWindowWatcher.cpp
@@ -964,6 +964,8 @@ nsresult nsWindowWatcher::OpenWindowInternal(
entryDoc->GetPolicyContainer();
openWindowInfo->mCoepToInheritForAboutBlank =
entryDoc->GetEmbedderPolicy();
+ openWindowInfo->mBaseUriToInheritForAboutBlank =
+ entryDoc->GetBaseURI();
}
}