commit e1d22ea27f3632f31e15b603fa1b6a3029d30221
parent 4002601fa705d444acbe2c659c681f16678dada3
Author: Vincent Hilla <vhilla@mozilla.com>
Date: Wed, 3 Dec 2025 19:06:46 +0000
Bug 2003255 - Force sync about:blank despite busy DocLoader. r=dom-core,smaug
Differential Revision: https://phabricator.services.mozilla.com/D274763
Diffstat:
4 files changed, 92 insertions(+), 3 deletions(-)
diff --git a/docshell/test/browser/browser.toml b/docshell/test/browser/browser.toml
@@ -316,6 +316,8 @@ skip-if = [
["browser_history_triggeringprincipal_viewsource.js"]
https_first_disabled = true
+["browser_initialDocument_noLoadBlocking.js"]
+
["browser_initial_entry_without_interaction.js"]
["browser_isInitialDocument.js"]
diff --git a/docshell/test/browser/browser_initialDocument_noLoadBlocking.js b/docshell/test/browser/browser_initialDocument_noLoadBlocking.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// See bug 2003255
+add_task(async function test_extension_cannot_block_initial_load() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ manifest_version: 2,
+ name: "insert script to about:blank",
+ version: "1.0",
+ content_scripts: [
+ {
+ matches: ["<all_urls>"],
+ js: ["content.js"],
+ run_at: "document_start",
+ all_frames: true,
+ match_about_blank: true,
+ },
+ ],
+ },
+
+ files: {
+ "content.js": function () {
+ if (window.location.href != "about:blank#unique-hash") {
+ return;
+ }
+
+ const script = document.createElement("script");
+ script.src =
+ "data:,(window.parent.postMessage(`script ${window.location.href}`))()";
+ (document.documentElement || document).appendChild(script);
+ },
+ },
+ });
+
+ await extension.startup();
+
+ const url = "https://example.com/";
+ const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+
+ await ContentTask.spawn(tab.linkedBrowser, [url], async function (url) {
+ Assert.equal(content.location.href, url, "Correct content document");
+
+ const scriptEvaluated = new Promise(
+ res =>
+ (content.onmessage = ({ data }) => {
+ if (data == "script about:blank#unique-hash") {
+ res();
+ }
+ })
+ );
+
+ let loaded = false;
+ const iframe = content.document.createElement("iframe");
+ iframe.onload = () => (loaded = true);
+ // Let's set a unique hash in case there are other about:blank
+ iframe.src = "about:blank#unique-hash";
+ content.document.body.append(iframe);
+ Assert.ok(loaded, "Load occurred synchronously");
+
+ const extScript = iframe.contentDocument.querySelector("script");
+ Assert.ok(!!extScript, "Extension inserted script synchronously");
+
+ await scriptEvaluated;
+ });
+
+ BrowserTestUtils.removeTab(tab);
+ await extension.unload();
+});
diff --git a/dom/base/Document.h b/dom/base/Document.h
@@ -4197,6 +4197,8 @@ class Document : public nsINode,
bool DOMNotificationsSuspended() const { return mSuspendDOMNotifications; }
+ bool IsExpectingEndLoad() { return mDidCallBeginLoad; }
+
protected:
RefPtr<DocumentL10n> mDocumentL10n;
diff --git a/uriloader/base/nsDocLoader.cpp b/uriloader/base/nsDocLoader.cpp
@@ -756,8 +756,19 @@ void nsDocLoader::DocLoaderIsEmpty(bool aFlushLayout,
alive long enough to survive this function call. */
nsCOMPtr<nsIDocumentLoader> kungFuDeathGrip(this);
+ nsCOMPtr<Document> doc = do_GetInterface(GetAsSupports(this));
+ // Force load if
+ // - we are currently in the synchronous load path of the docshell, i.e. we
+ // are completing the initial about:blank load
+ // - and Document::EndLoad was already called (we're likely called from
+ // there right now).
+ const bool forceInitialSyncLoad = doc &&
+ doc->InitialAboutBlankLoadCompleting() &&
+ !doc->IsExpectingEndLoad();
+ MOZ_ASSERT_IF(forceInitialSyncLoad, !mIsFlushingLayout);
+
// Don't flush layout if we're still busy.
- if (IsBusy()) {
+ if (IsBusy() && !forceInitialSyncLoad) {
return;
}
@@ -792,8 +803,11 @@ void nsDocLoader::DocLoaderIsEmpty(bool aFlushLayout,
//
// Note, mDocumentRequest can be null while mDocumentOpenedButNotLoaded is
// false if the flushing above re-entered this method.
- if (IsBusy() || (!mDocumentRequest && !mDocumentOpenedButNotLoaded &&
- !mIsLoadingJavascriptURI)) {
+ const bool hasActiveLoad = mDocumentRequest ||
+ mDocumentOpenedButNotLoaded ||
+ mIsLoadingJavascriptURI;
+ MOZ_ASSERT_IF(forceInitialSyncLoad, hasActiveLoad);
+ if ((IsBusy() && !forceInitialSyncLoad) || !hasActiveLoad) {
return;
}