← All issues

Site Isolation: Cross-Site iframe history.back() Support

e12ff02

Source/WebCore/loader/NavigationScheduler.cpp

-ScheduledHistoryNavigation::ScheduledHistoryNavigation(int historySteps)
- : ScheduledNavigation(0, lockHistory, lockBackForwardList, false, true)
- , m_historySteps(historySteps)
- , m_historyItem(page.backForward().itemAtIndex(historySteps))
-{ }
+ScheduledHistoryNavigation::ScheduledHistoryNavigation(int historySteps)
+ : ScheduledNavigation(0, lockHistory, lockBackForwardList, false, true)
+ , m_historySteps(historySteps)
+{ /* HistoryItem lookup deferred; lazy via targetHistoryItem() */ }
 
void ScheduledHistoryNavigation::fire(LocalFrame& frame)
{
+ if (frame.settings().useUIProcessForBackForwardItemLoading()) {
+ frame.loader().client().dispatchGoToBackForwardItemAtIndex(m_historySteps);
+ return;
+ }
auto* historyItem = targetHistoryItem();
...
}

Source/WebKit/UIProcess/WebPageProxy.cpp

+void WebPageProxy::goToBackForwardItemAtIndex(int32_t index, FrameIdentifier frameID)
+{
+ auto* item = m_backForwardList->itemAtIndex(index);
+ if (!item)
+ return;
+ updateFrameIdentifierForBackForwardItem(*item, frameID);
+ goToBackForwardItem(*item);
+}

Source/WebCore/loader/FrameLoader.cpp

+void FrameLoader::setPendingAsyncBackForwardNavigation()
+{
+ m_asyncBackForwardNavigationState = AsyncBackForwardNavigationState::Pending;
+}
+void FrameLoader::cancelPendingAsyncBackForwardNavigation()
+{
+ if (m_asyncBackForwardNavigationState == AsyncBackForwardNavigationState::Pending)
+ m_asyncBackForwardNavigationState = AsyncBackForwardNavigationState::Cancelled;
+}

WebKit's Site Isolation model places cross-site iframes in separate WebProcess instances, while the UIProcess holds the authoritative back-forward list (WebBackForwardList). Previously, NavigationScheduler in the WebProcess would eagerly resolve a HistoryItem via a sync IPC at schedule time, then call page->goToItem() directly — bypassing the UIProcess-driven path that correctly resolves per-frame state for multi-process navigations. This broke history.back() and history.go(n) for cross-site iframes under Site Isolation.

The fix makes HistoryItem lookup lazy: the WebProcess sends only a step count to the UIProcess via a new async GoToBackForwardItemAtIndex IPC message. The UIProcess resolves the correct HistoryItem and child frame state, then initiates navigation from there. A tri-state async navigation state machine (None → Pending → Cancelled) is added to FrameLoader to track traversal lifecycle. A subtle complication: spec-compliant same-document navigations (fragment changes, pushState) must not cancel in-flight traversals, so when they insert a new back-forward entry, the pending step count is adjusted.

Before (broken for cross-site frames):
  history.back() → sync IPC resolveHistoryItem(step) → page->goToItem()

After (new async path):
  history.back() → async IPC GoToBackForwardItemAtIndex(step)
                      └─► UIProcess: itemAtIndex(step) → goToBackForwardItem()

FrameLoader state:
  [None] ──► setPending() ──► [Pending] ──► fragment/new load ──► [Cancelled]

This is a significant architectural shift in cross-process back-forward navigation, eliminating sync IPCs from the scheduling path. The new async model correctly resolves per-frame state but introduces complexity at the intersection of navigation scheduling and the back-forward list — a historically sensitive area for both correctness and security.

🔒

Pattern-based audit directions for variant discovery

Subscribe to read more