Swipe snapshot is removed too early when swiping away from a page that is still loading
https://bugs.webkit.org/show_bug.cgi?id=213763
<rdar://problem/64576811>

Reviewed by Darin Adler.

Source/WebKit:

Test: http/tests/swipe/swipe-back-with-outstanding-load-cancellation.html

When swiping back from a page that is still loading, when the swipe commits,
and we start navigating back to the previous page, the unfinished forward navigation
is cancelled.

This cancellation resulted in a "didFailLoadForMainFrame" making it to ViewGestureController,
which took it as an indication that the navigation *it* was interested in (the back navigation)
had failed, and immediately removed the snapshot.

Instead of listening to any random navigation's load notifications, keep track of the navigation
started by goToBackForwardItem, and only listen to notifications from it, ignoring the others.

This requires a bunch of plumbing to get the navigation from WebPageProxy to ViewGestureController,
but is otherwise fairly trivial.

* UIProcess/API/gtk/PageClientImpl.cpp:
(WebKit::PageClientImpl::didFinishNavigation):
(WebKit::PageClientImpl::didFailNavigation):
(WebKit::PageClientImpl::didFinishLoadForMainFrame): Deleted.
(WebKit::PageClientImpl::didFailLoadForMainFrame): Deleted.
* UIProcess/API/gtk/PageClientImpl.h:
* UIProcess/API/gtk/WebKitWebViewBase.cpp:
(webkitWebViewBaseDidFinishLoadForMainFrame):
(webkitWebViewBaseDidFailLoadForMainFrame):
* UIProcess/API/gtk/WebKitWebViewBasePrivate.h:
* UIProcess/API/ios/WKWebViewIOS.h:
* UIProcess/API/ios/WKWebViewIOS.mm:
(-[WKWebView _didFinishNavigation:]):
(-[WKWebView _didFailNavigation:]):
(-[WKWebView _didFinishLoadForMainFrame]): Deleted.
(-[WKWebView _didFailLoadForMainFrame]): Deleted.
* UIProcess/API/wpe/PageClientImpl.cpp:
(WebKit::PageClientImpl::didFinishNavigation):
(WebKit::PageClientImpl::didFailNavigation):
(WebKit::PageClientImpl::didFinishLoadForMainFrame): Deleted.
(WebKit::PageClientImpl::didFailLoadForMainFrame): Deleted.
* UIProcess/API/wpe/PageClientImpl.h:
* UIProcess/PageClient.h:
* UIProcess/ViewGestureController.cpp:
(WebKit::ViewGestureController::didReachNavigationTerminalState):
(WebKit::ViewGestureController::willEndSwipeGesture):
(WebKit::ViewGestureController::didReachMainFrameLoadTerminalState): Deleted.
* UIProcess/ViewGestureController.h:
(WebKit::ViewGestureController::didFinishNavigation):
(WebKit::ViewGestureController::didFailNavigation):
(WebKit::ViewGestureController::didFinishLoadForMainFrame): Deleted.
(WebKit::ViewGestureController::didFailLoadForMainFrame): Deleted.
* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::didFinishLoadForFrame):
(WebKit::WebPageProxy::didFailLoadForFrame):
(WebKit::WebPageProxy::didSameDocumentNavigationForFrame):
* UIProcess/ios/PageClientImplIOS.h:
* UIProcess/ios/PageClientImplIOS.mm:
(WebKit::PageClientImpl::didFinishNavigation):
(WebKit::PageClientImpl::didFailNavigation):
(WebKit::PageClientImpl::didFinishLoadForMainFrame): Deleted.
(WebKit::PageClientImpl::didFailLoadForMainFrame): Deleted.
* UIProcess/ios/ViewGestureControllerIOS.mm:
(WebKit::ViewGestureController::willEndSwipeGesture):
(WebKit::ViewGestureController::resetState):
* UIProcess/mac/PageClientImplMac.h:
* UIProcess/mac/PageClientImplMac.mm:
(WebKit::PageClientImpl::didFinishNavigation):
(WebKit::PageClientImpl::didFailNavigation):
(WebKit::PageClientImpl::didFinishLoadForMainFrame): Deleted.
(WebKit::PageClientImpl::didFailLoadForMainFrame): Deleted.
* UIProcess/mac/ViewGestureControllerMac.mm:
(WebKit::ViewGestureController::resetState):
* UIProcess/playstation/PageClientImpl.cpp:
(WebKit::PageClientImpl::didFinishNavigation):
(WebKit::PageClientImpl::didFailNavigation):
(WebKit::PageClientImpl::didFinishLoadForMainFrame): Deleted.
(WebKit::PageClientImpl::didFailLoadForMainFrame): Deleted.
* UIProcess/playstation/PageClientImpl.h:
* UIProcess/win/PageClientImpl.cpp:
(WebKit::PageClientImpl::didFinishNavigation):
(WebKit::PageClientImpl::didFinishLoadForMainFrame): Deleted.
* UIProcess/win/PageClientImpl.h:

LayoutTests:

* http/tests/swipe/resources/swipe-test.js: Added.
* http/tests/swipe/swipe-back-with-outstanding-load-cancellation-expected.txt: Added.
* http/tests/swipe/swipe-back-with-outstanding-load-cancellation.html: Added.
Add a test that ensures that we don't remove the swipe snapshot before the "back" page is loaded.
Before this fix, this test fails the assertion that we're on the first page in didRemoveSwipeSnapshotCallback.


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@263825 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index 241ab06..ce8441f 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,17 @@
+2020-07-01  Tim Horton  <timothy_horton@apple.com>
+
+        Swipe snapshot is removed too early when swiping away from a page that is still loading
+        https://bugs.webkit.org/show_bug.cgi?id=213763
+        <rdar://problem/64576811>
+
+        Reviewed by Darin Adler.
+
+        * http/tests/swipe/resources/swipe-test.js: Added.
+        * http/tests/swipe/swipe-back-with-outstanding-load-cancellation-expected.txt: Added.
+        * http/tests/swipe/swipe-back-with-outstanding-load-cancellation.html: Added.
+        Add a test that ensures that we don't remove the swipe snapshot before the "back" page is loaded.
+        Before this fix, this test fails the assertion that we're on the first page in didRemoveSwipeSnapshotCallback.
+
 2020-07-01  Chris Fleizach  <cfleizach@apple.com>
 
         AX: Implement relevant simulated key presses for custom ARIA widgets for increment/decrement
