Bug 1246153 part 2. Create a way to ask for a clean new global that works on both mainthread and workers. r=bholley
authorBoris Zbarsky <bzbarsky@mit.edu>
Mon, 04 Apr 2016 12:29:16 -0400
changeset 331370 e0041fb22002f6bed55511900a91a6c4d1134c3d
parent 331369 5f15eba10b3e1887fbe449985f388535246025c3
child 331371 d911a849718c1ce6aa6b5becfa9a41f95f0bbd2f
push id1146
push userCallek@gmail.com
push dateMon, 25 Jul 2016 16:35:44 +0000
treeherdermozilla-release@a55778f9cd5a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley
bugs1246153
milestone48.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 1246153 part 2. Create a way to ask for a clean new global that works on both mainthread and workers. r=bholley
dom/bindings/SimpleGlobalObject.cpp
dom/bindings/SimpleGlobalObject.h
dom/bindings/moz.build
dom/workers/WorkerScope.cpp
new file mode 100644
--- /dev/null
+++ b/dom/bindings/SimpleGlobalObject.cpp
@@ -0,0 +1,169 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "mozilla/dom/SimpleGlobalObject.h"
+
+#include "jsapi.h"
+#include "js/Class.h"
+
+#include "nsContentUtils.h"
+#include "nsJSPrincipals.h"
+#include "nsNullPrincipal.h"
+#include "nsThreadUtils.h"
+
+#include "xpcprivate.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(SimpleGlobalObject)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SimpleGlobalObject)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+  tmp->UnlinkHostObjectURIs();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SimpleGlobalObject)
+
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+  tmp->TraverseHostObjectURIs(cb);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(SimpleGlobalObject)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(SimpleGlobalObject)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(SimpleGlobalObject)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SimpleGlobalObject)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsIGlobalObject)
+NS_INTERFACE_MAP_END
+
+static bool
+SimpleGlobal_enumerate(JSContext *cx, JS::Handle<JSObject *> obj)
+{
+  return JS_EnumerateStandardClasses(cx, obj);
+}
+
+static bool
+SimpleGlobal_resolve(JSContext *cx, JS::Handle<JSObject *> obj,
+                    JS::Handle<jsid> id, bool *resolvedp)
+{
+  return JS_ResolveStandardClass(cx, obj, id, resolvedp);
+}
+
+static void
+SimpleGlobal_finalize(js::FreeOp *fop, JSObject *obj)
+{
+  SimpleGlobalObject* globalObject =
+    static_cast<SimpleGlobalObject*>(JS_GetPrivate(obj));
+  NS_RELEASE(globalObject);
+}
+
+static void
+SimpleGlobal_moved(JSObject *obj, const JSObject *old)
+{
+  SimpleGlobalObject* globalObject =
+    static_cast<SimpleGlobalObject*>(JS_GetPrivate(obj));
+  globalObject->UpdateWrapper(obj, old);
+}
+
+const js::Class SimpleGlobalClass = {
+    "",
+    JSCLASS_GLOBAL_FLAGS | JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    SimpleGlobal_enumerate,
+    SimpleGlobal_resolve,
+    nullptr,
+    SimpleGlobal_finalize,
+    nullptr,
+    nullptr,
+    nullptr,
+    JS_GlobalObjectTraceHook,
+    JS_NULL_CLASS_SPEC, {
+      false,
+      nullptr,
+      SimpleGlobal_moved
+    }, JS_NULL_OBJECT_OPS
+};
+
+// static
+JSObject*
+SimpleGlobalObject::Create(GlobalType globalType, JS::Handle<JS::Value> proto)
+{
+  JSContext* cx = nsContentUtils::GetDefaultJSContextForThread();
+  JSAutoRequest ar(cx);
+
+  JS::CompartmentOptions options;
+  options.creationOptions().setInvisibleToDebugger(true);
+
+  nsCOMPtr<nsIPrincipal> principal;
+  if (NS_IsMainThread()) {
+    principal = nsNullPrincipal::Create();
+    if (!principal) {
+      return nullptr;
+    }
+  }
+
+  JS::Rooted<JSObject*> global(cx,
+    JS_NewGlobalObject(cx, js::Jsvalify(&SimpleGlobalClass),
+                       nsJSPrincipals::get(principal),
+                       JS::DontFireOnNewGlobalHook, options));
+
+  if (!global) {
+    JS_ClearPendingException(cx);
+    return nullptr;
+  }
+
+  JSAutoCompartment ac(cx, global);
+
+  // It's important to create the nsIGlobalObject for our new global before we
+  // start trying to wrap things like the prototype into its compartment,
+  // because the wrap operation relies on the global having its nsIGlobalObject
+  // already.
+  RefPtr<SimpleGlobalObject> globalObject =
+    new SimpleGlobalObject(global, globalType);
+
+  // Pass on ownership of globalObject to |global|.
+  JS_SetPrivate(global, globalObject.forget().take());
+
+  if (proto.isObjectOrNull()) {
+    JS::Rooted<JSObject*> protoObj(cx, proto.toObjectOrNull());
+    if (!JS_WrapObject(cx, &protoObj)) {
+      JS_ClearPendingException(cx);
+      return nullptr;
+    }
+
+    if (!JS_SetPrototype(cx, global, protoObj)) {
+      JS_ClearPendingException(cx);
+      return nullptr;
+    }
+  } else if (!proto.isUndefined()) {
+    // Bogus proto.
+    return nullptr;
+  }
+
+  JS_FireOnNewGlobalObject(cx, global);
+  return global;
+}
+
+// static
+SimpleGlobalObject::GlobalType
+SimpleGlobalObject::SimpleGlobalType(JSObject* obj)
+{
+  if (js::GetObjectClass(obj) != &SimpleGlobalClass) {
+    return SimpleGlobalObject::GlobalType::NotSimpleGlobal;
+  }
+
+  SimpleGlobalObject* globalObject =
+    static_cast<SimpleGlobalObject*>(JS_GetPrivate(obj));
+  return globalObject->Type();
+}
+
+} // namespace mozilla
+} // namespace dom
new file mode 100644
--- /dev/null
+++ b/dom/bindings/SimpleGlobalObject.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/**
+ * A simplere nsIGlobalObject implementation that can be used to set up a new
+ * global without anything interesting in it other than the JS builtins.  This
+ * is safe to use on both mainthread and worker threads.
+ */
+
+#ifndef mozilla_dom_SimpleGlobalObject_h__
+#define mozilla_dom_SimpleGlobalObject_h__
+
+#include "nsIGlobalObject.h"
+#include "nsWrapperCache.h"
+#include "js/TypeDecls.h"
+#include "nsISupportsImpl.h"
+#include "nsCycleCollectionParticipant.h"
+
+namespace mozilla {
+namespace dom {
+
+class SimpleGlobalObject : public nsIGlobalObject,
+                           public nsWrapperCache
+{
+public:
+  enum class GlobalType {
+    BindingDetail, // Should only be used by DOM bindings code.
+    WorkerDebuggerSandbox,
+    NotSimpleGlobal // Sentinel to be used by BasicGlobalType.
+  };
+
+  // Create a new JS global object that can be used to do some work.  This
+  // global will NOT have any DOM APIs exposed in it, will not be visible to the
+  // debugger, and will not have a useful concept of principals, so don't try to
+  // use it with any DOM objects.  Apart from that, running code with
+  // side-effects is safe in this global.  Importantly, when you are first
+  // handed this global it's guaranteed to have pristine built-ins.  The
+  // corresponding nsIGlobalObject* for this global object will be a
+  // SimpleGlobalObject of the type provided; JS_GetPrivate on the returned
+  // JSObject* will return the SimpleGlobalObject*.
+  //
+  // If the provided prototype value is undefined, it is ignored.  If it's an
+  // object or null, it's set as the prototype of the created global.  If it's
+  // anything else, this function returns null.
+  //
+  // Note that creating new globals is not cheap and should not be done
+  // gratuitously.  Please think carefully before you use this function.
+  static JSObject* Create(GlobalType globalType,
+                          JS::Handle<JS::Value> proto =
+                            JS::UndefinedHandleValue);
+
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(SimpleGlobalObject,
+                                                         nsIGlobalObject)
+
+  // Gets the GlobalType of this SimpleGlobalObject.
+  GlobalType Type() const
+  {
+    return mType;
+  }
+
+  // Gets the GlobalType of the SimpleGlobalObject for the given JSObject*, if
+  // the given JSObject* is the global corresponding to a SimpleGlobalObject.
+  // Oherwise, returns GlobalType::NotSimpleGlobal.
+  static GlobalType SimpleGlobalType(JSObject* obj);
+
+  virtual JSObject *GetGlobalJSObject() override
+  {
+    return GetWrapper();
+  }
+
+  virtual JSObject* WrapObject(JSContext* cx,
+                               JS::Handle<JSObject*> aGivenProto) override
+  {
+    MOZ_CRASH("SimpleGlobalObject doesn't use DOM bindings!");
+  }
+
+private:
+  SimpleGlobalObject(JSObject *global, GlobalType type)
+    : mType(type)
+  {
+    SetWrapper(global);
+  }
+
+  virtual ~SimpleGlobalObject()
+  {
+    ClearWrapper();
+  }
+
+  const GlobalType mType;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_SimpleGlobalObject_h__ */
--- a/dom/bindings/moz.build
+++ b/dom/bindings/moz.build
@@ -29,16 +29,17 @@ EXPORTS.mozilla.dom += [
     'Exceptions.h',
     'IterableIterator.h',
     'JSSlots.h',
     'MozMap.h',
     'NonRefcountedDOMObject.h',
     'Nullable.h',
     'PrimitiveConversions.h',
     'RootedDictionary.h',
+    'SimpleGlobalObject.h',
     'StructuredClone.h',
     'ToJSValue.h',
     'TypedArray.h',
     'UnionMember.h',
 ]
 
 # Generated bindings reference *Binding.h, not mozilla/dom/*Binding.h. And,
 # since we generate exported bindings directly to $(DIST)/include, we need
@@ -81,16 +82,17 @@ LOCAL_INCLUDES += [
 UNIFIED_SOURCES += [
     'BindingUtils.cpp',
     'CallbackInterface.cpp',
     'CallbackObject.cpp',
     'Date.cpp',
     'DOMJSProxyHandler.cpp',
     'Exceptions.cpp',
     'IterableIterator.cpp',
+    'SimpleGlobalObject.cpp',
     'ToJSValue.cpp',
 ]
 
 SOURCES += [
     'StructuredClone.cpp',
 ]
 
 # Tests for maplike and setlike require bindings to be built, which means they
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -14,16 +14,17 @@
 #include "mozilla/dom/Fetch.h"
 #include "mozilla/dom/FunctionBinding.h"
 #include "mozilla/dom/IDBFactory.h"
 #include "mozilla/dom/ImageBitmap.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/PromiseWorkerProxy.h"
 #include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
 #include "mozilla/dom/SharedWorkerGlobalScopeBinding.h"
+#include "mozilla/dom/SimpleGlobalObject.h"
 #include "mozilla/dom/WorkerDebuggerGlobalScopeBinding.h"
 #include "mozilla/dom/WorkerGlobalScopeBinding.h"
 #include "mozilla/dom/WorkerLocation.h"
 #include "mozilla/dom/WorkerNavigator.h"
 #include "mozilla/dom/cache/CacheStorage.h"
 #include "mozilla/Services.h"
 #include "nsServiceManagerUtils.h"
 
@@ -705,164 +706,38 @@ WorkerDebuggerGlobalScope::GetGlobal(JSC
   WorkerGlobalScope* scope = mWorkerPrivate->GetOrCreateGlobalScope(aCx);
   if (!scope) {
     aRv.Throw(NS_ERROR_FAILURE);
   }
 
   aGlobal.set(scope->GetWrapper());
 }
 
-class WorkerDebuggerSandboxPrivate : public nsIGlobalObject,
-                                     public nsWrapperCache
-{
-public:
-  explicit WorkerDebuggerSandboxPrivate(JSObject *global)
-  {
-    SetWrapper(global);
-  }
-
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(WorkerDebuggerSandboxPrivate,
-                                                         nsIGlobalObject)
-
-  virtual JSObject *GetGlobalJSObject() override
-  {
-    return GetWrapper();
-  }
-
-  virtual JSObject* WrapObject(JSContext* cx,
-                               JS::Handle<JSObject*> aGivenProto) override
-  {
-    MOZ_CRASH("WorkerDebuggerSandboxPrivate doesn't use DOM bindings!");
-  }
-
-private:
-  virtual ~WorkerDebuggerSandboxPrivate()
-  {
-    ClearWrapper();
-  }
-};
-
-NS_IMPL_CYCLE_COLLECTION_CLASS(WorkerDebuggerSandboxPrivate)
-
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WorkerDebuggerSandboxPrivate)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
-  tmp->UnlinkHostObjectURIs();
-NS_IMPL_CYCLE_COLLECTION_UNLINK_END
-
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WorkerDebuggerSandboxPrivate)
-
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
-  tmp->TraverseHostObjectURIs(cb);
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
-
-NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(WorkerDebuggerSandboxPrivate)
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(WorkerDebuggerSandboxPrivate)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(WorkerDebuggerSandboxPrivate)
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WorkerDebuggerSandboxPrivate)
-  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
-  NS_INTERFACE_MAP_ENTRY(nsIGlobalObject)
-NS_INTERFACE_MAP_END
-
-static bool
-workerdebuggersandbox_enumerate(JSContext *cx, JS::Handle<JSObject *> obj)
-{
-  return JS_EnumerateStandardClasses(cx, obj);
-}
-
-static bool
-workerdebuggersandbox_resolve(JSContext *cx, JS::Handle<JSObject *> obj,
-                              JS::Handle<jsid> id, bool *resolvedp)
-{
-  return JS_ResolveStandardClass(cx, obj, id, resolvedp);
-}
-
-static void
-workerdebuggersandbox_finalize(js::FreeOp *fop, JSObject *obj)
-{
-  WorkerDebuggerSandboxPrivate *sandboxPrivate =
-    static_cast<WorkerDebuggerSandboxPrivate *>(JS_GetPrivate(obj));
-  NS_RELEASE(sandboxPrivate);
-}
-
-static void
-workerdebuggersandbox_moved(JSObject *obj, const JSObject *old)
-{
-  WorkerDebuggerSandboxPrivate *sandboxPrivate =
-    static_cast<WorkerDebuggerSandboxPrivate *>(JS_GetPrivate(obj));
-  sandboxPrivate->UpdateWrapper(obj, old);
-}
-
-const js::Class workerdebuggersandbox_class = {
-    "workerdebuggersandbox",
-    JSCLASS_GLOBAL_FLAGS | JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS,
-    nullptr,
-    nullptr,
-    nullptr,
-    nullptr,
-    workerdebuggersandbox_enumerate,
-    workerdebuggersandbox_resolve,
-    nullptr, /* mayResolve */
-    workerdebuggersandbox_finalize,
-    nullptr,
-    nullptr,
-    nullptr,
-    JS_GlobalObjectTraceHook,
-    JS_NULL_CLASS_SPEC, {
-      false,
-      nullptr,
-      workerdebuggersandbox_moved
-    }, JS_NULL_OBJECT_OPS
-};
 
 void
 WorkerDebuggerGlobalScope::CreateSandbox(JSContext* aCx, const nsAString& aName,
                                          JS::Handle<JSObject*> aPrototype,
                                          JS::MutableHandle<JSObject*> aResult,
                                          ErrorResult& aRv)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   aResult.set(nullptr);
 
-  JS::CompartmentOptions options;
-  options.creationOptions().setInvisibleToDebugger(true);
-
+  JS::Rooted<JS::Value> protoVal(aCx);
+  protoVal.setObjectOrNull(aPrototype);
   JS::Rooted<JSObject*> sandbox(aCx,
-    JS_NewGlobalObject(aCx, js::Jsvalify(&workerdebuggersandbox_class), nullptr,
-                       JS::DontFireOnNewGlobalHook, options));
+    SimpleGlobalObject::Create(SimpleGlobalObject::GlobalType::WorkerDebuggerSandbox,
+                               protoVal));
+
   if (!sandbox) {
-    aRv.NoteJSContextException(aCx);
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return;
   }
 
