Bug 593742 - nsDOMWorker has to set the right compartment. r=bent.
authorJason Orendorff <jorendorff@mozilla.com>
Thu, 09 Sep 2010 11:13:06 -0500
changeset 53623 3a1ca1e40c07c989d0bbe99a6ddc2e75252cad4f
parent 53622 f5f47e8fbc15a57cb3d54e7360880870c8b0e043
child 53624 8ce3a4a29efc88888914cf4eea044f051c86924b
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbent
bugs593742
milestone2.0b6pre
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 593742 - nsDOMWorker has to set the right compartment. r=bent.
dom/src/threads/nsDOMThreadService.cpp
dom/src/threads/nsDOMWorker.cpp
dom/src/threads/nsDOMWorker.h
js/src/jsapi.h
--- a/dom/src/threads/nsDOMThreadService.cpp
+++ b/dom/src/threads/nsDOMThreadService.cpp
@@ -390,45 +390,51 @@ public:
     JS_SetContextPrivate(cx, mWorker);
 
     // Go ahead and trigger the operation callback for this context before we
     // try to run any JS. That way we'll be sure to cancel or suspend as soon as
     // possible if the compilation takes too long.
     JS_TriggerOperationCallback(cx);
 
     PRBool killWorkerWhenDone;
-
-    // Tell the worker which context it will be using
-    if (mWorker->SetGlobalForContext(cx)) {
-      RunQueue(cx, &killWorkerWhenDone);
-
-      // Code in XPConnect assumes that the context's global object won't be
-      // replaced outside of a request.
-      JSAutoRequest ar(cx);
+    {
+      nsLazyAutoRequest ar;
+      JSAutoCrossCompartmentCall axcc;
 
-      // Remove the global object from the context so that it might be garbage
-      // collected.
-      JS_SetGlobalObject(cx, NULL);
-      JS_SetContextPrivate(cx, NULL);
-    }
-    else {
-      {
-        // Code in XPConnect assumes that the context's global object won't be
-        // replaced outside of a request.
-        JSAutoRequest ar(cx);
+      // Tell the worker which context it will be using
+      if (mWorker->SetGlobalForContext(cx, &ar, &axcc)) {
+        NS_ASSERTION(ar.entered(), "SetGlobalForContext must enter request on success");
+        NS_ASSERTION(axcc.entered(), "SetGlobalForContext must enter xcc on success");
 
-        // This is usually due to a parse error in the worker script...
+        RunQueue(cx, &killWorkerWhenDone);
+
+        // Remove the global object from the context so that it might be garbage
+        // collected.
         JS_SetGlobalObject(cx, NULL);
         JS_SetContextPrivate(cx, NULL);
       }
+      else {
+        NS_ASSERTION(!ar.entered(), "SetGlobalForContext must not enter request on failure");
+        NS_ASSERTION(!axcc.entered(), "SetGlobalForContext must not enter xcc on failure");
 
-      nsAutoMonitor mon(gDOMThreadService->mMonitor);
-      killWorkerWhenDone = mKillWorkerWhenDone;
-      gDOMThreadService->WorkerComplete(this);
-      mon.NotifyAll();
+        {
+          // Code in XPConnect assumes that the context's global object won't be
+          // replaced outside of a request.
+          JSAutoRequest ar2(cx);
+
+          // This is usually due to a parse error in the worker script...
+          JS_SetGlobalObject(cx, NULL);
+          JS_SetContextPrivate(cx, NULL);
+        }
+
+        nsAutoMonitor mon(gDOMThreadService->mMonitor);
+        killWorkerWhenDone = mKillWorkerWhenDone;
+        gDOMThreadService->WorkerComplete(this);
+        mon.NotifyAll();
+      }
     }
 
     if (killWorkerWhenDone) {
       nsCOMPtr<nsIRunnable> runnable = new nsDOMWorkerKillRunnable(mWorker);
       NS_ENSURE_TRUE(runnable, NS_ERROR_OUT_OF_MEMORY);
 
       nsresult rv = NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL);
       NS_ENSURE_SUCCESS(rv, rv);
--- a/dom/src/threads/nsDOMWorker.cpp
+++ b/dom/src/threads/nsDOMWorker.cpp
@@ -1555,49 +1555,62 @@ nsDOMWorker::PostMessageInternal(PRBool 
     rv = nsDOMThreadService::get()->Dispatch(target, runnable);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 
 PRBool
-nsDOMWorker::SetGlobalForContext(JSContext* aCx)
+nsDOMWorker::SetGlobalForContext(JSContext* aCx, nsLazyAutoRequest *aRequest,
+                                 JSAutoCrossCompartmentCall *aCall)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
-  if (!CompileGlobalObject(aCx)) {
+  if (!CompileGlobalObject(aCx, aRequest, aCall)) {
     return PR_FALSE;
   }
 
-  JSAutoRequest ar(aCx);
-
   JS_SetGlobalObject(aCx, mGlobal);
   return PR_TRUE;
 }
 
 PRBool
-nsDOMWorker::CompileGlobalObject(JSContext* aCx)
+nsDOMWorker::CompileGlobalObject(JSContext* aCx, nsLazyAutoRequest *aRequest,
+                                 JSAutoCrossCompartmentCall *aCall)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
+  // On success, we enter a request and a cross-compartment call that both
+  // belong to the caller. But on failure, we must not remain in a request or
+  // cross-compartment call. So we enter both only locally at first. On
+  // failure, the local request and call will automatically get cleaned
+  // up. Once success is certain, we swap them into *aRequest and *aCall.
+  nsLazyAutoRequest localRequest;
+  JSAutoCrossCompartmentCall localCall;
+  localRequest.enter(aCx);
+
+  PRBool success;
   if (mGlobal) {
+    success = localCall.enter(aCx, mGlobal);
+    NS_ENSURE_TRUE(success, PR_FALSE);
+
+    aRequest->swap(localRequest);
+    aCall->swap(localCall);
     return PR_TRUE;
   }
 
   if (mCompileAttempted) {
     // Don't try to recompile a bad script.
     return PR_FALSE;
   }
   mCompileAttempted = PR_TRUE;
 
   NS_ASSERTION(!mScriptURL.IsEmpty(), "Must have a url here!");
 
-  JSAutoRequest ar(aCx);
-
   NS_ASSERTION(!JS_GetGlobalObject(aCx), "Global object should be unset!");
 
   nsRefPtr<nsDOMWorkerScope> scope = new nsDOMWorkerScope(this);
   NS_ENSURE_TRUE(scope, PR_FALSE);
 
   nsISupports* scopeSupports = NS_ISUPPORTS_CAST(nsIWorkerScope*, scope);
 
   nsIXPConnect* xpc = nsContentUtils::XPConnect();
@@ -1617,28 +1630,31 @@ nsDOMWorker::CompileGlobalObject(JSConte
   NS_ENSURE_SUCCESS(rv, PR_FALSE);
 
   JSObject* global;
   rv = globalWrapper->GetJSObject(&global);
   NS_ENSURE_SUCCESS(rv, PR_FALSE);
 
   NS_ASSERTION(JS_GetGlobalObject(aCx) == global, "Global object mismatch!");
 
+  success = localCall.enter(aCx, global);
+  NS_ENSURE_TRUE(success, PR_FALSE);
+
 #ifdef DEBUG
   {
     jsval components;
     if (JS_GetProperty(aCx, global, "Components", &components)) {
       NS_ASSERTION(components == JSVAL_VOID,
                    "Components property still defined!");
     }
   }
 #endif
 
   // Set up worker thread functions.
-  PRBool success = JS_DefineFunctions(aCx, global, gDOMWorkerFunctions);
+  success = JS_DefineFunctions(aCx, global, gDOMWorkerFunctions);
   NS_ENSURE_TRUE(success, PR_FALSE);
 
   if (mPrivilegeModel == CHROME) {
     // Add chrome functions.
     success = JS_DefineFunctions(aCx, global, gDOMWorkerChromeFunctions);
     NS_ENSURE_TRUE(success, PR_FALSE);
 
 #ifdef BUILD_CTYPES
@@ -1685,16 +1701,18 @@ nsDOMWorker::CompileGlobalObject(JSConte
     mGlobal = NULL;
     mInnerScope = nsnull;
     mScopeWN = nsnull;
     return PR_FALSE;
   }
 
   NS_ASSERTION(mPrincipal && mURI, "Script loader didn't set our principal!");
 
+  aRequest->swap(localRequest);
+  aCall->swap(localCall);
   return PR_TRUE;
 }
 
 void
 nsDOMWorker::SetPool(nsDOMWorkerPool* aPool)
 {
   NS_ASSERTION(!mPool, "Shouldn't ever set pool more than once!");
   mPool = aPool;
--- a/dom/src/threads/nsDOMWorker.h
+++ b/dom/src/threads/nsDOMWorker.h
@@ -100,16 +100,43 @@ private:
   nsDOMWorker* mWorker;
   nsIXPConnectWrappedNative* mWrappedNative;
 
   nsRefPtr<nsDOMWorkerNavigator> mNavigator;
 
   PRPackedBool mHasOnerror;
 };
 
+class nsLazyAutoRequest
+{
+public:
+  nsLazyAutoRequest() : mCx(nsnull) {}
+
+  ~nsLazyAutoRequest() {
+    if (mCx)
+      JS_EndRequest(mCx);
+  }
+
+  void enter(JSContext *aCx) {
+    JS_BeginRequest(aCx);
+    mCx = aCx;
+  }
+
+  bool entered() const { return mCx != nsnull; }
+
+  void swap(nsLazyAutoRequest &other) {
+    JSContext *tmp = mCx;
+    mCx = other.mCx;
+    other.mCx = tmp;
+  }
+
+private:
+  JSContext *mCx;
+};
+
 class nsDOMWorker : public nsDOMWorkerMessageHandler,
                     public nsIChromeWorker,
                     public nsITimerCallback,
                     public nsIJSNativeInitializer,
                     public nsIXPCScriptable
 {
   friend class nsDOMWorkerFeature;
   friend class nsDOMWorkerFunctions;
@@ -169,17 +196,17 @@ public:
   void Resume();
 
   // This just calls IsCanceledNoLock with an autolock around the call.
   PRBool IsCanceled();
 
   PRBool IsClosing();
   PRBool IsSuspended();
 
-  PRBool SetGlobalForContext(JSContext* aCx);
+  PRBool SetGlobalForContext(JSContext* aCx, nsLazyAutoRequest *aRequest, JSAutoCrossCompartmentCall *aCall);
 
   void SetPool(nsDOMWorkerPool* aPool);
 
   nsDOMWorkerPool* Pool() {
     return mPool;
   }
 
   PRLock* Lock() {
@@ -253,17 +280,17 @@ public:
     eKilled
   };
 
 private:
   ~nsDOMWorker();
 
   nsresult PostMessageInternal(PRBool aToInner);
 
-  PRBool CompileGlobalObject(JSContext* aCx);
+  PRBool CompileGlobalObject(JSContext* aCx, nsLazyAutoRequest *aRequest, JSAutoCrossCompartmentCall *aCall);
 
   PRUint32 NextTimeoutId() {
     return ++mNextTimeoutId;
   }
 
   nsresult AddFeature(nsDOMWorkerFeature* aFeature,
                       JSContext* aCx);
   void RemoveFeature(nsDOMWorkerFeature* aFeature,
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -963,20 +963,28 @@ JS_END_EXTERN_C
 class JS_PUBLIC_API(JSAutoCrossCompartmentCall)
 {
     JSCrossCompartmentCall *call;
   public:
     JSAutoCrossCompartmentCall() : call(NULL) {}
 
     bool enter(JSContext *cx, JSObject *target);
 
+    bool entered() const { return call != NULL; }
+
     ~JSAutoCrossCompartmentCall() {
         if (call)
             JS_LeaveCrossCompartmentCall(call);
     }
+
+    void swap(JSAutoCrossCompartmentCall &other) {
+        JSCrossCompartmentCall *tmp = call;
+        call = other.call;
+        other.call = tmp;
+    }
 };
 
 class JS_FRIEND_API(JSAutoEnterCompartment)
 {
     JSContext *cx;
     JSCompartment *compartment;
   public:
     JSAutoEnterCompartment(JSContext *cx, JSCompartment *newCompartment);