diff --git a/LayoutTests/TestExpectations b/LayoutTests/TestExpectations
index 3d08430..544697a 100644
--- a/LayoutTests/TestExpectations
+++ b/LayoutTests/TestExpectations
@@ -39,6 +39,7 @@
 fast/visual-viewport/watchos [ Skip ]
 fast/visual-viewport/tiled-drawing [ Skip ]
 swipe [ Skip ]
+http/tests/swipe [ Skip ]
 fast/zooming/ios [ Skip ]
 fast/forms/ios [ Skip ]
 fast/viewport/ios [ Skip ]
diff --git a/LayoutTests/http/tests/swipe/resources/swipe-test.js b/LayoutTests/http/tests/swipe/resources/swipe-test.js
new file mode 100644
index 0000000..a36e820
--- /dev/null
+++ b/LayoutTests/http/tests/swipe/resources/swipe-test.js
@@ -0,0 +1,84 @@
+function shouldBe(expected, actual, message)
+{
+    if (expected != actual)
+        log("Failure. " + message + " (expected: " + expected + ", actual: " + actual + ")");
+}
+
+function log(s)
+{
+    window.localStorage["swipeLogging"] += s + "<br/>";
+}
+
+function dumpLog()
+{
+    window.document.body.innerHTML = window.localStorage["swipeLogging"];
+}
+
+function testComplete()
+{
+    dumpLog();
+    window.testRunner.notifyDone();
+}
+
+function initializeSwipeTest()
+{
+    window.localStorage["swipeLogging"] = "";
+    testRunner.setNavigationGesturesEnabled(true);
+    testRunner.clearBackForwardList();
+}
+
+function hasInitializedSwipeTest()
+{
+    return ("swipeLogging" in window.localStorage);
+}
+
+function startMeasuringDuration(key)
+{
+    window.localStorage[key + "swipeStartTime"] = Date.now();
+}
+
+function measuredDurationShouldBeLessThan(key, timeInMS, message)
+{
+    let duration = Date.now() - window.localStorage[key + "swipeStartTime"];
+    if (duration >= timeInMS)
+        log("Failure. " + message + " (expected: " + timeInMS + ", actual: " + duration + ")");
+}
+
+function startSwipeGesture(callback)
+{
+    log("startSwipeGesture");
+    testRunner.runUIScript(`
+    (function() {
+        uiController.beginBackSwipe(function() {
+            uiController.uiScriptComplete();
+        });
+    })();`, callback || function () {});
+}
+
+function completeSwipeGesture(callback)
+{
+    log("completeSwipeGesture");
+    testRunner.runUIScript(`
+    (function() {
+        uiController.completeBackSwipe(function() {
+            uiController.uiScriptComplete();
+        });
+    })();`, callback || function () {});
+}
+
+function playEventStream(stream, callback)
+{
+    log("playEventStream");
+    if (testRunner.isIOSFamily) {
+        // FIXME: This test should probably not log playEventStream
+        // on iOS, where it doesn't actually do it.
+        setTimeout(callback, 0);
+        return;
+    }
+    testRunner.runUIScript(`
+    (function() {
+        uiController.playBackEventStream(\`${stream}\`, function() {
+            uiController.uiScriptComplete();
+        });
+    })();`, callback || function () {});
+}
diff --git a/LayoutTests/http/tests/swipe/swipe-back-with-outstanding-load-cancellation-expected.txt b/LayoutTests/http/tests/swipe/swipe-back-with-outstanding-load-cancellation-expected.txt
new file mode 100644
index 0000000..89decfd
--- /dev/null
+++ b/LayoutTests/http/tests/swipe/swipe-back-with-outstanding-load-cancellation-expected.txt
@@ -0,0 +1,7 @@
+startSwipeGesture
+didBeginSwipe
+completeSwipeGesture
+willEndSwipe
+didEndSwipe
+didRemoveSwipeSnapshot
+
diff --git a/LayoutTests/http/tests/swipe/swipe-back-with-outstanding-load-cancellation.html b/LayoutTests/http/tests/swipe/swipe-back-with-outstanding-load-cancellation.html
new file mode 100644
index 0000000..60fd36a
--- /dev/null
+++ b/LayoutTests/http/tests/swipe/swipe-back-with-outstanding-load-cancellation.html
@@ -0,0 +1,83 @@
+<head>
+<style>
+html {
+    font-size: 32pt;
+}
+</style>
+<script src="resources/swipe-test.js"></script>
+<script>
+function didBeginSwipeCallback()
+{
+    log("didBeginSwipe");
+
+    completeSwipeGesture();
+}
+
+function willEndSwipeCallback()
+{
+    log("willEndSwipe");
+
+    shouldBe(false, isFirstPage(), "The swipe should not yet have navigated away from the second page.");
+}
+
+function didEndSwipeCallback()
+{
+    log("didEndSwipe");
+
+    startMeasuringDuration("snapshotRemoval");
+}
+
+var hasRemovedSnapshot = false;
+
+function didRemoveSwipeSnapshotCallback()
+{
+    log("didRemoveSwipeSnapshot");
+
+    hasRemovedSnapshot = true;
+    
+    shouldBe(true, isFirstPage(), "The swipe should have navigated back to the first page.");
+
+    testComplete();
+}
+
+function isFirstPage()
+{
+    return window.location.href.indexOf("second") == -1;
+}
+
+window.onload = function () {
+    if (!window.eventSender || !window.testRunner) {
+        document.body.innerHTML = "This test must be run in WebKitTestRunner.";
+        return;
+    }
+
+    document.body.innerHTML = isFirstPage() ? "first" : "<iframe src='/resources/slow-image.php'>";
+
+    if (isFirstPage()) {
+        if (hasInitializedSwipeTest()) {
+            shouldBe(false, hasRemovedSnapshot, "The snapshot should not have been synchronously removed because of the cancelled forward load.");
+            return;
+        }
+
+        initializeSwipeTest();
+
+        testRunner.installDidBeginSwipeCallback(didBeginSwipeCallback);
+        testRunner.installWillEndSwipeCallback(willEndSwipeCallback);
+        testRunner.installDidEndSwipeCallback(didEndSwipeCallback);
+        testRunner.installDidRemoveSwipeSnapshotCallback(didRemoveSwipeSnapshotCallback);
+
+        testRunner.dumpAsText();
+        testRunner.waitUntilDone();
+
+        setTimeout(function () { 
+            window.location.href = window.location.href + "?second";
+        }, 0);
+        return;
+    }
+
+    startSwipeGesture();
+};
+</script>
+</head>
+<body>
+</body>
diff --git a/LayoutTests/platform/ios/TestExpectations b/LayoutTests/platform/ios/TestExpectations
index be81b4c..30b6997 100644
--- a/LayoutTests/platform/ios/TestExpectations
+++ b/LayoutTests/platform/ios/TestExpectations
@@ -15,6 +15,7 @@
 media/ios [ Pass ]
 quicklook [ Pass ]
 swipe [ Pass ]
