Bug 1493225, part 1 - Cancel content JS when navigating through history to prevent hangs r=smaug
authorJim Porter <jporter@mozilla.com>
Tue, 30 Apr 2019 23:56:17 +0000
changeset 472060 5c3bff8f45d696710545579b14346a65c07aa2cf
parent 472059 9242458b79d5ae6e9d48c9a2a591680d79c13211
child 472061 a7fa3041bff04a25fa39fb6f714071314f3cc69b
push id84445
push userjporter@mozilla.com
push dateWed, 01 May 2019 03:29:15 +0000
treeherderautoland@9a0ce3016f03 [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 1 - Cancel content JS when navigating through history to prevent hangs r=smaug This patch passes a message through the HangMonitor channel when navigating through history to cancel content JS that could hang the chrome JS in the content process responsible for history navigation. If the content JS is actually canceled, this also disables the BF cache for the current page, since it could end up in an inconsistent state due to the JS cancellation. Differential Revision: https://phabricator.services.mozilla.com/D23089
dom/base/nsGlobalWindowInner.h
dom/interfaces/base/nsIRemoteTab.idl
dom/ipc/BrowserParent.cpp
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PProcessHangMonitor.ipdl
dom/ipc/ProcessHangMonitor.cpp
dom/ipc/ProcessHangMonitor.h
js/xpconnect/src/XPCJSContext.cpp
js/xpconnect/src/xpcprivate.h
modules/libpref/init/StaticPrefList.h
toolkit/components/remotebrowserutils/RemoteWebNavigation.jsm
--- a/dom/base/nsGlobalWindowInner.h
+++ b/dom/base/nsGlobalWindowInner.h
@@ -389,16 +389,18 @@ class nsGlobalWindowInner final : public
   nsPIDOMWindowOuter* GetScriptableTop() override;
   inline nsGlobalWindowOuter* GetTopInternal();
 
   inline nsGlobalWindowOuter* GetScriptableTopInternal();
 
   already_AddRefed<mozilla::dom::BrowsingContext> GetChildWindow(
       const nsAString& aName);
 
+  inline nsIBrowserChild* GetBrowserChild() { return mBrowserChild.get(); }
+
   // These return true if we've reached the state in this top level window
   // where we ask the user if further dialogs should be blocked.
   //
   // DialogsAreBeingAbused must be called on the scriptable top inner window.
   //
   // nsGlobalWindowOuter::ShouldPromptToBlockDialogs is implemented in terms of
   // nsGlobalWindowInner::DialogsAreBeingAbused, and will get the scriptable top
   // inner window automatically. Inner windows only.
--- a/dom/interfaces/base/nsIRemoteTab.idl
+++ b/dom/interfaces/base/nsIRemoteTab.idl
@@ -116,9 +116,16 @@ interface nsIRemoteTab : nsISupports
    * content blocking events happened so far in the current tab from the
    * content process.
    *
    * This returns a Promise which resolves to a string on success, and is
    * rejected on failure.  For documentation on the string format, please
    * see nsISecureBrowserUI.contentBlockingLogJSON.
    */
   Promise getContentBlockingLog();
+
+  /**
+   * Interrupt content scripts if possible/needed to allow chrome scripts in the
+   * content process to run (in particular, to allow navigating through browser
+   * history.
+   */
+  void maybeCancelContentJSExecution();
 };
--- a/dom/ipc/BrowserParent.cpp
+++ b/dom/ipc/BrowserParent.cpp
@@ -36,16 +36,18 @@
 #include "mozilla/layers/InputAPZContext.h"
 #include "mozilla/layout/RenderFrame.h"
 #include "mozilla/plugins/PPluginWidgetParent.h"
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/net/NeckoChild.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/PresShell.h"
+#include "mozilla/ProcessHangMonitor.h"
+#include "mozilla/StaticPrefs.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/TouchEvents.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/Unused.h"
 #include "nsCOMPtr.h"
 #include "nsContentAreaDragDrop.h"
 #include "nsContentUtils.h"
 #include "nsDebug.h"
