Bug 1493225, part 2 - Cancel content JS when navigating through history to prevent hangs r=smaug
authorJim Porter <jporter@mozilla.com>
Tue, 30 Apr 2019 23:31:36 +0000
changeset 530861 a7fa3041bff04a25fa39fb6f714071314f3cc69b
parent 530860 5c3bff8f45d696710545579b14346a65c07aa2cf
child 530862 100acc204e5e0c5e8767740c4861c906e21139f5
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 2 - Cancel content JS when navigating through history to prevent hangs r=smaug In this part, we pass along the navigation type (and index for when using the dropdown on the back/forward buttons). This allows us to check if there's a top-level load "between" the start and end pages. The patch might look a bit strange, since we're passing the navigation operation to two places from RemoteWebNavigation.js (the normal message passing that existed before this patch and the HangMonitor channel in this patch). This is primarily to make it easier to stop passing the navigation info along the HangMonitor channel once session history is uplifted into the parent process. At that point, the check for whether there's a top-level load could happen in TabParent (I think). Differential Revision: https://phabricator.services.mozilla.com/D23090
dom/interfaces/base/nsIRemoteTab.idl
dom/ipc/BrowserChild.cpp
dom/ipc/BrowserChild.h
dom/ipc/BrowserParent.cpp
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PProcessHangMonitor.ipdl
dom/ipc/ProcessHangMonitor.cpp
dom/ipc/ProcessHangMonitor.h
dom/ipc/TabMessageUtils.h
dom/webidl/CancelContentJSOptions.webidl
dom/webidl/moz.build
toolkit/components/remotebrowserutils/RemoteWebNavigation.jsm
--- a/dom/interfaces/base/nsIRemoteTab.idl
+++ b/dom/interfaces/base/nsIRemoteTab.idl
@@ -117,15 +117,25 @@ interface nsIRemoteTab : nsISupports
    * 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();
 
+  cenum NavigationType : 8 {
+    NAVIGATE_BACK = 0,
+    NAVIGATE_FORWARD = 1,
+    NAVIGATE_INDEX = 2,
+    NAVIGATE_URL = 3
+  };
+
   /**
    * 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();
+  [implicit_jscontext, binaryname(MaybeCancelContentJSExecutionFromScript)]
+  void maybeCancelContentJSExecution(
+      in nsIRemoteTab_NavigationType aNavigationType,
+      [optional] in jsval aCancelContentJSOptions);
 };
--- a/dom/ipc/BrowserChild.cpp
+++ b/dom/ipc/BrowserChild.cpp
@@ -114,16 +114,17 @@
 #include "nsIURILoader.h"
 #include "nsIScriptError.h"
 #include "mozilla/EventForwards.h"
 #include "nsDeviceContext.h"
 #include "nsSandboxFlags.h"
 #include "FrameLayerBuilder.h"
 #include "VRManagerChild.h"
 #include "nsCommandParams.h"
+#include "nsISHEntry.h"
 #include "nsISHistory.h"
 #include "nsQueryObject.h"
 #include "nsIHttpChannel.h"
 #include "mozilla/dom/DocGroup.h"
 #include "nsString.h"
 #include "nsISupportsPrimitives.h"
 #include "mozilla/Telemetry.h"
 #include "nsDocShellLoadState.h"
@@ -3296,16 +3297,106 @@ void BrowserChild::PaintWhileInterruptin
     // message on the PContent channel.
     return;
   }
 
   nsAutoScriptBlocker scriptBlocker;
   RecvRenderLayers(true /* aEnabled */, aForceRepaint, aEpoch);
 }
 