+http/tests/swipe [ Pass ]
 
 fast/css/appearance-apple-pay-button.html [ Pass ]
 fast/css/appearance-apple-pay-button-border-radius.html [ Pass ]
diff --git a/LayoutTests/platform/mac-wk2/TestExpectations b/LayoutTests/platform/mac-wk2/TestExpectations
index 871b62f..f5503f8 100644
--- a/LayoutTests/platform/mac-wk2/TestExpectations
+++ b/LayoutTests/platform/mac-wk2/TestExpectations
@@ -20,6 +20,7 @@
 scrollingcoordinator [ Pass ]
 scrollingcoordinator/ios [ Skip ]
 swipe [ Pass ]
+http/tests/swipe [ Pass ]
 tiled-drawing [ Pass ]
 
 fast/events/autoscroll-when-zoomed.html [ Pass ]
diff --git a/Source/WebKit/ChangeLog b/Source/WebKit/ChangeLog
index 59ca052..e04ab44 100644
--- a/Source/WebKit/ChangeLog
+++ b/Source/WebKit/ChangeLog
@@ -1,3 +1,91 @@
+2020-07-01  Tim Horton  <timothy_horton@apple.com>
+
+        Swipe snapshot is removed too early when swiping away from a page that is still loading
+        https://bugs.webkit.org/show_bug.cgi?id=213763
+        <rdar://problem/64576811>
+
+        Reviewed by Darin Adler.
+
+        Test: http/tests/swipe/swipe-back-with-outstanding-load-cancellation.html
+
+        When swiping back from a page that is still loading, when the swipe commits,
+        and we start navigating back to the previous page, the unfinished forward navigation
+        is cancelled.
+
+        This cancellation resulted in a "didFailLoadForMainFrame" making it to ViewGestureController,
+        which took it as an indication that the navigation *it* was interested in (the back navigation)
+        had failed, and immediately removed the snapshot.
+
+        Instead of listening to any random navigation's load notifications, keep track of the navigation
+        started by goToBackForwardItem, and only listen to notifications from it, ignoring the others.
+
+        This requires a bunch of plumbing to get the navigation from WebPageProxy to ViewGestureController,
+        but is otherwise fairly trivial.
+
+        * UIProcess/API/gtk/PageClientImpl.cpp:
+        (WebKit::PageClientImpl::didFinishNavigation):
+        (WebKit::PageClientImpl::didFailNavigation):
+        (WebKit::PageClientImpl::didFinishLoadForMainFrame): Deleted.
+        (WebKit::PageClientImpl::didFailLoadForMainFrame): Deleted.
+        * UIProcess/API/gtk/PageClientImpl.h:
+        * UIProcess/API/gtk/WebKitWebViewBase.cpp:
+        (webkitWebViewBaseDidFinishLoadForMainFrame):
+        (webkitWebViewBaseDidFailLoadForMainFrame):
+        * UIProcess/API/gtk/WebKitWebViewBasePrivate.h:
+        * UIProcess/API/ios/WKWebViewIOS.h:
+        * UIProcess/API/ios/WKWebViewIOS.mm:
+        (-[WKWebView _didFinishNavigation:]):
+        (-[WKWebView _didFailNavigation:]):
+        (-[WKWebView _didFinishLoadForMainFrame]): Deleted.
+        (-[WKWebView _didFailLoadForMainFrame]): Deleted.
+        * UIProcess/API/wpe/PageClientImpl.cpp:
+        (WebKit::PageClientImpl::didFinishNavigation):
+        (WebKit::PageClientImpl::didFailNavigation):
+        (WebKit::PageClientImpl::didFinishLoadForMainFrame): Deleted.
+        (WebKit::PageClientImpl::didFailLoadForMainFrame): Deleted.
+        * UIProcess/API/wpe/PageClientImpl.h:
+        * UIProcess/PageClient.h:
+        * UIProcess/ViewGestureController.cpp:
+        (WebKit::ViewGestureController::didReachNavigationTerminalState):
+        (WebKit::ViewGestureController::willEndSwipeGesture):
+        (WebKit::ViewGestureController::didReachMainFrameLoadTerminalState): Deleted.
+        * UIProcess/ViewGestureController.h:
+        (WebKit::ViewGestureController::didFinishNavigation):
+        (WebKit::ViewGestureController::didFailNavigation):
+        (WebKit::ViewGestureController::didFinishLoadForMainFrame): Deleted.
+        (WebKit::ViewGestureController::didFailLoadForMainFrame): Deleted.
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::didFinishLoadForFrame):
+        (WebKit::WebPageProxy::didFailLoadForFrame):
+        (WebKit::WebPageProxy::didSameDocumentNavigationForFrame):
+        * UIProcess/ios/PageClientImplIOS.h:
+        * UIProcess/ios/PageClientImplIOS.mm:
+        (WebKit::PageClientImpl::didFinishNavigation):
+        (WebKit::PageClientImpl::didFailNavigation):
+        (WebKit::PageClientImpl::didFinishLoadForMainFrame): Deleted.
+        (WebKit::PageClientImpl::didFailLoadForMainFrame): Deleted.
+        * UIProcess/ios/ViewGestureControllerIOS.mm:
+        (WebKit::ViewGestureController::willEndSwipeGesture):
+        (WebKit::ViewGestureController::resetState):
+        * UIProcess/mac/PageClientImplMac.h:
+        * UIProcess/mac/PageClientImplMac.mm:
+        (WebKit::PageClientImpl::didFinishNavigation):
+        (WebKit::PageClientImpl::didFailNavigation):
+        (WebKit::PageClientImpl::didFinishLoadForMainFrame): Deleted.
+        (WebKit::PageClientImpl::didFailLoadForMainFrame): Deleted.
+        * UIProcess/mac/ViewGestureControllerMac.mm:
+        (WebKit::ViewGestureController::resetState):
+        * UIProcess/playstation/PageClientImpl.cpp:
+        (WebKit::PageClientImpl::didFinishNavigation):
+        (WebKit::PageClientImpl::didFailNavigation):
+        (WebKit::PageClientImpl::didFinishLoadForMainFrame): Deleted.
+        (WebKit::PageClientImpl::didFailLoadForMainFrame): Deleted.
+        * UIProcess/playstation/PageClientImpl.h:
+        * UIProcess/win/PageClientImpl.cpp:
+        (WebKit::PageClientImpl::didFinishNavigation):
+        (WebKit::PageClientImpl::didFinishLoadForMainFrame): Deleted.
+        * UIProcess/win/PageClientImpl.h:
+
 2020-07-01  Brent Fulgham  <bfulgham@apple.com>
 
         [iOS] Allow the WebContent sandbox to check the 'hw.product' sysctl
