summaryrefslogtreecommitdiffstats
path: root/dom/worklet
diff options
context:
space:
mode:
Diffstat (limited to 'dom/worklet')
-rw-r--r--dom/worklet/Worklet.cpp392
-rw-r--r--dom/worklet/Worklet.h70
-rw-r--r--dom/worklet/WorkletGlobalScope.cpp106
-rw-r--r--dom/worklet/WorkletGlobalScope.h75
-rw-r--r--dom/worklet/moz.build25
-rw-r--r--dom/worklet/tests/common.js14
-rw-r--r--dom/worklet/tests/file_basic.html57
-rw-r--r--dom/worklet/tests/file_console.html19
-rw-r--r--dom/worklet/tests/file_dump.html22
-rw-r--r--dom/worklet/tests/file_import_with_cache.html43
-rw-r--r--dom/worklet/tests/mochitest.ini13
-rw-r--r--dom/worklet/tests/server_import_with_cache.sjs13
-rw-r--r--dom/worklet/tests/test_basic.html21
-rw-r--r--dom/worklet/tests/test_console.html42
-rw-r--r--dom/worklet/tests/test_dump.html21
-rw-r--r--dom/worklet/tests/test_import_with_cache.html21
-rw-r--r--dom/worklet/tests/worklet_console.js1
-rw-r--r--dom/worklet/tests/worklet_dump.js1
18 files changed, 956 insertions, 0 deletions
diff --git a/dom/worklet/Worklet.cpp b/dom/worklet/Worklet.cpp
new file mode 100644
index 000000000..19a877ea8
--- /dev/null
+++ b/dom/worklet/Worklet.cpp
@@ -0,0 +1,392 @@
+/* -*- 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 "nsIInputStreamPump.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsNetUtil.h"
+#include "nsScriptLoader.h"
+#include "xpcprivate.h"
+
+namespace mozilla {
+namespace dom {
+
+// ---------------------------------------------------------------------------
+// WorkletFetchHandler
+
+class WorkletFetchHandler : public PromiseNativeHandler
+ , public nsIStreamLoaderObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ static already_AddRefed<Promise>
+ Fetch(Worklet* aWorklet, const nsAString& aModuleURL, ErrorResult& aRv)
+ {
+ MOZ_ASSERT(aWorklet);
+
+ nsCOMPtr<nsIGlobalObject> global =
+ do_QueryInterface(aWorklet->GetParentObject());
+ MOZ_ASSERT(global);
+
+ RefPtr<Promise> promise = Promise::Create(global, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> window = aWorklet->GetParentObject();
+ MOZ_ASSERT(window);
+
+ nsCOMPtr<nsIDocument> doc;
+ doc = window->GetExtantDoc();
+ if (!doc) {
+ promise->MaybeReject(NS_ERROR_FAILURE);
+ return promise.forget();
+ }
+
+ nsCOMPtr<nsIURI> baseURI = doc->GetBaseURI();
+ nsCOMPtr<nsIURI> resolvedURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(resolvedURI), aModuleURL, nullptr, baseURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ promise->MaybeReject(rv);
+ return promise.forget();
+ }
+
+ nsAutoCString spec;
+ rv = resolvedURI->GetSpec(spec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ promise->MaybeReject(rv);
+ return promise.forget();
+ }
+
+ // Maybe we already have an handler for this URI
+ {
+ WorkletFetchHandler* handler = aWorklet->GetImportFetchHandler(spec);
+ if (handler) {
+ handler->AddPromise(promise);
+ return promise.forget();
+ }
+ }
+
+ RequestOrUSVString request;
+ request.SetAsUSVString().Rebind(aModuleURL.Data(), aModuleURL.Length());
+
+ RequestInit init;
+
+ RefPtr<Promise> fetchPromise = FetchRequest(global, request, init, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ promise->MaybeReject(aRv);
+ return promise.forget();
+ }
+
+ RefPtr<WorkletFetchHandler> handler =
+ new WorkletFetchHandler(aWorklet, aModuleURL, promise);
+ fetchPromise->AppendNativeHandler(handler);
+
+ aWorklet->AddImportFetchHandler(spec, handler);
+ return promise.forget();
+ }
+
+ virtual void
+ ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+ {
+ if (!aValue.isObject()) {
+ RejectPromises(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<Response> response;
+ nsresult rv = UNWRAP_OBJECT(Response, &aValue.toObject(), response);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ RejectPromises(NS_ERROR_FAILURE);
+ return;
+ }
+
+ if (!response->Ok()) {
+ RejectPromises(NS_ERROR_DOM_NETWORK_ERR);
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ response->GetBody(getter_AddRefs(inputStream));
+ if (!inputStream) {
+ RejectPromises(NS_ERROR_DOM_NETWORK_ERR);
+ return;
+ }
+
+ nsCOMPtr<nsIInputStreamPump> pump;
+ rv = NS_NewInputStreamPump(getter_AddRefs(pump), inputStream);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ RejectPromises(rv);
+ return;
+ }
+
+ nsCOMPtr<nsIStreamLoader> loader;
+ rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ RejectPromises(rv);
+ return;
+ }
+
+ rv = pump->AsyncRead(loader, nullptr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ RejectPromises(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)) {
+ RejectPromises(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))) {
+ RejectPromises(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);
+ RejectPromises(error.StealNSResult());
+ return NS_OK;
+ }
+
+ // All done.
+ ResolvePromises();
+ return NS_OK;
+ }
+
+ virtual void
+ RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+ {
+ RejectPromises(NS_ERROR_DOM_NETWORK_ERR);
+ }
+
+private:
+ WorkletFetchHandler(Worklet* aWorklet, const nsAString& aURL,
+ Promise* aPromise)
+ : mWorklet(aWorklet)
+ , mStatus(ePending)
+ , mErrorStatus(NS_OK)
+ , mURL(aURL)
+ {
+ MOZ_ASSERT(aWorklet);
+ MOZ_ASSERT(aPromise);
+
+ mPromises.AppendElement(aPromise);
+ }
+
+ ~WorkletFetchHandler()
+ {}
+
+ void
+ AddPromise(Promise* aPromise)
+ {
+ MOZ_ASSERT(aPromise);
+
+ switch (mStatus) {
+ case ePending:
+ mPromises.AppendElement(aPromise);
+ return;
+
+ case eRejected:
+ MOZ_ASSERT(NS_FAILED(mErrorStatus));
+ aPromise->MaybeReject(mErrorStatus);
+ return;
+
+ case eResolved:
+ aPromise->MaybeResolveWithUndefined();
+ return;
+ }
+ }
+
+ void
+ RejectPromises(nsresult aResult)
+ {
+ MOZ_ASSERT(mStatus == ePending);
+ MOZ_ASSERT(NS_FAILED(aResult));
+
+ for (uint32_t i = 0; i < mPromises.Length(); ++i) {
+ mPromises[i]->MaybeReject(aResult);
+ }
+ mPromises.Clear();
+
+ mStatus = eRejected;
+ mErrorStatus = aResult;
+ mWorklet = nullptr;
+ }
+
+ void
+ ResolvePromises()
+ {
+ MOZ_ASSERT(mStatus == ePending);
+
+ for (uint32_t i = 0; i < mPromises.Length(); ++i) {
+ mPromises[i]->MaybeResolveWithUndefined();
+ }
+ mPromises.Clear();
+
+ mStatus = eResolved;
+ mWorklet = nullptr;
+ }
+
+ RefPtr<Worklet> mWorklet;
+ nsTArray<RefPtr<Promise>> mPromises;
+
+ enum {
+ ePending,
+ eRejected,
+ eResolved
+ } mStatus;
+
+ nsresult mErrorStatus;
+
+ nsString mURL;
+};
+
+NS_IMPL_ISUPPORTS(WorkletFetchHandler, nsIStreamLoaderObserver)
+
+// ---------------------------------------------------------------------------
+// Worklet
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Worklet, mWindow, 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(nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal)
+ : mWindow(aWindow)
+ , mPrincipal(aPrincipal)
+{
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(aPrincipal);
+
+#ifdef RELEASE_OR_BETA
+ MOZ_CRASH("This code should not go to release/beta yet!");
+#endif
+}
+
+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(mWindow);
+
+ 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;
+}
+
+WorkletFetchHandler*
+Worklet::GetImportFetchHandler(const nsACString& aURI)
+{
+ return mImportHandlers.GetWeak(aURI);
+}
+
+void
+Worklet::AddImportFetchHandler(const nsACString& aURI,
+ WorkletFetchHandler* aHandler)
+{
+ MOZ_ASSERT(aHandler);
+ MOZ_ASSERT(!mImportHandlers.GetWeak(aURI));
+
+ mImportHandlers.Put(aURI, aHandler);
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/worklet/Worklet.h b/dom/worklet/Worklet.h
new file mode 100644
index 000000000..228d2a5ae
--- /dev/null
+++ b/dom/worklet/Worklet.h
@@ -0,0 +1,70 @@
+/* -*- 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_Worklet_h
+#define mozilla_dom_Worklet_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "nsRefPtrHashtable.h"
+#include "nsWrapperCache.h"
+#include "nsCOMPtr.h"
+
+class nsPIDOMWindowInner;
+class nsIPrincipal;
+
+namespace mozilla {
+namespace dom {
+
+class Promise;
+class WorkletGlobalScope;
+class WorkletFetchHandler;
+
+class Worklet final : public nsISupports
+ , public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Worklet)
+
+ Worklet(nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal);
+
+ nsPIDOMWindowInner* GetParentObject() const
+ {
+ return mWindow;
+ }
+
+ 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();
+
+ WorkletFetchHandler*
+ GetImportFetchHandler(const nsACString& aURI);
+
+ void
+ AddImportFetchHandler(const nsACString& aURI, WorkletFetchHandler* aHandler);
+
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+
+ RefPtr<WorkletGlobalScope> mScope;
+ nsRefPtrHashtable<nsCStringHashKey, WorkletFetchHandler> mImportHandlers;
+
+ friend class WorkletFetchHandler;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_Worklet_h
diff --git a/dom/worklet/WorkletGlobalScope.cpp b/dom/worklet/WorkletGlobalScope.cpp
new file mode 100644
index 000000000..4caeae81a
--- /dev/null
+++ b/dom/worklet/WorkletGlobalScope.cpp
@@ -0,0 +1,106 @@
+/* -*- 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"
+#include "mozilla/dom/Console.h"
+#include "nsContentUtils.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
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsole)
+ tmp->UnlinkHostObjectURIs();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WorkletGlobalScope)
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsole)
+ 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_ENTRY(WorkletGlobalScope)
+NS_INTERFACE_MAP_END
+
+WorkletGlobalScope::WorkletGlobalScope(nsPIDOMWindowInner* aWindow)
+ : mWindow(aWindow)
+{
+ MOZ_ASSERT(aWindow);
+}
+
+WorkletGlobalScope::~WorkletGlobalScope()
+{
+}
+
+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);
+}
+
+Console*
+WorkletGlobalScope::GetConsole(ErrorResult& aRv)
+{
+ if (!mConsole) {
+ mConsole = Console::Create(mWindow, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ }
+
+ return mConsole;
+}
+
+void
+WorkletGlobalScope::Dump(const Optional<nsAString>& aString) const
+{
+ if (!nsContentUtils::DOMWindowDumpEnabled()) {
+ return;
+ }
+
+ if (!aString.WasPassed()) {
+ return;
+ }
+
+ NS_ConvertUTF16toUTF8 str(aString.Value());
+
+#ifdef ANDROID
+ __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", str.get());
+#endif
+
+ fputs(str.get(), stdout);
+ fflush(stdout);
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/worklet/WorkletGlobalScope.h b/dom/worklet/WorkletGlobalScope.h
new file mode 100644
index 000000000..7cf44d2e7
--- /dev/null
+++ b/dom/worklet/WorkletGlobalScope.h
@@ -0,0 +1,75 @@
+/* -*- 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 "mozilla/dom/BindingDeclarations.h"
+#include "nsIGlobalObject.h"
+#include "nsWrapperCache.h"
+
+#define WORKLET_IID \
+ { 0x1b3f62e7, 0xe357, 0x44be, \
+ { 0xbf, 0xe0, 0xdf, 0x85, 0xe6, 0x56, 0x85, 0xac } }
+
+class nsIPrincipal;
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+namespace dom {
+
+class Console;
+
+class WorkletGlobalScope final : public nsIGlobalObject
+ , public nsWrapperCache
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(WORKLET_IID)
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WorkletGlobalScope)
+
+ explicit WorkletGlobalScope(nsPIDOMWindowInner* aWindow);
+
+ 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();
+ }
+
+ Console*
+ GetConsole(ErrorResult& aRv);
+
+ void
+ Dump(const Optional<nsAString>& aString) const;
+
+private:
+ ~WorkletGlobalScope();
+
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ RefPtr<Console> mConsole;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(WorkletGlobalScope, WORKLET_IID)
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_WorkletGlobalScope_h
diff --git a/dom/worklet/moz.build b/dom/worklet/moz.build
new file mode 100644
index 000000000..80c0bf0c9
--- /dev/null
+++ b/dom/worklet/moz.build
@@ -0,0 +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',
+ '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'
diff --git a/dom/worklet/tests/common.js b/dom/worklet/tests/common.js
new file mode 100644
index 000000000..9d70f6aa7
--- /dev/null
+++ b/dom/worklet/tests/common.js
@@ -0,0 +1,14 @@
+function loadTest(file) {
+ var iframe = document.createElement('iframe');
+ iframe.src = file;
+
+ document.body.appendChild(iframe);
+}
+
+function setupTest() {
+ window.SimpleTest = parent.SimpleTest;
+ window.is = parent.is;
+ window.isnot = parent.isnot;
+ window.ok = parent.ok;
+ window.info = parent.info;
+}
diff --git a/dom/worklet/tests/file_basic.html b/dom/worklet/tests/file_basic.html
new file mode 100644
index 000000000..ef60ee27f
--- /dev/null
+++ b/dom/worklet/tests/file_basic.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Worklet</title>
+ <script type="application/javascript" src="common.js"></script>
+</head>
+<body>
+
+<script type="application/javascript">
+
+setupTest();
+
+var worklet = window.createWorklet();
+ok(!!worklet, "We have a Worklet");
+
+// First loading
+worklet.import("common.js")
+.then(() => {
+ ok(true, "Import should load a resource.");
+})
+
+// Second loading - same file
+.then(() => {
+ return worklet.import("common.js")
+})
+.then(() => {
+ ok(true, "Import should load a resource.");
+})
+
+// 3rd loading - a network error
+.then(() => {
+ return worklet.import("404.js");
+})
+.then(() => {
+ ok(false, "The loading should fail.");
+}, () => {
+ ok(true, "The loading should fail.");
+})
+
+// 4th loading - a network error
+.then(() => {
+ return worklet.import("404.js");
+})
+.then(() => {
+ ok(false, "The loading should fail.");
+}, () => {
+ ok(true, "The loading should fail.");
+})
+
+// done
+.then(() => {
+ SimpleTest.finish();
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/worklet/tests/file_console.html b/dom/worklet/tests/file_console.html
new file mode 100644
index 000000000..c4a71e172
--- /dev/null
+++ b/dom/worklet/tests/file_console.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Worklet</title>
+ <script type="application/javascript" src="common.js"></script>
+</head>
+<body>
+
+<script type="application/javascript">
+
+setupTest();
+
+var worklet = window.createWorklet();
+ok(!!worklet, "We have a Worklet");
+worklet.import("worklet_console.js");
+
+</script>
+</body>
+</html>
diff --git a/dom/worklet/tests/file_dump.html b/dom/worklet/tests/file_dump.html
new file mode 100644
index 000000000..123a625a9
--- /dev/null
+++ b/dom/worklet/tests/file_dump.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Worklet</title>
+ <script type="application/javascript" src="common.js"></script>
+</head>
+<body>
+
+<script type="application/javascript">
+
+setupTest();
+
+var worklet = window.createWorklet();
+ok(!!worklet, "We have a Worklet");
+worklet.import("worklet_dump.js")
+.then(() => {
+ SimpleTest.finish();
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/worklet/tests/file_import_with_cache.html b/dom/worklet/tests/file_import_with_cache.html
new file mode 100644
index 000000000..e98818d6f
--- /dev/null
+++ b/dom/worklet/tests/file_import_with_cache.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Worklet</title>
+ <script type="application/javascript" src="common.js"></script>
+</head>
+<body>
+
+<script type="application/javascript">
+
+setupTest();
+
+var worklet = window.createWorklet();
+ok(!!worklet, "We have a Worklet");
+
+function loading() {
+ worklet.import("server_import_with_cache.sjs")
+ .then(() => {
+ ok(true, "Import should load a resource.");
+ }, () => {
+ ok(false, "Import should load a resource.");
+ })
+ .then(() => {
+ done();
+ });
+}
+
+var count = 0;
+const MAX = 10;
+
+function done() {
+ if (++count == MAX) {
+ SimpleTest.finish();
+ }
+}
+
+for (var i = 0; i < MAX; ++i) {
+ loading();
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/worklet/tests/mochitest.ini b/dom/worklet/tests/mochitest.ini
new file mode 100644
index 000000000..2194361cc
--- /dev/null
+++ b/dom/worklet/tests/mochitest.ini
@@ -0,0 +1,13 @@
+[DEFAULT]
+skip-if = release_or_beta
+support-files =
+ common.js
+
+[test_basic.html]
+support-files=file_basic.html
+[test_console.html]
+support-files=file_console.html worklet_console.js
+[test_import_with_cache.html]
+support-files=file_import_with_cache.html server_import_with_cache.sjs
+[test_dump.html]
+support-files=file_dump.html worklet_dump.js
diff --git a/dom/worklet/tests/server_import_with_cache.sjs b/dom/worklet/tests/server_import_with_cache.sjs
new file mode 100644
index 000000000..79a934dd3
--- /dev/null
+++ b/dom/worklet/tests/server_import_with_cache.sjs
@@ -0,0 +1,13 @@
+function handleRequest(request, response)
+{
+ response.setHeader("Content-Type", "text/javascript", false);
+
+ var state = getState("alreadySent");
+ if (!state) {
+ setState("alreadySent", "1");
+ } else {
+ response.setStatusLine('1.1', 404, "Not Found");
+ }
+
+ response.write("42");
+}
diff --git a/dom/worklet/tests/test_basic.html b/dom/worklet/tests/test_basic.html
new file mode 100644
index 000000000..b3010d1a5
--- /dev/null
+++ b/dom/worklet/tests/test_basic.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Worklet</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="common.js"></script>
+</head>
+<body>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv(
+ {"set": [["dom.worklet.testing.enabled", true],
+ ["dom.worklet.enabled", true]]},
+ function() { loadTest("file_basic.html"); });
+
+</script>
+</body>
+</html>
diff --git a/dom/worklet/tests/test_console.html b/dom/worklet/tests/test_console.html
new file mode 100644
index 000000000..72a667389
--- /dev/null
+++ b/dom/worklet/tests/test_console.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Worklet - Console</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="common.js"></script>
+</head>
+<body>
+
+<script type="application/javascript">
+
+function consoleListener() {
+ SpecialPowers.addObserver(this, "console-api-log-event", false);
+}
+
+consoleListener.prototype = {
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic == "console-api-log-event") {
+ var obj = aSubject.wrappedJSObject;
+ if (obj.arguments[0] == "Hello world from a worklet") {
+ ok(true, "Message received \\o/");
+
+ SpecialPowers.removeObserver(this, "console-api-log-event");
+ SimpleTest.finish();
+ return;
+ }
+ }
+ }
+}
+
+var cl = new consoleListener();
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv(
+ {"set": [["dom.worklet.testing.enabled", true],
+ ["dom.worklet.enabled", true]]},
+ function() { loadTest("file_console.html"); });
+
+</script>
+</body>
+</html>
diff --git a/dom/worklet/tests/test_dump.html b/dom/worklet/tests/test_dump.html
new file mode 100644
index 000000000..011e6d498
--- /dev/null
+++ b/dom/worklet/tests/test_dump.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Worklet - Console</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="common.js"></script>
+</head>
+<body>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv(
+ {"set": [["dom.worklet.testing.enabled", true],
+ ["dom.worklet.enabled", true]]},
+ function() { loadTest("file_dump.html"); });
+
+</script>
+</body>
+</html>
diff --git a/dom/worklet/tests/test_import_with_cache.html b/dom/worklet/tests/test_import_with_cache.html
new file mode 100644
index 000000000..316f914eb
--- /dev/null
+++ b/dom/worklet/tests/test_import_with_cache.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Worklet</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="common.js"></script>
+</head>
+<body>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv(
+ {"set": [["dom.worklet.testing.enabled", true],
+ ["dom.worklet.enabled", true]]},
+ function() { loadTest("file_import_with_cache.html"); });
+
+</script>
+</body>
+</html>
diff --git a/dom/worklet/tests/worklet_console.js b/dom/worklet/tests/worklet_console.js
new file mode 100644
index 000000000..557beb1af
--- /dev/null
+++ b/dom/worklet/tests/worklet_console.js
@@ -0,0 +1 @@
+console.log("Hello world from a worklet");
diff --git a/dom/worklet/tests/worklet_dump.js b/dom/worklet/tests/worklet_dump.js
new file mode 100644
index 000000000..439d13f70
--- /dev/null
+++ b/dom/worklet/tests/worklet_dump.js
@@ -0,0 +1 @@
+dump("Hello world from a worklet");