Bug 1290021 - Implement a prototype version of Houdini "Worklets Level 1" spec - part 2 - WorkletGlobalScope, r=smaug
authorAndrea Marchesini <amarchesini@mozilla.com>
Sun, 06 Nov 2016 09:54:29 +0100
changeset 351371 59c14257aba5069d2c33102cc2cad24b581ed9cf
parent 351370 4bed3a0e2332f24ffe38f08e234c8898c42249ee
child 351372 3769e657d10451ef2da58a52b9c3858de0e56fd8
push id6795
push userjlund@mozilla.com
push dateMon, 23 Jan 2017 14:19:46 +0000
treeherdermozilla-esr52@76101b503191 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1290021
milestone52.0a1
Bug 1290021 - Implement a prototype version of Houdini "Worklets Level 1" spec - part 2 - WorkletGlobalScope, r=smaug
dom/base/nsGlobalWindow.cpp
dom/bindings/BindingUtils.cpp
dom/bindings/Codegen.py
dom/bindings/Configuration.py
dom/bindings/DOMJSClass.h
dom/bindings/mozwebidlcodegen/__init__.py
dom/bindings/parser/WebIDL.py
dom/webidl/WorkletGlobalScope.webidl
dom/webidl/moz.build
dom/worklet/Worklet.cpp
dom/worklet/Worklet.h
dom/worklet/WorkletGlobalScope.cpp
dom/worklet/WorkletGlobalScope.h
dom/worklet/moz.build
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -14847,15 +14847,20 @@ nsGlobalWindow::TemporarilyDisableDialog
   if (mTopWindow) {
     mTopWindow->mAreDialogsEnabled = mSavedDialogsEnabled;
   }
 }
 
 already_AddRefed<Worklet>
 nsGlobalWindow::CreateWorklet(ErrorResult& aRv)
 {
-  RefPtr<Worklet> worklet = new Worklet(this);
+  if (!mDoc) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  RefPtr<Worklet> worklet = new Worklet(this, mDoc->NodePrincipal());
   return worklet.forget();
 }
 
 template class nsPIDOMWindow<mozIDOMWindowProxy>;
 template class nsPIDOMWindow<mozIDOMWindow>;
 template class nsPIDOMWindow<nsISupports>;
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -2701,17 +2701,18 @@ IsNonExposedGlobal(JSContext* aCx, JSObj
 {
   MOZ_ASSERT(aNonExposedGlobals, "Why did we get called?");
   MOZ_ASSERT((aNonExposedGlobals &
               ~(GlobalNames::Window |
                 GlobalNames::BackstagePass |
                 GlobalNames::DedicatedWorkerGlobalScope |
                 GlobalNames::SharedWorkerGlobalScope |
                 GlobalNames::ServiceWorkerGlobalScope |
-                GlobalNames::WorkerDebuggerGlobalScope)) == 0,
+                GlobalNames::WorkerDebuggerGlobalScope |
+                GlobalNames::WorkletGlobalScope)) == 0,
              "Unknown non-exposed global type");
 
   const char* name = js::GetObjectClass(aGlobal)->name;
 
   if ((aNonExposedGlobals & GlobalNames::Window) &&
       !strcmp(name, "Window")) {
     return true;
   }
@@ -2736,16 +2737,21 @@ IsNonExposedGlobal(JSContext* aCx, JSObj
     return true;
   }
 
   if ((aNonExposedGlobals & GlobalNames::WorkerDebuggerGlobalScope) &&
       !strcmp(name, "WorkerDebuggerGlobalScopex")) {
     return true;
   }
 