diff --git a/Source/WebKit/UIProcess/API/gtk/PageClientImpl.cpp b/Source/WebKit/UIProcess/API/gtk/PageClientImpl.cpp
index e079c70..57139e2 100644
--- a/Source/WebKit/UIProcess/API/gtk/PageClientImpl.cpp
+++ b/Source/WebKit/UIProcess/API/gtk/PageClientImpl.cpp
@@ -522,14 +522,14 @@
     webkitWebViewBaseDidFirstVisuallyNonEmptyLayoutForMainFrame(WEBKIT_WEB_VIEW_BASE(m_viewWidget));
 }
 
-void PageClientImpl::didFinishLoadForMainFrame()
+void PageClientImpl::didFinishNavigation(API::Navigation* navigation)
 {
-    webkitWebViewBaseDidFinishLoadForMainFrame(WEBKIT_WEB_VIEW_BASE(m_viewWidget));
+    webkitWebViewBaseDidFinishNavigation(WEBKIT_WEB_VIEW_BASE(m_viewWidget), navigation);
 }
 
-void PageClientImpl::didFailLoadForMainFrame()
+void PageClientImpl::didFailNavigation(API::Navigation* navigation)
 {
-    webkitWebViewBaseDidFailLoadForMainFrame(WEBKIT_WEB_VIEW_BASE(m_viewWidget));
+    webkitWebViewBaseDidFailNavigation(WEBKIT_WEB_VIEW_BASE(m_viewWidget), navigation);
 }
 
 void PageClientImpl::didSameDocumentNavigationForMainFrame(SameDocumentNavigationType type)
diff --git a/Source/WebKit/UIProcess/API/gtk/PageClientImpl.h b/Source/WebKit/UIProcess/API/gtk/PageClientImpl.h
index 2700998..5c73827 100644
--- a/Source/WebKit/UIProcess/API/gtk/PageClientImpl.h
+++ b/Source/WebKit/UIProcess/API/gtk/PageClientImpl.h
@@ -111,7 +111,11 @@
     void handleDownloadRequest(DownloadProxy&) override;
     void didChangeContentSize(const WebCore::IntSize&) override;
     void didCommitLoadForMainFrame(const String& mimeType, bool useCustomContentProvider) override;
-    void didFailLoadForMainFrame() override;
+    void didStartProvisionalLoadForMainFrame() override;
+    void didFirstVisuallyNonEmptyLayoutForMainFrame() override;
+    void didFinishNavigation(API::Navigation*) override;
+    void didFailNavigation(API::Navigation*) override;
+    void didSameDocumentNavigationForMainFrame(SameDocumentNavigationType) override;
 
     // Auxiliary Client Creation
 #if ENABLE(FULLSCREEN_API)
@@ -137,11 +141,6 @@
     void willRecordNavigationSnapshot(WebBackForwardListItem&) override;
     void didRemoveNavigationGestureSnapshot() override;
 
-    void didStartProvisionalLoadForMainFrame() override;
-    void didFirstVisuallyNonEmptyLayoutForMainFrame() override;
-    void didFinishLoadForMainFrame() override;
-    void didSameDocumentNavigationForMainFrame(SameDocumentNavigationType) override;
-
 #if ENABLE(TOUCH_EVENTS)
     void doneWithTouchEvent(const NativeWebTouchEvent&, bool wasEventHandled) override;
 #endif