-  {
-    JSAutoCompartment ac(aCx, sandbox);
-
-    JS::Rooted<JSObject*> prototype(aCx, aPrototype);
-    if (!JS_WrapObject(aCx, &prototype)) {
-      aRv.NoteJSContextException(aCx);
-      return;
-    }
-
-    if (!JS_SetPrototype(aCx, sandbox, prototype)) {
-      aRv.NoteJSContextException(aCx);
-      return;
-    }
-
-    RefPtr<WorkerDebuggerSandboxPrivate> sandboxPrivate =
-      new WorkerDebuggerSandboxPrivate(sandbox);
-
-    // Pass on ownership of sandboxPrivate to |sandbox|.
-    JS_SetPrivate(sandbox, sandboxPrivate.forget().take());
-  }
-
-  JS_FireOnNewGlobalObject(aCx, sandbox);
-
   if (!JS_WrapObject(aCx, &sandbox)) {
     aRv.NoteJSContextException(aCx);
     return;
   }
 
   aResult.set(sandbox);
 }
 
@@ -995,17 +870,17 @@ GetGlobalObjectForGlobal(JSObject* globa
   nsIGlobalObject* globalObject = nullptr;
   UNWRAP_WORKER_OBJECT(WorkerGlobalScope, global, globalObject);
 
   if (!globalObject) {
     UNWRAP_OBJECT(WorkerDebuggerGlobalScope, global, globalObject);
 
     if (!globalObject) {
       MOZ_ASSERT(IsDebuggerSandbox(global));
-      globalObject = static_cast<WorkerDebuggerSandboxPrivate*>(JS_GetPrivate(global));
+      globalObject = static_cast<SimpleGlobalObject*>(JS_GetPrivate(global));
 
       MOZ_ASSERT(globalObject);
     }
   }
 
   return globalObject;
 }
 
@@ -1023,17 +898,18 @@ IsDebuggerGlobal(JSObject* object)
   nsIGlobalObject* globalObject = nullptr;
   return NS_SUCCEEDED(UNWRAP_OBJECT(WorkerDebuggerGlobalScope, object,
                                     globalObject)) && !!globalObject;
 }
 
 bool
 IsDebuggerSandbox(JSObject* object)
 {
-  return js::GetObjectClass(object) == &workerdebuggersandbox_class;
+  return SimpleGlobalObject::SimpleGlobalType(object) ==
+    SimpleGlobalObject::GlobalType::WorkerDebuggerSandbox;
 }
 
 bool
 GetterOnlyJSNative(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
 {
   JS_ReportErrorNumber(aCx, js::GetErrorMessage, nullptr, JSMSG_GETTER_ONLY);
   return false;
 }