Bug 1356334: Part 6 - Allow completely terminating a slow content script sandbox. r=billm draft
authorKris Maglione <maglione.k@gmail.com>
Sun, 16 Apr 2017 14:01:16 -0700
changeset 645269 19a4243c5bfcad87c0440232e8e958ba17b6239e
parent 645268 08d97950076e4d792a1ed9fd332f6583643ce08b
child 645270 4bf2e5edf1494d8ba1297a7770b38ef6fa10a39c
push id73722
push usermaglione.k@gmail.com
push dateSat, 12 Aug 2017 05:24:05 +0000
reviewersbillm
bugs1356334
milestone57.0a1
Bug 1356334: Part 6 - Allow completely terminating a slow content script sandbox. r=billm MozReview-Commit-ID: 5CDLHrAeuDt
browser/locales/en-US/chrome/browser/browser.properties
browser/modules/ProcessHangMonitor.jsm
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
dom/ipc/PProcessHangMonitor.ipdl
dom/ipc/ProcessHangMonitor.cpp
dom/ipc/ProcessHangMonitor.h
dom/ipc/nsIHangReport.idl
dom/locales/en-US/chrome/dom/dom.properties
js/xpconnect/src/XPCJSContext.cpp
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -587,16 +587,18 @@ dataReportingNotification.button.accessK
 processHang.label = A web page is slowing down your browser. What would you like to do?
 # LOCALIZATION NOTE (processHang.add-on.label): The first %S is the name of
 # an extension. The second %S is the name of the product (e.g., Firefox)
 processHang.add-on.label = A script in the extension "%S" is causing %S to slow down.
 processHang.add-on.learn-more.text = Learn more
 processHang.add-on.learn-more.url = https://support.mozilla.org/en-US/kb/warning-unresponsive-script?cache=no#w_other-causes
 processHang.button_stop.label = Stop It
 processHang.button_stop.accessKey = S
+processHang.button_stop_sandbox.label = Temporarily Disable Extension on Page
+processHang.button_stop_sandbox.accessKey = A
 processHang.button_wait.label = Wait
 processHang.button_wait.accessKey = W
 processHang.button_debug.label = Debug Script
 processHang.button_debug.accessKey = D
 
 # LOCALIZATION NOTE (fullscreenButton.tooltip): %S is the keyboard shortcut for full screen
 fullscreenButton.tooltip=Display the window in full screen (%S)
 