+nsresult BrowserChild::CanCancelContentJS(
+    nsIRemoteTab::NavigationType aNavigationType, int32_t aNavigationIndex,
+    nsIURI* aNavigationURI, bool* aCanCancel) {
+  nsresult rv;
+  *aCanCancel = false;
+
+  nsCOMPtr<nsISHistory> history = do_GetInterface(WebNavigation());
+  if (!history) {
+    return NS_ERROR_FAILURE;
+  }
+
+  int32_t current;
+  rv = history->GetIndex(&current);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (current == -1) {
+    // This tab has no history! Just return.
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsISHEntry> entry;
+  rv = history->GetEntryAtIndex(current, getter_AddRefs(entry));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (aNavigationType == nsIRemoteTab::NAVIGATE_BACK) {
+    aNavigationIndex = current - 1;
+  } else if (aNavigationType == nsIRemoteTab::NAVIGATE_FORWARD) {
+    aNavigationIndex = current + 1;
+  } else if (aNavigationType == nsIRemoteTab::NAVIGATE_URL) {
+    if (!aNavigationURI) {
+      return NS_ERROR_FAILURE;
+    }
+
+    nsCOMPtr<nsIURI> currentURI = entry->GetURI();
+    CanCancelContentJSBetweenURIs(currentURI, aNavigationURI, aCanCancel);
+    NS_ENSURE_SUCCESS(rv, rv);
+    return NS_OK;
+  }
+  // Note: aNavigationType may also be NAVIGATE_INDEX, in which case we don't
+  // need to do anything special.
+
+  int32_t delta = aNavigationIndex > current ? 1 : -1;
+  for (int32_t i = current + delta; i != aNavigationIndex + delta; i += delta) {
+    nsCOMPtr<nsISHEntry> nextEntry;
+    // If `i` happens to be negative, this call will fail (which is what we
+    // would want to happen).
+    rv = history->GetEntryAtIndex(i, getter_AddRefs(nextEntry));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<nsISHEntry> laterEntry = delta == 1 ? nextEntry : entry;
+    nsCOMPtr<nsIURI> uri = entry->GetURI();
+    nsCOMPtr<nsIURI> nextURI = nextEntry->GetURI();
+
+    // If we changed origin and the load wasn't in a subframe, we know it was
+    // a full document load, so we can cancel the content JS safely.
+    if (!laterEntry->GetIsSubFrame()) {
+      CanCancelContentJSBetweenURIs(uri, nextURI, aCanCancel);
+      NS_ENSURE_SUCCESS(rv, rv);
+      if (*aCanCancel) {
+        return NS_OK;
+      }
+    }
+
+    entry = nextEntry;
+  }
+
+  return NS_OK;
+}
+
+nsresult BrowserChild::CanCancelContentJSBetweenURIs(nsIURI* aFirstURI,
+                                                     nsIURI* aSecondURI,
+                                                     bool* aCanCancel) {
+  nsresult rv;
+  *aCanCancel = false;
+
+  nsAutoCString firstHost;
+  rv = aFirstURI->GetHostPort(firstHost);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsAutoCString secondHost;
+  rv = aSecondURI->GetHostPort(secondHost);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!firstHost.Equals(secondHost)) {
+    *aCanCancel = true;
+  }
+
+  return NS_OK;
+}
+
 void BrowserChild::BeforeUnloadAdded() {
   // Don't bother notifying the parent if we don't have an IPC link open.
   if (mBeforeUnloadListeners == 0 && IPCOpen()) {
     SendSetHasBeforeUnload(true);
   }
 
   mBeforeUnloadListeners++;
   MOZ_ASSERT(mBeforeUnloadListeners >= 0);
--- a/dom/ipc/BrowserChild.h
+++ b/dom/ipc/BrowserChild.h
@@ -607,16 +607,20 @@ class BrowserChild final : public Browse
   void ZoomToRect(const uint32_t& aPresShellId,
                   const ScrollableLayerGuid::ViewID& aViewId,
                   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);
+
   layers::LayersObserverEpoch LayersObserverEpoch() const {
     return mLayersObserverEpoch;
   }
 
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
   uintptr_t GetNativeWindowHandle() const { return mNativeWindowHandle; }
 #endif
 
@@ -803,16 +807,19 @@ class BrowserChild final : public Browse
   bool CreateRemoteLayerManager(
       mozilla::layers::PCompositorBridgeChild* aCompositorChild);
 
   nsresult PrepareProgressListenerData(nsIWebProgress* aWebProgress,
                                        nsIRequest* aRequest,
                                        Maybe<WebProgressData>& aWebProgressData,
                                        RequestData& aRequestData);
 
+  nsresult CanCancelContentJSBetweenURIs(nsIURI* aFirstURI, nsIURI* aSecondURI,
+                                         bool* aCanCancel);
+
   class DelayedDeleteRunnable;
 
   TextureFactoryIdentifier mTextureFactoryIdentifier;
   RefPtr<nsWebBrowser> mWebBrowser;
   nsCOMPtr<nsIWebNavigation> mWebNav;
   RefPtr<mozilla::dom::TabGroup> mTabGroup;
   RefPtr<PuppetWidget> mPuppetWidget;
   nsCOMPtr<nsIURI> mLastURI;
--- a/dom/ipc/BrowserParent.cpp
+++ b/dom/ipc/BrowserParent.cpp
@@ -8,16 +8,17 @@
 
 #include "BrowserParent.h"
 
 #ifdef ACCESSIBILITY
 #  include "mozilla/a11y/DocAccessibleParent.h"
 #  include "nsAccessibilityService.h"
 #endif
 #include "mozilla/BrowserElementParent.h"
+#include "mozilla/dom/CancelContentJSOptionsBinding.h"
 #include "mozilla/dom/ChromeMessageSender.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/DataTransfer.h"
 #include "mozilla/dom/DataTransferItemList.h"
 #include "mozilla/dom/Event.h"
 #include "mozilla/dom/FrameCrashedEvent.h"
 #include "mozilla/dom/indexedDB/ActorsParent.h"
 #include "mozilla/dom/IPCBlobUtils.h"
@@ -3116,19 +3117,26 @@ BrowserParent::GetContentBlockingLog(Pro
       [jsPromise](ResponseRejectReason&& aReason) {
         jsPromise->MaybeRejectWithUndefined();
       });
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-BrowserParent::MaybeCancelContentJSExecution() {
+BrowserParent::MaybeCancelContentJSExecutionFromScript(
+    nsIRemoteTab::NavigationType aNavigationType,
+    JS::Handle<JS::Value> aCancelContentJSOptions, JSContext* aCx) {
+  dom::CancelContentJSOptions cancelContentJSOptions;
+  if (!cancelContentJSOptions.Init(aCx, aCancelContentJSOptions)) {
+    return NS_ERROR_INVALID_ARG;
+  }
   if (StaticPrefs::dom_ipc_cancel_content_js_when_navigating()) {
-    Manager()->CancelContentJSExecutionIfRunning(this);
+    Manager()->CancelContentJSExecutionIfRunning(this, aNavigationType,
+                                                 cancelContentJSOptions);
   }
   return NS_OK;
 }
 
 void BrowserParent::SuppressDisplayport(bool aEnabled) {
   if (IsDestroyed()) {
     return;
   }
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -35,16 +35,17 @@
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Components.h"
 #include "mozilla/StyleSheetInlines.h"
 #include "mozilla/DataStorage.h"
 #include "mozilla/devtools/HeapSnapshotTempFileHelperParent.h"
 #include "mozilla/docshell/OfflineCacheUpdateParent.h"
 #include "mozilla/dom/BrowsingContext.h"
 #include "mozilla/dom/BrowsingContextGroup.h"
+#include "mozilla/dom/CancelContentJSOptionsBinding.h"
 #include "mozilla/dom/CanonicalBrowsingContext.h"
 #include "mozilla/dom/ClientManager.h"
 #include "mozilla/dom/ClientOpenWindowOpActors.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/DataTransfer.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/FileSystemSecurity.h"
@@ -5291,22 +5292,24 @@ void ContentParent::PaintTabWhileInterru
   if (!mHangMonitorActor) {
     return;
   }
   ProcessHangMonitor::PaintWhileInterruptingJS(
       mHangMonitorActor, aBrowserParent, aForceRepaint, aEpoch);
 }
 
 void ContentParent::CancelContentJSExecutionIfRunning(
-    BrowserParent* aBrowserParent) {
+    BrowserParent* aBrowserParent, nsIRemoteTab::NavigationType aNavigationType,
+    const CancelContentJSOptions& aCancelContentJSOptions) {
   if (!mHangMonitorActor) {
     return;
   }
-  ProcessHangMonitor::CancelContentJSExecutionIfRunning(mHangMonitorActor,
-                                                        aBrowserParent);
+  ProcessHangMonitor::CancelContentJSExecutionIfRunning(
+      mHangMonitorActor, aBrowserParent, aNavigationType,
+      aCancelContentJSOptions);
 }
 
 void ContentParent::UpdateCookieStatus(nsIChannel* aChannel) {
   PNeckoParent* neckoParent = LoneManagedOrNullAsserts(ManagedPNeckoParent());
   PCookieServiceParent* csParent =
       LoneManagedOrNullAsserts(neckoParent->ManagedPCookieServiceParent());
   if (csParent) {
     auto* cs = static_cast<CookieServiceParent*>(csParent);
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -27,16 +27,17 @@
 #include "mozilla/UniquePtr.h"
 
 #include "nsDataHashtable.h"
 #include "nsPluginTags.h"
 #include "nsFrameMessageManager.h"
 #include "nsHashKeys.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIObserver.h"
+#include "nsIRemoteTab.h"
 #include "nsIThreadInternal.h"
 #include "nsIDOMGeoPositionCallback.h"
 #include "nsIDOMGeoPositionErrorCallback.h"
 #include "nsRefPtrHashtable.h"
 #include "PermissionMessageUtils.h"
 #include "DriverCrashGuard.h"
 #include "nsIReferrerInfo.h"
 
@@ -104,16 +105,17 @@ namespace dom {
 class BrowsingContextGroup;
 class Element;
 class BrowserParent;
 class ClonedMessageData;
 class MemoryReport;
 class TabContext;
 class GetFilesHelper;
 class MemoryReportRequestHost;
+struct CancelContentJSOptions;
 
 #define NS_CONTENTPARENT_IID                         \
   {                                                  \
     0xeeec9ebf, 0x8ecf, 0x4e38, {                    \
       0x81, 0xda, 0xb7, 0x34, 0x13, 0x7e, 0xac, 0xf3 \
     }                                                \
   }
 
@@ -586,17 +588,20 @@ 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);
+  void CancelContentJSExecutionIfRunning(
+      BrowserParent* aBrowserParent,
+      nsIRemoteTab::NavigationType aNavigationType,
+      const CancelContentJSOptions& aCancelContentJSOptions);
 
   // 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
@@ -1,15 +1,19 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * vim: sw=2 ts=8 et :
  */
 /* 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/. */
 
+// ParamTraits stuff for nsIRemoteTab::NavigationType
+include "mozilla/dom/TabMessageUtils.h";
+using nsIRemoteTab::NavigationType from "nsIRemoteTab.h";
+
 using base::ProcessId from "base/process.h";
 using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h";
 using mozilla::layers::LayersObserverEpoch from "mozilla/layers/LayersTypes.h";
 
 namespace mozilla {
 
 struct SlowScriptData
 {
@@ -39,12 +43,14 @@ parent:
 child:
   async TerminateScript(bool aTerminateGlobal);
 
   async BeginStartingDebugger();
   async EndStartingDebugger();
 
   async PaintWhileInterruptingJS(TabId tabId, bool forceRepaint, LayersObserverEpoch aEpoch);
 
-  async CancelContentJSExecutionIfRunning(TabId tabId);
+  async CancelContentJSExecutionIfRunning(
+      TabId tabId, NavigationType aNavigationType,
+      int32_t aNavigationIndex, nsCString? aNavigationURI);
 };
 
 } // namespace mozilla
--- a/dom/ipc/ProcessHangMonitor.cpp
+++ b/dom/ipc/ProcessHangMonitor.cpp
@@ -7,32 +7,34 @@
 #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/CancelContentJSOptionsBinding.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"
 #include "mozilla/ipc/TaskFactory.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/plugins/PluginBridge.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Unused.h"
 #include "mozilla/WeakPtr.h"
 
 #include "nsExceptionHandler.h"
 #include "nsFrameLoader.h"
 #include "nsIHangReport.h"
 #include "nsIRemoteTab.h"
+#include "nsNetUtil.h"
 #include "nsQueryObject.h"
 #include "nsPluginHost.h"
 #include "nsThreadUtils.h"
 
 #include "base/task.h"
 #include "base/thread.h"
 
 #ifdef XP_WIN
@@ -109,17 +111,19 @@ class HangMonitorChild : public PProcess
   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;
+      const TabId& aTabId, const nsIRemoteTab::NavigationType& aNavigationType,
+      const int32_t& aNavigationIndex,
+      const mozilla::Maybe<nsCString>& aNavigationURI) override;
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
   void InterruptCallback();
   void Shutdown();
 
   static HangMonitorChild* Get() { return sInstance; }
 
@@ -149,16 +153,21 @@ class HangMonitorChild : public PProcess
   bool mTerminateScript;
   bool mTerminateGlobal;
   bool mStartDebugger;
   bool mFinishedStartingDebugger;
   bool mPaintWhileInterruptingJS;
   bool mPaintWhileInterruptingJSForce;
   TabId mPaintWhileInterruptingJSTab;
   MOZ_INIT_OUTSIDE_CTOR LayersObserverEpoch mPaintWhileInterruptingJSEpoch;
+  bool mCancelContentJS;
+  TabId mCancelContentJSTab;
+  nsIRemoteTab::NavigationType mCancelContentJSNavigationType;
+  int32_t mCancelContentJSNavigationIndex;
+  mozilla::Maybe<nsCString> mCancelContentJSNavigationURI;
   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.
@@ -233,17 +242,20 @@ 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 CancelContentJSExecutionIfRunning(
+      dom::BrowserParent* aBrowserParent,
+      nsIRemoteTab::NavigationType aNavigationType,
+      const dom::CancelContentJSOptions& aCancelContentJSOptions);
 
   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
@@ -262,17 +274,19 @@ 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 CancelContentJSExecutionIfRunningOnThread(
+      TabId aTabId, nsIRemoteTab::NavigationType aNavigationType,
+      int32_t aNavigationIndex, nsIURI* aNavigationURI);
 
   void ShutdownOnThread();
 
   const RefPtr<ProcessHangMonitor> mHangMonitor;
 
   // This field is read-only after construction.
   bool mReportHangs;
 
@@ -306,16 +320,19 @@ HangMonitorChild::HangMonitorChild(Proce
       mMonitor("HangMonitorChild lock", recordreplay::Behavior::DontPreserve),
       mSentReport(false),
       mTerminateScript(false),
       mTerminateGlobal(false),
       mStartDebugger(false),
       mFinishedStartingDebugger(false),
       mPaintWhileInterruptingJS(false),
       mPaintWhileInterruptingJSForce(false),
+      mCancelContentJS(false),
+      mCancelContentJSNavigationType(nsIRemoteTab::NAVIGATE_BACK),
+      mCancelContentJSNavigationIndex(0),
       mShutdownDone(false),
       mIPCOpen(true),
       mPaintWhileInterruptingJSActive(false) {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   mContext = danger::GetJSContext();
 
   BackgroundHangMonitor::RegisterAnnotator(*this);
 }
@@ -329,37 +346,79 @@ HangMonitorChild::~HangMonitorChild() {
 void HangMonitorChild::InterruptCallback() {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   bool paintWhileInterruptingJS;
   bool paintWhileInterruptingJSForce;
   TabId paintWhileInterruptingJSTab;
   LayersObserverEpoch paintWhileInterruptingJSEpoch;
 
+  bool cancelContentJS;
+  TabId cancelContentJSTab;
+  nsIRemoteTab::NavigationType cancelContentJSNavigationType;
+  int32_t cancelContentJSNavigationIndex;
+  mozilla::Maybe<nsCString> cancelContentJSNavigationURI;
+
   {
     MonitorAutoLock lock(mMonitor);
     paintWhileInterruptingJS = mPaintWhileInterruptingJS;
     paintWhileInterruptingJSForce = mPaintWhileInterruptingJSForce;
     paintWhileInterruptingJSTab = mPaintWhileInterruptingJSTab;
     paintWhileInterruptingJSEpoch = mPaintWhileInterruptingJSEpoch;
 
+    cancelContentJS = mCancelContentJS;
+    cancelContentJSTab = mCancelContentJSTab;
+    cancelContentJSNavigationType = mCancelContentJSNavigationType;
+    cancelContentJSNavigationIndex = mCancelContentJSNavigationIndex;
+    cancelContentJSNavigationURI = std::move(mCancelContentJSNavigationURI);
+
     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()) {
     RefPtr<BrowserChild> browserChild =
         BrowserChild::FindBrowserChild(paintWhileInterruptingJSTab);
     if (browserChild) {
       js::AutoAssertNoContentJS nojs(mContext);
       browserChild->PaintWhileInterruptingJS(paintWhileInterruptingJSEpoch,
                                              paintWhileInterruptingJSForce);
     }
   }
+
+  if (cancelContentJS) {
+    RefPtr<BrowserChild> browserChild =
+        BrowserChild::FindBrowserChild(cancelContentJSTab);
+    if (browserChild) {
+      js::AutoAssertNoContentJS nojs(mContext);
+      nsresult rv;
+      nsCOMPtr<nsIURI> uri;
+
+      if (cancelContentJSNavigationURI) {
+        rv = NS_NewURI(getter_AddRefs(uri),
+                       cancelContentJSNavigationURI.value());
+        if (NS_FAILED(rv)) {
+          return;
+        }
+      }
+
+      bool canCancel;
+      rv = browserChild->CanCancelContentJS(cancelContentJSNavigationType,
+                                            cancelContentJSNavigationIndex, uri,
+                                            &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);
+      }
+    }
+  }
 }
 
 void HangMonitorChild::AnnotateHang(BackgroundHangAnnotations& aAnnotations) {
   if (mPaintWhileInterruptingJSActive) {
     aAnnotations.AddAnnotation(NS_LITERAL_STRING("PaintWhileInterruptingJS"),
                                true);
   }
 }
@@ -450,22 +509,30 @@ 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) {
+    const TabId& aTabId, const nsIRemoteTab::NavigationType& aNavigationType,
+    const int32_t& aNavigationIndex,
+    const mozilla::Maybe<nsCString>& aNavigationURI) {
   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);
+  {
+    MonitorAutoLock lock(mMonitor);
+    mCancelContentJS = true;
+    mCancelContentJSTab = aTabId;
+    mCancelContentJSNavigationType = aNavigationType;
+    mCancelContentJSNavigationIndex = aNavigationIndex;
+    mCancelContentJSNavigationURI = aNavigationURI;
+  }
+
   JS_RequestInterruptCallback(mContext);
 
   return IPC_OK();
 }
 
 void HangMonitorChild::Bind(Endpoint<PProcessHangMonitorChild>&& aEndpoint) {
   MOZ_RELEASE_ASSERT(IsOnThread());
 
@@ -670,31 +737,47 @@ void HangMonitorParent::PaintWhileInterr
   MOZ_RELEASE_ASSERT(IsOnThread());
 
   if (mIPCOpen) {
     Unused << SendPaintWhileInterruptingJS(aTabId, aForceRepaint, aEpoch);
   }
 }
 
 void HangMonitorParent::CancelContentJSExecutionIfRunning(
-    dom::BrowserParent* aBrowserParent) {
+    dom::BrowserParent* aBrowserParent,
+    nsIRemoteTab::NavigationType aNavigationType,
+    const dom::CancelContentJSOptions& aCancelContentJSOptions) {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   TabId id = aBrowserParent->GetTabId();
-  Dispatch(NewNonOwningRunnableMethod<TabId>(
+  Dispatch(NewNonOwningRunnableMethod<TabId, nsIRemoteTab::NavigationType,
+                                      int32_t, nsIURI*>(
       "HangMonitorParent::CancelContentJSExecutionIfRunningOnThread", this,
-      &HangMonitorParent::CancelContentJSExecutionIfRunningOnThread, id));
+      &HangMonitorParent::CancelContentJSExecutionIfRunningOnThread, id,
+      aNavigationType, aCancelContentJSOptions.mIndex,
+      aCancelContentJSOptions.mUri));
 }
 
 void HangMonitorParent::CancelContentJSExecutionIfRunningOnThread(
-    TabId aTabId) {
+    TabId aTabId, nsIRemoteTab::NavigationType aNavigationType,
+    int32_t aNavigationIndex, nsIURI* aNavigationURI) {
   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);
+    Unused << SendCancelContentJSExecutionIfRunning(aTabId, aNavigationType,
+                                                    aNavigationIndex, spec);
   }
 }
 
 void HangMonitorParent::ActorDestroy(ActorDestroyReason aWhy) {
   MOZ_RELEASE_ASSERT(IsOnThread());
   mIPCOpen = false;
 }
 
