Branch: refs/heads/main
  Home:   https://github.com/WebKit/WebKit
  Commit: e566e1134bd0182a0a7cb0ca4f782bb95dc66719
      
https://github.com/WebKit/WebKit/commit/e566e1134bd0182a0a7cb0ca4f782bb95dc66719
  Author: Basuke Suzuki <[email protected]>
  Date:   2026-07-15 (Wed, 15 Jul 2026)

  Changed paths:
    M Source/WebCore/loader/EmptyClients.cpp
    M Source/WebCore/loader/EmptyFrameLoaderClient.h
    M Source/WebCore/loader/LocalFrameLoaderClient.h
    M Source/WebCore/loader/NavigationScheduler.cpp
    M Source/WebCore/page/LocalFrame.cpp
    M Source/WebKit/Sources.txt
    M Source/WebKit/UIProcess/API/APINavigation.h
    A Source/WebKit/UIProcess/SessionHistoryTraversalQueue.cpp
    A Source/WebKit/UIProcess/SessionHistoryTraversalQueue.h
    M Source/WebKit/UIProcess/WebBackForwardList.cpp
    M Source/WebKit/UIProcess/WebBackForwardList.swift
    M Source/WebKit/UIProcess/WebPageProxy.cpp
    M Source/WebKit/UIProcess/WebPageProxy.h
    M Source/WebKit/UIProcess/WebPageProxy.messages.in
    M Source/WebKit/WebKit.xcodeproj/project.pbxproj
    M Source/WebKit/WebProcess/WebCoreSupport/WebLocalFrameLoaderClient.cpp
    M Source/WebKit/WebProcess/WebCoreSupport/WebLocalFrameLoaderClient.h
    M Source/WebKitLegacy/mac/WebCoreSupport/WebFrameLoaderClient.h
    M Source/WebKitLegacy/mac/WebCoreSupport/WebFrameLoaderClient.mm
    M Tools/TestWebKitAPI/Tests/WebKit/WKWebView/SiteIsolation.mm

  Log Message:
  -----------
  [Re-land] [NavigationScheduler] history.back/forward/go(n) coalescing across 
processes via UIProcess session history traversal queue
https://bugs.webkit.org/show_bug.cgi?id=317229
rdar://179857636

Reviewed by Charlie Wolfe.

