commit 4552a2f867a8074cfdd2c69fdde2aab0e33f129a
parent 66d18cee690a1df212878254c24f1730fee6af63
Author: Tom Forbes <tom.forbes1@gmail.com>
Date: Fri, 21 Nov 2025 10:13:48 +0000
Bug 1897424 - Add NotifyNetworkMonitorAlternateStack support for main-thread fetch requests. r=sunil,devtools-reviewers,bomsy
Differential Revision: https://phabricator.services.mozilla.com/D270808
Diffstat:
6 files changed, 183 insertions(+), 11 deletions(-)
diff --git a/devtools/shared/commands/resource/tests/browser.toml b/devtools/shared/commands/resource/tests/browser.toml
@@ -69,6 +69,8 @@ support-files = [
["browser_resources_network_event_stacktraces.js"]
+["browser_resources_network_event_stacktraces_keepalive.js"]
+
["browser_resources_network_events.js"]
["browser_resources_network_events_cache.js"]
diff --git a/devtools/shared/commands/resource/tests/browser_resources_network_event_stacktraces_keepalive.js b/devtools/shared/commands/resource/tests/browser_resources_network_event_stacktraces_keepalive.js
@@ -0,0 +1,122 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test the ResourceCommand API for NETWORK_EVENT, NETWORK_EVENT_STACKTRACE with keepalive requests
+
+const TEST_URI = `${URL_ROOT_SSL}network_document.html`;
+
+const REQUEST_STUB = {
+ code: `await fetch("/request_post_0.html", {
+ method: "POST",
+ keepalive: true
+ });`,
+ expected: {
+ url: "https://example.com/request_post_0.html",
+ stacktraceAvailable: true,
+ lastFrame: {
+ filename:
+ "https://example.com/browser/devtools/shared/commands/resource/tests/network_document.html",
+ lineNumber: 1,
+ columnNumber: 40,
+ functionName: "triggerRequest",
+ asyncCause: null,
+ },
+ },
+};
+
+add_task(async function () {
+ info("Test network events and stacktraces for keepalive fetch requests");
+ const tab = await addTab(TEST_URI);
+ const { client, resourceCommand, targetCommand } =
+ await initResourceCommand(tab);
+
+ const networkEvents = new Map();
+ let stacktraceReceived = false;
+ let noOfNetworkUpdatesResources = 0;
+
+ function onResourceAvailable(resources) {
+ for (const resource of resources) {
+ if (
+ resource.resourceType === resourceCommand.TYPES.NETWORK_EVENT_STACKTRACE
+ ) {
+ is(
+ resource.stacktraceAvailable,
+ REQUEST_STUB.expected.stacktraceAvailable,
+ "The stacktrace is available"
+ );
+ is(
+ JSON.stringify(resource.lastFrame),
+ JSON.stringify(REQUEST_STUB.expected.lastFrame),
+ "The last frame of the stacktrace is available"
+ );
+
+ stacktraceReceived = true;
+ return;
+ }
+
+ if (resource.resourceType === resourceCommand.TYPES.NETWORK_EVENT) {
+ is(
+ resource.url,
+ REQUEST_STUB.expected.url,
+ "The keepalive network request is available"
+ );
+ networkEvents.set(resource.resourceId, resource);
+ }
+ }
+ }
+
+ function onResourceUpdated(updates) {
+ for (const { resource } of updates) {
+ const networkResource = networkEvents.get(resource.resourceId);
+ is(
+ resource.url,
+ networkResource.url,
+ "Found a matching available notification for the update: " +
+ resource.url
+ );
+
+ noOfNetworkUpdatesResources++;
+ }
+ }
+
+ await resourceCommand.watchResources(
+ [
+ resourceCommand.TYPES.NETWORK_EVENT_STACKTRACE,
+ resourceCommand.TYPES.NETWORK_EVENT,
+ ],
+ {
+ onAvailable: onResourceAvailable,
+ onUpdated: onResourceUpdated,
+ }
+ );
+
+ await triggerNetworkRequests(tab.linkedBrowser, [REQUEST_STUB.code]);
+
+ // Wait for the network updates related to the network event
+ await waitUntil(() => noOfNetworkUpdatesResources >= 1);
+
+ ok(stacktraceReceived, "Stack trace for keepalive request was received");
+ Assert.greater(networkEvents.size, 0, "Keepalive network event was received");
+ Assert.greater(
+ noOfNetworkUpdatesResources,
+ 0,
+ "Keepalive network updates were received"
+ );
+
+ resourceCommand.unwatchResources(
+ [
+ resourceCommand.TYPES.NETWORK_EVENT_STACKTRACE,
+ resourceCommand.TYPES.NETWORK_EVENT,
+ ],
+ {
+ onAvailable: onResourceAvailable,
+ onUpdated: onResourceUpdated,
+ }
+ );
+
+ targetCommand.destroy();
+ await client.close();
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/devtools/shared/network-observer/NetworkUtils.sys.mjs b/devtools/shared/network-observer/NetworkUtils.sys.mjs
@@ -159,6 +159,11 @@ function getChannelBrowsingContextID(channel) {
if (channel.loadInfo.browsingContextID) {
return channel.loadInfo.browsingContextID;
}
+
+ if (channel.loadInfo.workerAssociatedBrowsingContextID) {
+ return channel.loadInfo.workerAssociatedBrowsingContextID;
+ }
+
// At least WebSocket channel aren't having a browsingContextID set on their loadInfo
// We fallback on top frame element, which works, but will be wrong for WebSocket
// in same-process iframes...
diff --git a/dom/fetch/Fetch.cpp b/dom/fetch/Fetch.cpp
@@ -699,6 +699,16 @@ already_AddRefed<Promise> FetchRequest(nsIGlobalObject* aGlobal,
ipcArgs.hasCSPEventListener() = false;
ipcArgs.isWorkerRequest() = false;
+ if (window && window->GetBrowsingContext()) {
+ ipcArgs.associatedBrowsingContextID() =
+ window->GetBrowsingContext()->Id();
+ }
+
+ UniquePtr<SerializedStackHolder> stack = GetCurrentStackForNetMonitor(cx);
+ if (stack) {
+ actor->SetOriginStack(std::move(stack));
+ }
+
actor->DoFetchOp(ipcArgs);
return p.forget();
diff --git a/dom/fetch/FetchChild.cpp b/dom/fetch/FetchChild.cpp
@@ -383,10 +383,41 @@ mozilla::ipc::IPCResult FetchChild::RecvOnNotifyNetworkMonitorAlternateStack(
});
MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r.forget()));
+ } else {
+ // Handle main-thread fetch requests
+ if (!mOriginStack) {
+ return IPC_OK();
+ }
+
+ if (!mWorkerChannelInfo) {
+ // Get browsing context from the promise's global object
+ uint64_t browsingContextID = 0;
+ if (mPromise && mPromise->GetGlobalObject()) {
+ if (auto* innerWindow =
+ mPromise->GetGlobalObject()->GetAsInnerWindow()) {
+ if (auto* browsingContext = innerWindow->GetBrowsingContext()) {
+ browsingContextID = browsingContext->Id();
+ }
+ }
+ }
+ if (browsingContextID == 0) {
+ FETCH_LOG(
+ ("FetchChild::RecvOnNotifyNetworkMonitorAlternateStack: unable to "
+ "get browsingContextID for main-thread fetch, channelID=%" PRIu64,
+ aChannelID));
+ }
+ mWorkerChannelInfo =
+ MakeRefPtr<WorkerChannelInfo>(aChannelID, browsingContextID);
+ }
+
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ __func__, [channel = mWorkerChannelInfo,
+ stack = std::move(mOriginStack)]() mutable {
+ NotifyNetworkMonitorAlternateStack(channel, std::move(stack));
+ });
+
+ MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r.forget()));
}
- // Currently we only support sending notifications for worker-thread initiated
- // Fetch requests. We need to extend this to main-thread fetch requests as
- // well. See Bug 1897424.
return IPC_OK();
}
diff --git a/dom/fetch/FetchService.cpp b/dom/fetch/FetchService.cpp
@@ -284,6 +284,8 @@ RefPtr<FetchServicePromises> FetchService::FetchInstance::Fetch() {
if (mArgsType == FetchArgsType::MainThreadFetch) {
auto& args = mArgs.as<MainThreadFetchArgs>();
+ mFetchDriver->SetAssociatedBrowsingContextID(
+ args.mAssociatedBrowsingContextID);
mFetchDriver->SetIsThirdPartyContext(Some(args.mIsThirdPartyContext));
}
@@ -576,25 +578,25 @@ void FetchService::FetchInstance::OnNotifyNetworkMonitorAlternateStack(
FETCH_LOG(("FetchInstance::OnNotifyNetworkMonitorAlternateStack [%p]", this));
MOZ_ASSERT(mFetchDriver);
MOZ_ASSERT(mPromises);
- if (mArgsType != FetchArgsType::WorkerFetch) {
- // We need to support this for Main thread fetch requests as well
- // See Bug 1897129
+
+ if (mArgsType != FetchArgsType::WorkerFetch &&
+ mArgsType != FetchArgsType::MainThreadFetch) {
+ // Fetch type not supported
return;
}
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
- __func__, [actorID = mArgs.as<WorkerFetchArgs>().mActorID,
- channelID = aChannelID]() {
+ __func__, [actorID = GetActorID(), channelID = aChannelID]() {
FETCH_LOG(
- ("FetchInstance::NotifyNetworkMonitorAlternateStack, Runnable"));
+ ("FetchInstance::OnNotifyNetworkMonitorAlternateStack, Runnable"));
RefPtr<FetchParent> actor = FetchParent::GetActorByID(actorID);
if (actor) {
actor->OnNotifyNetworkMonitorAlternateStack(channelID);
}
});
- MOZ_ALWAYS_SUCCEEDS(mArgs.as<WorkerFetchArgs>().mEventTarget->Dispatch(
- r, nsIThread::DISPATCH_NORMAL));
+ MOZ_ALWAYS_SUCCEEDS(
+ GetBackgroundEventTarget()->Dispatch(r, nsIThread::DISPATCH_NORMAL));
}
nsID FetchService::FetchInstance::GetActorID() {