@@ -3113,16 +3115,24 @@ BrowserParent::GetContentBlockingLog(Pro
       },
       [jsPromise](ResponseRejectReason&& aReason) {
         jsPromise->MaybeRejectWithUndefined();
       });
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+BrowserParent::MaybeCancelContentJSExecution() {
+  if (StaticPrefs::dom_ipc_cancel_content_js_when_navigating()) {
+    Manager()->CancelContentJSExecutionIfRunning(this);
+  }
+  return NS_OK;
+}
+
 void BrowserParent::SuppressDisplayport(bool aEnabled) {
   if (IsDestroyed()) {
     return;
   }
 
 #ifdef DEBUG
   if (aEnabled) {
     mActiveSupressDisplayportCount++;
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -5290,16 +5290,25 @@ void ContentParent::PaintTabWhileInterru
     const layers::LayersObserverEpoch& aEpoch) {
   if (!mHangMonitorActor) {
     return;
   }
   ProcessHangMonitor::PaintWhileInterruptingJS(
       mHangMonitorActor, aBrowserParent, aForceRepaint, aEpoch);
 }
 
+void ContentParent::CancelContentJSExecutionIfRunning(
+    BrowserParent* aBrowserParent) {
+  if (!mHangMonitorActor) {
+    return;
+  }
+  ProcessHangMonitor::CancelContentJSExecutionIfRunning(mHangMonitorActor,
+                                                        aBrowserParent);
+}
+
 void ContentParent::UpdateCookieStatus(nsIChannel* aChannel) {
   PNeckoParent* neckoParent = LoneManagedOrNullAsserts(ManagedPNeckoParent());
   PCookieServiceParent* csParent =
       LoneManagedOrNullAsserts(neckoParent->ManagedPCookieServiceParent());
   if (csParent) {
     auto* cs = static_cast<CookieServiceParent*>(csParent);
     cs->TrackCookieLoad(aChannel);
   }
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -586,16 +586,18 @@ class ContentParent final : public PCont
 
   bool DeallocPURLClassifierParent(PURLClassifierParent* aActor);
 
   // Use the PHangMonitor channel to ask the child to repaint a tab.
   void PaintTabWhileInterruptingJS(BrowserParent* aBrowserParent,
                                    bool aForceRepaint,
                                    const layers::LayersObserverEpoch& aEpoch);
 
+  void CancelContentJSExecutionIfRunning(BrowserParent* aBrowserParent);
+
   // This function is called when we are about to load a document from an
   // HTTP(S) or FTP channel for a content process.  It is a useful place
   // to start to kick off work as early as possible in response to such
   // document loads.
   nsresult AboutToLoadHttpFtpDocumentForChild(nsIChannel* aChannel);
 
   nsresult TransmitPermissionsForPrincipal(nsIPrincipal* aPrincipal);
 
--- a/dom/ipc/PProcessHangMonitor.ipdl
+++ b/dom/ipc/PProcessHangMonitor.ipdl
@@ -38,11 +38,13 @@ parent:
 
 child:
   async TerminateScript(bool aTerminateGlobal);
 
   async BeginStartingDebugger();
   async EndStartingDebugger();
 
   async PaintWhileInterruptingJS(TabId tabId, bool forceRepaint, LayersObserverEpoch aEpoch);
+
+  async CancelContentJSExecutionIfRunning(TabId tabId);
 };
 
 } // namespace mozilla
--- a/dom/ipc/ProcessHangMonitor.cpp
+++ b/dom/ipc/ProcessHangMonitor.cpp
@@ -3,16 +3,17 @@
 /* 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/. */
 
 #include "mozilla/ProcessHangMonitor.h"
 #include "mozilla/ProcessHangMonitorIPC.h"
 
 #include "jsapi.h"
+#include "xpcprivate.h"
 
 #include "mozilla/Atomics.h"
 #include "mozilla/BackgroundHangMonitor.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/BrowserChild.h"
 #include "mozilla/dom/BrowserParent.h"
