Bug 1279086 - Allow multiple interrupt callbacks (r=dvander)
authorBill McCloskey <billm@mozilla.com>
Thu, 23 Jun 2016 17:03:03 -0700
changeset 314335 0a47c0c2ba3495fa0a62e6bfc010badbd24ef6c9
parent 314334 321c8756602053c827f9833c7e25a4d3a7e0a733
child 314336 cfcd8c4f3a36b958002010a9c6d461a7769996d5
push idunknown
push userunknown
push dateunknown
reviewersdvander
bugs1279086
milestone51.0a1
Bug 1279086 - Allow multiple interrupt callbacks (r=dvander)
dom/base/nsGlobalWindow.cpp
dom/workers/RuntimeService.cpp
js/src/jsapi-tests/testSlowScript.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/shell/js.cpp
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
js/xpconnect/src/XPCJSContext.cpp
js/xpconnect/src/XPCShellImpl.cpp
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -11149,24 +11149,24 @@ nsGlobalWindow::ShowSlowScriptDialog()
                          (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;
 
   // Null out the operation callback while we're re-entering JS here.
-  JSInterruptCallback old = JS_SetInterruptCallback(cx, nullptr);
+  bool old = JS_DisableInterruptCallback(cx);
 
   // Open the dialog.
   rv = prompt->ConfirmEx(title, msg, buttonFlags, waitButton, stopButton,
                          debugButton, neverShowDlg, &neverShowDlgChk,
                          &buttonPressed);
 
-  JS_SetInterruptCallback(cx, old);
+  JS_ResetInterruptCallback(cx, old);
 
   if (NS_SUCCEEDED(rv) && (buttonPressed == 0)) {
     return neverShowDlgChk ? AlwaysContinueSlowScript : ContinueSlowScript;
   }
   if (buttonPressed == 2) {
     if (debugCallback) {
       rv = debugCallback->HandleSlowScriptDebug(this);
       return NS_SUCCEEDED(rv) ? ContinueSlowScript : KillSlowScript;
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -989,17 +989,17 @@ InitJSContextForWorker(WorkerPrivate* aW
 
   JS::SetAsyncTaskCallbacks(aWorkerCx, StartAsyncTaskCallback, FinishAsyncTaskCallback);
 
   if (!JS::InitSelfHostedCode(aWorkerCx)) {
     NS_WARNING("Could not init self-hosted code!");
     return false;
   }
 
-  JS_SetInterruptCallback(aWorkerCx, InterruptCallback);
+  JS_AddInterruptCallback(aWorkerCx, InterruptCallback);
 
   js::SetCTypesActivityCallback(aWorkerCx, CTypesActivityCallback);
 
 #ifdef JS_GC_ZEAL
   JS_SetGCZeal(aWorkerCx, settings.gcZeal, settings.gcZealFrequency);
 #endif
 
   return true;
--- a/js/src/jsapi-tests/testSlowScript.cpp
+++ b/js/src/jsapi-tests/testSlowScript.cpp
@@ -19,17 +19,17 @@ RequestInterruptCallback(JSContext* cx, 
     if (!sRemain--)
         JS_RequestInterruptCallback(cx);
     args.rval().setUndefined();
     return true;
 }
 
 BEGIN_TEST(testSlowScript)
 {
-    JS_SetInterruptCallback(cx, InterruptCallback);
+    JS_AddInterruptCallback(cx, InterruptCallback);
     JS_DefineFunction(cx, global, "requestInterruptCallback", RequestInterruptCallback, 0, 0);
 
     test("while (true)"
          "  for (i in [0,0,0,0])"
          "    requestInterruptCallback();");
 
     test("while (true)"
          "  for (i in [0,0,0,0])"
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4612,28 +4612,34 @@ JS_New(JSContext* cx, HandleObject ctor,
 }
 
 JS_PUBLIC_API(bool)
 JS_CheckForInterrupt(JSContext* cx)
 {
     return js::CheckForInterrupt(cx);
 }
 
-JS_PUBLIC_API(JSInterruptCallback)
-JS_SetInterruptCallback(JSContext* cx, JSInterruptCallback callback)
-{
-    JSInterruptCallback old = cx->interruptCallback;
-    cx->interruptCallback = callback;
-    return old;
-}
-
-JS_PUBLIC_API(JSInterruptCallback)
-JS_GetInterruptCallback(JSContext* cx)
-{
-    return cx->interruptCallback;
+JS_PUBLIC_API(bool)
+JS_AddInterruptCallback(JSContext* cx, JSInterruptCallback callback)
+{
+    return cx->interruptCallbacks.append(callback);
+}
+
+JS_PUBLIC_API(bool)
+JS_DisableInterruptCallback(JSContext* cx)
+{
+    bool result = cx->interruptCallbackDisabled;
+    cx->interruptCallbackDisabled = true;
+    return result;
+}
+
+JS_PUBLIC_API(void)
+JS_ResetInterruptCallback(JSContext* cx, bool enable)
+{
+    cx->interruptCallbackDisabled = enable;
 }
 
 /************************************************************************/
 
 /*
  * Promises.
  */
 JS_PUBLIC_API(void)
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -4300,21 +4300,24 @@ JS_CheckForInterrupt(JSContext* cx);
  * To schedule the GC and for other activities the engine internally triggers
  * interrupt callbacks. The embedding should thus not rely on callbacks being
  * triggered through the external API only.
  *
  * Important note: Additional callbacks can occur inside the callback handler
  * if it re-enters the JS engine. The embedding must ensure that the callback
  * is disconnected before attempting such re-entry.
  */
-extern JS_PUBLIC_API(JSInterruptCallback)
-JS_SetInterruptCallback(JSContext* cx, JSInterruptCallback callback);
-
-extern JS_PUBLIC_API(JSInterruptCallback)
-JS_GetInterruptCallback(JSContext* cx);
+extern JS_PUBLIC_API(bool)
+JS_AddInterruptCallback(JSContext* cx, JSInterruptCallback callback);
+
+extern JS_PUBLIC_API(bool)
+JS_DisableInterruptCallback(JSContext* cx);
+
+extern JS_PUBLIC_API(void)
+JS_ResetInterruptCallback(JSContext* cx, bool enable);
 
 extern JS_PUBLIC_API(void)
 JS_RequestInterruptCallback(JSContext* cx);
 
 namespace JS {
 
 /**
  * Sets the callback that's invoked whenever an incumbent global is required.
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -7618,17 +7618,17 @@ main(int argc, char** argv, char** envp)
     size_t availMem = op.getIntOption("available-memory");
     if (availMem > 0)
         JS_SetGCParametersBasedOnAvailableMemory(cx, availMem);
 
     JS_SetTrustedPrincipals(cx, &ShellPrincipals::fullyTrusted);
     JS_SetSecurityCallbacks(cx, &ShellPrincipals::securityCallbacks);
     JS_InitDestroyPrincipalsCallback(cx, ShellPrincipals::destroy);
 
-    JS_SetInterruptCallback(cx, ShellInterruptCallback);
+    JS_AddInterruptCallback(cx, ShellInterruptCallback);
     JS::SetBuildIdOp(cx, ShellBuildId);
     JS::SetAsmJSCacheOps(cx, &asmJSCacheOps);
 
     JS_SetNativeStackQuota(cx, gMaxStackSize);
 
     JS::dbg::SetDebuggerMallocSizeOf(cx, moz_malloc_size_of);
 
     if (!JS::InitSelfHostedCode(cx))
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -144,17 +144,17 @@ JSRuntime::JSRuntime(JSRuntime* parentRu
     parentRuntime(parentRuntime),
 #ifdef DEBUG
     updateChildRuntimeCount(parentRuntime),
 #endif
     interrupt_(false),
     telemetryCallback(nullptr),
     handlingSegFault(false),
     handlingJitInterrupt_(false),
-    interruptCallback(nullptr),
+    interruptCallbackDisabled(false),
     getIncumbentGlobalCallback(nullptr),
     enqueuePromiseJobCallback(nullptr),
     enqueuePromiseJobCallbackData(nullptr),
     promiseRejectionTrackerCallback(nullptr),
     promiseRejectionTrackerCallbackData(nullptr),
     startAsyncTaskCallback(nullptr),
     finishAsyncTaskCallback(nullptr),
 #ifdef DEBUG
@@ -525,21 +525,26 @@ InvokeInterruptCallback(JSContext* cx)
 
     // A worker thread may have requested an interrupt after finishing an Ion
     // compilation.
     jit::AttachFinishedCompilations(cx);
 
     // Important: Additional callbacks can occur inside the callback handler
     // if it re-enters the JS engine. The embedding must ensure that the
     // callback is disconnected before attempting such re-entry.
-    JSInterruptCallback cb = cx->runtime()->interruptCallback;
-    if (!cb)
+    if (cx->runtime()->interruptCallbackDisabled)
         return true;
 
-    if (cb(cx)) {
+    bool stop = false;
+    for (JSInterruptCallback cb : cx->runtime()->interruptCallbacks) {
+        if (!cb(cx))
+            stop = true;
+    }
+
+    if (!stop) {
         // Debugger treats invoking the interrupt callback as a "step", so
         // invoke the onStep handler.
         if (cx->compartment()->isDebuggee()) {
             ScriptFrameIter iter(cx);
             if (!iter.done() &&
                 cx->compartment() == iter.compartment() &&
                 iter.script()->stepModeEnabled())
             {
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -633,17 +633,19 @@ struct JSRuntime : public JS::shadow::Ru
     void finishHandlingJitInterrupt() {
         MOZ_ASSERT(handlingJitInterrupt_);
         handlingJitInterrupt_ = false;
     }
     bool handlingJitInterrupt() const {
         return handlingJitInterrupt_;
     }
 
-    JSInterruptCallback interruptCallback;
+    using InterruptCallbackVector = js::Vector<JSInterruptCallback, 2, js::SystemAllocPolicy>;
+    InterruptCallbackVector interruptCallbacks;
+    bool interruptCallbackDisabled;
 
     JSGetIncumbentGlobalCallback getIncumbentGlobalCallback;
     JSEnqueuePromiseJobCallback enqueuePromiseJobCallback;
     void* enqueuePromiseJobCallbackData;
 
     JSPromiseRejectionTrackerCallback promiseRejectionTrackerCallback;
     void* promiseRejectionTrackerCallbackData;
 
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -3471,17 +3471,17 @@ XPCJSContext::Initialize()
     JS_SetWrapObjectCallbacks(cx, &WrapObjectCallbacks);
     js::SetPreserveWrapperCallback(cx, PreserveWrapper);
 #ifdef MOZ_ENABLE_PROFILER_SPS
     if (PseudoStack* stack = mozilla_get_pseudo_stack())
         stack->sampleContext(cx);
 #endif
     JS_SetAccumulateTelemetryCallback(cx, AccumulateTelemetryCallback);
     js::SetActivityCallback(cx, ActivityCallback, this);
-    JS_SetInterruptCallback(cx, InterruptCallback);
+    JS_AddInterruptCallback(cx, InterruptCallback);
     js::SetWindowProxyClass(cx, &OuterWindowProxyClass);
 
     // The JS engine needs to keep the source code around in order to implement
     // Function.prototype.toSource(). It'd be nice to not have to do this for
     // chrome code and simply stub out requests for source on it. Life is not so
     // easy, unfortunately. Nobody relies on chrome toSource() working in core
     // browser code, but chrome tests use it. The worst offenders are addons,
     // which like to monkeypatch chrome functions by calling toSource() on them
--- a/js/xpconnect/src/XPCShellImpl.cpp
+++ b/js/xpconnect/src/XPCShellImpl.cpp
@@ -1416,17 +1416,17 @@ XRE_XPCShellMain(int argc, char** argv, 
         cx = jsapi.cx();
 
         // Override the default XPConnect interrupt callback. We could store the
         // old one and restore it before shutting down, but there's not really a
         // reason to bother.
         sScriptedInterruptCallback = new PersistentRootedValue;
         sScriptedInterruptCallback->init(cx, UndefinedValue());
 
-        JS_SetInterruptCallback(cx, XPCShellInterruptCallback);
+        JS_AddInterruptCallback(cx, XPCShellInterruptCallback);
 
         argc--;
         argv++;
         ProcessArgsForCompartment(cx, argv, argc);
 
         nsCOMPtr<nsIXPConnect> xpc = do_GetService(nsIXPConnect::GetCID());
         if (!xpc) {
             printf("failed to get nsXPConnect service!\n");