← WebKit — 2026-W38

63846dd9cddbfeecbdba0d1e12608c0a944e36b5  Back/forward cache restore reports the outgoing document's identity in didCommitLoadForFrame

severity medium class CrossOrigin confidence 0.62 signal score 6.2 Source/WebCore/loader patch-now
Primitive: DidCommitLoadForFrame reporting the outgoing document's origin/CSP on a back/forward-cache restore
Triage note: Introduces BackForwardCacheCommitData so a bfcache restore commits with the restored document's securityOrigin/topOrigin, documentID, DocumentSecurityPolicy, CSP upgrade origins and isPluginDocument; previously dispatchDidCommitLoad read m_localFrame->document() which still held the outgoing Document at commit time, so the UIProcess associated the wrong origin/CSP with the committed frame (origin confusion).
Structural signals: smartptr_member_added (+3.0), null_guard (+1.2), area:Source/WebCore/loader (+2.0)

Commit message

Back/forward cache restore reports the outgoing document's identity in didCommitLoadForFrame
https://bugs.webkit.org/show_bug.cgi?id=324164
rdar://187390103

Reviewed by Per Arne Vollan.

`WebLocalFrameLoaderClient::dispatchDidCommitLoad()` reports the frame's info (`WebFrame::info()`) to the UI process,
along with a few values it reads off the frame's current document directly: whether it is a plugin document,
its COOP/COEP pair, and its upgrade-insecure-requests origin set.

However, when the load is completed by restoring from the back/forward cache, the cached document is not set on the
frame yet by the time `dispatchDidCommitLoad()` is invoked -- in `FrameLoader::commitProvisionalLoad()`,
`dispatchDidCommitLoad()` is invoked before `cachedPage->restore()` (-> `FrameLoader::open(CachedFrameBase&)` ->
`frame->setDocument()`). Therefore the commit reports the correct URL (since the URL comes from the document loader, not
the frame) together with the outgoing document's identifier, security origin and policies.

This only happens when the restore does not swap processes. After a cross-site navigation the cached page's process
never hosted the intermediate document, so the frame already holds the document being restored and the commit is
correct. Subframes get no `didCommitLoadForFrame` at all on a restore, so only the main frame is affected.

There is an established pattern for fixing this -- `FrameLoader::commitProvisionalLoad()` passes the states of the
cached page via parameters to `FrameLoader::dispatchDidCommitLoad()`, and
`WebLocalFrameLoaderClient::dispatchDidCommitLoad()` passes that info to the UI process instead of retrieving it from
the frame; `hasInsecureContent`, `usedLegacyTLS` and `wasPrivateRelayed` were each added that way. This patch adopts the
same approach: introducing `BackForwardCacheCommitData`, collapsing those three parameters into it, carrying the
restored document's identifier, origin and policies in it as well, and using it as the parameter for
`dispatchDidCommitLoad()`. Then `WebLocalFrameLoaderClient::dispatchDidCommitLoad()` will know to use the cached data if
it exists. The struct needs its own header rather than `FrameLoaderTypes.h`, which `CrossOriginOpenerPolicy.h` includes
and which therefore cannot include `DocumentSecurityPolicy.h`.

`frameMetrics` is the one thing left as it is: it comes from the `FrameView`, which is also swapped inside `restore()`,
but `CachedFrame` holds nothing to read instead.

The stale values are exposed through `WKFrameInfo._documentIdentifier`, `WKFrameInfo.securityOrigin`,
`PageLoadState::origin()` and WebDriver BiDi, probably without user visible consequence today. They matter with Site
Isolation, where the committed document identifier will be how the UI process decides whether a message from a web
process applies to the document the page is currently showing, so it has to tracked properly.

Tests: WKNavigation.BackForwardCacheRestoreReportsRestoredDocument
       WKNavigation.BackForwardCacheRestoreAcrossOriginsReportsRestoredDocument
       WKNavigation.BackForwardCacheRestoreAfterProcessSwapReportsRestoredDocument

* Source/WebCore/WebCore.xcodeproj/project.pbxproj:
* Source/WebCore/loader/BackForwardCacheCommitData.h: Added.
* Source/WebCore/loader/EmptyClients.cpp:
(WebCore::EmptyFrameLoaderClient::dispatchDidCommitLoad):
* Source/WebCore/loader/EmptyFrameLoaderClient.h:
* Source/WebCore/loader/FrameLoader.cpp:
(WebCore::FrameLoader::receivedFirstData):
(WebCore::FrameLoader::commitProvisionalLoad):
(WebCore::FrameLoader::dispatchDidCommitLoad):
* Source/WebCore/loader/FrameLoader.h:
* Source/WebCore/loader/LocalFrameLoaderClient.h:
* Source/WebKit/WebProcess/WebCoreSupport/WebLocalFrameLoaderClient.cpp:
(WebKit::WebLocalFrameLoaderClient::dispatchDidCommitLoad):
* Source/WebKit/WebProcess/WebCoreSupport/WebLocalFrameLoaderClient.h:
* Source/WebKitLegacy/mac/WebCoreSupport/WebFrameLoaderClient.h:
* Source/WebKitLegacy/mac/WebCoreSupport/WebFrameLoaderClient.mm:
(WebFrameLoaderClient::dispatchDidCommitLoad):
* Tools/TestWebKitAPI/Tests/WebKit/WKWebView/Navigation.mm:
(testBackForwardCacheRestoreReportsRestoredDocument):
(TEST(WKNavigation, BackForwardCacheRestoreReportsRestoredDocument)):
(TEST(WKNavigation, BackForwardCacheRestoreAcrossOriginsReportsRestoredDocument)):
(TEST(WKNavigation, BackForwardCacheRestoreAfterProcessSwapReportsRestoredDocument)):

Canonical link: https://commits.webkit.org/321205@main

Before / after

Loading diff…