From 91972506c19efdd56cd1192901cea6d8e965485f Mon Sep 17 00:00:00 2001 From: Olli Pettay Date: Wed, 9 Sep 2026 13:57:55 +0000 Subject: [PATCH] Bug 2062317, be more synchronous with DOMContentLoaded, r=farre Differential Revision: https://phabricator.services.mozilla.com/D321732 --- docshell/base/nsDocShell.cpp | 2 +- dom/base/Document.cpp | 53 +++++++++++----- dom/base/Document.h | 12 +++- .../file_domcontentloaded_document_open.html | 18 ++++++ ...ile_domcontentloaded_terminated_parse.html | 13 ++++ dom/base/test/mochitest.toml | 6 ++ .../test_domcontentloaded_document_open.html | 22 +++++++ ...est_domcontentloaded_terminated_parse.html | 22 +++++++ dom/base/test/unit/test_blockParsing.js | 60 +++++++++++++++++++ .../PrototypeDocumentContentSink.cpp | 2 +- dom/script/ScriptLoader.cpp | 10 ++-- dom/script/ScriptLoader.h | 2 +- dom/xml/XMLDocument.cpp | 4 +- dom/xml/XMLDocument.h | 3 +- dom/xml/nsXMLContentSink.cpp | 5 +- modules/libpref/init/StaticPrefList.yaml | 6 ++ parser/html/nsHtml5TreeOpExecutor.cpp | 12 ++-- ...Loaded-defer-task-order.tentative.html.ini | 2 + .../parsing/DOMContentLoaded-defer.html.ini | 7 ++- ...tentLoaded-defer-task-order.tentative.html | 28 +++++++++ .../scripted/a.rel-noreferrer-policy.html | 28 ++++----- .../a.rel-noreferrer-policy-frame.html | 11 ++++ .../a.rel-noreferrer-policy-target.html | 7 +-- widget/tests/test_dnd_ignoreNuls.html | 2 + 24 files changed, 281 insertions(+), 56 deletions(-) create mode 100644 dom/base/test/file_domcontentloaded_document_open.html create mode 100644 dom/base/test/file_domcontentloaded_terminated_parse.html create mode 100644 dom/base/test/test_domcontentloaded_document_open.html create mode 100644 dom/base/test/test_domcontentloaded_terminated_parse.html create mode 100644 testing/web-platform/meta/html/syntax/parsing/DOMContentLoaded-defer-task-order.tentative.html.ini create mode 100644 testing/web-platform/tests/html/syntax/parsing/DOMContentLoaded-defer-task-order.tentative.html create mode 100644 testing/web-platform/tests/svg/linking/scripted/resources/a.rel-noreferrer-policy-frame.html diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 48be2ce822c1..8980380f6c61 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -10342,7 +10342,7 @@ nsresult nsDocShell::CompleteInitialAboutBlankLoad( // Mechanisms in Document will force a load from EndLoad() // even if there are still blockers. - doc->EndLoad(); + doc->EndLoad(/* aFireDOMContentLoadedSync = */ true); // Can't assert any postcondition, because the load event // handler may have started loading something new in this // docshell. diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp index a2ffaef1cc43..3fd3cded13f7 100644 --- a/dom/base/Document.cpp +++ b/dom/base/Document.cpp @@ -8770,7 +8770,7 @@ void Document::MozSetImageElement(const nsAString& aImageElementId, } } -void Document::DispatchContentLoadedEvents() { +void Document::DispatchContentLoadedEvents(bool aFinishSync) { // If you add early returns from this method, make sure you're // calling UnblockOnload properly. @@ -8882,6 +8882,19 @@ void Document::DispatchContentLoadedEvents() { } } + if (aFinishSync) { + FinishDOMContentLoaded(); + return; + } + + // Keep the load event on a task, so that its timing does not change. + nsCOMPtr ev = + NewRunnableMethod("Document::FinishDOMContentLoaded", this, + &Document::FinishDOMContentLoaded); + Dispatch(ev.forget()); +} + +void Document::FinishDOMContentLoaded() { if (mSetCompleteAfterDOMContentLoaded) { SetReadyStateInternal(ReadyState::READYSTATE_COMPLETE); mSetCompleteAfterDOMContentLoaded = false; @@ -8890,7 +8903,7 @@ void Document::DispatchContentLoadedEvents() { UnblockOnload(true); } -void Document::EndLoad() { +void Document::EndLoad(bool aFireDOMContentLoadedSync) { bool turnOnEditing = mParser && (IsInDesignMode() || mContentEditableCount > 0); @@ -8938,7 +8951,7 @@ void Document::EndLoad() { } mDidCallBeginLoad = false; - UnblockDOMContentLoaded(); + UnblockDOMContentLoaded(aFireDOMContentLoadedSync); if (turnOnEditing) { EditingStateChanged(); @@ -8963,7 +8976,7 @@ void Document::EndLoad() { } } -void Document::UnblockDOMContentLoaded() { +void Document::UnblockDOMContentLoaded(bool aFireSync) { MOZ_ASSERT(mBlockDOMContentLoaded); if (--mBlockDOMContentLoaded != 0 || mDidFireDOMContentLoaded) { return; @@ -8974,17 +8987,28 @@ void Document::UnblockDOMContentLoaded() { mDidFireDOMContentLoaded = true; + MOZ_RELEASE_ASSERT(NS_IsMainThread()); MOZ_ASSERT(IsInitialDocument() || mReadyState == READYSTATE_INTERACTIVE); - if (!mSynchronousDOMContentLoaded) { - MOZ_RELEASE_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(!IsInitialDocument()); - nsCOMPtr ev = - NewRunnableMethod("Document::DispatchContentLoadedEvents", this, - &Document::DispatchContentLoadedEvents); - Dispatch(ev.forget()); - } else { - DispatchContentLoadedEvents(); + + // These documents need the load event unblocked before we return. + if (mSynchronousDOMContentLoaded) { + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); + DispatchContentLoadedEvents(/* aFinishSync = */ true); + return; } + + if (aFireSync && + StaticPrefs::dom_document_domcontentloaded_synchronous_enabled()) { + nsContentUtils::AddScriptRunner( + NewRunnableMethod("Document::DispatchContentLoadedEvents", this, + &Document::DispatchContentLoadedEvents, false)); + return; + } + + MOZ_ASSERT(!IsInitialDocument()); + Dispatch(NewRunnableMethod("Document::DispatchContentLoadedEvents", + this, &Document::DispatchContentLoadedEvents, + true)); } void Document::ElementStateChanged(Element* aElement, ElementState aStateMask) { @@ -15326,7 +15350,8 @@ class UnblockParsingPromiseHandler final : public PromiseNativeHandler { // parser state for this document. Maybe someone caused it to stop being // parsed, so CreatorParserOrNull() is returning null, but we still want // to unblock these. - mDocument->UnblockDOMContentLoaded(); + // Async, because this also runs from our destructor. + mDocument->UnblockDOMContentLoaded(/* aFireSync = */ false); mDocument->UnblockOnload(false); } mParser = nullptr; diff --git a/dom/base/Document.h b/dom/base/Document.h index 916c02519382..f7a6db7383a7 100644 --- a/dom/base/Document.h +++ b/dom/base/Document.h @@ -1670,7 +1670,9 @@ class Document : public nsINode, NotNull& aEncoding, nsHtml5TreeOpExecutor* aExecutor); - MOZ_CAN_RUN_SCRIPT void DispatchContentLoadedEvents(); + MOZ_CAN_RUN_SCRIPT void DispatchContentLoadedEvents(bool aFinishSync); + // Unblocks the load event. An aborted load also gets readyState complete. + MOZ_CAN_RUN_SCRIPT void FinishDOMContentLoaded(); // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230) MOZ_CAN_RUN_SCRIPT_BOUNDARY void DispatchPageTransition( @@ -2240,7 +2242,10 @@ class Document : public nsINode, uint32_t UpdateNestingLevel() { return mUpdateNestLevel; } void BeginLoad(); - virtual void EndLoad(); + // aFireDOMContentLoadedSync must be false for a terminated parse. + // See bug 344305. + MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual void EndLoad( + bool aFireDOMContentLoadedSync); enum ReadyState { READYSTATE_UNINITIALIZED = 0, @@ -2643,7 +2648,8 @@ class Document : public nsINode, void BlockDOMContentLoaded() { ++mBlockDOMContentLoaded; } - MOZ_CAN_RUN_SCRIPT_BOUNDARY void UnblockDOMContentLoaded(); + // aFireSync false fires DOMContentLoaded from a task. See bug 344305. + MOZ_CAN_RUN_SCRIPT_BOUNDARY void UnblockDOMContentLoaded(bool aFireSync); /** * Notification that the page has been shown, for documents which are loaded diff --git a/dom/base/test/file_domcontentloaded_document_open.html b/dom/base/test/file_domcontentloaded_document_open.html new file mode 100644 index 000000000000..bf1d4469aeb7 --- /dev/null +++ b/dom/base/test/file_domcontentloaded_document_open.html @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/dom/base/test/file_domcontentloaded_terminated_parse.html b/dom/base/test/file_domcontentloaded_terminated_parse.html new file mode 100644 index 000000000000..be1d8916150a --- /dev/null +++ b/dom/base/test/file_domcontentloaded_terminated_parse.html @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/dom/base/test/mochitest.toml b/dom/base/test/mochitest.toml index af011cbc84a8..812f015adce6 100644 --- a/dom/base/test/mochitest.toml +++ b/dom/base/test/mochitest.toml @@ -12,6 +12,8 @@ support-files = [ "iframe_main_bug1022229.html", "iframe_sandbox_bug1022229.html", "file_empty.html", + "file_domcontentloaded_document_open.html", + "file_domcontentloaded_terminated_parse.html", "iframe_postMessage_solidus.html", "file_setname.html", "345339_iframe.html", @@ -352,6 +354,10 @@ skip-if = [ "http3", ] +["test_domcontentloaded_document_open.html"] + +["test_domcontentloaded_terminated_parse.html"] + ["test_domparser_null_char.html"] ["test_domparsing.html"] diff --git a/dom/base/test/test_domcontentloaded_document_open.html b/dom/base/test/test_domcontentloaded_document_open.html new file mode 100644 index 000000000000..d1f4913b38c0 --- /dev/null +++ b/dom/base/test/test_domcontentloaded_document_open.html @@ -0,0 +1,22 @@ + + + +Test document.open() from a DOMContentLoaded listener + + + + + + + + diff --git a/dom/base/test/test_domcontentloaded_terminated_parse.html b/dom/base/test/test_domcontentloaded_terminated_parse.html new file mode 100644 index 000000000000..3ab892e90005 --- /dev/null +++ b/dom/base/test/test_domcontentloaded_terminated_parse.html @@ -0,0 +1,22 @@ + + + +Test DOMContentLoaded after a terminated parse + + + + + + + + diff --git a/dom/base/test/unit/test_blockParsing.js b/dom/base/test/unit/test_blockParsing.js index e5d35f6eeffd..f7e6ea1dd341 100644 --- a/dom/base/test/unit/test_blockParsing.js +++ b/dom/base/test/unit/test_blockParsing.js @@ -24,6 +24,7 @@ const server = XPCShellContentUtils.createHttpServer({ // XML document with only a + +
+ + diff --git a/testing/web-platform/tests/svg/linking/scripted/a.rel-noreferrer-policy.html b/testing/web-platform/tests/svg/linking/scripted/a.rel-noreferrer-policy.html index 47c6f3014688..51d8b5f89a3d 100644 --- a/testing/web-platform/tests/svg/linking/scripted/a.rel-noreferrer-policy.html +++ b/testing/web-platform/tests/svg/linking/scripted/a.rel-noreferrer-policy.html @@ -4,19 +4,17 @@ Rel attribute with noreferrer value - - - + - + const iframe = document.createElement("iframe"); + iframe.src = "resources/a.rel-noreferrer-policy-frame.html"; + document.body.appendChild(iframe); +}, "No Referrer policy attribute on svg anchor element is applied"); + diff --git a/testing/web-platform/tests/svg/linking/scripted/resources/a.rel-noreferrer-policy-frame.html b/testing/web-platform/tests/svg/linking/scripted/resources/a.rel-noreferrer-policy-frame.html new file mode 100644 index 000000000000..e570e0fd1fd9 --- /dev/null +++ b/testing/web-platform/tests/svg/linking/scripted/resources/a.rel-noreferrer-policy-frame.html @@ -0,0 +1,11 @@ + + + + + diff --git a/testing/web-platform/tests/svg/linking/scripted/resources/a.rel-noreferrer-policy-target.html b/testing/web-platform/tests/svg/linking/scripted/resources/a.rel-noreferrer-policy-target.html index 59ffe0132d8b..ddd76dd0cbe5 100644 --- a/testing/web-platform/tests/svg/linking/scripted/resources/a.rel-noreferrer-policy-target.html +++ b/testing/web-platform/tests/svg/linking/scripted/resources/a.rel-noreferrer-policy-target.html @@ -1,10 +1,5 @@ - - - diff --git a/widget/tests/test_dnd_ignoreNuls.html b/widget/tests/test_dnd_ignoreNuls.html index 7ac189d6f719..1d7e009adfb9 100644 --- a/widget/tests/test_dnd_ignoreNuls.html +++ b/widget/tests/test_dnd_ignoreNuls.html @@ -64,6 +64,8 @@ } async function runTests() { + // The drag needs a focused window. + await SimpleTest.promiseFocus(); await SpecialPowers.contentTransformsReceived(window); for (let currentTest of tests) { await test(currentTest.actual, currentTest.expected);