tor-browser

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

commit b1c9db5a25f20822f9ca7817a5af0005ff55df20
parent dc8e9469e3d372882b972797491104079603c2a7
Author: Tooru Fujisawa <arai_a@mac.com>
Date:   Wed, 29 Oct 2025 23:24:56 +0000

Bug 1996160 - Part 1: Use observer notifications for ScriptLoader tests. r=nbp

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

Diffstat:
Mdom/base/test/test_delazification_strategy.html | 86++++++++++++++++++++++++++++++++++++++-----------------------------------------
Mdom/script/ScriptLoadHandler.cpp | 6+++---
Mdom/script/ScriptLoader.cpp | 24++++++++++--------------
Adom/script/ScriptTrace.cpp | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdom/script/ScriptTrace.h | 54++++++++++++++++++++----------------------------------
Mdom/script/moz.build | 1+
Mjs/loader/ModuleLoaderBase.cpp | 2+-
7 files changed, 154 insertions(+), 97 deletions(-)

diff --git a/dom/base/test/test_delazification_strategy.html b/dom/base/test/test_delazification_strategy.html @@ -3,10 +3,10 @@ <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1753709 --> <!-- Script delazification strategy is not supposed to have any observable side-effect. To make it observable, the ScriptLoader is instrumented to - trigger events on the script tag. These events are used to validate that - the strategy is used as execpected. This does not garantee that all - functions are delazified properly, but this should be checked in the JS - engine test suite. + trigger notifications on the script tag. These notifications are used to + validate that the strategy is used as execpected. This does not garantee + that all functions are delazified properly, but this should be checked in + the JS engine test suite. --> <head> <meta charset="utf-8"> @@ -14,34 +14,41 @@ <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <script type="application/javascript"> - async function WaitForScriptTagEvent() { + async function WaitForScriptNotification() { var url = "file_delazification_strategy.html"; var iframe = document.createElement("iframe"); - // Call the resolve function when the event is one of the expected events. - // This is made to be used by a promise and provided to event listeners. - function resolve_with_event(resolve, evt) { + // Call the resolve function when the notification is one of the expected + // notifications. This is made to be used by a promise and provided to + // notification observer. + function resolve_with_notification(resolve, subject, topic, data) { + const param = {}; + for (const line of data.split("\n")) { + const m = line.match(/^([^:]+):(.+)/); + param[m[1]] = m[2]; + } + // If we have multiple script tags in the loaded source, make sure // we only watch a single one. - if (evt.target.id != "watchme") + if (param.id != "watchme") return; - switch (evt.type) { - case "delazification_on_demand_only": - case "delazification_concurrent_depth_first": - case "delazification_parse_everything_eagerly": - resolve(evt.type.split('_').slice(1).join('_')); + switch (param.event) { + case "delazification:OnDemandOnly": + case "delazification:ConcurrentDepthFirst": + case "delazification:ParseEverythingEagerly": + resolve(param.event.replace("delazification:", "")); break; - case "scriptloader_main_thread_compile": - resolve(evt.type); + case "compile:main thread": + resolve(param.event); break; } } - // Create an event listener, which resolves a promise. - let log_event; + // Create an notification observer, which resolves a promise. + let log_notification; let scriptLoaderTrace = new Promise((resolve, reject) => { - log_event = resolve_with_event.bind(this, resolve); + log_notification = resolve_with_notification.bind(this, resolve); }); // Wait until the iframe is fully loaded. @@ -51,41 +58,30 @@ document.body.appendChild(iframe); }); - // Register all events. - let events = [ - "delazification_on_demand_only", - "delazification_concurrent_depth_first", - "delazification_parse_everything_eagerly", - "scriptloader_main_thread_compile" - ]; - let iwin = iframe.contentWindow; - for (let evt of events) { - iwin.addEventListener(evt, log_event); - } + // Register all notifications. + SpecialPowers.Services.obs.addObserver(log_notification, "ScriptLoaderTest"); - // Add a script tag, which will trigger one of the previous events. + // Add a script tag, which will trigger one of the previous + // notifications.. let script = document.createElement("script"); script.setAttribute("id", "watchme"); script.setAttribute("src", "file_delazification_strategy.js"); iframe.contentDocument.body.appendChild(script); - // Wait for the event emitted by ScriptLoader, while processing the + // Wait for the notification emitted by ScriptLoader, while processing the // previous script. let result = await scriptLoaderTrace; - // Remove the events and the iframe. - for (let evt of events) { - iwin.removeEventListener(evt, log_event); - } + SpecialPowers.Services.obs.removeObserver(log_notification, "ScriptLoaderTest"); document.body.removeChild(iframe); return result; } // Setting dom.expose_test_interfaces pref causes the - // nsScriptLoadRequest to fire event on script tags, with information - // about its internal state. The ScriptLoader source send events to - // trace these and resolve a promise with the path taken by the - // script loader. + // nsScriptLoadRequest to fire notifications on script tags, with + // information about its internal state. The ScriptLoader source send + // notifications to trace these and resolve a promise with the path taken + // by the script loader. // // Setting dom.script_loader.bytecode_cache.enabled to false in order // to prevent the bytecode cache to perturb this test case. @@ -109,7 +105,7 @@ ['dom.script_loader.delazification.min_mem', 0], ]}); - assert_equals(await WaitForScriptTagEvent(), "on_demand_only", + assert_equals(await WaitForScriptNotification(), "OnDemandOnly", "[1] AttemptAsyncScriptCompile: On demand only"); }, "Check that max_size can disable delazification strategy"); @@ -129,7 +125,7 @@ ['dom.script_loader.delazification.min_mem', 4096], ]}); - assert_equals(await WaitForScriptTagEvent(), "on_demand_only", + assert_equals(await WaitForScriptNotification(), "OnDemandOnly", "[2] AttemptAsyncScriptCompile: On demand only"); }, "Check that min_mem can disable delazification strategy"); @@ -149,19 +145,19 @@ await SpecialPowers.pushPrefEnv({set: [ ['dom.script_loader.delazification.strategy', 0], ]}); - assert_equals(await WaitForScriptTagEvent(), "on_demand_only", + assert_equals(await WaitForScriptNotification(), "OnDemandOnly", "[3] AttemptAsyncScriptCompile: On demand only"); await SpecialPowers.pushPrefEnv({set: [ ['dom.script_loader.delazification.strategy', 2], ]}); - assert_equals(await WaitForScriptTagEvent(), "concurrent_depth_first", + assert_equals(await WaitForScriptNotification(), "ConcurrentDepthFirst", "[3] AttemptAsyncScriptCompile: Concurrent Depth First"); await SpecialPowers.pushPrefEnv({set: [ ['dom.script_loader.delazification.strategy', 255], ]}); - assert_equals(await WaitForScriptTagEvent(), "parse_everything_eagerly", + assert_equals(await WaitForScriptNotification(), "ParseEverythingEagerly", "[3] AttemptAsyncScriptCompile: Parse Everything Eagerly"); }, "Check enabling delazification strategy works"); diff --git a/dom/script/ScriptLoadHandler.cpp b/dom/script/ScriptLoadHandler.cpp @@ -335,7 +335,7 @@ nsresult ScriptLoadHandler::EnsureKnownDataType( if (mRequest->mFetchSourceOnly) { mRequest->SetTextSource(mRequest->mLoadContext.get()); - TRACE_FOR_TEST(mRequest, "scriptloader_load_source"); + TRACE_FOR_TEST(mRequest, "load:source"); return NS_OK; } @@ -345,14 +345,14 @@ nsresult ScriptLoadHandler::EnsureKnownDataType( cic->GetAlternativeDataType(altDataType); if (altDataType.Equals(ScriptLoader::BytecodeMimeTypeFor(mRequest))) { mRequest->SetBytecode(); - TRACE_FOR_TEST(mRequest, "scriptloader_load_bytecode"); + TRACE_FOR_TEST(mRequest, "load:diskcache"); return NS_OK; } MOZ_ASSERT(altDataType.IsEmpty()); } mRequest->SetTextSource(mRequest->mLoadContext.get()); - TRACE_FOR_TEST(mRequest, "scriptloader_load_source"); + TRACE_FOR_TEST(mRequest, "load:source"); MOZ_ASSERT(!mRequest->IsUnknownDataType()); MOZ_ASSERT(mRequest->IsFetching()); diff --git a/dom/script/ScriptLoader.cpp b/dom/script/ScriptLoader.cpp @@ -592,7 +592,7 @@ void ScriptLoader::RunScriptWhenSafe(ScriptLoadRequest* aRequest) { nsresult ScriptLoader::RestartLoad(ScriptLoadRequest* aRequest) { aRequest->DropBytecode(); - TRACE_FOR_TEST(aRequest, "scriptloader_fallback"); + TRACE_FOR_TEST(aRequest, "load:fallback"); // Notify preload restart so that we can register this preload request again. aRequest->GetScriptLoadContext()->NotifyRestart(mDocument); @@ -1656,7 +1656,7 @@ bool ScriptLoader::ProcessInlineScript(nsIScriptElement* aElement, aElement->GetScriptColumnNumber(); request->mFetchSourceOnly = true; request->SetTextSource(request->mLoadContext.get()); - TRACE_FOR_TEST_BOOL(request, "scriptloader_load_source"); + TRACE_FOR_TEST(request, "load:source"); CollectScriptTelemetry(request); // Only the 'async' attribute is heeded on an inline module script and @@ -2056,7 +2056,7 @@ nsresult ScriptLoader::AttemptOffThreadScriptCompile( if (aRequest->IsTextSource()) { if (!StaticPrefs::javascript_options_parallel_parsing() || aRequest->ScriptTextLength() < OffThreadMinimumTextLength) { - TRACE_FOR_TEST(aRequest, "scriptloader_main_thread_compile"); + TRACE_FOR_TEST(aRequest, "compile:main thread"); return NS_OK; } } else { @@ -2357,17 +2357,17 @@ nsresult ScriptLoader::CreateOffThreadTask( if (StaticPrefs::dom_expose_test_interfaces()) { switch (aOptions.eagerDelazificationStrategy()) { case JS::DelazificationOption::OnDemandOnly: - TRACE_FOR_TEST(aRequest, "delazification_on_demand_only"); + TRACE_FOR_TEST(aRequest, "delazification:OnDemandOnly"); break; case JS::DelazificationOption::CheckConcurrentWithOnDemand: case JS::DelazificationOption::ConcurrentDepthFirst: - TRACE_FOR_TEST(aRequest, "delazification_concurrent_depth_first"); + TRACE_FOR_TEST(aRequest, "delazification:ConcurrentDepthFirst"); break; case JS::DelazificationOption::ConcurrentLargeFirst: - TRACE_FOR_TEST(aRequest, "delazification_concurrent_large_first"); + TRACE_FOR_TEST(aRequest, "delazification:ConcurrentLargeFirst"); break; case JS::DelazificationOption::ParseEverythingEagerly: - TRACE_FOR_TEST(aRequest, "delazification_parse_everything_eagerly"); + TRACE_FOR_TEST(aRequest, "delazification:ParseEverythingEagerly"); break; } } @@ -3349,7 +3349,7 @@ nsresult ScriptLoader::MaybePrepareForDiskCacheAfterExecute( if (!aRequest->PassedConditionForDiskCache() || !aRequest->HasStencil()) { LOG(("ScriptLoadRequest (%p): Bytecode-cache: disabled (rv = %X)", aRequest, unsigned(aRv))); - TRACE_FOR_TEST_NONE(aRequest, "scriptloader_no_encode"); + TRACE_FOR_TEST(aRequest, "diskcache:disabled"); // For in-memory cached requests, the disk cache references are necessary // for later load. @@ -3364,7 +3364,7 @@ nsresult ScriptLoader::MaybePrepareForDiskCacheAfterExecute( return aRv; } - TRACE_FOR_TEST(aRequest, "scriptloader_encode"); + TRACE_FOR_TEST(aRequest, "diskcache:register"); // Bytecode-encoding branch is used for 2 purposes right now: // * If the request is stencil, reflect delazifications to cached stencil // * otherwise, encode the initial stencil and delazifications @@ -3453,7 +3453,6 @@ nsresult ScriptLoader::EvaluateScript(nsIGlobalObject* aGlobalObject, mTotalFullParseSize)); } - TRACE_FOR_TEST(aRequest, "scriptloader_execute"); JS::Rooted<JSObject*> global(cx, aGlobalObject->GetGlobalJSObject()); if (MOZ_UNLIKELY(!xpc::Scriptability::Get(global).Allowed())) { return NS_OK; @@ -3474,6 +3473,7 @@ nsresult ScriptLoader::EvaluateScript(nsIGlobalObject* aGlobalObject, profilerLabelString); MOZ_ASSERT(options.noScriptRval); + TRACE_FOR_TEST(aRequest, "evaluate:classic"); ExecuteCompiledScript(cx, classicScript, script, erv); } rv = EvaluationExceptionToNSResult(erv); @@ -4799,10 +4799,6 @@ nsAutoScriptLoaderDisabler::~nsAutoScriptLoaderDisabler() { } } -#undef TRACE_FOR_TEST -#undef TRACE_FOR_TEST_BOOL -#undef TRACE_FOR_TEST_NONE - #undef LOG } // namespace mozilla::dom diff --git a/dom/script/ScriptTrace.cpp b/dom/script/ScriptTrace.cpp @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ScriptTrace.h" + +#include "mozilla/Services.h" // services::* +#include "mozilla/dom/Element.h" // mozilla::dom::Element +#include "mozilla/dom/ScriptLoadContext.h" // mozilla::dom::ScriptLoadContext +#include "nsCOMPtr.h" // nsCOMPtr.h +#include "nsIObserverService.h" // nsIObserverService +#include "nsIScriptElement.h" // nsIScriptElement +#include "nsThreadUtils.h" // mozilla::Runnable, NS_DispatchToCurrentThread + +namespace mozilla::dom::script { + +class ScriptLoaderTestRunnable : public Runnable { + public: + ScriptLoaderTestRunnable(JS::loader::ScriptLoadRequest* aRequest, + JS::loader::LoadedScript* aLoadedScript, + const char* aEvent) + : Runnable("dom::script::ScriptLoaderTestRunnable") { + mData.AppendLiteral(u"event:"); + mData.AppendASCII(aEvent); + if (aLoadedScript) { + mData.AppendLiteral(u"\nurl:"); + mData.Append( + NS_ConvertUTF8toUTF16(aLoadedScript->GetURI()->GetSpecOrDefault())); + } + + if (aRequest) { + nsIScriptElement* scriptElement = + aRequest->GetScriptLoadContext()->GetScriptElementForTrace(); + nsCOMPtr<Element> target(do_QueryInterface(scriptElement)); + if (target) { + nsAutoString id; + target->GetId(id); + mData.AppendLiteral(u"\nid:"); + mData.Append(id); + } + } + } + + NS_IMETHOD Run() override { + nsCOMPtr<nsIObserverService> obsService = services::GetObserverService(); + obsService->NotifyObservers(nullptr, "ScriptLoaderTest", + mData.BeginReading()); + return NS_OK; + } + + protected: + ~ScriptLoaderTestRunnable() = default; + + private: + nsAutoString mData; +}; + +void TestingNotifyObserver(JS::loader::ScriptLoadRequest* aRequest, + JS::loader::LoadedScript* aLoadedScript, + const char* aEvent) { + nsCOMPtr<nsIObserverService> obsService = services::GetObserverService(); + + if (!obsService->HasObservers("ScriptLoaderTest")) { + return; + } + + // NOTE: There can be pending exception for the script load itself, + // and the observer notification shouldn't be performed in that + // situation. + // Use a separate task to avoid the collision. + RefPtr<ScriptLoaderTestRunnable> runnable = + new ScriptLoaderTestRunnable(aRequest, aLoadedScript, aEvent); + (void)NS_DispatchToCurrentThread(runnable); +} + +} // namespace mozilla::dom::script diff --git a/dom/script/ScriptTrace.h b/dom/script/ScriptTrace.h @@ -7,56 +7,42 @@ #ifndef mozilla_dom_ScriptTrace_h #define mozilla_dom_ScriptTrace_h -#include "js/loader/ScriptLoadRequest.h" -#include "mozilla/AsyncEventDispatcher.h" +#include "js/loader/LoadedScript.h" // JS::loader::LoadedScript +#include "js/loader/ScriptLoadRequest.h" // JS::loader::ScriptLoadRequest #include "mozilla/StaticPrefs_dom.h" -#include "mozilla/dom/ScriptLoadContext.h" // This macro is used to wrap a tracing mechanism which is scheduling events // which are then used by the JavaScript code of test cases to track the code // path to verify the optimizations are working as expected. -#define TRACE_FOR_TEST(request, str) \ - PR_BEGIN_MACRO \ - nsresult rv = NS_OK; \ - rv = mozilla::dom::script::TestingDispatchEvent( \ - request, NS_LITERAL_STRING_FROM_CSTRING(str)); \ - NS_ENSURE_SUCCESS(rv, rv); \ - PR_END_MACRO - -#define TRACE_FOR_TEST_BOOL(request, str) \ - PR_BEGIN_MACRO \ - nsresult rv = NS_OK; \ - rv = mozilla::dom::script::TestingDispatchEvent( \ - request, NS_LITERAL_STRING_FROM_CSTRING(str)); \ - NS_ENSURE_SUCCESS(rv, false); \ - PR_END_MACRO - -#define TRACE_FOR_TEST_NONE(request, str) \ - PR_BEGIN_MACRO \ - mozilla::dom::script::TestingDispatchEvent( \ - request, NS_LITERAL_STRING_FROM_CSTRING(str)); \ +#define TRACE_FOR_TEST(requestOrScript, str) \ + PR_BEGIN_MACRO \ + mozilla::dom::script::TestingNotifyObserver(requestOrScript, str); \ PR_END_MACRO namespace mozilla::dom::script { -static nsresult TestingDispatchEvent(JS::loader::ScriptLoadRequest* aRequest, - const nsAString& aEventType) { +void TestingNotifyObserver(JS::loader::ScriptLoadRequest* aRequest, + JS::loader::LoadedScript* aLoadedScript, + const char* aEvent); + +inline void TestingNotifyObserver(JS::loader::LoadedScript* aLoadedScript, + const char* aEvent) { if (!StaticPrefs::dom_expose_test_interfaces()) { - return NS_OK; + return; } - nsIScriptElement* scriptElement = - aRequest->GetScriptLoadContext()->GetScriptElementForTrace(); + TestingNotifyObserver(nullptr, aLoadedScript, aEvent); +} - nsCOMPtr<nsINode> target(do_QueryInterface(scriptElement)); - if (!target) { - return NS_OK; +inline void TestingNotifyObserver(JS::loader::ScriptLoadRequest* aRequest, + const char* aEvent) { + if (!StaticPrefs::dom_expose_test_interfaces()) { + return; } - RefPtr<AsyncEventDispatcher> dispatcher = new AsyncEventDispatcher( - target, aEventType, CanBubble::eYes, ChromeOnlyDispatch::eNo); - return dispatcher->PostDOMEvent(); + TestingNotifyObserver(aRequest, aRequest->getLoadedScript(), aEvent); } + } // namespace mozilla::dom::script #endif // mozilla_dom_ScriptTrace_h diff --git a/dom/script/moz.build b/dom/script/moz.build @@ -38,6 +38,7 @@ UNIFIED_SOURCES += [ "ScriptLoader.cpp", "ScriptLoadHandler.cpp", "ScriptSettings.cpp", + "ScriptTrace.cpp", "ShadowRealmGlobalScope.cpp", "SharedScriptCache.cpp", ] diff --git a/js/loader/ModuleLoaderBase.cpp b/js/loader/ModuleLoaderBase.cpp @@ -1520,7 +1520,7 @@ nsresult ModuleLoaderBase::EvaluateModuleInContext( } if (aRequest->HasScriptLoadContext()) { - TRACE_FOR_TEST(aRequest, "scriptloader_evaluate_module"); + TRACE_FOR_TEST(aRequest, "evaluate:module"); } Rooted<Value> rval(aCx);