diff --git a/Source/WebKit/UIProcess/API/gtk/WebKitWebViewBase.cpp b/Source/WebKit/UIProcess/API/gtk/WebKitWebViewBase.cpp
index 2a1a483..12b0abb 100644
--- a/Source/WebKit/UIProcess/API/gtk/WebKitWebViewBase.cpp
+++ b/Source/WebKit/UIProcess/API/gtk/WebKitWebViewBase.cpp
@@ -2348,21 +2348,21 @@
 #endif
 }
 
-void webkitWebViewBaseDidFinishLoadForMainFrame(WebKitWebViewBase* webkitWebViewBase)
+void webkitWebViewBaseDidFinishNavigation(WebKitWebViewBase* webkitWebViewBase, API::Navigation* navigation)
 {
 #if !USE(GTK4)
     ViewGestureController* controller = webkitWebViewBaseViewGestureController(webkitWebViewBase);
     if (controller && controller->isSwipeGestureEnabled())
-        controller->didFinishLoadForMainFrame();
+        controller->didFinishNavigation(navigation);
 #endif
 }
 
-void webkitWebViewBaseDidFailLoadForMainFrame(WebKitWebViewBase* webkitWebViewBase)
+void webkitWebViewBaseDidFailNavigation(WebKitWebViewBase* webkitWebViewBase, API::Navigation* navigation)
 {
 #if !USE(GTK4)
     ViewGestureController* controller = webkitWebViewBaseViewGestureController(webkitWebViewBase);
     if (controller && controller->isSwipeGestureEnabled())
-        controller->didFailLoadForMainFrame();
+        controller->didFailNavigation(navigation);
 #endif
 }
 
diff --git a/Source/WebKit/UIProcess/API/gtk/WebKitWebViewBasePrivate.h b/Source/WebKit/UIProcess/API/gtk/WebKitWebViewBasePrivate.h
index eb7f86e..1dda1fc 100644
--- a/Source/WebKit/UIProcess/API/gtk/WebKitWebViewBasePrivate.h
+++ b/Source/WebKit/UIProcess/API/gtk/WebKitWebViewBasePrivate.h
@@ -101,8 +101,8 @@
 
 void webkitWebViewBaseDidStartProvisionalLoadForMainFrame(WebKitWebViewBase*);
 void webkitWebViewBaseDidFirstVisuallyNonEmptyLayoutForMainFrame(WebKitWebViewBase*);
-void webkitWebViewBaseDidFinishLoadForMainFrame(WebKitWebViewBase*);
-void webkitWebViewBaseDidFailLoadForMainFrame(WebKitWebViewBase*);
+void webkitWebViewBaseDidFinishNavigation(WebKitWebViewBase*, API::Navigation*);
+void webkitWebViewBaseDidFailNavigation(WebKitWebViewBase*, API::Navigation*);
 void webkitWebViewBaseDidSameDocumentNavigationForMainFrame(WebKitWebViewBase*, WebKit::SameDocumentNavigationType);
 void webkitWebViewBaseDidRestoreScrollPosition(WebKitWebViewBase*);
 
diff --git a/Source/WebKit/UIProcess/API/ios/WKWebViewIOS.h b/Source/WebKit/UIProcess/API/ios/WKWebViewIOS.h
index 77bc083..61f6372 100644
--- a/Source/WebKit/UIProcess/API/ios/WKWebViewIOS.h
+++ b/Source/WebKit/UIProcess/API/ios/WKWebViewIOS.h
@@ -84,8 +84,8 @@
 - (void)_didCompleteAnimatedResize;
 
 - (void)_didStartProvisionalLoadForMainFrame;
-- (void)_didFinishLoadForMainFrame;
-- (void)_didFailLoadForMainFrame;
+- (void)_didFinishNavigation:(API::Navigation*)navigation;
+- (void)_didFailNavigation:(API::Navigation*)navigation;
 - (void)_didSameDocumentNavigationForMainFrame:(WebKit::SameDocumentNavigationType)navigationType;
 
 - (BOOL)_isShowingVideoPictureInPicture;
diff --git a/Source/WebKit/UIProcess/API/ios/WKWebViewIOS.mm b/Source/WebKit/UIProcess/API/ios/WKWebViewIOS.mm
index a0e8b18..9200ddc 100644
--- a/Source/WebKit/UIProcess/API/ios/WKWebViewIOS.mm
+++ b/Source/WebKit/UIProcess/API/ios/WKWebViewIOS.mm
@@ -2146,16 +2146,16 @@
         _callbacksDeferredDuringResize.takeLast()();
 }
 
-- (void)_didFinishLoadForMainFrame
+- (void)_didFinishNavigation:(API::Navigation*)navigation
 {
     if (_gestureController)
-        _gestureController->didFinishLoadForMainFrame();
+        _gestureController->didFinishNavigation(navigation);
 }
 
-- (void)_didFailLoadForMainFrame
+- (void)_didFailNavigation:(API::Navigation*)navigation
 {
     if (_gestureController)
-        _gestureController->didFailLoadForMainFrame();
+        _gestureController->didFailNavigation(navigation);
 }
 
 - (void)_didSameDocumentNavigationForMainFrame:(WebKit::SameDocumentNavigationType)navigationType
diff --git a/Source/WebKit/UIProcess/API/wpe/PageClientImpl.cpp b/Source/WebKit/UIProcess/API/wpe/PageClientImpl.cpp
index c8b41f4..573263e 100644
--- a/Source/WebKit/UIProcess/API/wpe/PageClientImpl.cpp
+++ b/Source/WebKit/UIProcess/API/wpe/PageClientImpl.cpp
@@ -318,11 +318,11 @@
 {
 }
 
-void PageClientImpl::didFinishLoadForMainFrame()
+void PageClientImpl::didFinishNavigation(API::Navigation*)
 {
 }
 
