tor-browser

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

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:
Mdevtools/shared/commands/resource/tests/browser.toml | 2++
Adevtools/shared/commands/resource/tests/browser_resources_network_event_stacktraces_keepalive.js | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdevtools/shared/network-observer/NetworkUtils.sys.mjs | 5+++++
Mdom/fetch/Fetch.cpp | 10++++++++++
Mdom/fetch/FetchChild.cpp | 37++++++++++++++++++++++++++++++++++---
Mdom/fetch/FetchService.cpp | 18++++++++++--------
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() {