--- a/browser/modules/ProcessHangMonitor.jsm
+++ b/browser/modules/ProcessHangMonitor.jsm
@@ -60,16 +60,24 @@ var ProcessHangMonitor = {
    * Terminate JavaScript associated with the hang being reported for
    * the selected browser in |win|.
    */
   terminateScript(win) {
     this.handleUserInput(win, report => report.terminateScript());
   },
 
   /**
+   * Terminate Sandbox globals associated with the hang being reported
+   * for the selected browser in |win|.
+   */
+  terminateGlobal(win) {
+    this.handleUserInput(win, report => report.terminateGlobal());
+  },
+
+  /**
    * Start devtools debugger for JavaScript associated with the hang
    * being reported for the selected browser in |win|.
    */
   debugScript(win) {
     this.handleUserInput(win, report => {
       function callback() {
         report.endStartingDebugger();
       }
@@ -107,16 +115,33 @@ var ProcessHangMonitor = {
         break;
       case report.PLUGIN_HANG:
         this.terminatePlugin(win);
         break;
     }
   },
 
   /**
+   * Stop all scripts from running in the Sandbox global attached to
+   * this window.
+   */
+  stopGlobal(win) {
+    let report = this.findActiveReport(win.gBrowser.selectedBrowser);
+    if (!report) {
+      return;
+    }
+
+    switch (report.hangType) {
+      case report.SLOW_SCRIPT:
+        this.terminateGlobal(win);
+        break;
+    }
+  },
+
+  /**
    * Dismiss the notification, clear the report from the active list and set up
    * a new timer to track a wait period during which we won't notify.
    */
   waitLonger(win) {
     let report = this.findActiveReport(win.gBrowser.selectedBrowser);
     if (!report) {
       return;
     }
@@ -323,16 +348,24 @@ var ProcessHangMonitor = {
       link.setAttribute("class", "text-link");
       link.setAttribute("role", "link");
       link.setAttribute("onclick", `openUILinkIn(${JSON.stringify(linkURL)}, "tab")`);
       link.setAttribute("value", linkText);
 
       message = doc.createDocumentFragment();
       message.appendChild(doc.createTextNode(label + " "));
       message.appendChild(link);
+
+      buttons.unshift({
+        label: bundle.getString("processHang.button_stop_sandbox.label"),
+        accessKey: bundle.getString("processHang.button_stop_sandbox.accessKey"),
+        callback() {
+          ProcessHangMonitor.stopGlobal(win);
+        }
+      });
     }
 
     if (AppConstants.MOZ_DEV_EDITION && report.hangType == report.SLOW_SCRIPT) {
       buttons.push({
         label: bundle.getString("processHang.button_debug.label"),
         accessKey: bundle.getString("processHang.button_debug.accessKey"),
         callback() {
           ProcessHangMonitor.debugScript(win);
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -11743,16 +11743,19 @@ nsGlobalWindow::ShowSlowScriptDialog(con
     nsIDocShell* docShell = GetDocShell();
     nsCOMPtr<nsITabChild> child = docShell ? docShell->GetTabChild() : nullptr;
     action = monitor->NotifySlowScript(child,
                                        filename.get(),
                                        aAddonId);
     if (action == ProcessHangMonitor::Terminate) {
       return KillSlowScript;
     }
+    if (action == ProcessHangMonitor::TerminateGlobal) {
+      return KillScriptGlobal;
+    }
 
     if (action == ProcessHangMonitor::StartDebugger) {
       // Spin a nested event loop so that the debugger in the parent can fetch
       // any information it needs. Once the debugger has started, return to the
       // script.
       RefPtr<nsGlobalWindow> outer = GetOuterWindowInternal();
       outer->EnterModalState();
       SpinEventLoopUntil([&]() { return monitor->IsDebuggerStartupComplete(); });
@@ -11796,19 +11799,20 @@ nsGlobalWindow::ShowSlowScriptDialog(con
     return Move(result);
   };
 
   bool isAddonScript = !aAddonId.IsEmpty();
   bool showDebugButton = debugCallback && !isAddonScript;
 
   // Get localizable strings
 
-  nsAutoString title, debugButton, msg;
+  nsAutoString title, checkboxMsg, debugButton, msg;
   if (isAddonScript) {
     title = getString("KillAddonScriptTitle");
+    checkboxMsg = getString("KillAddonScriptGlobalMessage");
 
     auto appName = getString("brandShortName", nsContentUtils::eBRAND_PROPERTIES);
 
     nsCOMPtr<nsIAddonPolicyService> aps = do_GetService("@mozilla.org/addons/policy-service;1");
     nsString addonName;
     if (!aps || NS_FAILED(aps->GetExtensionName(aAddonId, addonName))) {
       addonName = aAddonId;
     }
@@ -11816,26 +11820,26 @@ nsGlobalWindow::ShowSlowScriptDialog(con
     const char16_t* params[] = {addonName.get(), appName.get()};
     rv = nsContentUtils::FormatLocalizedString(
         nsContentUtils::eDOM_PROPERTIES, "KillAddonScriptMessage",
         params, msg);
 
     failed = failed || NS_FAILED(rv);
   } else {
     title = getString("KillScriptTitle");
+    checkboxMsg = getString("DontAskAgain");
 
     if (showDebugButton) {
       debugButton = getString("DebugScriptButton");
       msg = getString("KillScriptWithDebugMessage");
     } else {
       msg = getString("KillScriptMessage");
     }
   }
 
-  auto neverShowDlg = getString("DontAskAgain");
   auto stopButton = getString("StopScriptButton");
   auto waitButton = getString("WaitForScriptButton");
 
   if (failed) {
     NS_ERROR("Failed to get localized strings.");
     return ContinueSlowScript;
   }
 
@@ -11877,46 +11881,54 @@ nsGlobalWindow::ShowSlowScriptDialog(con
     if (NS_SUCCEEDED(rv)) {
       msg.AppendLiteral("\n\n");
       msg.Append(scriptLocation);
       msg.Append(':');
       msg.AppendInt(lineno);
     }
   }
 
-  int32_t buttonPressed = 0; // In case the user exits dialog by clicking X.
-  bool neverShowDlgChk = false;
   uint32_t buttonFlags = nsIPrompt::BUTTON_POS_1_DEFAULT +
                          (nsIPrompt::BUTTON_TITLE_IS_STRING *
                           (nsIPrompt::BUTTON_POS_0 + nsIPrompt::BUTTON_POS_1));
 
   // Add a third button if necessary.
   if (showDebugButton)
     buttonFlags += nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_2;
 
+  bool checkboxValue = false;
+  int32_t buttonPressed = 0; // In case the user exits dialog by clicking X.
   {
     // Null out the operation callback while we're re-entering JS here.
     AutoDisableJSInterruptCallback disabler(cx);
+
     // Open the dialog.
     rv = prompt->ConfirmEx(title.get(), msg.get(), buttonFlags,
                            waitButton.get(), stopButton.get(),
-                           debugButton.get(), neverShowDlg.get(),
-                           &neverShowDlgChk, &buttonPressed);
-  }
-
-  if (NS_SUCCEEDED(rv) && (buttonPressed == 0)) {
-    return neverShowDlgChk ? AlwaysContinueSlowScript : ContinueSlowScript;
-  }
+                           debugButton.get(), checkboxMsg.get(),
+                           &checkboxValue, &buttonPressed);
+  }
+
+  if (buttonPressed == 0) {
+    if (checkboxValue && !isAddonScript && NS_SUCCEEDED(rv))
+      return AlwaysContinueSlowScript;
+    return ContinueSlowScript;
+  }
+
   if (buttonPressed == 2) {
-    if (debugCallback) {
-      rv = debugCallback->HandleSlowScriptDebug(this);
-      return NS_SUCCEEDED(rv) ? ContinueSlowScript : KillSlowScript;
-    }
-  }
+    MOZ_RELEASE_ASSERT(debugCallback);
+
+    rv = debugCallback->HandleSlowScriptDebug(this);
+    return NS_SUCCEEDED(rv) ? ContinueSlowScript : KillSlowScript;
+  }
+
   JS_ClearPendingException(cx);
+
+  if (checkboxValue && isAddonScript)
+    return KillScriptGlobal;
   return KillSlowScript;
 }
 
 uint32_t
 nsGlobalWindow::FindInsertionIndex(IdleObserverHolder* aIdleObserver)
 {
   MOZ_ASSERT(IsInnerWindow());
   MOZ_ASSERT(aIdleObserver, "Idle observer not instantiated.");
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -732,17 +732,18 @@ public:
   {
     mAllowScriptsToClose = true;
   }
 
   enum SlowScriptResponse {
     ContinueSlowScript = 0,
     ContinueSlowScriptAndKeepNotifying,
     AlwaysContinueSlowScript,
-    KillSlowScript
+    KillSlowScript,
+    KillScriptGlobal
   };
   SlowScriptResponse ShowSlowScriptDialog(const nsString& aAddonId);
 
   // Inner windows only.
   void AddGamepad(uint32_t aIndex, mozilla::dom::Gamepad* aGamepad);
   void RemoveGamepad(uint32_t aIndex);
   void GetGamepads(nsTArray<RefPtr<mozilla::dom::Gamepad> >& aGamepads);
   already_AddRefed<mozilla::dom::Gamepad> GetGamepad(uint32_t aIndex);
--- a/dom/ipc/PProcessHangMonitor.ipdl
+++ b/dom/ipc/PProcessHangMonitor.ipdl
@@ -31,17 +31,17 @@ union HangData
 
 protocol PProcessHangMonitor
 {
 parent:
   async HangEvidence(HangData data);
   async ClearHang();
 
 child:
-  async TerminateScript();
+  async TerminateScript(bool aTerminateGlobal);
 
   async BeginStartingDebugger();
   async EndStartingDebugger();
 
   async ForcePaint(TabId tabId, uint64_t aLayerObserverEpoch);
 };
 
 } // namespace mozilla
--- a/dom/ipc/ProcessHangMonitor.cpp
+++ b/dom/ipc/ProcessHangMonitor.cpp
@@ -95,17 +95,17 @@ class HangMonitorChild
 
   void NotifyPluginHang(uint32_t aPluginId);
   void NotifyPluginHangAsync(uint32_t aPluginId);
 
   void ClearHang();
   void ClearHangAsync();
   void ClearForcePaint();
 
-  mozilla::ipc::IPCResult RecvTerminateScript() override;
+  mozilla::ipc::IPCResult RecvTerminateScript(const bool& aTerminateGlobal) override;
   mozilla::ipc::IPCResult RecvBeginStartingDebugger() override;
   mozilla::ipc::IPCResult RecvEndStartingDebugger() override;
 
   mozilla::ipc::IPCResult RecvForcePaint(const TabId& aTabId, const uint64_t& aLayerObserverEpoch) override;
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
   void InterruptCallback();
@@ -128,16 +128,17 @@ class HangMonitorChild
   const RefPtr<ProcessHangMonitor> mHangMonitor;
   Monitor mMonitor;
 
   // Main thread-only.
   bool mSentReport;
 
   // These fields must be accessed with mMonitor held.
   bool mTerminateScript;
+  bool mTerminateGlobal;
   bool mStartDebugger;
   bool mFinishedStartingDebugger;
   bool mForcePaint;
   TabId mForcePaintTab;
   MOZ_INIT_OUTSIDE_CTOR uint64_t mForcePaintEpoch;
   JSContext* mContext;
   bool mShutdownDone;
 
@@ -164,16 +165,17 @@ public:
   NS_IMETHOD GetHangType(uint32_t* aHangType) override;
   NS_IMETHOD GetScriptBrowser(nsIDOMElement** aBrowser) override;
   NS_IMETHOD GetScriptFileName(nsACString& aFileName) override;
   NS_IMETHOD GetAddonId(nsAString& aAddonId) override;
 
   NS_IMETHOD GetPluginName(nsACString& aPluginName) override;
 
   NS_IMETHOD TerminateScript() override;
+  NS_IMETHOD TerminateGlobal() override;
   NS_IMETHOD BeginStartingDebugger() override;
   NS_IMETHOD EndStartingDebugger() override;
   NS_IMETHOD TerminatePlugin() override;
   NS_IMETHOD UserCanceled() override;
 
   NS_IMETHOD IsReportForBrowser(nsIFrameLoader* aFrameLoader, bool* aResult) override;
 
   // Called when a content process shuts down.
@@ -228,17 +230,17 @@ public:
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
   void SetProcess(HangMonitoredProcess* aProcess) { mProcess = aProcess; }
 
   void Shutdown();
 
   void ForcePaint(dom::TabParent* aTabParent, uint64_t aLayerObserverEpoch);
 
-  void TerminateScript();
+  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
    * is used to replace a browser minidump with a full minidump. If aDumpId is
    * empty this is a no-op.
@@ -293,16 +295,17 @@ bool HangMonitorParent::sShouldForcePain
 
 /* HangMonitorChild implementation */
 
 HangMonitorChild::HangMonitorChild(ProcessHangMonitor* aMonitor)
  : mHangMonitor(aMonitor),
    mMonitor("HangMonitorChild lock"),
    mSentReport(false),
    mTerminateScript(false),
+   mTerminateGlobal(false),
    mStartDebugger(false),
    mFinishedStartingDebugger(false),
    mForcePaint(false),
    mShutdownDone(false),
    mIPCOpen(true)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   mContext = danger::GetJSContext();
@@ -379,22 +382,26 @@ HangMonitorChild::ActorDestroy(ActorDest
   // We use a task here to ensure that IPDL is finished with this
   // HangMonitorChild before it gets deleted on the main thread.
   Dispatch(NewNonOwningRunnableMethod("HangMonitorChild::ShutdownOnThread",
                                       this,
                                       &HangMonitorChild::ShutdownOnThread));
 }
 
 mozilla::ipc::IPCResult
-HangMonitorChild::RecvTerminateScript()
+HangMonitorChild::RecvTerminateScript(const bool& aTerminateGlobal)
 {
   MOZ_RELEASE_ASSERT(IsOnThread());
 
   MonitorAutoLock lock(mMonitor);
-  mTerminateScript = true;
+  if (aTerminateGlobal) {
+    mTerminateGlobal = true;
+  } else {
+    mTerminateScript = true;
+  }
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 HangMonitorChild::RecvBeginStartingDebugger()
 {
   MOZ_RELEASE_ASSERT(IsOnThread());
 
@@ -475,16 +482,21 @@ HangMonitorChild::NotifySlowScript(nsITa
   {
     MonitorAutoLock lock(mMonitor);
 
     if (mTerminateScript) {
       mTerminateScript = false;
       return SlowScriptAction::Terminate;
     }
 
+    if (mTerminateGlobal) {
+      mTerminateGlobal = false;
+      return SlowScriptAction::TerminateGlobal;
+    }
+
     if (mStartDebugger) {
       mStartDebugger = false;
       return SlowScriptAction::StartDebugger;
     }
   }
 
   TabId id;
   if (aTabChild) {
@@ -554,16 +566,17 @@ HangMonitorChild::ClearHang()
     // bounce to background thread
     Dispatch(NewNonOwningRunnableMethod("HangMonitorChild::ClearHangAsync",
                                         this,
                                         &HangMonitorChild::ClearHangAsync));
 
     MonitorAutoLock lock(mMonitor);
     mSentReport = false;
     mTerminateScript = false;
+    mTerminateGlobal = false;
     mStartDebugger = false;
     mFinishedStartingDebugger = false;
   }
 }
 
 void
 HangMonitorChild::ClearHangAsync()
 {
@@ -836,22 +849,22 @@ HangMonitorParent::RecvClearHang()
   NS_DispatchToMainThread(
     mMainThreadTaskFactory.NewRunnableMethod(
       &HangMonitorParent::ClearHangNotification));
 
   return IPC_OK();
 }
 
 void
-HangMonitorParent::TerminateScript()
+HangMonitorParent::TerminateScript(bool aTerminateGlobal)
 {
   MOZ_RELEASE_ASSERT(IsOnThread());
 
   if (mIPCOpen) {
-    Unused << SendTerminateScript();
+    Unused << SendTerminateScript(aTerminateGlobal);
   }
 }
 
 void
 HangMonitorParent::BeginStartingDebugger()
 {
   MOZ_RELEASE_ASSERT(IsOnThread());
 
@@ -1000,19 +1013,38 @@ HangMonitoredProcess::TerminateScript()
     return NS_ERROR_UNEXPECTED;
   }
 
   if (!mActor) {
     return NS_ERROR_UNEXPECTED;
   }
 
   ProcessHangMonitor::Get()->Dispatch(
-    NewNonOwningRunnableMethod("HangMonitorParent::TerminateScript",
-                               mActor,
-                               &HangMonitorParent::TerminateScript));
+    NewNonOwningRunnableMethod<bool>("HangMonitorParent::TerminateScript",
+                                     mActor,
+                                     &HangMonitorParent::TerminateScript, false));
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HangMonitoredProcess::TerminateGlobal()
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+  if (mHangData.type() != HangData::TSlowScriptData) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  if (!mActor) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  ProcessHangMonitor::Get()->Dispatch(
+    NewNonOwningRunnableMethod<bool>("HangMonitorParent::TerminateScript",
+                                     mActor,
+                                     &HangMonitorParent::TerminateScript, true));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HangMonitoredProcess::BeginStartingDebugger()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   if (mHangData.type() != HangData::TSlowScriptData) {
--- a/dom/ipc/ProcessHangMonitor.h
+++ b/dom/ipc/ProcessHangMonitor.h
@@ -48,17 +48,18 @@ class ProcessHangMonitor final
   static void ForcePaint(PProcessHangMonitorParent* aParent,
                          dom::TabParent* aTab,
                          uint64_t aLayerObserverEpoch);
   static void ClearForcePaint();
 
   enum SlowScriptAction {
     Continue,
     Terminate,
-    StartDebugger
+    StartDebugger,
+    TerminateGlobal,
   };
   SlowScriptAction NotifySlowScript(nsITabChild* aTabChild,
                                     const char* aFileName,
                                     const nsString& aAddonId);
 
   void NotifyPluginHang(uint32_t aPluginId);
 
   bool IsDebuggerStartupComplete();
--- a/dom/ipc/nsIHangReport.idl
+++ b/dom/ipc/nsIHangReport.idl
@@ -41,16 +41,21 @@ interface nsIHangReport : nsISupports
   // Called by front end code when user ignores or cancels
   // the notification.
   void userCanceled();
 
   // Terminate the slow script if it is still running.
   // Only valid for SLOW_SCRIPT reports.
   void terminateScript();
 
+  // Terminate all scripts on the global that triggered the slow script
+  // warning.
+  // Only valid for SLOW_SCRIPT reports.
+  void terminateGlobal();
+
   // Terminate the plugin if it is still hung.
   // Only valid for PLUGIN_HANG reports.
   void terminatePlugin();
 
   // Ask the content process to start up the slow script debugger.
   // Only valid for SLOW_SCRIPT reports.
   void beginStartingDebugger();
 
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -5,16 +5,17 @@
 KillScriptTitle=Warning: Unresponsive script
 KillScriptMessage=A script on this page may be busy, or it may have stopped responding. You can stop the script now, or you can continue to see if the script will complete.
 KillScriptWithDebugMessage=A script on this page may be busy, or it may have stopped responding. You can stop the script now, open the script in the debugger, or let the script continue.
 KillScriptLocation=Script: %S
 
 KillAddonScriptTitle=Warning: Unresponsive add-on script
 # LOCALIZATION NOTE (KillAddonScriptMessage): The first %S is the name of an add-on. The second %S is the name of the application (e.g., Firefox).
 KillAddonScriptMessage=A script from the add-on "%S" is running on this page, and making %S unresponsive.\n\nIt may be busy, or it may have stopped responsing permanently. You can stop the script now, or you can continue to see if it will complete.
+KillAddonScriptGlobalMessage=Prevent the add-on script from running on this page until it next reloads.
 
 StopScriptButton=Stop script
 DebugScriptButton=Debug script
 WaitForScriptButton=Continue
 DontAskAgain=&Don’t ask me again
 JSURLLoadBlockedWarning=Attempt to load a javascript: URL from one host\nin a window displaying content from another host\nwas blocked by the security manager.
 WindowCloseBlockedWarning=Scripts may not close windows that were not opened by script.
 OnBeforeUnloadTitle=Are you sure?
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -611,16 +611,38 @@ XPCJSContext::InterruptCallback(JSContex
 
     // Show the prompt to the user, and kill if requested.
     nsGlobalWindow::SlowScriptResponse response = win->ShowSlowScriptDialog(addonId);
     if (response == nsGlobalWindow::KillSlowScript) {
         if (Preferences::GetBool("dom.global_stop_script", true))
             xpc::Scriptability::Get(global).Block();
         return false;
     }
+    if (response == nsGlobalWindow::KillScriptGlobal) {
+        nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+
+        if (!IsSandbox(global) || !obs)
+            return false;
+
+        // Notify the extensions framework that the sandbox should be killed.
+        nsIXPConnect* xpc = nsContentUtils::XPConnect();
+        JS::RootedObject wrapper(cx, JS_NewPlainObject(cx));
+        nsCOMPtr<nsISupports> supports;
+
+        // Store the sandbox object on the wrappedJSObject property of the
+        // subject so that JS recipients can access the JS value directly.
+        if (!wrapper ||
+            !JS_DefineProperty(cx, wrapper, "wrappedJSObject", global, JSPROP_ENUMERATE) ||
+            NS_FAILED(xpc->WrapJS(cx, wrapper, NS_GET_IID(nsISupports), getter_AddRefs(supports)))) {
+            return false;
+        }
+
+        obs->NotifyObservers(supports, "kill-content-script-sandbox", nullptr);
+        return false;
+    }
 
     // The user chose to continue the script. Reset the timer, and disable this
     // machinery with a pref of the user opted out of future slow-script dialogs.
     if (response != nsGlobalWindow::ContinueSlowScriptAndKeepNotifying)
         self->mSlowScriptCheckpoint = TimeStamp::NowLoRes();
 
     if (response == nsGlobalWindow::AlwaysContinueSlowScript)
         Preferences::SetInt(prefName, 0);