-void PageClientImpl::didFailLoadForMainFrame()
+void PageClientImpl::didFailNavigation(API::Navigation*)
 {
 }
 
diff --git a/Source/WebKit/UIProcess/API/wpe/PageClientImpl.h b/Source/WebKit/UIProcess/API/wpe/PageClientImpl.h
index 372b30f..a25a1cb 100644
--- a/Source/WebKit/UIProcess/API/wpe/PageClientImpl.h
+++ b/Source/WebKit/UIProcess/API/wpe/PageClientImpl.h
@@ -129,8 +129,8 @@
 
     void didStartProvisionalLoadForMainFrame() override;
     void didFirstVisuallyNonEmptyLayoutForMainFrame() override;
-    void didFinishLoadForMainFrame() override;
-    void didFailLoadForMainFrame() override;
+    void didFinishNavigation(API::Navigation*) override;
+    void didFailNavigation(API::Navigation*) override;
     void didSameDocumentNavigationForMainFrame(SameDocumentNavigationType) override;
 
     void didChangeBackgroundColor() override;
diff --git a/Source/WebKit/UIProcess/PageClient.h b/Source/WebKit/UIProcess/PageClient.h
index 392a9df..5941f02 100644
--- a/Source/WebKit/UIProcess/PageClient.h
+++ b/Source/WebKit/UIProcess/PageClient.h
@@ -67,6 +67,7 @@
 namespace API {
 class Attachment;
 class HitTestResult;
+class Navigation;
 class Object;
 class OpenPanelParameters;
 class SecurityOrigin;
@@ -461,8 +462,8 @@
     virtual void didRemoveNavigationGestureSnapshot() = 0;
 
     virtual void didFirstVisuallyNonEmptyLayoutForMainFrame() = 0;
-    virtual void didFinishLoadForMainFrame() = 0;
-    virtual void didFailLoadForMainFrame() = 0;
+    virtual void didFinishNavigation(API::Navigation*) = 0;
+    virtual void didFailNavigation(API::Navigation*) = 0;
     virtual void didSameDocumentNavigationForMainFrame(SameDocumentNavigationType) = 0;
 
     virtual void didChangeBackgroundColor() = 0;
diff --git a/Source/WebKit/UIProcess/ViewGestureController.cpp b/Source/WebKit/UIProcess/ViewGestureController.cpp
index 9cea2b0..f9ec34e 100644
--- a/Source/WebKit/UIProcess/ViewGestureController.cpp
+++ b/Source/WebKit/UIProcess/ViewGestureController.cpp
@@ -207,8 +207,11 @@
     m_snapshotRemovalTracker.eventOccurred(SnapshotRemovalTracker::ScrollPositionRestoration);
 }
 
