← WebKit — 2026-W41
dd106cbc0209a9b343b8205c4982c02c1403db10 [Site Isolation] A back/forward cached local root frame stays registered in Page::rootFrames()
severity low
class LogicError
confidence 0.25
signal score 6.47
Source/WebCore/history
Primitive: cached local root frame left in Page::rootFrames() and receiving rendering updates
Triage note: Moves root-frame removal/re-add for a cached (non-main) local root into the CachedFrame constructor/open() and removes the BackForwardCache m_detachedRootFrames WeakRef tracking, adding an assert that a cached local root must not stay in Page::rootFrames(). BFCache/site-isolation lifecycle refactor that prevents rendering updates on a cached frame; largely refactor and no concrete exploit shown.
Triage note: Moves root-frame removal/re-add for a cached (non-main) local root into the CachedFrame constructor/open() and removes the BackForwardCache m_detachedRootFrames WeakRef tracking, adding an assert that a cached local root must not stay in Page::rootFrames(). BFCache/site-isolation lifecycle refactor that prevents rendering updates on a cached frame; largely refactor and no concrete exploit shown.
Structural signals: smartptr_member_added (+6.47)
Commit message
[Site Isolation] A back/forward cached local root frame stays registered in Page::rootFrames()
https://bugs.webkit.org/show_bug.cgi?id=326311
rdar://189157017
Reviewed by Per Arne Vollan.
A local root frame in the back/forward cache can still participate in rendering updates (layout, compositing flush),
because it is not removed from Page::rootFrames(). The problematic flow (as in the new test) is:
1. A page loads a.com/a1, which has a b.com iframe, which in turn has an a.com/grandchild iframe. a.com's process has
three frames: the main frame (local), the b.com iframe (remote), and the grandchild (local). Both the main frame and
the grandchild are local root frames.
2. a.com/a1 navigates same-site to a.com/a2. The main frame process is not swapped, and it puts a1's frames into the
back/forward cache as part of committing the load. The grandchild is unlinked from the frame tree but stays in
Page::rootFrames().
3. a.com/a2 is loaded in a.com's process, and each rendering update of the live page still lays out and flushes the
compositing state of the cached grandchild.
The cause is that root frames are only removed from Page::rootFrames() in WebPage::suspendWithFrameItem, which handles a
message the UI process sends to tell a web process to suspend its frames. In a same-site navigation, this message is
only sent to iframe processes, because the main frame process performs the caching itself during load commit: it is the
main frame process that tells the UI process caching happened (via WebPageProxy::DidCacheBackForwardItem), not the UI
process that tells the main frame process to cache.
To fix this, this patch moves root frame removal and re-addition into CachedFrame, which every caching path goes
through: CachedFrame::CachedFrame removes the frame, and CachedFrame::open re-adds it. The re-addition has to happen
before FrameLoader::open(), because that rebuilds the render tree, and RenderLayerCompositor attaches the root
compositing layer there only once. If the drawing area has no root layer entry for the frame at that point, the layer
is dropped and the frame is blank after restore. The main frame is excluded, as in CachedFrame::destroy(), because it
is reused for the next document. The removal and re-addition in WebPage::suspendWithFrameItem and
WebPage::restoreWithFrameItem, along with the detached root frame storage on CachedPage, are deleted, since keeping both
would remove the same frame twice.
This patch also adds an assertion in Page::finalizeRenderingUpdateForRootFrame that a non-main-frame root frame being
rendered is not in the back/forward cache. Without the fix, the new test hits it during an ordinary rendering update.
Test: SiteIsolation.MultiProcessBFCacheSameSiteCachedLocalRootFrameNoRenderingUpdate
* Source/WebCore/history/BackForwardCache.cpp:
(WebCore::BackForwardCache::setDetachedRootFramesForFrameItem): Deleted.
* Source/WebCore/history/BackForwardCache.h:
* Source/WebCore/history/CachedFrame.cpp:
(WebCore::CachedFrame::CachedFrame):
(WebCore::CachedFrame::open):
* Source/WebCore/history/CachedPage.h:
* Source/WebCore/page/Frame.cpp:
(WebCore::Frame::detachFromPage):
* Source/WebCore/page/Page.cpp:
(WebCore::Page::finalizeRenderingUpdateForRootFrame):
* Source/WebKit/WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::suspendWithFrameItem):
(WebKit::WebPage::restoreWithFrameItem):
* Tools/TestWebKitAPI/Tests/WebKit/WKWebView/SiteIsolation.mm:
(TestWebKitAPI::(SiteIsolation, MultiProcessBFCacheSameSiteCachedLocalRootFrameNoRenderingUpdate)):
Canonical link: https://commits.webkit.org/322853@main
Before / after
Loading diff…
On This Page