+  if ((aNonExposedGlobals & GlobalNames::WorkletGlobalScope) &&
+      !strcmp(name, "WorkletGlobalScope")) {
+    return true;
+  }
+
   return false;
 }
 
 void
 HandlePrerenderingViolation(nsPIDOMWindowInner* aWindow)
 {
   // Freeze the window and its workers, and its children too.
   aWindow->Freeze();
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -13235,16 +13235,41 @@ class CGRegisterWorkerDebuggerBindings(C
                     "%s::ConstructorEnabled(aCx, aObj) && " % bindingNS
                     + condition)
             conditions.append(condition)
         lines = [CGIfWrapper(CGGeneric("return false;\n"), condition) for
                  condition in conditions]
         lines.append(CGGeneric("return true;\n"))
         return CGList(lines, "\n").define()
 
+class CGRegisterWorkletBindings(CGAbstractMethod):
+    def __init__(self, config):
+        CGAbstractMethod.__init__(self, None, 'RegisterWorkletBindings', 'bool',
+                                  [Argument('JSContext*', 'aCx'),
+                                   Argument('JS::Handle<JSObject*>', 'aObj')])
+        self.config = config
+
+    def definition_body(self):
+        descriptors = self.config.getDescriptors(hasInterfaceObject=True,
+                                                 isExposedInAnyWorklet=True,
+                                                 register=True)
+        conditions = []
+        for desc in descriptors:
+            bindingNS = toBindingNamespace(desc.name)
+            condition = "!%s::GetConstructorObject(aCx)" % bindingNS
+            if desc.isExposedConditionally():
+                condition = (
+                    "%s::ConstructorEnabled(aCx, aObj) && " % bindingNS
+                    + condition)
+            conditions.append(condition)
+        lines = [CGIfWrapper(CGGeneric("return false;\n"), condition) for
+                 condition in conditions]
+        lines.append(CGGeneric("return true;\n"))
+        return CGList(lines, "\n").define()
+
 class CGResolveSystemBinding(CGAbstractMethod):
     def __init__(self, config):
         CGAbstractMethod.__init__(self, None, 'ResolveSystemBinding', 'bool',
                                   [Argument('JSContext*', 'aCx'),
                                    Argument('JS::Handle<JSObject*>', 'aObj'),
                                    Argument('JS::Handle<jsid>', 'aId'),
                                    Argument('bool*', 'aResolvedp')])
         self.config = config
@@ -16604,16 +16629,41 @@ class GlobalGenRoots():
 
         # Add include guards.
         curr = CGIncludeGuard('RegisterWorkerDebuggerBindings', curr)
 
         # Done.
         return curr
 
     @staticmethod
+    def RegisterWorkletBindings(config):
+
+        curr = CGRegisterWorkletBindings(config)
+
+        # Wrap all of that in our namespaces.
+        curr = CGNamespace.build(['mozilla', 'dom'],
+                                 CGWrapper(curr, post='\n'))
+        curr = CGWrapper(curr, post='\n')
+
+        # Add the includes
+        defineIncludes = [CGHeaders.getDeclarationFilename(desc.interface)
+                          for desc in config.getDescriptors(hasInterfaceObject=True,
+                                                            register=True,
+                                                            isExposedInAnyWorklet=True)]
+
+        curr = CGHeaders([], [], [], [], [], defineIncludes,
+                         'RegisterWorkletBindings', curr)
+
+        # Add include guards.
+        curr = CGIncludeGuard('RegisterWorkletBindings', curr)
+
+        # Done.
+        return curr
+
+    @staticmethod
     def ResolveSystemBinding(config):
 
         curr = CGResolveSystemBinding(config)
 
         # Wrap all of that in our namespaces.
         curr = CGNamespace.build(['mozilla', 'dom'],
                                  CGWrapper(curr, post='\n'))
         curr = CGWrapper(curr, post='\n')
--- a/dom/bindings/Configuration.py
+++ b/dom/bindings/Configuration.py
@@ -193,16 +193,18 @@ class Configuration(DescriptorProvider):
             elif key == 'isJSImplemented':
                 getter = lambda x: x.interface.isJSImplemented()
             elif key == 'isNavigatorProperty':
                 getter = lambda x: x.interface.isNavigatorProperty()
             elif key == 'isExposedInAnyWorker':
                 getter = lambda x: x.interface.isExposedInAnyWorker()
             elif key == 'isExposedInWorkerDebugger':
                 getter = lambda x: x.interface.isExposedInWorkerDebugger()
+            elif key == 'isExposedInAnyWorklet':
+                getter = lambda x: x.interface.isExposedInAnyWorklet()
             elif key == 'isExposedInSystemGlobals':
                 getter = lambda x: x.interface.isExposedInSystemGlobals()
             elif key == 'isExposedInWindow':
                 getter = lambda x: x.interface.isExposedInWindow()
             else:
                 # Have to watch out: just closing over "key" is not enough,
                 # since we're about to mutate its value
                 getter = (lambda attrName: lambda x: getattr(x, attrName))(key)
--- a/dom/bindings/DOMJSClass.h
+++ b/dom/bindings/DOMJSClass.h
@@ -98,16 +98,17 @@ namespace GlobalNames {
 // interfaces, not of the global names used to refer to them in IDL [Exposed]
 // annotations.
 static const uint32_t Window = 1u << 0;
 static const uint32_t BackstagePass = 1u << 1;
 static const uint32_t DedicatedWorkerGlobalScope = 1u << 2;
 static const uint32_t SharedWorkerGlobalScope = 1u << 3;
 static const uint32_t ServiceWorkerGlobalScope = 1u << 4;
 static const uint32_t WorkerDebuggerGlobalScope = 1u << 5;
+static const uint32_t WorkletGlobalScope = 1u << 6;
 } // namespace GlobalNames
 
 struct PrefableDisablers {
   inline bool isEnabled(JSContext* cx, JS::Handle<JSObject*> obj) const {
     // Reading "enabled" on a worker thread is technically undefined behavior,
     // because it's written only on main threads, with no barriers of any sort.
     // So we want to avoid doing that.  But we don't particularly want to make
     // expensive NS_IsMainThread calls here.
--- a/dom/bindings/mozwebidlcodegen/__init__.py
+++ b/dom/bindings/mozwebidlcodegen/__init__.py
@@ -128,26 +128,28 @@ class WebIDLCodegenManager(LoggingMixin)
     # Global parser derived declaration files.
     GLOBAL_DECLARE_FILES = {
         'GeneratedAtomList.h',
         'GeneratedEventList.h',
         'PrototypeList.h',
         'RegisterBindings.h',
         'RegisterWorkerBindings.h',
         'RegisterWorkerDebuggerBindings.h',
+        'RegisterWorkletBindings.h',
         'ResolveSystemBinding.h',
         'UnionConversions.h',
         'UnionTypes.h',
     }
 
     # Global parser derived definition files.
     GLOBAL_DEFINE_FILES = {
         'RegisterBindings.cpp',
         'RegisterWorkerBindings.cpp',
         'RegisterWorkerDebuggerBindings.cpp',
+        'RegisterWorkletBindings.cpp',
         'ResolveSystemBinding.cpp',
         'UnionTypes.cpp',
         'PrototypeList.cpp',
     }
 
     def __init__(self, config_path, inputs, exported_header_dir,
                  codegen_dir, state_path, cache_dir=None, make_deps_path=None,
                  make_deps_target=None):
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -510,16 +510,19 @@ class IDLExposureMixins():
                 self.isExposedInSystemGlobals())
 
     def isExposedInAnyWorker(self):
         return len(self.getWorkerExposureSet()) > 0
 
     def isExposedInWorkerDebugger(self):
         return len(self.getWorkerDebuggerExposureSet()) > 0
 
+    def isExposedInAnyWorklet(self):
+        return len(self.getWorkletExposureSet()) > 0
+
     def isExposedInSystemGlobals(self):
         return 'BackstagePass' in self.exposureSet
 
     def isExposedInSomeButNotAllWorkers(self):
         """
         Returns true if the Exposed extended attribute for this interface
         exposes it in some worker globals but not others.  The return value does
         not depend on whether the interface is exposed in Window or System
@@ -529,16 +532,20 @@ class IDLExposureMixins():
             return False
         workerScopes = self.parentScope.globalNameMapping["Worker"]
         return len(workerScopes.difference(self.exposureSet)) > 0
 
     def getWorkerExposureSet(self):
         workerScopes = self._globalScope.globalNameMapping["Worker"]
         return workerScopes.intersection(self.exposureSet)
 
+    def getWorkletExposureSet(self):
+        workletScopes = self._globalScope.globalNameMapping["Worklet"]
+        return workletScopes.intersection(self.exposureSet)
+
     def getWorkerDebuggerExposureSet(self):
         workerDebuggerScopes = self._globalScope.globalNameMapping["WorkerDebugger"]
         return workerDebuggerScopes.intersection(self.exposureSet)
 
 
 class IDLExternalInterface(IDLObjectWithIdentifier, IDLExposureMixins):
     def __init__(self, location, parentScope, identifier):
         assert isinstance(identifier, IDLUnresolvedIdentifier)
new file mode 100644
--- /dev/null
+++ b/dom/webidl/WorkletGlobalScope.webidl
@@ -0,0 +1,12 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ *
+ * The origin of this IDL file is
+ * https://drafts.css-houdini.org/worklets/#idl-index
+ */
+
+[Global=(Worklet), Exposed=(Worklet)]
+interface WorkletGlobalScope {
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -577,16 +577,17 @@ WEBIDL_FILES = [
     'WindowOrWorkerGlobalScope.webidl',
     'WindowRoot.webidl',
     'Worker.webidl',
     'WorkerDebuggerGlobalScope.webidl',
     'WorkerGlobalScope.webidl',
     'WorkerLocation.webidl',
     'WorkerNavigator.webidl',
     'Worklet.webidl',
+    'WorkletGlobalScope.webidl',
     'XMLDocument.webidl',
     'XMLHttpRequest.webidl',
     'XMLHttpRequestEventTarget.webidl',
     'XMLHttpRequestUpload.webidl',
     'XMLSerializer.webidl',
     'XMLStylesheetProcessingInstruction.webidl',
     'XPathEvaluator.webidl',
     'XPathExpression.webidl',
--- a/dom/worklet/Worklet.cpp
+++ b/dom/worklet/Worklet.cpp
@@ -1,27 +1,35 @@
 /* -*- 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 "Worklet.h"
+#include "WorkletGlobalScope.h"
 #include "mozilla/dom/WorkletBinding.h"
 #include "mozilla/dom/BlobBinding.h"
 #include "mozilla/dom/Fetch.h"
 #include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/dom/RegisterWorkletBindings.h"
 #include "mozilla/dom/Response.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsNetUtil.h"
+#include "nsScriptLoader.h"
+#include "xpcprivate.h"
 
 namespace mozilla {
 namespace dom {
 
 namespace {
 
 class WorkletFetchHandler : public PromiseNativeHandler
+                          , public nsIStreamLoaderObserver
 {
 public:
   NS_DECL_ISUPPORTS
 
   static already_AddRefed<Promise>
   Fetch(Worklet* aWorklet, const nsAString& aModuleURL, ErrorResult& aRv)
   {
     RefPtr<Promise> promise = Promise::Create(aWorklet->GetParentObject(), aRv);
@@ -37,17 +45,17 @@ public:
     RefPtr<Promise> fetchPromise =
       FetchRequest(aWorklet->GetParentObject(), request, init, aRv);
     if (NS_WARN_IF(aRv.Failed())) {
       promise->MaybeReject(aRv);
       return promise.forget();
     }
 
     RefPtr<WorkletFetchHandler> handler =
-      new WorkletFetchHandler(aWorklet, promise);
+      new WorkletFetchHandler(aWorklet, aModuleURL, promise);
     fetchPromise->AppendNativeHandler(handler);
 
     return promise.forget();
   }
 
   virtual void
   ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
   {
@@ -63,59 +71,191 @@ public:
       return;
     }
 
     if (!response->Ok()) {
       mPromise->MaybeReject(NS_ERROR_DOM_NETWORK_ERR);
       return;
     }
 
-    // TODO: do something with this response...
+    nsCOMPtr<nsIInputStream> inputStream;
+    response->GetBody(getter_AddRefs(inputStream));
+    if (!inputStream) {
+      mPromise->MaybeReject(NS_ERROR_DOM_NETWORK_ERR);
+      return;
+    }
+
+    nsCOMPtr<nsIInputStreamPump> pump;
+    rv = NS_NewInputStreamPump(getter_AddRefs(pump), inputStream);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      mPromise->MaybeReject(rv);
+      return;
+    }
+
+    nsCOMPtr<nsIStreamLoader> loader;
+    rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      mPromise->MaybeReject(rv);
+      return;
+    }
+
+    rv = pump->AsyncRead(loader, nullptr);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      mPromise->MaybeReject(rv);
+      return;
+    }
+
+    nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(pump);
+    if (rr) {
+      nsCOMPtr<nsIEventTarget> sts =
+        do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+      rv = rr->RetargetDeliveryTo(sts);
+      if (NS_FAILED(rv)) {
+        NS_WARNING("Failed to dispatch the nsIInputStreamPump to a IO thread.");
+      }
+    }
+  }
+
+  NS_IMETHOD
+  OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
+                   nsresult aStatus, uint32_t aStringLen,
+                   const uint8_t* aString) override
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    if (NS_FAILED(aStatus)) {
+      mPromise->MaybeReject(aStatus);
+      return NS_OK;
+    }
 
+    char16_t* scriptTextBuf;
+    size_t scriptTextLength;
+    nsresult rv =
+      nsScriptLoader::ConvertToUTF16(nullptr, aString, aStringLen,
+                                     NS_LITERAL_STRING("UTF-8"), nullptr,
+                                     scriptTextBuf, scriptTextLength);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      mPromise->MaybeReject(rv);
+      return NS_OK;
+    }
+
+    // Moving the ownership of the buffer
+    JS::SourceBufferHolder buffer(scriptTextBuf, scriptTextLength,
+                                  JS::SourceBufferHolder::GiveOwnership);
+
+    AutoJSAPI jsapi;
+    jsapi.Init();
+
+    RefPtr<WorkletGlobalScope> globalScope =
+      mWorklet->GetOrCreateGlobalScope(jsapi.cx());
+    MOZ_ASSERT(globalScope);
+
+    AutoEntryScript aes(globalScope, "Worklet");
+    JSContext* cx = aes.cx();
+
+    JS::Rooted<JSObject*> globalObj(cx, globalScope->GetGlobalJSObject());
+
+    (void) new XPCWrappedNativeScope(cx, globalObj);
+
+    JS::CompileOptions compileOptions(cx);
+    compileOptions.setIntroductionType("Worklet");
+    compileOptions.setFileAndLine(NS_ConvertUTF16toUTF8(mURL).get(), 0);
+    compileOptions.setVersion(JSVERSION_DEFAULT);
+    compileOptions.setIsRunOnce(true);
+
+    // We only need the setNoScriptRval bit when compiling off-thread here,
+    // since otherwise nsJSUtils::EvaluateString will set it up for us.
+    compileOptions.setNoScriptRval(true);
+
+    JS::Rooted<JS::Value> unused(cx);
+    if (!JS::Evaluate(cx, compileOptions, buffer, &unused)) {
+      ErrorResult error;
+      error.StealExceptionFromJSContext(cx);
+      mPromise->MaybeReject(error);
+      return NS_OK;
+    }
+
+    // All done.
     mPromise->MaybeResolveWithUndefined();
+    return NS_OK;
   }
 
   virtual void
   RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
   {
     mPromise->MaybeReject(aCx, aValue);
   }
 
 private:
-  WorkletFetchHandler(Worklet* aWorklet, Promise* aPromise)
+  WorkletFetchHandler(Worklet* aWorklet, const nsAString& aURL,
+                      Promise* aPromise)
     : mWorklet(aWorklet)
     , mPromise(aPromise)
+    , mURL(aURL)
   {}
 
   ~WorkletFetchHandler()
   {}
 
   RefPtr<Worklet> mWorklet;
   RefPtr<Promise> mPromise;
+
+  nsString mURL;
 };
 
-NS_IMPL_ISUPPORTS0(WorkletFetchHandler)
+NS_IMPL_ISUPPORTS(WorkletFetchHandler, nsIStreamLoaderObserver)
 
 } // anonymous namespace
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Worklet, mGlobal)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Worklet, mGlobal, mScope)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(Worklet)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(Worklet)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Worklet)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
+Worklet::Worklet(nsIGlobalObject* aGlobal, nsIPrincipal* aPrincipal)
+  : mGlobal(aGlobal)
+  , mPrincipal(aPrincipal)
+{}
+
+Worklet::~Worklet()
+{}
+
 JSObject*
 Worklet::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return WorkletBinding::Wrap(aCx, this, aGivenProto);
 }
 
 already_AddRefed<Promise>
 Worklet::Import(const nsAString& aModuleURL, ErrorResult& aRv)
 {
   return WorkletFetchHandler::Fetch(this, aModuleURL, aRv);
 }
 
+WorkletGlobalScope*
+Worklet::GetOrCreateGlobalScope(JSContext* aCx)
+{
+  if (!mScope) {
+    mScope = new WorkletGlobalScope();
+
+    JS::Rooted<JSObject*> global(aCx);
+    NS_ENSURE_TRUE(mScope->WrapGlobalObject(aCx, mPrincipal, &global), nullptr);
+
+    JSAutoCompartment ac(aCx, global);
+
+    // Init Web IDL bindings
+    if (!RegisterWorkletBindings(aCx, global)) {
+      mScope = nullptr;
+      return nullptr;
+    }
+
+    JS_FireOnNewGlobalObject(aCx, global);
+  }
+
+  return mScope;
+}
+
 } // dom namespace
 } // mozilla namespace
--- a/dom/worklet/Worklet.h
+++ b/dom/worklet/Worklet.h
@@ -8,47 +8,52 @@
 #define mozilla_dom_Worklet_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/ErrorResult.h"
 #include "nsWrapperCache.h"
 #include "nsCOMPtr.h"
 
 class nsIGlobalObject;
+class nsIPrincipal;
 
 namespace mozilla {
 namespace dom {
 
 class Promise;
+class WorkletGlobalScope;
 
 class Worklet final : public nsISupports
                     , public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Worklet)
 
-  explicit Worklet(nsIGlobalObject* aGlobal)
-    : mGlobal(aGlobal)
-  {}
+  Worklet(nsIGlobalObject* aGlobal, nsIPrincipal* aPrincipal);
 
   nsIGlobalObject* GetParentObject() const
   {
     return mGlobal;
   }
 
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   already_AddRefed<Promise>
   Import(const nsAString& aModuleURL, ErrorResult& aRv);
 
+  WorkletGlobalScope*
+  GetOrCreateGlobalScope(JSContext* aCx);
+
 private:
-  ~Worklet()
-  {}
+  ~Worklet();
 
   nsCOMPtr<nsIGlobalObject> mGlobal;
+  nsCOMPtr<nsIPrincipal> mPrincipal;
+
+  RefPtr<WorkletGlobalScope> mScope;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_Worklet_h
new file mode 100644
--- /dev/null
+++ b/dom/worklet/WorkletGlobalScope.cpp
@@ -0,0 +1,54 @@
+/* -*- 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 "WorkletGlobalScope.h"
+#include "mozilla/dom/WorkletGlobalScopeBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(WorkletGlobalScope)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WorkletGlobalScope)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+  tmp->UnlinkHostObjectURIs();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WorkletGlobalScope)
+
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+  tmp->TraverseHostObjectURIs(cb);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(WorkletGlobalScope)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(WorkletGlobalScope)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(WorkletGlobalScope)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WorkletGlobalScope)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsIGlobalObject)
+NS_INTERFACE_MAP_END
+
+JSObject*
+WorkletGlobalScope::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  MOZ_CRASH("We should never get here!");
+  return nullptr;
+}
+
+bool
+WorkletGlobalScope::WrapGlobalObject(JSContext* aCx,
+                                     nsIPrincipal* aPrincipal,
+                                     JS::MutableHandle<JSObject*> aReflector)
+{
+  JS::CompartmentOptions options;
+  return WorkletGlobalScopeBinding::Wrap(aCx, this, this,
+                                         options,
+                                         nsJSPrincipals::get(aPrincipal),
+                                         true, aReflector);
+}
+} // dom namespace
+} // mozilla namespace
new file mode 100644
--- /dev/null
+++ b/dom/worklet/WorkletGlobalScope.h
@@ -0,0 +1,56 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_WorkletGlobalScope_h
+#define mozilla_dom_WorkletGlobalScope_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "nsIGlobalObject.h"
+#include "nsWrapperCache.h"
+
+class nsIPrincipal;
+
+namespace mozilla {
+namespace dom {
+
+class WorkletGlobalScope final : public nsIGlobalObject
+                               , public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WorkletGlobalScope)
+
+  WorkletGlobalScope()
+  {}
+
+  nsIGlobalObject* GetParentObject() const
+  {
+    return nullptr;
+  }
+
+  virtual JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  bool
+  WrapGlobalObject(JSContext* aCx, nsIPrincipal* aPrincipal,
+                   JS::MutableHandle<JSObject*> aReflector);
+
+  virtual JSObject*
+  GetGlobalJSObject() override
+  {
+    return GetWrapper();
+  }
+
+private:
+  ~WorkletGlobalScope()
+  {}
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_WorkletGlobalScope_h
--- a/dom/worklet/moz.build
+++ b/dom/worklet/moz.build
@@ -1,19 +1,25 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 EXPORTS.mozilla.dom += [
-    'Worklet.h'
+    'Worklet.h',
+    'WorkletGlobalScope.h',
 ]
 
 UNIFIED_SOURCES += [
     'Worklet.cpp',
+    'WorkletGlobalScope.cpp',
+]
+
+LOCAL_INCLUDES += [
+    '/js/xpconnect/src',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
 
 FINAL_LIBRARY = 'xul'