-void ViewGestureController::didReachMainFrameLoadTerminalState()
+void ViewGestureController::didReachNavigationTerminalState(API::Navigation* navigation)
 {
+    if (!m_pendingNavigation || navigation != m_pendingNavigation)
+        return;
+
     if (m_snapshotRemovalTracker.isPaused() && m_snapshotRemovalTracker.hasRemovalCallback()) {
         removeSwipeSnapshot();
         return;
@@ -576,7 +579,7 @@
     auto renderTreeSizeThreshold = renderTreeSize * swipeSnapshotRemovalRenderTreeSizeTargetFraction;
 
     m_didStartProvisionalLoad = false;
-    m_webPageProxy.goToBackForwardItem(targetItem);
+    m_pendingNavigation = m_webPageProxy.goToBackForwardItem(targetItem);
 
     auto* currentItem = m_webPageProxy.backForwardList().currentItem();
     // The main frame will not be navigated so hide the snapshot right away.
diff --git a/Source/WebKit/UIProcess/ViewGestureController.h b/Source/WebKit/UIProcess/ViewGestureController.h
index 4343490..f25e792 100644
--- a/Source/WebKit/UIProcess/ViewGestureController.h
+++ b/Source/WebKit/UIProcess/ViewGestureController.h
@@ -67,6 +67,10 @@
 }
 #endif
 
+namespace API {
+class Navigation;
+}
+
 #if PLATFORM(MAC)
 typedef NSEvent* PlatformScrollEvent;
 #elif PLATFORM(GTK)
@@ -150,13 +154,13 @@
     WebCore::Color backgroundColorForCurrentSnapshot() const { return m_backgroundColorForCurrentSnapshot; }
 
     void didStartProvisionalLoadForMainFrame();
-    void didFinishLoadForMainFrame() { didReachMainFrameLoadTerminalState(); }
-    void didFailLoadForMainFrame() { didReachMainFrameLoadTerminalState(); }
+    void didFinishNavigation(API::Navigation* navigation) { didReachNavigationTerminalState(navigation); }
+    void didFailNavigation(API::Navigation* navigation) { didReachNavigationTerminalState(navigation); }
     void didFirstVisuallyNonEmptyLayoutForMainFrame();
     void didRepaintAfterNavigation();
     void didHitRenderTreeSizeThreshold();
     void didRestoreScrollPosition();
-    void didReachMainFrameLoadTerminalState();
+    void didReachNavigationTerminalState(API::Navigation*);
     void didSameDocumentNavigationForMainFrame(SameDocumentNavigationType);
 
     void checkForActiveLoads();
@@ -327,6 +331,8 @@
     WeakPtr<WebPageProxy> m_alternateBackForwardListSourcePage;
     RefPtr<WebPageProxy> m_webPageProxyForBackForwardListForCurrentSwipe;
 
+    RefPtr<API::Navigation> m_pendingNavigation;
+
     GestureID m_currentGestureID;
 
 #if !PLATFORM(IOS_FAMILY)
diff --git a/Source/WebKit/UIProcess/WebPageProxy.cpp b/Source/WebKit/UIProcess/WebPageProxy.cpp
index a990186..d9bd744 100644
--- a/Source/WebKit/UIProcess/WebPageProxy.cpp
+++ b/Source/WebKit/UIProcess/WebPageProxy.cpp
@@ -4875,7 +4875,7 @@
 
     if (isMainFrame) {
         reportPageLoadResult();
-        pageClient().didFinishLoadForMainFrame();
+        pageClient().didFinishNavigation(navigation.get());
 
         if (navigation)
             navigation->setClientNavigationActivity(nullptr);
@@ -4929,7 +4929,7 @@
 
     if (isMainFrame) {
         reportPageLoadResult(error);
-        pageClient().didFailLoadForMainFrame();
+        pageClient().didFailNavigation(navigation.get());
         if (navigation)
             navigation->setClientNavigationActivity(nullptr);
     }
diff --git a/Source/WebKit/UIProcess/gtk/ViewGestureControllerGtk.cpp b/Source/WebKit/UIProcess/gtk/ViewGestureControllerGtk.cpp
index e6fedd0..1eebd9e 100644
--- a/Source/WebKit/UIProcess/gtk/ViewGestureControllerGtk.cpp
+++ b/Source/WebKit/UIProcess/gtk/ViewGestureControllerGtk.cpp
@@ -26,6 +26,7 @@
 #include "config.h"
 #include "ViewGestureController.h"
 
+#include "APINavigation.h"
 #include "DrawingAreaProxy.h"
 #include "WebBackForwardList.h"
 #include <WebCore/GRefPtrGtk.h>
@@ -506,6 +507,8 @@
 
     m_backgroundColorForCurrentSnapshot = Color();
 
+    m_pendingNavigation = nullptr;
+
     didEndGesture();
 
     m_swipeProgressTracker.reset();
diff --git a/Source/WebKit/UIProcess/ios/PageClientImplIOS.h b/Source/WebKit/UIProcess/ios/PageClientImplIOS.h
index c2fdc93..761eef8 100644
--- a/Source/WebKit/UIProcess/ios/PageClientImplIOS.h
+++ b/Source/WebKit/UIProcess/ios/PageClientImplIOS.h
@@ -220,8 +220,8 @@
     void didRemoveNavigationGestureSnapshot() override;
 
     void didFirstVisuallyNonEmptyLayoutForMainFrame() override;
-    void didFinishLoadForMainFrame() override;
-    void didFailLoadForMainFrame() override;
+    void didFinishNavigation(API::Navigation*) override;
+    void didFailNavigation(API::Navigation*) override;
     void didSameDocumentNavigationForMainFrame(SameDocumentNavigationType) override;
     void didNotHandleTapAsClick(const WebCore::IntPoint&) override;
     void didCompleteSyntheticClick() override;
diff --git a/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm b/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm
index b289f05..9613122 100644
--- a/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm
+++ b/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm
@@ -791,14 +791,14 @@
 {
 }
 
-void PageClientImpl::didFinishLoadForMainFrame()
+void PageClientImpl::didFinishNavigation(API::Navigation* navigation)
 {
-    [m_webView _didFinishLoadForMainFrame];
+    [m_webView _didFinishNavigation:navigation];
 }
 
-void PageClientImpl::didFailLoadForMainFrame()
+void PageClientImpl::didFailNavigation(API::Navigation* navigation)
 {
-    [m_webView _didFailLoadForMainFrame];
+    [m_webView _didFailNavigation:navigation];
 }
 
 void PageClientImpl::didSameDocumentNavigationForMainFrame(SameDocumentNavigationType navigationType)
diff --git a/Source/WebKit/UIProcess/ios/ViewGestureControllerIOS.mm b/Source/WebKit/UIProcess/ios/ViewGestureControllerIOS.mm
index 5295f1d..97c15c6 100644
--- a/Source/WebKit/UIProcess/ios/ViewGestureControllerIOS.mm
+++ b/Source/WebKit/UIProcess/ios/ViewGestureControllerIOS.mm
@@ -292,7 +292,7 @@
         m_snapshotRemovalTargetRenderTreeSize = snapshot->renderTreeSize() * swipeSnapshotRemovalRenderTreeSizeTargetFraction;
 
     m_didStartProvisionalLoad = false;
-    m_webPageProxyForBackForwardListForCurrentSwipe->goToBackForwardItem(targetItem);
+    m_pendingNavigation = m_webPageProxyForBackForwardListForCurrentSwipe->goToBackForwardItem(targetItem);
 
     auto* currentItem = m_webPageProxyForBackForwardListForCurrentSwipe->backForwardList().currentItem();
     // The main frame will not be navigated so hide the snapshot right away.
@@ -437,6 +437,8 @@
 
     m_backgroundColorForCurrentSnapshot = WebCore::Color();
 
+    m_pendingNavigation = nullptr;
+
     didEndGesture();
 }
 
diff --git a/Source/WebKit/UIProcess/mac/PageClientImplMac.h b/Source/WebKit/UIProcess/mac/PageClientImplMac.h
index 7a1c5b3..5b0d317 100644
--- a/Source/WebKit/UIProcess/mac/PageClientImplMac.h
+++ b/Source/WebKit/UIProcess/mac/PageClientImplMac.h
@@ -215,8 +215,8 @@
 
     void didStartProvisionalLoadForMainFrame() override;
     void didFirstVisuallyNonEmptyLayoutForMainFrame() override;
-    void didFinishLoadForMainFrame() override;
-    void didFailLoadForMainFrame() override;
+    void didFinishNavigation(API::Navigation*) override;
+    void didFailNavigation(API::Navigation*) override;
     void didSameDocumentNavigationForMainFrame(SameDocumentNavigationType) override;
     void handleControlledElementIDResponse(const String&) override;
 
diff --git a/Source/WebKit/UIProcess/mac/PageClientImplMac.mm b/Source/WebKit/UIProcess/mac/PageClientImplMac.mm
index 5f4c68d..370b2fc5 100644
--- a/Source/WebKit/UIProcess/mac/PageClientImplMac.mm
+++ b/Source/WebKit/UIProcess/mac/PageClientImplMac.mm
@@ -790,18 +790,18 @@
         gestureController->didFirstVisuallyNonEmptyLayoutForMainFrame();
 }
 
