Bug 1493225, part 3 - Cancel content JS when navigating through history to prevent hangs r=smaug
authorJim Porter <jporter@mozilla.com>
Tue, 30 Apr 2019 23:31:46 +0000
changeset 530862 100acc204e5e0c5e8767740c4861c906e21139f5
parent 530861 a7fa3041bff04a25fa39fb6f714071314f3cc69b
child 530863 9a0ce3016f03a914367e86c8339a8b885e309c83
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1493225
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1493225, part 3 - Cancel content JS when navigating through history to prevent hangs r=smaug This patch adds an ID to ensure that we avoid canceling content JS if the next page already started loading by the time we're ready to try canceling the JS. Differential Revision: https://phabricator.services.mozilla.com/D25164
docshell/base/nsDocShell.cpp
docshell/base/nsIDocShell.idl
dom/ipc/BrowserChild.cpp
dom/ipc/BrowserChild.h
dom/ipc/PProcessHangMonitor.ipdl
dom/ipc/ProcessHangMonitor.cpp
dom/webidl/CancelContentJSOptions.webidl
toolkit/actors/WebNavigationChild.jsm
toolkit/components/remotebrowserutils/RemoteWebNavigation.jsm
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -663,16 +663,26 @@ nsDocShell::GetInterface(const nsIID& aI
     return nsDocLoader::GetInterface(aIID, aSink);
   }
 
   NS_IF_ADDREF(((nsISupports*)*aSink));
   return *aSink ? NS_OK : NS_NOINTERFACE;
 }
 
 NS_IMETHODIMP
