Bug 901291 - Get WebIDL callbacks working on Workers. r=khuey
authorNikhil Marathe <nsm.nikhil@gmail.com>
Mon, 09 Sep 2013 14:58:29 -0700
changeset 159192 2eb63267254ca6bc694a2f7f02ae612feeff46e5
parent 159191 57b393ac2cb4acf5dfbb24e458276a86cdb6ac08
child 159193 a9ccc26e524b000b6dbd6219d00129294e7961c6
push id2961
push userlsblakk@mozilla.com
push dateMon, 28 Oct 2013 21:59:28 +0000
treeherdermozilla-beta@73ef4f13486f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskhuey
bugs901291
milestone26.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 901291 - Get WebIDL callbacks working on Workers. r=khuey
content/base/public/nsContentUtils.h
content/base/src/nsContentUtils.cpp
dom/bindings/BindingUtils.cpp
dom/bindings/CallbackObject.cpp
dom/bindings/CallbackObject.h
js/xpconnect/src/XPCConvert.cpp
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -1529,16 +1529,17 @@ public:
   static nsresult ProcessViewportInfo(nsIDocument *aDocument,
                                       const nsAString &viewportInfo);
 
   static nsIScriptContext* GetContextForEventHandlers(nsINode* aNode,
                                                       nsresult* aRv);
 
   static JSContext *GetCurrentJSContext();
   static JSContext *GetSafeJSContext();