@@ -107,16 +108,19 @@ class HangMonitorChild : public PProcess
       const bool& aTerminateGlobal) override;
   mozilla::ipc::IPCResult RecvBeginStartingDebugger() override;
   mozilla::ipc::IPCResult RecvEndStartingDebugger() override;
 
   mozilla::ipc::IPCResult RecvPaintWhileInterruptingJS(
       const TabId& aTabId, const bool& aForceRepaint,
       const LayersObserverEpoch& aEpoch) override;
 
+  mozilla::ipc::IPCResult RecvCancelContentJSExecutionIfRunning(
+      const TabId& aTabId) override;
+
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
   void InterruptCallback();
   void Shutdown();
 
   static HangMonitorChild* Get() { return sInstance; }
 
   void Dispatch(already_AddRefed<nsIRunnable> aRunnable) {
@@ -229,16 +233,17 @@ class HangMonitorParent : public PProces
 
   void SetProcess(HangMonitoredProcess* aProcess) { mProcess = aProcess; }
 
   void Shutdown();
 
   void PaintWhileInterruptingJS(dom::BrowserParent* aBrowserParent,
                                 bool aForceRepaint,
                                 const LayersObserverEpoch& aEpoch);
+  void CancelContentJSExecutionIfRunning(dom::BrowserParent* aBrowserParent);
 
   void TerminateScript(bool aTerminateGlobal);
   void BeginStartingDebugger();
   void EndStartingDebugger();
   void CleanupPluginHang(uint32_t aPluginId, bool aRemoveFiles);
 
   /**
    * Update the dump for the specified plugin. This method is thread-safe and
@@ -257,16 +262,17 @@ class HangMonitorParent : public PProces
 
   void SendHangNotification(const HangData& aHangData,
                             const nsString& aBrowserDumpId, bool aTakeMinidump);
 
   void ClearHangNotification();
 
   void PaintWhileInterruptingJSOnThread(TabId aTabId, bool aForceRepaint,
                                         const LayersObserverEpoch& aEpoch);
+  void CancelContentJSExecutionIfRunningOnThread(TabId aTabId);
 
   void ShutdownOnThread();
 
   const RefPtr<ProcessHangMonitor> mHangMonitor;
 
   // This field is read-only after construction.
   bool mReportHangs;
 
@@ -443,16 +449,28 @@ void HangMonitorChild::MaybeStartPaintWh
 
 void HangMonitorChild::ClearPaintWhileInterruptingJS(
     const LayersObserverEpoch& aEpoch) {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(XRE_IsContentProcess());
   mPaintWhileInterruptingJSActive = false;
 }
 
+mozilla::ipc::IPCResult HangMonitorChild::RecvCancelContentJSExecutionIfRunning(
+    const TabId& aTabId) {
+  MOZ_RELEASE_ASSERT(IsOnThread());
+
+  // Tell xpconnect that we want to cancel the content JS in this tab during the
+  // next interrupt callback.
+  XPCJSContext::SetTabIdToCancelContentJS(aTabId);
+  JS_RequestInterruptCallback(mContext);
+
+  return IPC_OK();
+}
+
 void HangMonitorChild::Bind(Endpoint<PProcessHangMonitorChild>&& aEndpoint) {
   MOZ_RELEASE_ASSERT(IsOnThread());
 
   MOZ_ASSERT(!sInstance);
   sInstance = this;
 
   DebugOnly<bool> ok = aEndpoint.Bind(this);
   MOZ_ASSERT(ok);
@@ -651,16 +669,35 @@ void HangMonitorParent::PaintWhileInterr
     TabId aTabId, bool aForceRepaint, const LayersObserverEpoch& aEpoch) {
   MOZ_RELEASE_ASSERT(IsOnThread());
 
   if (mIPCOpen) {
     Unused << SendPaintWhileInterruptingJS(aTabId, aForceRepaint, aEpoch);
   }
 }
 
+void HangMonitorParent::CancelContentJSExecutionIfRunning(
+    dom::BrowserParent* aBrowserParent) {
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+  TabId id = aBrowserParent->GetTabId();
+  Dispatch(NewNonOwningRunnableMethod<TabId>(
+      "HangMonitorParent::CancelContentJSExecutionIfRunningOnThread", this,
+      &HangMonitorParent::CancelContentJSExecutionIfRunningOnThread, id));
+}
+
+void HangMonitorParent::CancelContentJSExecutionIfRunningOnThread(
+    TabId aTabId) {
+  MOZ_RELEASE_ASSERT(IsOnThread());
+
+  if (mIPCOpen) {
+    Unused << SendCancelContentJSExecutionIfRunning(aTabId);
+  }
+}
+
 void HangMonitorParent::ActorDestroy(ActorDestroyReason aWhy) {
   MOZ_RELEASE_ASSERT(IsOnThread());
   mIPCOpen = false;
 }
 
 void HangMonitorParent::Bind(Endpoint<PProcessHangMonitorParent>&& aEndpoint) {
   MOZ_RELEASE_ASSERT(IsOnThread());
 
@@ -1251,8 +1288,16 @@ void ProcessHangMonitor::ClearPaintWhile
 void ProcessHangMonitor::MaybeStartPaintWhileInterruptingJS() {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(XRE_IsContentProcess());
 
   if (HangMonitorChild* child = HangMonitorChild::Get()) {
     child->MaybeStartPaintWhileInterruptingJS();
   }
 }
+
+/* static */
+void ProcessHangMonitor::CancelContentJSExecutionIfRunning(
+    PProcessHangMonitorParent* aParent, dom::BrowserParent* aBrowserParent) {
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+  auto parent = static_cast<HangMonitorParent*>(aParent);
+  parent->CancelContentJSExecutionIfRunning(aBrowserParent);
+}
--- a/dom/ipc/ProcessHangMonitor.h
+++ b/dom/ipc/ProcessHangMonitor.h
@@ -50,16 +50,19 @@ class ProcessHangMonitor final : public 
 
   static void PaintWhileInterruptingJS(
       PProcessHangMonitorParent* aParent, dom::BrowserParent* aTab,
       bool aForceRepaint, const layers::LayersObserverEpoch& aEpoch);
   static void ClearPaintWhileInterruptingJS(
       const layers::LayersObserverEpoch& aEpoch);
   static void MaybeStartPaintWhileInterruptingJS();
 
+  static void CancelContentJSExecutionIfRunning(
+      PProcessHangMonitorParent* aParent, dom::BrowserParent* aTab);
+
   enum SlowScriptAction {
     Continue,
     Terminate,
     StartDebugger,
     TerminateGlobal,
   };
   SlowScriptAction NotifySlowScript(nsIBrowserChild* aBrowserChild,
                                     const char* aFileName,
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -17,16 +17,17 @@
 #include "mozJSComponentLoader.h"
 #include "nsAutoPtr.h"
 #include "nsNetUtil.h"
 #include "nsThreadUtils.h"
 
 #include "nsIMemoryInfoDumper.h"
 #include "nsIMemoryReporter.h"
 #include "nsIObserverService.h"
+#include "nsIBrowserChild.h"
 #include "nsIDebug2.h"
 #include "nsIDocShell.h"
 #include "nsIRunnable.h"
 #include "nsPIDOMWindow.h"
 #include "nsPrintfCString.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/Services.h"
@@ -633,36 +634,16 @@ bool XPCJSContext::InterruptCallback(JSC
     policy->GetId(addonId);
     prefName = PREF_MAX_SCRIPT_RUN_TIME_EXT_CONTENT;
     limit = Preferences::GetInt(prefName, 5);
   } else {
     prefName = PREF_MAX_SCRIPT_RUN_TIME_CONTENT;
     limit = Preferences::GetInt(prefName, 10);
   }
 
-  // If there's no limit, or we're within the limit, let it go.
-  if (limit == 0 || duration.ToSeconds() < limit / 2.0) {
-    return true;
-  }
-
-  self->mSlowScriptActualWait += duration;
-
-  // In order to guard against time changes or laptops going to sleep, we
-  // don't trigger the slow script warning until (limit/2) seconds have
-  // elapsed twice.
-  if (!self->mSlowScriptSecondHalf) {
-    self->mSlowScriptCheckpoint = TimeStamp::NowLoRes();
-    self->mSlowScriptSecondHalf = true;
-    return true;
-  }
-
-  //
-  // This has gone on long enough! Time to take action. ;-)
-  //
-
   // Get the DOM window associated with the running script. If the script is
   // running in a non-DOM scope, we have to just let it keep running.
   RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
   RefPtr<nsGlobalWindowInner> win = WindowOrNull(global);
   if (!win && IsSandbox(global)) {
     // If this is a sandbox associated with a DOMWindow via a
     // sandboxPrototype, use that DOMWindow. This supports GreaseMonkey
     // and JetPack content scripts.
@@ -677,16 +658,50 @@ bool XPCJSContext::InterruptCallback(JSC
     }
   }
 
   if (!win) {
     NS_WARNING("No active window");
     return true;
   }
 
+  // Check if we're waiting to cancel JS when going back/forward in a tab.
+  if (sTabIdToCancelContentJS) {
+    if (nsIBrowserChild* browserChild = win->GetBrowserChild()) {
+      uint64_t tabId;
+      browserChild->GetTabId(&tabId);
+      if (sTabIdToCancelContentJS == tabId) {
+        // Don't add this page to the BF cache, since we're cancelling its JS.
+        win->GetExtantDoc()->GetTopLevelContentDocument()->DisallowBFCaching();
+        sTabIdToCancelContentJS = 0;
+        return false;
+      }
+    }
+  }
+
+  // If there's no limit, or we're within the limit, let it go.
+  if (limit == 0 || duration.ToSeconds() < limit / 2.0) {
+    return true;
+  }
+
+  self->mSlowScriptActualWait += duration;
+
+  // In order to guard against time changes or laptops going to sleep, we
+  // don't trigger the slow script warning until (limit/2) seconds have
+  // elapsed twice.
+  if (!self->mSlowScriptSecondHalf) {
+    self->mSlowScriptCheckpoint = TimeStamp::NowLoRes();
+    self->mSlowScriptSecondHalf = true;
+    return true;
+  }
+
+  //
+  // This has gone on long enough! Time to take action. ;-)
+  //
+
   if (win->IsDying()) {
     // The window is being torn down. When that happens we try to prevent
     // the dispatch of new runnables, so it also makes sense to kill any
     // long-running script. The user is primarily interested in this page
     // going away.
     return false;
   }
 
@@ -1212,16 +1227,21 @@ nsresult XPCJSContext::Initialize(XPCJSC
 #ifdef FUZZING
   Preferences::RegisterCallback(ReloadPrefsCallback, "fuzzing.enabled", this);
 #endif
 
   return NS_OK;
 }
 
 // static
+void XPCJSContext::SetTabIdToCancelContentJS(uint64_t aTabId) {
+  sTabIdToCancelContentJS = aTabId;
+}
+
+// static
 uint32_t XPCJSContext::sInstanceCount;
 
 // static
 StaticAutoPtr<WatchdogManager> XPCJSContext::sWatchdogInstance;
 
 // static
 WatchdogManager* XPCJSContext::GetWatchdogManager() {
   if (sWatchdogInstance) {
@@ -1229,16 +1249,19 @@ WatchdogManager* XPCJSContext::GetWatchd
   }
 
   MOZ_ASSERT(sInstanceCount == 0);
   sWatchdogInstance = new WatchdogManager();
   return sWatchdogInstance;
 }
 
 // static
+mozilla::Atomic<uint64_t> XPCJSContext::sTabIdToCancelContentJS(0);
+
+// static
 void XPCJSContext::InitTLS() { MOZ_RELEASE_ASSERT(gTlsContext.init()); }
 
 // static
 XPCJSContext* XPCJSContext::NewXPCJSContext(XPCJSContext* aPrimaryContext) {
   XPCJSContext* self = new XPCJSContext();
   nsresult rv = self->Initialize(aPrimaryContext);
   if (NS_FAILED(rv)) {
     MOZ_CRASH("new XPCJSContext failed to initialize.");
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -416,16 +416,18 @@ class XPCJSContext final : public mozill
     IDX_INTERFACE_ID,
     IDX_INITIALIZER,
     IDX_TOTAL_COUNT  // just a count of the above
   };
 
   inline JS::HandleId GetStringID(unsigned index) const;
   inline const char* GetStringName(unsigned index) const;
 
+  static void SetTabIdToCancelContentJS(uint64_t aTabId);
+
  private:
   XPCJSContext();
 
   MOZ_IS_CLASS_INIT
   nsresult Initialize(XPCJSContext* aPrimaryContext);
 
   XPCCallContext* mCallContext;
   AutoMarkingPtr* mAutoRoots;
@@ -433,16 +435,18 @@ class XPCJSContext final : public mozill
   XPCWrappedNative* mResolvingWrapper;
   WatchdogManager* mWatchdogManager;
 
   // Number of XPCJSContexts currently alive.
   static uint32_t sInstanceCount;
   static mozilla::StaticAutoPtr<WatchdogManager> sWatchdogInstance;
   static WatchdogManager* GetWatchdogManager();
 
+  static mozilla::Atomic<uint64_t> sTabIdToCancelContentJS;
+
   // If we spend too much time running JS code in an event handler, then we
   // want to show the slow script UI. The timeout T is controlled by prefs. We
   // invoke the interrupt callback once after T/2 seconds and set
   // mSlowScriptSecondHalf to true. After another T/2 seconds, we invoke the
   // interrupt callback again. Since mSlowScriptSecondHalf is now true, it
   // shows the slow script UI. The reason we invoke the callback twice is to
   // ensure that putting the computer to sleep while running a script doesn't
   // cause the UI to be shown. If the laptop goes to sleep during one of the
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -1270,16 +1270,27 @@ VARCACHE_PREF(
 )
 
 VARCACHE_PREF(
   "javascript.options.experimental.await_fix",
    javascript_options_experimental_await_fix,
   RelaxedAtomicBool, false
 )
 
+#ifdef NIGHTLY_BUILD
+# define PREF_VALUE  true
+#else
+# define PREF_VALUE  false
+#endif
+VARCACHE_PREF(
+  "dom.ipc.cancel_content_js_when_navigating",
+   dom_ipc_cancel_content_js_when_navigating,
+  bool, PREF_VALUE
+)
+#undef PREF_VALUE
 
 //---------------------------------------------------------------------------
 // Media prefs
 //---------------------------------------------------------------------------
 
 // These prefs use camel case instead of snake case for the getter because one
 // reviewer had an unshakeable preference for that.
 
--- a/toolkit/components/remotebrowserutils/RemoteWebNavigation.jsm
+++ b/toolkit/components/remotebrowserutils/RemoteWebNavigation.jsm
@@ -50,22 +50,25 @@ RemoteWebNavigation.prototype = {
 
   STOP_NETWORK: 1,
   STOP_CONTENT: 2,
   STOP_ALL: 3,
 
   canGoBack: false,
   canGoForward: false,
   goBack() {
+    this._browser.frameLoader.remoteTab.maybeCancelContentJSExecution();
     this._sendMessage("WebNavigation:GoBack", {});
   },
   goForward() {
+    this._browser.frameLoader.remoteTab.maybeCancelContentJSExecution();
     this._sendMessage("WebNavigation:GoForward", {});
   },
   gotoIndex(aIndex) {
+    this._browser.frameLoader.remoteTab.maybeCancelContentJSExecution();
     this._sendMessage("WebNavigation:GotoIndex", {index: aIndex});
   },
   loadURI(aURI, aLoadURIOptions) {
     // 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.
     if (aURI.startsWith("http:") || aURI.startsWith("https:")) {
@@ -84,16 +87,17 @@ 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.
       }
     }
 
+    this._browser.frameLoader.remoteTab.maybeCancelContentJSExecution();
     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(