+nsDocShell::SetCancelContentJSEpoch(int32_t aEpoch) {
+  // Note: this gets called fairly early (before a pageload actually starts).
+  // We could probably defer this even longer.
+  nsCOMPtr<nsIBrowserChild> browserChild = GetBrowserChild();
+  static_cast<BrowserChild*>(browserChild.get())
+      ->SetCancelContentJSEpoch(aEpoch);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDocShell::LoadURI(nsDocShellLoadState* aLoadState) {
   MOZ_ASSERT(aLoadState, "Must have a valid load state!");
   MOZ_ASSERT(
       (aLoadState->LoadFlags() & INTERNAL_LOAD_FLAGS_LOADURI_SETUP_FLAGS) == 0,
       "Should not have these flags set");
 
   if (!aLoadState->TriggeringPrincipal()) {
 #ifndef ANDROID
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -70,16 +70,18 @@ native nsDocShellLoadStatePtr(nsDocShell
 webidl BrowsingContext;
 webidl ContentFrameMessageManager;
 webidl EventTarget;
 webidl Document;
 
 [scriptable, builtinclass, uuid(049234fe-da10-478b-bc5d-bc6f9a1ba63d)]
 interface nsIDocShell : nsIDocShellTreeItem
 {
+  void setCancelContentJSEpoch(in long aEpoch);
+
   /**
    * Loads a given URI.  This will give priority to loading the requested URI
    * in the object implementing this interface.  If it can't be loaded here
    * however, the URL dispatcher will go through its normal process of content
    * loading.
    *
    * @param loadState   - This is the extended load info for this load.
    */
--- a/dom/ipc/BrowserChild.cpp
+++ b/dom/ipc/BrowserChild.cpp
@@ -402,17 +402,18 @@ BrowserChild::BrowserChild(ContentChild*
 #if defined(ACCESSIBILITY)
       ,
     mTopLevelDocAccessibleChild(nullptr)
 #endif
         ,
     mPendingDocShellIsActive(false), mPendingDocShellReceivedMessage(false),
     mPendingRenderLayers(false),
     mPendingRenderLayersReceivedMessage(false), mPendingLayersObserverEpoch{0},
-    mPendingDocShellBlockers(0), mWidgetNativeData(0) {
+    mPendingDocShellBlockers(0), mCancelContentJSEpoch(0),
+    mWidgetNativeData(0) {
   mozilla::HoldJSObjects(this);
 
   nsWeakPtr weakPtrThis(do_GetWeakReference(
       static_cast<nsIBrowserChild*>(this)));  // for capture by the lambda
   mSetAllowedTouchBehaviorCallback =
       [weakPtrThis](uint64_t aInputBlockId,
                     const nsTArray<TouchBehaviorFlags>& aFlags) {
         if (nsCOMPtr<nsIBrowserChild> browserChild =
@@ -3299,20 +3300,27 @@ void BrowserChild::PaintWhileInterruptin
   }
 
   nsAutoScriptBlocker scriptBlocker;
   RecvRenderLayers(true /* aEnabled */, aForceRepaint, aEpoch);
 }
 
 nsresult BrowserChild::CanCancelContentJS(
     nsIRemoteTab::NavigationType aNavigationType, int32_t aNavigationIndex,
-    nsIURI* aNavigationURI, bool* aCanCancel) {
+    nsIURI* aNavigationURI, int32_t aEpoch, bool* aCanCancel) {
   nsresult rv;
   *aCanCancel = false;
 
+  if (aEpoch <= mCancelContentJSEpoch) {
+    // The next page loaded before we got here, so we shouldn't try to cancel
+    // the content JS.
+    TABC_LOG("Unable to cancel content JS; the next page is already loaded!\n");
+    return NS_OK;
+  }
+
   nsCOMPtr<nsISHistory> history = do_GetInterface(WebNavigation());
   if (!history) {
     return NS_ERROR_FAILURE;
   }
 
   int32_t current;
   rv = history->GetIndex(&current);
   NS_ENSURE_SUCCESS(rv, rv);
--- a/dom/ipc/BrowserChild.h
+++ b/dom/ipc/BrowserChild.h
@@ -609,17 +609,17 @@ class BrowserChild final : public Browse
                   const CSSRect& aRect, const uint32_t& aFlags);
 
   // Request that the docshell be marked as active.
   void PaintWhileInterruptingJS(const layers::LayersObserverEpoch& aEpoch,
                                 bool aForceRepaint);
 
   nsresult CanCancelContentJS(nsIRemoteTab::NavigationType aNavigationType,
                               int32_t aNavigationIndex, nsIURI* aNavigationURI,
-                              bool* aCanCancel);
+                              int32_t aEpoch, bool* aCanCancel);
 
   layers::LayersObserverEpoch LayersObserverEpoch() const {
     return mLayersObserverEpoch;
   }
 
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
   uintptr_t GetNativeWindowHandle() const { return mNativeWindowHandle; }
 #endif
@@ -659,16 +659,20 @@ class BrowserChild final : public Browse
   // dispatching some mouse events other than mousemove.
   void FlushAllCoalescedMouseData();
   void ProcessPendingCoalescedMouseDataAndDispatchEvents();
 
   void HandleRealMouseButtonEvent(const WidgetMouseEvent& aEvent,
                                   const ScrollableLayerGuid& aGuid,
                                   const uint64_t& aInputBlockId);
 
+  void SetCancelContentJSEpoch(int32_t aEpoch) {
+    mCancelContentJSEpoch = aEpoch;
+  }
+
   static bool HasVisibleTabs() {
     return sVisibleTabs && !sVisibleTabs->IsEmpty();
   }
 
   // Returns the set of BrowserChilds that are currently rendering layers. There
   // can be multiple BrowserChilds in this state if Firefox has multiple windows
   // open or is warming tabs up. There can also be zero BrowserChilds in this
   // state. Note that this function should only be called if HasVisibleTabs()
@@ -915,16 +919,17 @@ class BrowserChild final : public Browse
   bool mPendingDocShellIsActive;
   bool mPendingDocShellReceivedMessage;
   bool mPendingRenderLayers;
   bool mPendingRenderLayersReceivedMessage;
   layers::LayersObserverEpoch mPendingLayersObserverEpoch;
   // When mPendingDocShellBlockers is greater than 0, the DocShell is blocked,
   // and once it reaches 0, it is no longer blocked.
   uint32_t mPendingDocShellBlockers;
+  int32_t mCancelContentJSEpoch;
 
   WindowsHandle mWidgetNativeData;
 
   Maybe<LayoutDeviceToLayoutDeviceMatrix4x4> mChildToParentConversionMatrix;
 
   // This state is used to keep track of the current visible tabs (the ones
   // rendering layers). There may be more than one if there are multiple browser
   // windows open, or tabs are being warmed up. There may be none if this
--- a/dom/ipc/PProcessHangMonitor.ipdl
+++ b/dom/ipc/PProcessHangMonitor.ipdl
@@ -45,12 +45,12 @@ child:
 
   async BeginStartingDebugger();
   async EndStartingDebugger();
 
   async PaintWhileInterruptingJS(TabId tabId, bool forceRepaint, LayersObserverEpoch aEpoch);
 
   async CancelContentJSExecutionIfRunning(
       TabId tabId, NavigationType aNavigationType,
-      int32_t aNavigationIndex, nsCString? aNavigationURI);
+      int32_t aNavigationIndex, nsCString? aNavigationURI, int32_t aEpoch);
 };
 
 } // namespace mozilla
--- a/dom/ipc/ProcessHangMonitor.cpp
+++ b/dom/ipc/ProcessHangMonitor.cpp
@@ -113,17 +113,18 @@ class HangMonitorChild : public PProcess
 
   mozilla::ipc::IPCResult RecvPaintWhileInterruptingJS(
       const TabId& aTabId, const bool& aForceRepaint,
       const LayersObserverEpoch& aEpoch) override;
 
   mozilla::ipc::IPCResult RecvCancelContentJSExecutionIfRunning(
       const TabId& aTabId, const nsIRemoteTab::NavigationType& aNavigationType,
       const int32_t& aNavigationIndex,
-      const mozilla::Maybe<nsCString>& aNavigationURI) override;
+      const mozilla::Maybe<nsCString>& aNavigationURI,
+      const int32_t& aEpoch) override;
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
   void InterruptCallback();
   void Shutdown();
 
   static HangMonitorChild* Get() { return sInstance; }
 
@@ -158,16 +159,17 @@ class HangMonitorChild : public PProcess
   bool mPaintWhileInterruptingJSForce;
   TabId mPaintWhileInterruptingJSTab;
   MOZ_INIT_OUTSIDE_CTOR LayersObserverEpoch mPaintWhileInterruptingJSEpoch;
   bool mCancelContentJS;
   TabId mCancelContentJSTab;
   nsIRemoteTab::NavigationType mCancelContentJSNavigationType;
   int32_t mCancelContentJSNavigationIndex;
   mozilla::Maybe<nsCString> mCancelContentJSNavigationURI;
+  int32_t mCancelContentJSEpoch;
   JSContext* mContext;
   bool mShutdownDone;
 
   // This field is only accessed on the hang thread.
   bool mIPCOpen;
 
   // Allows us to ensure we NotifyActivity only once, allowing
   // either thread to do so.
@@ -276,17 +278,17 @@ class HangMonitorParent : public PProces
                             const nsString& aBrowserDumpId, bool aTakeMinidump);
 
   void ClearHangNotification();
 
   void PaintWhileInterruptingJSOnThread(TabId aTabId, bool aForceRepaint,
                                         const LayersObserverEpoch& aEpoch);
   void CancelContentJSExecutionIfRunningOnThread(
       TabId aTabId, nsIRemoteTab::NavigationType aNavigationType,
-      int32_t aNavigationIndex, nsIURI* aNavigationURI);
+      int32_t aNavigationIndex, nsIURI* aNavigationURI, int32_t aEpoch);
 
   void ShutdownOnThread();
 
   const RefPtr<ProcessHangMonitor> mHangMonitor;
 
   // This field is read-only after construction.
   bool mReportHangs;
 
@@ -323,16 +325,17 @@ HangMonitorChild::HangMonitorChild(Proce
       mTerminateGlobal(false),
       mStartDebugger(false),
       mFinishedStartingDebugger(false),
       mPaintWhileInterruptingJS(false),
       mPaintWhileInterruptingJSForce(false),
       mCancelContentJS(false),
       mCancelContentJSNavigationType(nsIRemoteTab::NAVIGATE_BACK),
       mCancelContentJSNavigationIndex(0),
+      mCancelContentJSEpoch(0),
       mShutdownDone(false),
       mIPCOpen(true),
       mPaintWhileInterruptingJSActive(false) {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   mContext = danger::GetJSContext();
 
   BackgroundHangMonitor::RegisterAnnotator(*this);
 }
@@ -351,29 +354,31 @@ void HangMonitorChild::InterruptCallback
   TabId paintWhileInterruptingJSTab;
   LayersObserverEpoch paintWhileInterruptingJSEpoch;
 
   bool cancelContentJS;
   TabId cancelContentJSTab;
   nsIRemoteTab::NavigationType cancelContentJSNavigationType;
   int32_t cancelContentJSNavigationIndex;
   mozilla::Maybe<nsCString> cancelContentJSNavigationURI;
+  int32_t cancelContentJSEpoch;
 
   {
     MonitorAutoLock lock(mMonitor);
     paintWhileInterruptingJS = mPaintWhileInterruptingJS;
     paintWhileInterruptingJSForce = mPaintWhileInterruptingJSForce;
     paintWhileInterruptingJSTab = mPaintWhileInterruptingJSTab;
     paintWhileInterruptingJSEpoch = mPaintWhileInterruptingJSEpoch;
 
     cancelContentJS = mCancelContentJS;
     cancelContentJSTab = mCancelContentJSTab;
     cancelContentJSNavigationType = mCancelContentJSNavigationType;
     cancelContentJSNavigationIndex = mCancelContentJSNavigationIndex;
     cancelContentJSNavigationURI = std::move(mCancelContentJSNavigationURI);
+    cancelContentJSEpoch = mCancelContentJSEpoch;
 
     mPaintWhileInterruptingJS = false;
     mCancelContentJS = false;
   }
 
   // Don't paint from the interrupt callback when recording or replaying, as
   // the interrupt callback is triggered non-deterministically.
   if (paintWhileInterruptingJS && !recordreplay::IsRecordingOrReplaying()) {
@@ -400,17 +405,17 @@ void HangMonitorChild::InterruptCallback
         if (NS_FAILED(rv)) {
           return;
         }
       }
 
       bool canCancel;
       rv = browserChild->CanCancelContentJS(cancelContentJSNavigationType,
                                             cancelContentJSNavigationIndex, uri,
-                                            &canCancel);
+                                            cancelContentJSEpoch, &canCancel);
       if (NS_SUCCEEDED(rv) && canCancel) {
         // Tell xpconnect that we want to cancel the content JS in this tab
         // during the next interrupt callback.
         XPCJSContext::SetTabIdToCancelContentJS(cancelContentJSTab);
         JS_RequestInterruptCallback(mContext);
       }
     }
   }