@@ -1291,13 +1374,16 @@ void ProcessHangMonitor::MaybeStartPaint
 
   if (HangMonitorChild* child = HangMonitorChild::Get()) {
     child->MaybeStartPaintWhileInterruptingJS();
   }
 }
 
 /* static */
 void ProcessHangMonitor::CancelContentJSExecutionIfRunning(
-    PProcessHangMonitorParent* aParent, dom::BrowserParent* aBrowserParent) {
+    PProcessHangMonitorParent* aParent, dom::BrowserParent* aBrowserParent,
+    nsIRemoteTab::NavigationType aNavigationType,
+    const dom::CancelContentJSOptions& aCancelContentJSOptions) {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   auto parent = static_cast<HangMonitorParent*>(aParent);
-  parent->CancelContentJSExecutionIfRunning(aBrowserParent);
+  parent->CancelContentJSExecutionIfRunning(aBrowserParent, aNavigationType,
+                                            aCancelContentJSOptions);
 }
--- a/dom/ipc/ProcessHangMonitor.h
+++ b/dom/ipc/ProcessHangMonitor.h
@@ -6,27 +6,29 @@
 
 #ifndef mozilla_ProcessHangMonitor_h
 #define mozilla_ProcessHangMonitor_h
 
 #include "mozilla/AlreadyAddRefed.h"
 #include "mozilla/Atomics.h"
 #include "nsCOMPtr.h"
 #include "nsIObserver.h"