+  static JSContext *GetDefaultJSContextForThread();
 
   /**
    * Case insensitive comparison between two strings. However it only ignores
    * case for ASCII characters a-z.
    */
   static bool EqualsIgnoreASCIICase(const nsAString& aStr1,
                                     const nsAString& aStr2);
 
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -1761,16 +1761,17 @@ nsContentUtils::IsCallerChrome()
   // If the check failed, look for UniversalXPConnect on the cx compartment.
   return xpc::IsUniversalXPConnectEnabled(GetCurrentJSContext());
 }
 
 namespace mozilla {
 namespace dom {
 namespace workers {
 extern bool IsCurrentThreadRunningChromeWorker();
+extern JSContext* GetCurrentThreadJSContext();
 }
 }
 }
 
 bool
 nsContentUtils::ThreadsafeIsCallerChrome()
 {
   return NS_IsMainThread() ?
@@ -5193,16 +5194,27 @@ nsContentUtils::GetCurrentJSContext()
 JSContext *
 nsContentUtils::GetSafeJSContext()
 {
   MOZ_ASSERT(NS_IsMainThread());
   return sXPConnect->GetSafeJSContext();
 }
 
 /* static */
+JSContext *
+nsContentUtils::GetDefaultJSContextForThread()
+{
+  if (MOZ_LIKELY(NS_IsMainThread())) {
+    return GetSafeJSContext();
+  } else {
+    return workers::GetCurrentThreadJSContext();
+  }
+}
+
+/* static */
 nsresult
 nsContentUtils::ASCIIToLower(nsAString& aStr)
 {
   PRUnichar* iter = aStr.BeginWriting();
   PRUnichar* end = aStr.EndWriting();
   if (MOZ_UNLIKELY(!iter || !end)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -668,16 +668,38 @@ bool
 NativeInterface2JSObjectAndThrowIfFailed(JSContext* aCx,
                                          JS::Handle<JSObject*> aScope,
                                          JS::Value* aRetval,
                                          xpcObjectHelper& aHelper,
                                          const nsIID* aIID,
                                          bool aAllowNativeWrapper)
 {
   nsresult rv;
+  // Inline some logic from XPCConvert::NativeInterfaceToJSObject that we need
+  // on all threads.
+  nsWrapperCache *cache = aHelper.GetWrapperCache();
+
+  if (cache && cache->IsDOMBinding()) {
+      JS::RootedObject obj(aCx, cache->GetWrapper());
+      if (!obj) {
+          obj = cache->WrapObject(aCx, aScope);
+      }
+
+      if (obj && aAllowNativeWrapper && !JS_WrapObject(aCx, obj.address())) {
+        return false;
+      }
+
+      if (obj) {
+        *aRetval = JS::ObjectValue(*obj);
+        return true;
+      }
+  }
+
+  MOZ_ASSERT(NS_IsMainThread());
+
   if (!XPCConvert::NativeInterface2JSObject(aRetval, NULL, aHelper, aIID,
                                             NULL, aAllowNativeWrapper, &rv)) {
     // I can't tell if NativeInterface2JSObject throws JS exceptions
     // or not.  This is a sloppy stab at the right semantics; the
     // method really ought to be fixed to behave consistently.
     if (!JS_IsExceptionPending(aCx)) {
       Throw(aCx, NS_FAILED(rv) ? rv : NS_ERROR_UNEXPECTED);
     }
--- a/dom/bindings/CallbackObject.cpp
+++ b/dom/bindings/CallbackObject.cpp
@@ -12,16 +12,17 @@
 #include "nsIScriptGlobalObject.h"
 #include "nsIXPConnect.h"
 #include "nsIScriptContext.h"
 #include "nsPIDOMWindow.h"
 #include "nsJSUtils.h"
 #include "nsCxPusher.h"
 #include "nsIScriptSecurityManager.h"
 #include "xpcprivate.h"
+#include "WorkerPrivate.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CallbackObject)
   NS_INTERFACE_MAP_ENTRY(mozilla::dom::CallbackObject)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
@@ -44,86 +45,96 @@ NS_IMPL_CYCLE_COLLECTION_TRACE_END
 CallbackObject::CallSetup::CallSetup(JS::Handle<JSObject*> aCallback,
                                      ErrorResult& aRv,
                                      ExceptionHandling aExceptionHandling,
                                      JSCompartment* aCompartment)
   : mCx(nullptr)
   , mCompartment(aCompartment)
   , mErrorResult(aRv)
   , mExceptionHandling(aExceptionHandling)
+  , mIsMainThread(NS_IsMainThread())
 {
+  if (mIsMainThread) {
+    nsContentUtils::EnterMicroTask();
+  }
   // We need to produce a useful JSContext here.  Ideally one that the callback
   // is in some sense associated with, so that we can sort of treat it as a
   // "script entry point".  Though once we actually have script entry points,
   // we'll need to do the script entry point bits once we have an actual
   // callable.
 
   // First, find the real underlying callback.
   JSObject* realCallback = js::UncheckedUnwrap(aCallback);
+  JSContext* cx = nullptr;
 
-  // Now get the nsIScriptGlobalObject for this callback.
-  JSContext* cx = nullptr;
-  nsIScriptContext* ctx = nullptr;
-  nsIScriptGlobalObject* sgo = nsJSUtils::GetStaticScriptGlobal(realCallback);
-  if (sgo) {
-    // Make sure that if this is a window it's the current inner, since the
-    // nsIScriptContext and hence JSContext are associated with the outer
-    // window.  Which means that if someone holds on to a function from a
-    // now-unloaded document we'd have the new document as the script entry
-    // point...
-    nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(sgo);
-    if (win) {
-      MOZ_ASSERT(win->IsInnerWindow());
-      nsPIDOMWindow* outer = win->GetOuterWindow();
-      if (!outer || win != outer->GetCurrentInnerWindow()) {
-        // Just bail out from here
-        return;
+  if (mIsMainThread) {
+    // Now get the nsIScriptGlobalObject for this callback.
+    nsIScriptContext* ctx = nullptr;
+    nsIScriptGlobalObject* sgo = nsJSUtils::GetStaticScriptGlobal(realCallback);
+    if (sgo) {
+      // Make sure that if this is a window it's the current inner, since the
+      // nsIScriptContext and hence JSContext are associated with the outer
+      // window.  Which means that if someone holds on to a function from a
+      // now-unloaded document we'd have the new document as the script entry
+      // point...
+      nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(sgo);
+      if (win) {
+        MOZ_ASSERT(win->IsInnerWindow());
+        nsPIDOMWindow* outer = win->GetOuterWindow();
+        if (!outer || win != outer->GetCurrentInnerWindow()) {
+          // Just bail out from here
+          return;
+        }
+      }
+      // if not a window at all, just press on
+
+      ctx = sgo->GetContext();
+      if (ctx) {
+        // We don't check whether scripts are enabled on ctx, because
+        // CheckFunctionAccess will do that anyway... and because we ignore them
+        // being disabled if the callee is system.
+        cx = ctx->GetNativeContext();
       }
     }
-    // if not a window at all, just press on
 
-    ctx = sgo->GetContext();
-    if (ctx) {
-      // We don't check whether scripts are enabled on ctx, because
-      // CheckFunctionAccess will do that anyway... and because we ignore them
-      // being disabled if the callee is system.
-      cx = ctx->GetNativeContext();
+    if (!cx) {
+      // We didn't manage to hunt down a script global to work with.  Just fall
+      // back on using the safe context.
+      cx = nsContentUtils::GetSafeJSContext();
     }
+
+    // Make sure our JSContext is pushed on the stack.
+    mCxPusher.Push(cx);
+  } else {
+    cx = workers::GetCurrentThreadJSContext();
   }
 
-  if (!cx) {
-    // We didn't manage to hunt down a script global to work with.  Just fall
-    // back on using the safe context.
-    cx = nsContentUtils::GetSafeJSContext();
-  }
-
-  // Make sure our JSContext is pushed on the stack.
-  mCxPusher.Push(cx);
-
   // Unmark the callable, and stick it in a Rooted before it can go gray again.
   // Nothing before us in this function can trigger a CC, so it's safe to wait
   // until here it do the unmark. This allows us to order the following two
   // operations _after_ the Push() above, which lets us take advantage of the
   // JSAutoRequest embedded in the pusher.
   //
   // We can do this even though we're not in the right compartment yet, because
   // Rooted<> does not care about compartments.
   JS::ExposeObjectToActiveJS(aCallback);
   mRootedCallable.construct(cx, aCallback);
 
-  // Check that it's ok to run this callback at all.
-  // FIXME: Bug 807371: we want a less silly check here.
-  // Make sure to unwrap aCallback before passing it in, because
-  // getting principals from wrappers is silly.
-  nsresult rv = nsContentUtils::GetSecurityManager()->
-    CheckFunctionAccess(cx, js::UncheckedUnwrap(aCallback), nullptr);
+  if (mIsMainThread) {
+    // Check that it's ok to run this callback at all.
+    // FIXME: Bug 807371: we want a less silly check here.
+    // Make sure to unwrap aCallback before passing it in, because
+    // getting principals from wrappers is silly.
+    nsresult rv = nsContentUtils::GetSecurityManager()->
+      CheckFunctionAccess(cx, js::UncheckedUnwrap(aCallback), nullptr);
 
-  if (NS_FAILED(rv)) {
-    // Security check failed.  We're done here.
-    return;
+    if (NS_FAILED(rv)) {
+      // Security check failed.  We're done here.
+      return;
+    }
   }
 
   // Enter the compartment of our callback, so we can actually work with it.
   mAc.construct(cx, aCallback);
 
   // And now we're ready to go.
   mCx = cx;
 
@@ -199,22 +210,29 @@ CallbackObject::CallSetup::~CallSetup()
   // XXXbz For that matter why do we need to manually call ScriptEvaluated at
   // all?  nsCxPusher::Pop will do that nowadays if !mScriptIsRunning, so the
   // concerns from bug 295983 don't seem relevant anymore.  Do we want to make
   // sure it's still called when !mScriptIsRunning?  I guess play it safe for
   // now and do what CallEventHandler did, which is call always.
 
   // Popping an nsCxPusher is safe even if it never got pushed.
   mCxPusher.Pop();
+
+  // It is important that this is the last thing we do, after leaving the
+  // compartment and popping the context.
+  if (mIsMainThread) {
+    nsContentUtils::LeaveMicroTask();
+  }
 }
 
 already_AddRefed<nsISupports>
 CallbackObjectHolderBase::ToXPCOMCallback(CallbackObject* aCallback,
                                           const nsIID& aIID) const
 {
+  MOZ_ASSERT(NS_IsMainThread());
   if (!aCallback) {
     return nullptr;
   }
 
   AutoSafeJSContext cx;
 
   JS::Rooted<JSObject*> callback(cx, aCallback->Callback());
 
--- a/dom/bindings/CallbackObject.h
+++ b/dom/bindings/CallbackObject.h
@@ -146,20 +146,16 @@ protected:
     JSContext* mCx;
 
     // Caller's compartment. This will only have a sensible value if
     // mExceptionHandling == eRethrowContentExceptions.
     JSCompartment* mCompartment;
 
     // And now members whose construction/destruction order we need to control.
 
-    // Put our nsAutoMicrotask first, so it gets destroyed after everything else
-    // is gone
-    nsAutoMicroTask mMt;
-
     nsCxPusher mCxPusher;
 
     // Constructed the rooter within the scope of mCxPusher above, so that it's
     // always within a request during its lifetime.
     Maybe<JS::Rooted<JSObject*> > mRootedCallable;
 
     // Can't construct a JSAutoCompartment without a JSContext either.  Also,
     // Put mAc after mCxPusher so that we exit the compartment before we pop the
@@ -167,16 +163,17 @@ protected:
     // things.
     Maybe<JSAutoCompartment> mAc;
 
     // An ErrorResult to possibly re-throw exceptions on and whether
     // we should re-throw them.
     ErrorResult& mErrorResult;
     const ExceptionHandling mExceptionHandling;
     uint32_t mSavedJSContextOptions;
+    const bool mIsMainThread;
   };
 };
 
 template<class WebIDLCallbackT, class XPCOMCallbackT>
 class CallbackObjectHolder;
 
 template<class T, class U>
 void ImplCycleCollectionUnlink(CallbackObjectHolder<T, U>& aField);
--- a/js/xpconnect/src/XPCConvert.cpp
+++ b/js/xpconnect/src/XPCConvert.cpp
@@ -822,17 +822,17 @@ XPCConvert::NativeInterface2JSObject(jsv
     // optimal -- we could detect this and roll the functionality into a
     // single wrapper, but the current solution is good enough for now.
     AutoJSContext cx;
     XPCWrappedNativeScope* xpcscope = GetObjectScope(JS::CurrentGlobalOrNull(cx));
     if (!xpcscope)
         return false;
 
     // First, see if this object supports the wrapper cache.
-    // Note: If |cache->IsProxy()| is true, then it means that the object
+    // Note: If |cache->IsDOMBinding()| is true, then it means that the object
     // implementing it doesn't want a wrapped native as its JS Object, but
     // instead it provides its own proxy object. In that case, the object
     // to use is found as cache->GetWrapper(). If that is null, then the
     // object will create (and fill the cache) from its WrapObject call.
     nsWrapperCache *cache = aHelper.GetWrapperCache();
 
     RootedObject flat(cx);
     if (cache) {
@@ -857,17 +857,17 @@ XPCConvert::NativeInterface2JSObject(jsv
     // Don't double wrap CPOWs. This is a temporary measure for compatibility
     // with objects that don't provide necessary QIs (such as objects under
     // the new DOM bindings). We expect the other side of the CPOW to have
     // the appropriate wrappers in place.
     RootedObject cpow(cx, UnwrapNativeCPOW(aHelper.Object()));
     if (cpow) {
         if (!JS_WrapObject(cx, cpow.address()))
             return false;
-        *d = OBJECT_TO_JSVAL(cpow);
+        *d = JS::ObjectValue(*cpow);
         return true;
     }
 
     // We can't simply construct a slim wrapper. Go ahead and create an
     // XPCWrappedNative for this object. At this point, |flat| could be
     // non-null, meaning that either we already have a wrapped native from
     // the cache (which might need to be QI'd to the new interface) or that
     // we found a slim wrapper that we'll have to morph.