0

[discard] Ensure FrameTree::Discard clears service workers

This CL ensures that any service workers registered against a
discarded RenderFrameHostImpl are cleared following the a
discard operation.

Previously service workers were not being cleared as expected
because the commit params used to commit the new empty document
for discard copied over the service worker network provider
in DocumentLoader::CreateWebNavigationParamsToCloneDocument() [1].

As a result service workers could persist for the discarded frame
in the case the associated process was not proactively shut down.

This CL addresses this by clearing the ServiceWorkerNetworkProvider
when a discarded document is installed. This breaks the container
host pipe with the browser and the ServiceWorkerClient is
destroyed [2].

The ServiceWorkerNetworkProviderForFrame class lives in //content/
and must be replaced with
ServiceWorkerNetworkProviderForFrame::CreateInvalidInstance()
instead of being nullified in blink as the network provider is
expected to be not-null, see [3].

[1] https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/loader/document_loader.cc;l=743;drc=492dc9719f6e1845f4f5c0553cd5c7651115f671

[2] https://source.chromium.org/chromium/chromium/src/+/main:content/browser/service_worker/service_worker_context_core.cc;l=527;drc=492dc9719f6e1845f4f5c0553cd5c7651115f671

[3] https://source.chromium.org/chromium/chromium/src/+/main:content/renderer/render_frame_impl.cc;l=3045;drc=4c6d5b3c3ed16fa75258c6b4763188f840cb433d

Bug: 404877558
Change-Id: If0d495b94d4b5be38f2832152437a4b460f63692
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6367564
Reviewed-by: Rakina Zata Amni <rakina@chromium.org>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Commit-Queue: Tom Lukaszewicz <tluk@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1436434}
This commit is contained in:
Thomas Lukaszewicz
2025-03-22 00:16:54 -07:00
committed by Chromium LUCI CQ
parent 10bc435ad0
commit bbd83a20cf
6 changed files with 46 additions and 1 deletions

@ -477,6 +477,31 @@ IN_PROC_BROWSER_TEST_P(FrameTreeBrowserWithDiscardTest,
base::test::RunUntil([&]() { return 0u == root->child_count(); }));
}
IN_PROC_BROWSER_TEST_P(FrameTreeBrowserWithDiscardTest,
DiscardClearsServiceWorkers) {
WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell()->web_contents());
FrameTree& frame_tree = wc->GetPrimaryFrameTree();
FrameTreeNode* root = frame_tree.root();
// Load a new page, register a service worker and wait for it to become ready.
EXPECT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL(
"/register_service_worker.html")));
EXPECT_EQ("DONE", EvalJs(shell(), "register('/fetch_event_passthrough.js')"));
RenderFrameHostImplWrapper rfh(wc->GetPrimaryMainFrame());
EXPECT_EQ(1u, rfh->service_worker_clients_for_testing().size());
// Discard the frame tree.
EXPECT_FALSE(root->was_discarded());
EXPECT_FALSE(wc->GetController().NeedsReload());
DiscardFrameTree(frame_tree);
EXPECT_TRUE(root->was_discarded());
EXPECT_TRUE(wc->GetController().NeedsReload());
// Assert the service worker has been de-registered post discard.
ASSERT_TRUE(base::test::RunUntil(
[&]() { return rfh->service_worker_clients_for_testing().size() == 0; }));
}
// Runs pending navigation discard browsertests with RenderDocument enabled for
// all frames to ensure a speculative RFH is created during navigation.
class FrameTreeDiscardPendingNavigationTest

@ -3261,6 +3261,11 @@ class CONTENT_EXPORT RenderFrameHostImpl
bool AncestorsAllowSameSiteNoneCookiesOverride(
const url::Origin& frame_origin) const;
const std::map<std::string, base::WeakPtr<ServiceWorkerClient>>&
service_worker_clients_for_testing() const {
return service_worker_clients_;
}
protected:
friend class RenderFrameHostFactory;

@ -35,10 +35,15 @@ class ServiceWorkerNetworkProviderForFrame::NewDocumentObserver
render_frame()->GetWebFrame()->GetDocumentLoader();
DCHECK_EQ(owner_, web_loader->GetServiceWorkerNetworkProvider());
if (web_frame->GetSecurityOrigin().IsOpaque()) {
if (web_frame->GetSecurityOrigin().IsOpaque() ||
web_loader->IsForDiscard()) {
// At navigation commit we thought the document was eligible to use
// service workers so created the network provider, but it turns out it is
// not eligible because it is CSP sandboxed.
// In the case a frame navigation was committed and the document was
// eligible to use service workers, a network provider would have been
// created. However once the frame has been discarded and the
// corresponding empty document installed it is no longer eligible.
web_loader->SetServiceWorkerNetworkProvider(
ServiceWorkerNetworkProviderForFrame::CreateInvalidInstance());
// |this| and its owner are destroyed.

@ -147,6 +147,11 @@ class BLINK_EXPORT WebDocumentLoader {
// an initial empty document.
virtual bool HasLoadedNonInitialEmptyDocument() const = 0;
// Returns whether the navigation associated with this datasource is for a
// frame discard operation, performed with the intention to clear away
// associated resources.
virtual bool IsForDiscard() const = 0;
protected:
~WebDocumentLoader() = default;
};

@ -3889,6 +3889,10 @@ bool DocumentLoader::HasLoadedNonInitialEmptyDocument() const {
return GetFrameLoader().HasLoadedNonInitialEmptyDocument();
}
bool DocumentLoader::IsForDiscard() const {
return commit_reason_ == CommitReason::kDiscard;
}
// static
void DocumentLoader::DisableCodeCacheForTesting() {
GetDisableCodeCacheForTesting() = true;

@ -218,6 +218,7 @@ class CORE_EXPORT DocumentLoader : public GarbageCollected<DocumentLoader>,
return origin_calculation_debug_info_;
}
bool HasLoadedNonInitialEmptyDocument() const override;
bool IsForDiscard() const override;
MHTMLArchive* Archive() const { return archive_.Get(); }