Re-land of 316426@main, reverted as 316667@main
(https://bugs.webkit.org/show_bug.cgi?id=318806) for making
SiteIsolation.CrossProcessHistoryTraversalCoalesce flaky. The coalescing
itself was correct; the flakiness came from the cross-process
back/forward-cache lifecycle a split traversal exercises, fixed by the four
changes described under "Stabilization" below (test now 60/60 in debug, no
crash, no hang).

Follow-up to https://bugs.webkit.org/show_bug.cgi?id=317077.

Per HTML's "traverse the history by a delta" algorithm, history.back/
forward/go(n) appends a task to the top-level traversable's session
history traversal queue. The previous fix (bug 317077) implemented
per-WebProcess coalescing by forwarding to the top frame's
NavigationScheduler when reachable in this WebProcess. Under Site
Isolation, when the top frame is a RemoteFrame (a cross-site iframe
initiates the traversal), the queue lives above any single WebProcess
and the originating scheduler can't see traversals from other
processes targeting the same traversable.

Symptom: two cross-site iframes in different WebProcesses each call
history.back() in the same task; both navigations execute and the
user sees an extra intermediate document load that the spec says
should not occur.

Introduce SessionHistoryTraversalQueue (defined in its own
UIProcess/SessionHistoryTraversalQueue.{h,cpp} rather than nested in
WebPageProxy), owned by WebPageProxy (top-level traversable proxy).
The queue accumulates pending delta across all WebProcesses targeting
this page and flushes on a 0-delay timer, calling
goToBackForwardItemAtIndex with the net delta (no-op when delta == 0,
matching "going nowhere"). NavigationScheduler's existing
per-WebProcess aggregation in scheduleHistoryNavigation now gains a
second branch: when the top frame is in another process (top frame
downcasts to RemoteFrame), forward via a new LocalFrameLoaderClient
hook (dispatchEnqueueHistoryTraversalDelta) that posts
WebPageProxy::EnqueueHistoryTraversalDelta to the UIProcess.

Trust model note: EnqueueHistoryTraversalDelta accepts an int32_t
delta from any WebProcess and forwards it to goToBackForwardItemAtIndex
on flush. This is the same trust boundary as the existing
GoToBackForwardItemAtIndex IPC; Site Isolation does not introduce a new
attack vector here. The eventual WebBackForwardList lookup clamps to
the available range, so an out-of-range delta degrades to a no-op
rather than memory unsafety.

Stabilization (new since 316426@main): the flakiness was in the
cross-process back/forward-cache lifecycle a coalesced (and, more
importantly, a SPLIT) traversal exercises. Two back() calls arriving in
separate tasks cannot coalesce into one go(-2); the queue serializes them
into two legs (c->b, then b->a), and that split path had four independent
defects, each fixed and covered here:

1. Intermediate-entry restore hang / lost split traversal. An iframe
   (cross-site subframe) WebProcess hosts one WebPage with a single
   m_isSuspended flag, so it could cache only the first BFCache entry;
   a later SuspendWithFrameItem for an intermediate entry early-returned
   completionHandler(true) (a false success), so the UIProcess recorded
   iframe children it never had and the back navigation hung. And the
   queue released the next leg while the previous one was still settling,
   so the second back could resolve against a stale current index and be
   lost. suspendWithFrameItem now returns whether the frame item is
   actually cached (isInBackForwardCache), and the queue holds further
   deltas until the in-flight traversal settles.

2. LocalFrame::frameWasDisconnectedFromOwner asserted when tearing down
   a back/forward-cached subframe: it called willBeRemovedFromFrame()
   unconditionally, unlike its siblings (setView/setDocument/
   FrameLoader::clear) which skip it while the document is in the
   back/forward cache. Guarded to match.

3. A stale/duplicate BackForwardGoToItem from an earlier split leg could
   arrive after the current index had already advanced to a later leg's
   destination and clobber it backward (WebBackForwardList::goToItem
   moves the current index unconditionally), so the main frame committed
   the destination while the back/forward list still reported the
   intermediate entry -- the second back appeared lost. The queue now
   records the in-flight traversal's direction and
   backForwardGoToItemShared ignores an index move opposite to it (a
   legitimate leg always moves the index in the traversal direction).
   The same guard is mirrored in the ENABLE(BACK_FORWARD_LIST_SWIFT) path.

4. URL="" hang: during the split traversal a subframe could be detached
   while an UpdateScrollingMode IPC for it was in flight; by the time it
   reached the UIProcess the frame's parent was gone, so the MESSAGE_CHECK
   in updateScrollingMode terminated the process, resetStateAfterProcessExited
   reset PageLoadState to an empty active URL, and the navigation hung.
   updateScrollingMode now ignores an update for a frame whose parent is
   already gone (a benign straggler for a detached frame); the message
   check is kept for the case where the parent still exists.

This is the observable-coalescing approximation; the spec-faithful
"sequential apply + synchronous queue jumping" is left for a future
epic. The class name (SessionHistoryTraversalQueue) leaves room for
that evolution.

Test plan: SiteIsolation.CrossProcessHistoryTraversalCoalesce builds a
3-entry main-frame history (a, b, c) where each page hosts two
cross-site iframes (webkit.org and apple.com) in distinct WebProcesses,
then issues two history.back() from the iframes. New coverage:
CrossProcessHistoryTraversalGoMinus2 (the single-traversal go(-2) form)
and CrossProcessHistoryTraversalSameFrameBackTwice (two back() from one
iframe -- the most reliable split reproducer). All are 60/60 in debug;
the MultiProcessBFCache*, GoBackToPageWithIframe and
CrossProcessSameDocumentHistoryTraversalDoesNotStall regression sets do
not regress.

* Source/WebCore/loader/EmptyClients.cpp:
(WebCore::EmptyFrameLoaderClient::dispatchEnqueueHistoryTraversalDelta):
* Source/WebCore/loader/EmptyFrameLoaderClient.h:
* Source/WebCore/loader/LocalFrameLoaderClient.h:
* Source/WebCore/loader/NavigationScheduler.cpp:
(WebCore::NavigationScheduler::scheduleHistoryNavigation): Forward 
cross-process traversal deltas to the UIProcess queue.
* Source/WebCore/page/LocalFrame.cpp:
(WebCore::LocalFrame::frameWasDisconnectedFromOwner const): Skip 
willBeRemovedFromFrame() while the document is in the back/forward cache.
* Source/WebKit/Sources.txt:
* Source/WebKit/UIProcess/API/APINavigation.h:
* Source/WebKit/UIProcess/SessionHistoryTraversalQueue.cpp: Added.
(WebKit::SessionHistoryTraversalQueue::enqueueDelta):
(WebKit::SessionHistoryTraversalQueue::cancel):
(WebKit::SessionHistoryTraversalQueue::traversalDidSettle): Release held deltas 
once the in-flight traversal settles.
(WebKit::SessionHistoryTraversalQueue::flush): Record the in-flight traversal 
direction.
* Source/WebKit/UIProcess/SessionHistoryTraversalQueue.h: Added.
(WebKit::SessionHistoryTraversalQueue::inFlightDirection const):
* Source/WebKit/UIProcess/WebBackForwardList.cpp:
(WebKit::WebBackForwardList::backForwardGoToItemShared): Ignore a stale index 
move opposite to the in-flight traversal direction.
* Source/WebKit/UIProcess/WebBackForwardList.swift:
(backForwardGoToItemShared): Mirror the stale-index-move guard for the 
ENABLE(BACK_FORWARD_LIST_SWIFT) path.
* Source/WebKit/UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::goToBackForwardItemAtIndex):
(WebKit::WebPageProxy::enqueueHistoryTraversalDelta):
(WebKit::WebPageProxy::inFlightTraversalDirection const):
(WebKit::WebPageProxy::updateScrollingMode): Ignore an UpdateScrollingMode 
straggler for a detached frame; keep the message check when the parent still 
exists.
(WebKit::WebPageProxy::didCommitLoadForFrame): Settle the traversal queue on 
the main-frame commit.
(WebKit::WebPageProxy::didFailProvisionalLoadForFrameShared): Settle the 
traversal queue on a failed main-frame provisional load.
(WebKit::WebPageProxy::didSameDocumentNavigationForFrame): Settle the traversal 
queue on a same-document main-frame traversal.
* Source/WebKit/UIProcess/WebPageProxy.h:
* Source/WebKit/UIProcess/WebPageProxy.messages.in:
* Source/WebKit/WebKit.xcodeproj/project.pbxproj:
* Source/WebKit/WebProcess/WebCoreSupport/WebLocalFrameLoaderClient.cpp:
(WebKit::WebLocalFrameLoaderClient::dispatchEnqueueHistoryTraversalDelta):
* Source/WebKit/WebProcess/WebCoreSupport/WebLocalFrameLoaderClient.h:
* Source/WebKitLegacy/mac/WebCoreSupport/WebFrameLoaderClient.h:
* Source/WebKitLegacy/mac/WebCoreSupport/WebFrameLoaderClient.mm:
(WebFrameLoaderClient::dispatchEnqueueHistoryTraversalDelta):
* Tools/TestWebKitAPI/Tests/WebKit/WKWebView/SiteIsolation.mm:
(TEST(SiteIsolation, CrossProcessHistoryTraversalCoalesce)):
(TEST(SiteIsolation, CrossProcessHistoryTraversalGoMinus2)):
(TEST(SiteIsolation, CrossProcessHistoryTraversalSameFrameBackTwice)):

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



To unsubscribe from these emails, change your notification settings at 
https://github.com/WebKit/WebKit/settings/notifications

Reply via email to