@@ -511,26 +516,27 @@ void HangMonitorChild::ClearPaintWhileIn
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(XRE_IsContentProcess());
   mPaintWhileInterruptingJSActive = false;
 }
 
 mozilla::ipc::IPCResult HangMonitorChild::RecvCancelContentJSExecutionIfRunning(
     const TabId& aTabId, const nsIRemoteTab::NavigationType& aNavigationType,
     const int32_t& aNavigationIndex,
-    const mozilla::Maybe<nsCString>& aNavigationURI) {
+    const mozilla::Maybe<nsCString>& aNavigationURI, const int32_t& aEpoch) {
   MOZ_RELEASE_ASSERT(IsOnThread());
 
   {
     MonitorAutoLock lock(mMonitor);
     mCancelContentJS = true;
     mCancelContentJSTab = aTabId;
     mCancelContentJSNavigationType = aNavigationType;
     mCancelContentJSNavigationIndex = aNavigationIndex;
     mCancelContentJSNavigationURI = aNavigationURI;
+    mCancelContentJSEpoch = aEpoch;
   }
 
   JS_RequestInterruptCallback(mContext);
 
   return IPC_OK();
 }
 
 void HangMonitorChild::Bind(Endpoint<PProcessHangMonitorChild>&& aEndpoint) {
@@ -744,40 +750,40 @@ void HangMonitorParent::PaintWhileInterr
 void HangMonitorParent::CancelContentJSExecutionIfRunning(
     dom::BrowserParent* aBrowserParent,
     nsIRemoteTab::NavigationType aNavigationType,
     const dom::CancelContentJSOptions& aCancelContentJSOptions) {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   TabId id = aBrowserParent->GetTabId();
   Dispatch(NewNonOwningRunnableMethod<TabId, nsIRemoteTab::NavigationType,
-                                      int32_t, nsIURI*>(
+                                      int32_t, nsIURI*, int32_t>(
       "HangMonitorParent::CancelContentJSExecutionIfRunningOnThread", this,
       &HangMonitorParent::CancelContentJSExecutionIfRunningOnThread, id,
       aNavigationType, aCancelContentJSOptions.mIndex,
-      aCancelContentJSOptions.mUri));
+      aCancelContentJSOptions.mUri, aCancelContentJSOptions.mEpoch));
 }
 
 void HangMonitorParent::CancelContentJSExecutionIfRunningOnThread(
     TabId aTabId, nsIRemoteTab::NavigationType aNavigationType,
-    int32_t aNavigationIndex, nsIURI* aNavigationURI) {
+    int32_t aNavigationIndex, nsIURI* aNavigationURI, int32_t aEpoch) {
   MOZ_RELEASE_ASSERT(IsOnThread());
 
   mozilla::Maybe<nsCString> spec;
   if (aNavigationURI) {
     nsAutoCString tmp;
     nsresult rv = aNavigationURI->GetSpec(tmp);
     if (NS_SUCCEEDED(rv)) {
       spec.emplace(tmp);
     }
   }
 
   if (mIPCOpen) {
-    Unused << SendCancelContentJSExecutionIfRunning(aTabId, aNavigationType,
-                                                    aNavigationIndex, spec);
+    Unused << SendCancelContentJSExecutionIfRunning(
+        aTabId, aNavigationType, aNavigationIndex, spec, aEpoch);
   }
 }
 
 void HangMonitorParent::ActorDestroy(ActorDestroyReason aWhy) {
   MOZ_RELEASE_ASSERT(IsOnThread());
   mIPCOpen = false;
 }
 
