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:
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);