+#include "nsIRemoteTab.h"
 #include "nsStringFwd.h"
 
 class nsIRunnable;
 class nsIBrowserChild;
 class nsIThread;
 
 namespace mozilla {
 
 namespace dom {
 class ContentParent;
 class BrowserParent;
+struct CancelContentJSOptions;
 }  // namespace dom
 
 namespace layers {
 struct LayersObserverEpoch;
 }  // namespace layers
 
 class PProcessHangMonitorParent;
 
@@ -51,17 +53,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);
+      PProcessHangMonitorParent* aParent, dom::BrowserParent* aTab,
+      nsIRemoteTab::NavigationType aNavigationType,
+      const dom::CancelContentJSOptions& aCancelContentJSOptions);
 
   enum SlowScriptAction {
     Continue,
     Terminate,
     StartDebugger,
     TerminateGlobal,
   };
   SlowScriptAction NotifySlowScript(nsIBrowserChild* aBrowserChild,
--- a/dom/ipc/TabMessageUtils.h
+++ b/dom/ipc/TabMessageUtils.h
@@ -6,16 +6,17 @@
 
 #ifndef TABMESSAGE_UTILS_H
 #define TABMESSAGE_UTILS_H
 
 #include "ipc/IPCMessageUtils.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/dom/Event.h"
 #include "nsExceptionHandler.h"
+#include "nsIRemoteTab.h"
 #include "nsPIDOMWindow.h"
 #include "nsCOMPtr.h"
 
 namespace mozilla {
 namespace dom {
 class Event;
 
 struct RemoteDOMEvent {
@@ -60,11 +61,18 @@ struct ParamTraits<nsSizeMode>
                                       nsSizeMode_Invalid> {};
 
 template <>
 struct ParamTraits<UIStateChangeType>
     : public ContiguousEnumSerializer<UIStateChangeType,
                                       UIStateChangeType_NoChange,
                                       UIStateChangeType_Invalid> {};
 
+template <>
+struct ParamTraits<nsIRemoteTab::NavigationType>
+    : public ContiguousEnumSerializerInclusive<
+          nsIRemoteTab::NavigationType,
+          nsIRemoteTab::NavigationType::NAVIGATE_BACK,
+          nsIRemoteTab::NavigationType::NAVIGATE_URL> {};
+
 }  // namespace IPC
 
 #endif  // TABMESSAGE_UTILS_H
new file mode 100644
--- /dev/null
+++ b/dom/webidl/CancelContentJSOptions.webidl
@@ -0,0 +1,8 @@
+/* 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;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -401,16 +401,17 @@ WEBIDL_FILES = [
     'BiquadFilterNode.webidl',
     'Blob.webidl',
     'BoxObject.webidl',
     'BroadcastChannel.webidl',
     'BrowserElement.webidl',
     'BrowserElementDictionaries.webidl',
     'Cache.webidl',
     'CacheStorage.webidl',
+    'CancelContentJSOptions.webidl',
     'CanvasCaptureMediaStream.webidl',
     'CanvasRenderingContext2D.webidl',
     'CaretPosition.webidl',
     'CDATASection.webidl',
     'ChannelMergerNode.webidl',
     'ChannelSplitterNode.webidl',
     'CharacterData.webidl',
     'CheckerboardReportService.webidl',
--- a/toolkit/components/remotebrowserutils/RemoteWebNavigation.jsm
+++ b/toolkit/components/remotebrowserutils/RemoteWebNavigation.jsm
@@ -50,35 +50,40 @@ RemoteWebNavigation.prototype = {
 
   STOP_NETWORK: 1,
   STOP_CONTENT: 2,
   STOP_ALL: 3,
 
   canGoBack: false,
   canGoForward: false,
   goBack() {
-    this._browser.frameLoader.remoteTab.maybeCancelContentJSExecution();
+    this._browser.frameLoader.remoteTab.maybeCancelContentJSExecution(
+      Ci.nsIRemoteTab.NAVIGATE_BACK);
     this._sendMessage("WebNavigation:GoBack", {});
   },
   goForward() {
-    this._browser.frameLoader.remoteTab.maybeCancelContentJSExecution();
+    this._browser.frameLoader.remoteTab.maybeCancelContentJSExecution(
+      Ci.nsIRemoteTab.NAVIGATE_FORWARD);
     this._sendMessage("WebNavigation:GoForward", {});
   },
   gotoIndex(aIndex) {
-    this._browser.frameLoader.remoteTab.maybeCancelContentJSExecution();
+    this._browser.frameLoader.remoteTab.maybeCancelContentJSExecution(
+      Ci.nsIRemoteTab.NAVIGATE_INDEX, {index: aIndex});
     this._sendMessage("WebNavigation:GotoIndex", {index: aIndex});
   },
   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.
     if (aURI.startsWith("http:") || aURI.startsWith("https:")) {
       try {
-        let uri = makeURI(aURI);
+        uri = makeURI(aURI);
         let principal = aLoadURIOptions.triggeringPrincipal;
         // We usually have a triggeringPrincipal assigned, but in case we
         // don't have one or if it's a SystemPrincipal, let's create it with OA
         // inferred from the current context.
         if (!principal || principal.isSystemPrincipal) {
           let attrs = {
             userContextId: this._browser.getAttribute("usercontextid") || 0,
             privateBrowsingId: PrivateBrowsingUtils.isBrowserPrivate(this._browser) ? 1 : 0,
@@ -87,17 +92,18 @@ 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._browser.frameLoader.remoteTab.maybeCancelContentJSExecution(
+      Ci.nsIRemoteTab.NAVIGATE_URL, {uri});
     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(