--- a/dom/webidl/CancelContentJSOptions.webidl
+++ b/dom/webidl/CancelContentJSOptions.webidl
@@ -1,8 +1,9 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 dictionary CancelContentJSOptions {
   long index = 0;
   URI? uri = null;
+  long epoch = 0;
 };
--- a/toolkit/actors/WebNavigationChild.jsm
+++ b/toolkit/actors/WebNavigationChild.jsm
@@ -22,23 +22,23 @@ XPCOMUtils.defineLazyServiceGetter(this,
 class WebNavigationChild extends ActorChild {
   get webNavigation() {
     return this.mm.docShell.QueryInterface(Ci.nsIWebNavigation);
   }
 
   receiveMessage(message) {
     switch (message.name) {
       case "WebNavigation:GoBack":
-        this.goBack();
+        this.goBack(message.data);
         break;
       case "WebNavigation:GoForward":
-        this.goForward();
+        this.goForward(message.data);
         break;
       case "WebNavigation:GotoIndex":
-        this.gotoIndex(message.data.index);
+        this.gotoIndex(message.data);
         break;
       case "WebNavigation:LoadURI":
         let histogram = Services.telemetry.getKeyedHistogramById("FX_TAB_REMOTE_NAVIGATION_DELAY_MS");
         histogram.add("WebNavigation:LoadURI",
                       Services.telemetry.msSystemNow() - message.data.requestTime);
 
         this.loadURI(message.data);
 
@@ -60,42 +60,50 @@ class WebNavigationChild extends ActorCh
     try {
       fn();
     } finally {
       this.mm.WebProgress.inLoadURI = false;
       this.mm.WebProgress.sendLoadCallResult();
     }
   }
 
-  goBack() {
+  goBack(params) {
     if (this.webNavigation.canGoBack) {
+      this.mm.docShell.setCancelContentJSEpoch(params.cancelContentJSEpoch);
       this._wrapURIChangeCall(() => this.webNavigation.goBack());
     }
   }
 
-  goForward() {
+  goForward(params) {
     if (this.webNavigation.canGoForward) {
+      this.mm.docShell.setCancelContentJSEpoch(params.cancelContentJSEpoch);
       this._wrapURIChangeCall(() => this.webNavigation.goForward());
     }
   }
 
-  gotoIndex(index) {
+  gotoIndex(params) {
+    let {
+      index,
+      cancelContentJSEpoch,
+    } = params || {};
+    this.mm.docShell.setCancelContentJSEpoch(cancelContentJSEpoch);
     this._wrapURIChangeCall(() => this.webNavigation.gotoIndex(index));
   }
 
   loadURI(params) {
     let {
       uri,
       flags,
       referrerInfo,
       postData,
       headers,
       baseURI,
       triggeringPrincipal,
       csp,
+      cancelContentJSEpoch,
     } = params || {};
 
     if (AppConstants.MOZ_CRASHREPORTER && CrashReporter.enabled) {
       let annotation = uri;
       try {
         let url = Services.io.newURI(uri);
         // If the current URI contains a username/password, remove it.
         url = url.mutate()
@@ -126,16 +134,17 @@ class WebNavigationChild extends ActorCh
       triggeringPrincipal,
       csp,
       loadFlags: flags,
       referrerInfo: E10SUtils.deserializeReferrerInfo(referrerInfo),
       postData,
       headers,
       baseURI,
     };
+    this.mm.docShell.setCancelContentJSEpoch(cancelContentJSEpoch);
     this._wrapURIChangeCall(() => {
       return this.webNavigation.loadURI(uri, loadURIOptions);
     });
   }
 
   _assert(condition, msg, line = 0) {
     let debug = Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2);
     if (!condition && debug.isDebugBuild) {
--- a/toolkit/components/remotebrowserutils/RemoteWebNavigation.jsm
+++ b/toolkit/components/remotebrowserutils/RemoteWebNavigation.jsm
@@ -13,16 +13,17 @@ ChromeUtils.defineModuleGetter(this, "E1
   "resource://gre/modules/E10SUtils.jsm");
 
 function makeURI(url) {
   return Services.io.newURI(url);
 }
 
 function RemoteWebNavigation() {
   this.wrappedJSObject = this;
+  this._cancelContentJSEpoch = 1;
 }
 
 RemoteWebNavigation.prototype = {
   classDescription: "nsIWebNavigation for remote browsers",
   classID: Components.ID("{4b56964e-cdf3-4bb8-830c-0e2dad3f4ebd}"),
   contractID: "@mozilla.org/remote-web-navigation;1",
 
   QueryInterface: ChromeUtils.generateQI([Ci.nsIWebNavigation]),
@@ -50,29 +51,34 @@ RemoteWebNavigation.prototype = {
 
   STOP_NETWORK: 1,
   STOP_CONTENT: 2,
   STOP_ALL: 3,
 
   canGoBack: false,
   canGoForward: false,
   goBack() {
+    let cancelContentJSEpoch = this._cancelContentJSEpoch++;
     this._browser.frameLoader.remoteTab.maybeCancelContentJSExecution(
-      Ci.nsIRemoteTab.NAVIGATE_BACK);
-    this._sendMessage("WebNavigation:GoBack", {});
+      Ci.nsIRemoteTab.NAVIGATE_BACK, {epoch: cancelContentJSEpoch});
+    this._sendMessage("WebNavigation:GoBack", {cancelContentJSEpoch});
   },
   goForward() {
+    let cancelContentJSEpoch = this._cancelContentJSEpoch++;
     this._browser.frameLoader.remoteTab.maybeCancelContentJSExecution(
-      Ci.nsIRemoteTab.NAVIGATE_FORWARD);
-    this._sendMessage("WebNavigation:GoForward", {});
+      Ci.nsIRemoteTab.NAVIGATE_FORWARD, {epoch: cancelContentJSEpoch});
+    this._sendMessage("WebNavigation:GoForward", {cancelContentJSEpoch});
   },
   gotoIndex(aIndex) {
+    let cancelContentJSEpoch = this._cancelContentJSEpoch++;
     this._browser.frameLoader.remoteTab.maybeCancelContentJSExecution(
-      Ci.nsIRemoteTab.NAVIGATE_INDEX, {index: aIndex});
-    this._sendMessage("WebNavigation:GotoIndex", {index: aIndex});
+      Ci.nsIRemoteTab.NAVIGATE_INDEX,
+      {index: aIndex, epoch: cancelContentJSEpoch});
+    this._sendMessage("WebNavigation:GotoIndex", {index: aIndex,
+                                                  cancelContentJSEpoch});
   },
   loadURI(aURI, aLoadURIOptions) {
     let uri;
 
     // We know the url is going to be loaded, let's start requesting network
     // connection before the content process asks.
     // Note that we might have already setup the speculative connection in some
     // cases, especially when the url is from location bar or its popup menu.
@@ -92,29 +98,31 @@ RemoteWebNavigation.prototype = {
         }
         Services.io.speculativeConnect(uri, principal, null);
       } catch (ex) {
         // Can't setup speculative connection for this uri string for some
         // reason (such as failing to parse the URI), just ignore it.
       }
     }
 
+    let cancelContentJSEpoch = this._cancelContentJSEpoch++;
     this._browser.frameLoader.remoteTab.maybeCancelContentJSExecution(
-      Ci.nsIRemoteTab.NAVIGATE_URL, {uri});
+      Ci.nsIRemoteTab.NAVIGATE_URL, {uri, epoch: cancelContentJSEpoch});
     this._sendMessage("WebNavigation:LoadURI", {
       uri: aURI,
       flags: aLoadURIOptions.loadFlags,
       referrerInfo: E10SUtils.serializeReferrerInfo(aLoadURIOptions.referrerInfo),
       postData: aLoadURIOptions.postData ? Utils.serializeInputStream(aLoadURIOptions.postData) : null,
       headers: aLoadURIOptions.headers ? Utils.serializeInputStream(aLoadURIOptions.headers) : null,
       baseURI: aLoadURIOptions.baseURI ? aLoadURIOptions.baseURI.spec : null,
       triggeringPrincipal: E10SUtils.serializePrincipal(
                            aLoadURIOptions.triggeringPrincipal || Services.scriptSecurityManager.createNullPrincipal({})),
       csp: aLoadURIOptions.csp ? E10SUtils.serializeCSP(aLoadURIOptions.csp) : null,
       requestTime: Services.telemetry.msSystemNow(),
+      cancelContentJSEpoch,
     });
   },
   setOriginAttributesBeforeLoading(aOriginAttributes) {
     this._sendMessage("WebNavigation:SetOriginAttributes", {
       originAttributes: aOriginAttributes,
     });
   },
   reload(aReloadFlags) {