← WebKit — 2026-W41

bf9a4e5956dda4dc463e71192868668c6aa947c0  REGRESSION(322127@main) [macOS iOS] TestWebKitAPI.SiteIsolation.NavigateFrameWithSiblingsBackForward is flaky timeout

severity low class LogicError confidence 0.33 signal score 7.67 Source/WebKit/UIProcess
Primitive: positional fallback pairing wrong from-child in per-frame back/forward traversal, double-dispatching to a live frame
Triage note: matchFromChildItems() does a two-pass match (all frameID matches first, then positional fallback) so an earlier sibling's positional fallback can no longer steal a from-child that a later sibling matches by frameID, which the comment says would 'dispatch a second traversal to the same live frame and clobber the intended navigation'. Genuine history-traversal bug fix; security impact (wrong-frame navigation) is speculative and no memory safety is involved.
Structural signals: smartptr_member_added (+6.47), null_guard (+1.2)

Commit message

REGRESSION(322127@main) [macOS iOS] TestWebKitAPI.SiteIsolation.NavigateFrameWithSiblingsBackForward is flaky timeout
https://bugs.webkit.org/show_bug.cgi?id=326001
rdar://188947135

Reviewed by Per Arne Vollan.

SiteIsolation.NavigateFrameWithSiblingsBackForward does:
Step 1: load example.com with two webkit.org iframes, frameA (webkit.org/a) and frameB (webkit.org/b).
Step 2: once frameB has loaded, navigate frameB to webkit.org/c.
Step 3: go back.
Step 4: go forward.

Normally, frameA commits before frameB navigates to /c, so the back-forward list is (noted as list1):
- item0: main(example) -> [A(/a), B(/b)]
- item1: main(example) -> [A(/a), B(/c)]

However, since load commits are async, frameA may commit after frameB has navigated to /c. An iframe's first load only
adds its child item to the entry that is current when the load commits, so frameA is added to item1 but not item0.
Before 322127@main, the back-forward list in this case is (noted as list2):
- item0: main(example) -> [B(/b)]
- item1: main(example) -> [B(/c), A(/a)]

322127@main changed WebBackForwardListFrameItem::setChild() to order children by frame tree position instead of
appending them, so in this case the list becomes (noted as list3):
- item0: main(example) -> [B(/b)]
- item1: main(example) -> [A(/a), B(/c)]

WebPageProxy::dispatchPerFrameTraversals() pairs each child of the target item with a child of the source item. For each
child item, it first tries to match by frameID, and then falls back to matching by position. So when going forward from
item0 to item1 (Step 4) with list3, the function matches A(/a) in item1 with B(/b) in item0 by position. B(/c) in item1
then fails to find a match (since B(/b) is taken), so no traversal is sent to frameB and the test times out.

To fix this issue, this patch makes WebPageProxy::dispatchPerFrameTraversals() match all child items by frameID first,
and only fall back to matching by position for the child items that fail the frameID match. For the test, B(/c) in item1
is now matched to B(/b) in item0 by frameID, and A(/a) finds no match since B(/b) in item0 is taken. The matching is
moved into a new helper, matchFromChildItems().

This patch adds a dedicated test that holds back frameA's load until frameB has navigated to /c, so the bug can be
reproduced deterministically without the fix.

Test: SiteIsolation.NavigateFrameWithSiblingsBackForwardWhenSiblingCommitsLate

* Source/WebKit/UIProcess/WebPageProxy.cpp:
(WebKit::matchFromChildItems):
(WebKit::WebPageProxy::dispatchPerFrameTraversals):
* TestExpectations/apitests:
* Tools/TestWebKitAPI/Tests/WebKit/WKWebView/SiteIsolation.mm:
(TestWebKitAPI::TEST(SiteIsolation, NavigateFrameWithSiblingsBackForwardWhenSiblingCommitsLate)):

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

Before / after

Loading diff…