-void PageClientImpl::didFinishLoadForMainFrame()
+void PageClientImpl::didFinishNavigation(API::Navigation* navigation)
 {
     if (auto gestureController = m_impl->gestureController())
-        gestureController->didFinishLoadForMainFrame();
+        gestureController->didFinishNavigation(navigation);
 
     NSAccessibilityPostNotification(NSAccessibilityUnignoredAncestor(m_view), kAXLoadCompleteNotification);
 }
 
-void PageClientImpl::didFailLoadForMainFrame()
+void PageClientImpl::didFailNavigation(API::Navigation* navigation)
 {
     if (auto gestureController = m_impl->gestureController())
-        gestureController->didFailLoadForMainFrame();
+        gestureController->didFailNavigation(navigation);
 
     NSAccessibilityPostNotification(NSAccessibilityUnignoredAncestor(m_view), kAXLoadCompleteNotification);
 }
diff --git a/Source/WebKit/UIProcess/mac/ViewGestureControllerMac.mm b/Source/WebKit/UIProcess/mac/ViewGestureControllerMac.mm
index 9be9f56..e60a2df 100644
--- a/Source/WebKit/UIProcess/mac/ViewGestureControllerMac.mm
+++ b/Source/WebKit/UIProcess/mac/ViewGestureControllerMac.mm
@@ -28,6 +28,7 @@
 
 #if PLATFORM(MAC)
 
+#import "APINavigation.h"
 #import "DrawingAreaProxy.h"
 #import "FrameLoadState.h"
 #import "Logging.h"
@@ -643,6 +644,8 @@
 
     m_backgroundColorForCurrentSnapshot = Color();
 
+    m_pendingNavigation = nullptr;
+
     didEndGesture();
 }
 
diff --git a/Source/WebKit/UIProcess/playstation/PageClientImpl.cpp b/Source/WebKit/UIProcess/playstation/PageClientImpl.cpp
index c99acd2..64a4ca7 100644
--- a/Source/WebKit/UIProcess/playstation/PageClientImpl.cpp
+++ b/Source/WebKit/UIProcess/playstation/PageClientImpl.cpp
@@ -245,11 +245,11 @@
 {
 }
 
-void PageClientImpl::didFinishLoadForMainFrame()
+void PageClientImpl::didFinishNavigation(API::Navigation*)
 {
 }
 
-void PageClientImpl::didFailLoadForMainFrame()
+void PageClientImpl::didFailNavigation(API::Navigation*)
 {
 }
 
diff --git a/Source/WebKit/UIProcess/playstation/PageClientImpl.h b/Source/WebKit/UIProcess/playstation/PageClientImpl.h
index 9bfe162..38b97eb 100644
--- a/Source/WebKit/UIProcess/playstation/PageClientImpl.h
+++ b/Source/WebKit/UIProcess/playstation/PageClientImpl.h
@@ -124,8 +124,8 @@
     void didRemoveNavigationGestureSnapshot() final;
 
     void didFirstVisuallyNonEmptyLayoutForMainFrame() final;
-    void didFinishLoadForMainFrame() final;
-    void didFailLoadForMainFrame() final;
+    void didFinishNavigation(API::Navigation*) final;
+    void didFailNavigation(API::Navigation*) final;
     void didSameDocumentNavigationForMainFrame(SameDocumentNavigationType) final;
 
     void didChangeBackgroundColor() final;
diff --git a/Source/WebKit/UIProcess/win/PageClientImpl.cpp b/Source/WebKit/UIProcess/win/PageClientImpl.cpp
index be61bbd..eeca84b 100644
--- a/Source/WebKit/UIProcess/win/PageClientImpl.cpp
+++ b/Source/WebKit/UIProcess/win/PageClientImpl.cpp
@@ -330,7 +330,7 @@
     notImplemented();
 }
 
-void PageClientImpl::didFinishLoadForMainFrame()
+void PageClientImpl::didFinishNavigation(API::Navigation*)
 {
     notImplemented();
 }
diff --git a/Source/WebKit/UIProcess/win/PageClientImpl.h b/Source/WebKit/UIProcess/win/PageClientImpl.h
index 3767251..c625747 100644
--- a/Source/WebKit/UIProcess/win/PageClientImpl.h
+++ b/Source/WebKit/UIProcess/win/PageClientImpl.h
@@ -99,7 +99,10 @@
     void handleDownloadRequest(DownloadProxy&) override;
     void didChangeContentSize(const WebCore::IntSize&) override;
     void didCommitLoadForMainFrame(const String& mimeType, bool useCustomContentProvider) override;
-    void didFailLoadForMainFrame() override { }
+    void didFirstVisuallyNonEmptyLayoutForMainFrame() override;
+    void didFinishNavigation(API::Navigation*) override;
+    void didFailNavigation(API::Navigation*) override { }
+    void didSameDocumentNavigationForMainFrame(SameDocumentNavigationType) override;
 
     // Auxiliary Client Creation
 #if ENABLE(FULLSCREEN_API)
@@ -125,9 +128,6 @@
     void willRecordNavigationSnapshot(WebBackForwardListItem&) override;
     void didRemoveNavigationGestureSnapshot() override;
 
-    void didFirstVisuallyNonEmptyLayoutForMainFrame() override;
-    void didFinishLoadForMainFrame() override;
-    void didSameDocumentNavigationForMainFrame(SameDocumentNavigationType) override;
 
 #if ENABLE(TOUCH_EVENTS)
     void doneWithTouchEvent(const NativeWebTouchEvent&, bool wasEventHandled) override;