summaryrefslogtreecommitdiffstats
path: root/dom/workers/WorkerPrivate.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/workers/WorkerPrivate.cpp')
-rw-r--r--dom/workers/WorkerPrivate.cpp6752
1 files changed, 6752 insertions, 0 deletions
diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp
new file mode 100644
index 000000000..1df4e5551
--- /dev/null
+++ b/dom/workers/WorkerPrivate.cpp
@@ -0,0 +1,6752 @@
+/* -*- 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 "WorkerPrivate.h"
+
+#include "amIAddonManager.h"
+#include "nsIClassInfo.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIConsoleService.h"
+#include "nsIDOMDOMException.h"
+#include "nsIDOMEvent.h"
+#include "nsIDocument.h"
+#include "nsIDocShell.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIMemoryReporter.h"
+#include "nsINetworkInterceptController.h"
+#include "nsIPermissionManager.h"
+#include "nsIScriptError.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIScriptTimeoutHandler.h"
+#include "nsITabChild.h"
+#include "nsITextToSubURI.h"
+#include "nsIThreadInternal.h"
+#include "nsITimer.h"
+#include "nsIURI.h"
+#include "nsIURL.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsIWorkerDebugger.h"
+#include "nsIXPConnect.h"
+#include "nsPIDOMWindow.h"
+#include "nsGlobalWindow.h"
+
+#include <algorithm>
+#include "ImageContainer.h"
+#include "jsfriendapi.h"
+#include "js/MemoryMetrics.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ContentEvents.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/Likely.h"
+#include "mozilla/LoadContext.h"
+#include "mozilla/Move.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/Console.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/ErrorEvent.h"
+#include "mozilla/dom/ErrorEventBinding.h"
+#include "mozilla/dom/Exceptions.h"
+#include "mozilla/dom/ExtendableMessageEventBinding.h"
+#include "mozilla/dom/FunctionBinding.h"
+#include "mozilla/dom/IndexedDatabaseManager.h"
+#include "mozilla/dom/MessageEvent.h"
+#include "mozilla/dom/MessageEventBinding.h"
+#include "mozilla/dom/MessagePort.h"
+#include "mozilla/dom/MessagePortBinding.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/PMessagePort.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseDebugging.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/dom/SimpleGlobalObject.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/StructuredCloneHolder.h"
+#include "mozilla/dom/TabChild.h"
+#include "mozilla/dom/WorkerBinding.h"
+#include "mozilla/dom/WorkerDebuggerGlobalScopeBinding.h"
+#include "mozilla/dom/WorkerGlobalScopeBinding.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ThrottledEventQueue.h"
+#include "mozilla/TimelineConsumers.h"
+#include "mozilla/WorkerTimelineMarker.h"
+#include "nsAlgorithm.h"
+#include "nsContentUtils.h"
+#include "nsCycleCollector.h"
+#include "nsError.h"
+#include "nsDOMJSUtils.h"
+#include "nsHostObjectProtocolHandler.h"
+#include "nsJSEnvironment.h"
+#include "nsJSUtils.h"
+#include "nsNetUtil.h"
+#include "nsPrintfCString.h"
+#include "nsProxyRelease.h"
+#include "nsQueryObject.h"
+#include "nsSandboxFlags.h"
+#include "nsUTF8Utils.h"
+#include "prthread.h"
+#include "xpcpublic.h"
+
+#ifdef ANDROID
+#include <android/log.h>
+#endif
+
+#ifdef DEBUG
+#include "nsThreadManager.h"
+#endif
+
+#include "Navigator.h"
+#include "Principal.h"
+#include "RuntimeService.h"
+#include "ScriptLoader.h"
+#include "ServiceWorkerEvents.h"
+#include "ServiceWorkerManager.h"
+#include "ServiceWorkerWindowClient.h"
+#include "SharedWorker.h"
+#include "WorkerDebuggerManager.h"
+#include "WorkerHolder.h"
+#include "WorkerNavigator.h"
+#include "WorkerRunnable.h"
+#include "WorkerScope.h"
+#include "WorkerThread.h"
+
+// JS_MaybeGC will run once every second during normal execution.
+#define PERIODIC_GC_TIMER_DELAY_SEC 1
+
+// A shrinking GC will run five seconds after the last event is processed.
+#define IDLE_GC_TIMER_DELAY_SEC 5
+
+#define PREF_WORKERS_ENABLED "dom.workers.enabled"
+
+static mozilla::LazyLogModule sWorkerPrivateLog("WorkerPrivate");
+static mozilla::LazyLogModule sWorkerTimeoutsLog("WorkerTimeouts");
+
+mozilla::LogModule*
+WorkerLog()
+{
+ return sWorkerPrivateLog;
+}
+
+mozilla::LogModule*
+TimeoutsLog()
+{
+ return sWorkerTimeoutsLog;
+}
+
+#define LOG(log, _args) MOZ_LOG(log, LogLevel::Debug, _args);
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+
+USING_WORKERS_NAMESPACE
+
+MOZ_DEFINE_MALLOC_SIZE_OF(JsWorkerMallocSizeOf)
+
+#ifdef DEBUG
+
+BEGIN_WORKERS_NAMESPACE
+
+void
+AssertIsOnMainThread()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+}
+
+END_WORKERS_NAMESPACE
+
+#endif
+
+namespace {
+
+#ifdef DEBUG
+
+const nsIID kDEBUGWorkerEventTargetIID = {
+ 0xccaba3fa, 0x5be2, 0x4de2, { 0xba, 0x87, 0x3b, 0x3b, 0x5b, 0x1d, 0x5, 0xfb }
+};
+
+#endif
+
+template <class T>
+class AutoPtrComparator
+{
+ typedef nsAutoPtr<T> A;
+ typedef T* B;
+
+public:
+ bool Equals(const A& a, const B& b) const {
+ return a && b ? *a == *b : !a && !b ? true : false;
+ }
+ bool LessThan(const A& a, const B& b) const {
+ return a && b ? *a < *b : b ? true : false;
+ }
+};
+
+template <class T>
+inline AutoPtrComparator<T>
+GetAutoPtrComparator(const nsTArray<nsAutoPtr<T> >&)
+{
+ return AutoPtrComparator<T>();
+}
+
+// Specialize this if there's some class that has multiple nsISupports bases.
+template <class T>
+struct ISupportsBaseInfo
+{
+ typedef T ISupportsBase;
+};
+
+template <template <class> class SmartPtr, class T>
+inline void
+SwapToISupportsArray(SmartPtr<T>& aSrc,
+ nsTArray<nsCOMPtr<nsISupports> >& aDest)
+{
+ nsCOMPtr<nsISupports>* dest = aDest.AppendElement();
+
+ T* raw = nullptr;
+ aSrc.swap(raw);
+
+ nsISupports* rawSupports =
+ static_cast<typename ISupportsBaseInfo<T>::ISupportsBase*>(raw);
+ dest->swap(rawSupports);
+}
+
+// This class is used to wrap any runnables that the worker receives via the
+// nsIEventTarget::Dispatch() method (either from NS_DispatchToCurrentThread or
+// from the worker's EventTarget).
+class ExternalRunnableWrapper final : public WorkerRunnable
+{
+ nsCOMPtr<nsIRunnable> mWrappedRunnable;
+
+public:
+ ExternalRunnableWrapper(WorkerPrivate* aWorkerPrivate,
+ nsIRunnable* aWrappedRunnable)
+ : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mWrappedRunnable(aWrappedRunnable)
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ MOZ_ASSERT(aWrappedRunnable);
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+private:
+ ~ExternalRunnableWrapper()
+ { }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ nsresult rv = mWrappedRunnable->Run();
+ if (NS_FAILED(rv)) {
+ if (!JS_IsExceptionPending(aCx)) {
+ Throw(aCx, rv);
+ }
+ return false;
+ }
+ return true;
+ }
+
+ nsresult
+ Cancel() override
+ {
+ nsresult rv;
+ nsCOMPtr<nsICancelableRunnable> cancelable =
+ do_QueryInterface(mWrappedRunnable);
+ MOZ_ASSERT(cancelable); // We checked this earlier!
+ rv = cancelable->Cancel();
+ nsresult rv2 = WorkerRunnable::Cancel();
+ return NS_FAILED(rv) ? rv : rv2;
+ }
+};
+
+struct WindowAction
+{
+ nsPIDOMWindowInner* mWindow;
+ bool mDefaultAction;
+
+ MOZ_IMPLICIT WindowAction(nsPIDOMWindowInner* aWindow)
+ : mWindow(aWindow), mDefaultAction(true)
+ { }
+
+ bool
+ operator==(const WindowAction& aOther) const
+ {
+ return mWindow == aOther.mWindow;
+ }
+};
+
+void
+LogErrorToConsole(const nsAString& aMessage,
+ const nsAString& aFilename,
+ const nsAString& aLine,
+ uint32_t aLineNumber,
+ uint32_t aColumnNumber,
+ uint32_t aFlags,
+ uint64_t aInnerWindowId)
+{
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsIScriptError> scriptError =
+ do_CreateInstance(NS_SCRIPTERROR_CONTRACTID);
+ NS_WARNING_ASSERTION(scriptError, "Failed to create script error!");
+
+ if (scriptError) {
+ if (NS_FAILED(scriptError->InitWithWindowID(aMessage, aFilename, aLine,
+ aLineNumber, aColumnNumber,
+ aFlags, "Web Worker",
+ aInnerWindowId))) {
+ NS_WARNING("Failed to init script error!");
+ scriptError = nullptr;
+ }
+ }
+
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ NS_WARNING_ASSERTION(consoleService, "Failed to get console service!");
+
+ if (consoleService) {
+ if (scriptError) {
+ if (NS_SUCCEEDED(consoleService->LogMessage(scriptError))) {
+ return;
+ }
+ NS_WARNING("LogMessage failed!");
+ } else if (NS_SUCCEEDED(consoleService->LogStringMessage(
+ aMessage.BeginReading()))) {
+ return;
+ }
+ NS_WARNING("LogStringMessage failed!");
+ }
+
+ NS_ConvertUTF16toUTF8 msg(aMessage);
+ NS_ConvertUTF16toUTF8 filename(aFilename);
+
+ static const char kErrorString[] = "JS error in Web Worker: %s [%s:%u]";
+
+#ifdef ANDROID
+ __android_log_print(ANDROID_LOG_INFO, "Gecko", kErrorString, msg.get(),
+ filename.get(), aLineNumber);
+#endif
+
+ fprintf(stderr, kErrorString, msg.get(), filename.get(), aLineNumber);
+ fflush(stderr);
+}
+
+class MainThreadReleaseRunnable final : public Runnable
+{
+ nsTArray<nsCOMPtr<nsISupports>> mDoomed;
+ nsCOMPtr<nsILoadGroup> mLoadGroupToCancel;
+
+public:
+ MainThreadReleaseRunnable(nsTArray<nsCOMPtr<nsISupports>>& aDoomed,
+ nsCOMPtr<nsILoadGroup>& aLoadGroupToCancel)
+ {
+ mDoomed.SwapElements(aDoomed);
+ mLoadGroupToCancel.swap(aLoadGroupToCancel);
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_IMETHOD
+ Run() override
+ {
+ if (mLoadGroupToCancel) {
+ mLoadGroupToCancel->Cancel(NS_BINDING_ABORTED);
+ mLoadGroupToCancel = nullptr;
+ }
+
+ mDoomed.Clear();
+ return NS_OK;
+ }
+
+private:
+ ~MainThreadReleaseRunnable()
+ { }
+};
+
+class WorkerFinishedRunnable final : public WorkerControlRunnable
+{
+ WorkerPrivate* mFinishedWorker;
+
+public:
+ WorkerFinishedRunnable(WorkerPrivate* aWorkerPrivate,
+ WorkerPrivate* aFinishedWorker)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mFinishedWorker(aFinishedWorker)
+ { }
+
+private:
+ virtual bool
+ PreDispatch(WorkerPrivate* aWorkerPrivate) override
+ {
+ // Silence bad assertions.
+ return true;
+ }
+
+ virtual void
+ PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
+ {
+ // Silence bad assertions.
+ }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ nsCOMPtr<nsILoadGroup> loadGroupToCancel;
+ mFinishedWorker->ForgetOverridenLoadGroup(loadGroupToCancel);
+
+ nsTArray<nsCOMPtr<nsISupports>> doomed;
+ mFinishedWorker->ForgetMainThreadObjects(doomed);
+
+ RefPtr<MainThreadReleaseRunnable> runnable =
+ new MainThreadReleaseRunnable(doomed, loadGroupToCancel);
+ if (NS_FAILED(mWorkerPrivate->DispatchToMainThread(runnable.forget()))) {
+ NS_WARNING("Failed to dispatch, going to leak!");
+ }
+
+ RuntimeService* runtime = RuntimeService::GetService();
+ NS_ASSERTION(runtime, "This should never be null!");
+
+ mFinishedWorker->DisableDebugger();
+
+ runtime->UnregisterWorker(mFinishedWorker);
+
+ mFinishedWorker->ClearSelfRef();
+ return true;
+ }
+};
+
+class TopLevelWorkerFinishedRunnable final : public Runnable
+{
+ WorkerPrivate* mFinishedWorker;
+
+public:
+ explicit TopLevelWorkerFinishedRunnable(WorkerPrivate* aFinishedWorker)
+ : mFinishedWorker(aFinishedWorker)
+ {
+ aFinishedWorker->AssertIsOnWorkerThread();
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+private:
+ ~TopLevelWorkerFinishedRunnable() {}
+
+ NS_IMETHOD
+ Run() override
+ {
+ AssertIsOnMainThread();
+
+ RuntimeService* runtime = RuntimeService::GetService();
+ MOZ_ASSERT(runtime);
+
+ mFinishedWorker->DisableDebugger();
+
+ runtime->UnregisterWorker(mFinishedWorker);
+
+ nsCOMPtr<nsILoadGroup> loadGroupToCancel;
+ mFinishedWorker->ForgetOverridenLoadGroup(loadGroupToCancel);
+
+ nsTArray<nsCOMPtr<nsISupports> > doomed;
+ mFinishedWorker->ForgetMainThreadObjects(doomed);
+
+ RefPtr<MainThreadReleaseRunnable> runnable =
+ new MainThreadReleaseRunnable(doomed, loadGroupToCancel);
+ if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
+ NS_WARNING("Failed to dispatch, going to leak!");
+ }
+
+ mFinishedWorker->ClearSelfRef();
+ return NS_OK;
+ }
+};
+
+class ModifyBusyCountRunnable final : public WorkerControlRunnable
+{
+ bool mIncrease;
+
+public:
+ ModifyBusyCountRunnable(WorkerPrivate* aWorkerPrivate, bool aIncrease)
+ : WorkerControlRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount),
+ mIncrease(aIncrease)
+ { }
+
+private:
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ return aWorkerPrivate->ModifyBusyCount(mIncrease);
+ }
+
+ virtual void
+ PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult)
+ override
+ {
+ if (mIncrease) {
+ WorkerControlRunnable::PostRun(aCx, aWorkerPrivate, aRunResult);
+ return;
+ }
+ // Don't do anything here as it's possible that aWorkerPrivate has been
+ // deleted.
+ }
+};
+
+class CompileScriptRunnable final : public WorkerRunnable
+{
+ nsString mScriptURL;
+
+public:
+ explicit CompileScriptRunnable(WorkerPrivate* aWorkerPrivate,
+ const nsAString& aScriptURL)
+ : WorkerRunnable(aWorkerPrivate),
+ mScriptURL(aScriptURL)
+ { }
+
+private:
+ // We can't implement PreRun effectively, because at the point when that would
+ // run we have not yet done our load so don't know things like our final
+ // principal and whatnot.
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ ErrorResult rv;
+ scriptloader::LoadMainScript(aWorkerPrivate, mScriptURL, WorkerScript, rv);
+ rv.WouldReportJSException();
+ // Explicitly ignore NS_BINDING_ABORTED on rv. Or more precisely, still
+ // return false and don't SetWorkerScriptExecutedSuccessfully() in that
+ // case, but don't throw anything on aCx. The idea is to not dispatch error
+ // events if our load is canceled with that error code.
+ if (rv.ErrorCodeIs(NS_BINDING_ABORTED)) {
+ rv.SuppressException();
+ return false;
+ }
+
+ WorkerGlobalScope* globalScope = aWorkerPrivate->GlobalScope();
+ if (NS_WARN_IF(!globalScope)) {
+ // We never got as far as calling GetOrCreateGlobalScope, or it failed.
+ // We have no way to enter a compartment, hence no sane way to report this
+ // error. :(
+ rv.SuppressException();
+ return false;
+ }
+
+ // Make sure to propagate exceptions from rv onto aCx, so that they will get
+ // reported after we return. We do this for all failures on rv, because now
+ // we're using rv to track all the state we care about.
+ //
+ // This is a little dumb, but aCx is in the null compartment here because we
+ // set it up that way in our Run(), since we had not created the global at
+ // that point yet. So we need to enter the compartment of our global,
+ // because setting a pending exception on aCx involves wrapping into its
+ // current compartment. Luckily we have a global now.
+ JSAutoCompartment ac(aCx, globalScope->GetGlobalJSObject());
+ if (rv.MaybeSetPendingException(aCx)) {
+ return false;
+ }
+
+ aWorkerPrivate->SetWorkerScriptExecutedSuccessfully();
+ return true;
+ }
+};
+
+class CompileDebuggerScriptRunnable final : public WorkerDebuggerRunnable
+{
+ nsString mScriptURL;
+
+public:
+ CompileDebuggerScriptRunnable(WorkerPrivate* aWorkerPrivate,
+ const nsAString& aScriptURL)
+ : WorkerDebuggerRunnable(aWorkerPrivate),
+ mScriptURL(aScriptURL)
+ { }
+
+private:
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ WorkerDebuggerGlobalScope* globalScope =
+ aWorkerPrivate->CreateDebuggerGlobalScope(aCx);
+ if (!globalScope) {
+ NS_WARNING("Failed to make global!");
+ return false;
+ }
+
+ JS::Rooted<JSObject*> global(aCx, globalScope->GetWrapper());
+
+ ErrorResult rv;
+ JSAutoCompartment ac(aCx, global);
+ scriptloader::LoadMainScript(aWorkerPrivate, mScriptURL,
+ DebuggerScript, rv);
+ rv.WouldReportJSException();
+ // Explicitly ignore NS_BINDING_ABORTED on rv. Or more precisely, still
+ // return false and don't SetWorkerScriptExecutedSuccessfully() in that
+ // case, but don't throw anything on aCx. The idea is to not dispatch error
+ // events if our load is canceled with that error code.
+ if (rv.ErrorCodeIs(NS_BINDING_ABORTED)) {
+ rv.SuppressException();
+ return false;
+ }
+ // Make sure to propagate exceptions from rv onto aCx, so that they will get
+ // reported after we return. We do this for all failures on rv, because now
+ // we're using rv to track all the state we care about.
+ if (rv.MaybeSetPendingException(aCx)) {
+ return false;
+ }
+
+ return true;
+ }
+};
+
+class MessageEventRunnable final : public WorkerRunnable
+ , public StructuredCloneHolder
+{
+ // This is only used for messages dispatched to a service worker.
+ UniquePtr<ServiceWorkerClientInfo> mEventSource;
+
+ RefPtr<PromiseNativeHandler> mHandler;
+
+public:
+ MessageEventRunnable(WorkerPrivate* aWorkerPrivate,
+ TargetAndBusyBehavior aBehavior)
+ : WorkerRunnable(aWorkerPrivate, aBehavior)
+ , StructuredCloneHolder(CloningSupported, TransferringSupported,
+ StructuredCloneScope::SameProcessDifferentThread)
+ {
+ }
+
+ void
+ SetServiceWorkerData(UniquePtr<ServiceWorkerClientInfo>&& aSource,
+ PromiseNativeHandler* aHandler)
+ {
+ mEventSource = Move(aSource);
+ mHandler = aHandler;
+ }
+
+ bool
+ DispatchDOMEvent(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ DOMEventTargetHelper* aTarget, bool aIsMainThread)
+ {
+ nsCOMPtr<nsIGlobalObject> parent = do_QueryInterface(aTarget->GetParentObject());
+
+ // For some workers without window, parent is null and we try to find it
+ // from the JS Context.
+ if (!parent) {
+ JS::Rooted<JSObject*> globalObject(aCx, JS::CurrentGlobalOrNull(aCx));
+ if (NS_WARN_IF(!globalObject)) {
+ return false;
+ }
+
+ parent = xpc::NativeGlobal(globalObject);
+ if (NS_WARN_IF(!parent)) {
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(parent);
+
+ JS::Rooted<JS::Value> messageData(aCx);
+ ErrorResult rv;
+
+ UniquePtr<AbstractTimelineMarker> start;
+ UniquePtr<AbstractTimelineMarker> end;
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+ bool isTimelineRecording = timelines && !timelines->IsEmpty();
+
+ if (isTimelineRecording) {
+ start = MakeUnique<WorkerTimelineMarker>(aIsMainThread
+ ? ProfileTimelineWorkerOperationType::DeserializeDataOnMainThread
+ : ProfileTimelineWorkerOperationType::DeserializeDataOffMainThread,
+ MarkerTracingType::START);
+ }
+
+ Read(parent, aCx, &messageData, rv);
+
+ if (isTimelineRecording) {
+ end = MakeUnique<WorkerTimelineMarker>(aIsMainThread
+ ? ProfileTimelineWorkerOperationType::DeserializeDataOnMainThread
+ : ProfileTimelineWorkerOperationType::DeserializeDataOffMainThread,
+ MarkerTracingType::END);
+ timelines->AddMarkerForAllObservedDocShells(start);
+ timelines->AddMarkerForAllObservedDocShells(end);
+ }
+
+ if (NS_WARN_IF(rv.Failed())) {
+ xpc::Throw(aCx, rv.StealNSResult());
+ return false;
+ }
+
+ Sequence<OwningNonNull<MessagePort>> ports;
+ if (!TakeTransferredPortsAsSequence(ports)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIDOMEvent> domEvent;
+ RefPtr<ExtendableMessageEvent> extendableEvent;
+ // For messages dispatched to service worker, use ExtendableMessageEvent
+ // https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#extendablemessage-event-section
+ if (mEventSource) {
+ RefPtr<ServiceWorkerClient> client =
+ new ServiceWorkerWindowClient(aTarget, *mEventSource);
+
+ RootedDictionary<ExtendableMessageEventInit> init(aCx);
+
+ init.mBubbles = false;
+ init.mCancelable = false;
+
+ init.mData = messageData;
+ init.mPorts = ports;
+ init.mSource.SetValue().SetAsClient() = client;
+
+ ErrorResult rv;
+ extendableEvent = ExtendableMessageEvent::Constructor(
+ aTarget, NS_LITERAL_STRING("message"), init, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return false;
+ }
+
+ domEvent = do_QueryObject(extendableEvent);
+ } else {
+ RefPtr<MessageEvent> event = new MessageEvent(aTarget, nullptr, nullptr);
+ event->InitMessageEvent(nullptr,
+ NS_LITERAL_STRING("message"),
+ false /* non-bubbling */,
+ false /* cancelable */,
+ messageData,
+ EmptyString(),
+ EmptyString(),
+ nullptr,
+ ports);
+ domEvent = do_QueryObject(event);
+ }
+
+ domEvent->SetTrusted(true);
+
+ nsEventStatus dummy = nsEventStatus_eIgnore;
+ aTarget->DispatchDOMEvent(nullptr, domEvent, nullptr, &dummy);
+
+ if (extendableEvent && mHandler) {
+ RefPtr<Promise> waitUntilPromise = extendableEvent->GetPromise();
+ if (!waitUntilPromise) {
+ waitUntilPromise = Promise::Resolve(parent, aCx,
+ JS::UndefinedHandleValue, rv);
+ MOZ_RELEASE_ASSERT(!rv.Failed());
+ }
+
+ MOZ_ASSERT(waitUntilPromise);
+
+ waitUntilPromise->AppendNativeHandler(mHandler);
+ }
+
+ return true;
+ }
+
+private:
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ if (mBehavior == ParentThreadUnchangedBusyCount) {
+ // Don't fire this event if the JS object has been disconnected from the
+ // private object.
+ if (!aWorkerPrivate->IsAcceptingEvents()) {
+ return true;
+ }
+
+ if (aWorkerPrivate->IsFrozen() ||
+ aWorkerPrivate->IsParentWindowPaused()) {
+ MOZ_ASSERT(!IsDebuggerRunnable());
+ aWorkerPrivate->QueueRunnable(this);
+ return true;
+ }
+
+ aWorkerPrivate->AssertInnerWindowIsCorrect();
+
+ return DispatchDOMEvent(aCx, aWorkerPrivate, aWorkerPrivate,
+ !aWorkerPrivate->GetParent());
+ }
+
+ MOZ_ASSERT(aWorkerPrivate == GetWorkerPrivateFromContext(aCx));
+
+ return DispatchDOMEvent(aCx, aWorkerPrivate, aWorkerPrivate->GlobalScope(),
+ false);
+ }
+};
+
+class DebuggerMessageEventRunnable : public WorkerDebuggerRunnable {
+ nsString mMessage;
+
+public:
+ DebuggerMessageEventRunnable(WorkerPrivate* aWorkerPrivate,
+ const nsAString& aMessage)
+ : WorkerDebuggerRunnable(aWorkerPrivate),
+ mMessage(aMessage)
+ {
+ }
+
+private:
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ WorkerDebuggerGlobalScope* globalScope = aWorkerPrivate->DebuggerGlobalScope();
+ MOZ_ASSERT(globalScope);
+
+ JS::Rooted<JSString*> message(aCx, JS_NewUCStringCopyN(aCx, mMessage.get(),
+ mMessage.Length()));
+ if (!message) {
+ return false;
+ }
+ JS::Rooted<JS::Value> data(aCx, JS::StringValue(message));
+
+ RefPtr<MessageEvent> event = new MessageEvent(globalScope, nullptr,
+ nullptr);
+ event->InitMessageEvent(nullptr,
+ NS_LITERAL_STRING("message"),
+ false, // canBubble
+ true, // cancelable
+ data,
+ EmptyString(),
+ EmptyString(),
+ nullptr,
+ Sequence<OwningNonNull<MessagePort>>());
+ event->SetTrusted(true);
+
+ nsCOMPtr<nsIDOMEvent> domEvent = do_QueryObject(event);
+ nsEventStatus status = nsEventStatus_eIgnore;
+ globalScope->DispatchDOMEvent(nullptr, domEvent, nullptr, &status);
+ return true;
+ }
+};
+
+class NotifyRunnable final : public WorkerControlRunnable
+{
+ Status mStatus;
+
+public:
+ NotifyRunnable(WorkerPrivate* aWorkerPrivate, Status aStatus)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mStatus(aStatus)
+ {
+ MOZ_ASSERT(aStatus == Closing || aStatus == Terminating ||
+ aStatus == Canceling || aStatus == Killing);
+ }
+
+private:
+ virtual bool
+ PreDispatch(WorkerPrivate* aWorkerPrivate) override
+ {
+ aWorkerPrivate->AssertIsOnParentThread();
+ return aWorkerPrivate->ModifyBusyCount(true);
+ }
+
+ virtual void
+ PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
+ {
+ aWorkerPrivate->AssertIsOnParentThread();
+ if (!aDispatchResult) {
+ // We couldn't dispatch to the worker, which means it's already dead.
+ // Undo the busy count modification.
+ aWorkerPrivate->ModifyBusyCount(false);
+ }
+ }
+
+ virtual void
+ PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult)
+ override
+ {
+ aWorkerPrivate->ModifyBusyCountFromWorker(false);
+ return;
+ }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ bool ok = aWorkerPrivate->NotifyInternal(aCx, mStatus);
+ MOZ_ASSERT(!JS_IsExceptionPending(aCx));
+ return ok;
+ }
+};
+
+class CloseRunnable final : public WorkerControlRunnable
+{
+public:
+ explicit CloseRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerControlRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount)
+ { }
+
+private:
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ return aWorkerPrivate->Close();
+ }
+};
+
+class FreezeRunnable final : public WorkerControlRunnable
+{
+public:
+ explicit FreezeRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+ { }
+
+private:
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ return aWorkerPrivate->FreezeInternal();
+ }
+};
+
+class ThawRunnable final : public WorkerControlRunnable
+{
+public:
+ explicit ThawRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+ { }
+
+private:
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ return aWorkerPrivate->ThawInternal();
+ }
+};
+
+class ReportErrorToConsoleRunnable final : public WorkerRunnable
+{
+ const char* mMessage;
+
+public:
+ // aWorkerPrivate is the worker thread we're on (or the main thread, if null)
+ static void
+ Report(WorkerPrivate* aWorkerPrivate, const char* aMessage)
+ {
+ if (aWorkerPrivate) {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ } else {
+ AssertIsOnMainThread();
+ }
+
+ // Now fire a runnable to do the same on the parent's thread if we can.
+ if (aWorkerPrivate) {
+ RefPtr<ReportErrorToConsoleRunnable> runnable =
+ new ReportErrorToConsoleRunnable(aWorkerPrivate, aMessage);
+ runnable->Dispatch();
+ return;
+ }
+
+ // Log a warning to the console.
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("DOM"),
+ nullptr,
+ nsContentUtils::eDOM_PROPERTIES,
+ aMessage);
+ }
+
+private:
+ ReportErrorToConsoleRunnable(WorkerPrivate* aWorkerPrivate, const char* aMessage)
+ : WorkerRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount),
+ mMessage(aMessage)
+ { }
+
+ virtual void
+ PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
+ {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ // Dispatch may fail if the worker was canceled, no need to report that as
+ // an error, so don't call base class PostDispatch.
+ }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ WorkerPrivate* parent = aWorkerPrivate->GetParent();
+ MOZ_ASSERT_IF(!parent, NS_IsMainThread());
+ Report(parent, mMessage);
+ return true;
+ }
+};
+
+class ReportErrorRunnable final : public WorkerRunnable
+{
+ nsString mMessage;
+ nsString mFilename;
+ nsString mLine;
+ uint32_t mLineNumber;
+ uint32_t mColumnNumber;
+ uint32_t mFlags;
+ uint32_t mErrorNumber;
+ JSExnType mExnType;
+ bool mMutedError;
+
+public:
+ // aWorkerPrivate is the worker thread we're on (or the main thread, if null)
+ // aTarget is the worker object that we are going to fire an error at
+ // (if any).
+ static void
+ ReportError(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ bool aFireAtScope, WorkerPrivate* aTarget,
+ const nsString& aMessage, const nsString& aFilename,
+ const nsString& aLine, uint32_t aLineNumber,
+ uint32_t aColumnNumber, uint32_t aFlags,
+ uint32_t aErrorNumber, JSExnType aExnType,
+ bool aMutedError, uint64_t aInnerWindowId,
+ JS::Handle<JS::Value> aException = JS::NullHandleValue)
+ {
+ if (aWorkerPrivate) {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ } else {
+ AssertIsOnMainThread();
+ }
+
+ // We should not fire error events for warnings but instead make sure that
+ // they show up in the error console.
+ if (!JSREPORT_IS_WARNING(aFlags)) {
+ // First fire an ErrorEvent at the worker.
+ RootedDictionary<ErrorEventInit> init(aCx);
+
+ if (aMutedError) {
+ init.mMessage.AssignLiteral("Script error.");
+ } else {
+ init.mMessage = aMessage;
+ init.mFilename = aFilename;
+ init.mLineno = aLineNumber;
+ init.mError = aException;
+ }
+
+ init.mCancelable = true;
+ init.mBubbles = false;
+
+ if (aTarget) {
+ RefPtr<ErrorEvent> event =
+ ErrorEvent::Constructor(aTarget, NS_LITERAL_STRING("error"), init);
+ event->SetTrusted(true);
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ aTarget->DispatchDOMEvent(nullptr, event, nullptr, &status);
+
+ if (status == nsEventStatus_eConsumeNoDefault) {
+ return;
+ }
+ }
+
+ // Now fire an event at the global object, but don't do that if the error
+ // code is too much recursion and this is the same script threw the error.
+ // XXXbz the interaction of this with worker errors seems kinda broken.
+ // An overrecursion in the debugger or debugger sandbox will get turned
+ // into an error event on our parent worker!
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1271441 tracks making this
+ // better.
+ if (aFireAtScope && (aTarget || aErrorNumber != JSMSG_OVER_RECURSED)) {
+ JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
+ NS_ASSERTION(global, "This should never be null!");
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ nsIScriptGlobalObject* sgo;
+
+ if (aWorkerPrivate) {
+ WorkerGlobalScope* globalScope = nullptr;
+ UNWRAP_OBJECT(WorkerGlobalScope, &global, globalScope);
+
+ if (!globalScope) {
+ WorkerDebuggerGlobalScope* globalScope = nullptr;
+ UNWRAP_OBJECT(WorkerDebuggerGlobalScope, &global, globalScope);
+
+ MOZ_ASSERT_IF(globalScope, globalScope->GetWrapperPreserveColor() == global);
+ if (globalScope || IsDebuggerSandbox(global)) {
+ aWorkerPrivate->ReportErrorToDebugger(aFilename, aLineNumber,
+ aMessage);
+ return;
+ }
+
+ MOZ_ASSERT(SimpleGlobalObject::SimpleGlobalType(global) ==
+ SimpleGlobalObject::GlobalType::BindingDetail);
+ // XXXbz We should really log this to console, but unwinding out of
+ // this stuff without ending up firing any events is ... hard. Just
+ // return for now.
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1271441 tracks
+ // making this better.
+ return;
+ }
+
+ MOZ_ASSERT(globalScope->GetWrapperPreserveColor() == global);
+ nsIDOMEventTarget* target = static_cast<nsIDOMEventTarget*>(globalScope);
+
+ RefPtr<ErrorEvent> event =
+ ErrorEvent::Constructor(aTarget, NS_LITERAL_STRING("error"), init);
+ event->SetTrusted(true);
+
+ if (NS_FAILED(EventDispatcher::DispatchDOMEvent(target, nullptr,
+ event, nullptr,
+ &status))) {
+ NS_WARNING("Failed to dispatch worker thread error event!");
+ status = nsEventStatus_eIgnore;
+ }
+ }
+ else if ((sgo = nsJSUtils::GetStaticScriptGlobal(global))) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (NS_FAILED(sgo->HandleScriptError(init, &status))) {
+ NS_WARNING("Failed to dispatch main thread error event!");
+ status = nsEventStatus_eIgnore;
+ }
+ }
+
+ // Was preventDefault() called?
+ if (status == nsEventStatus_eConsumeNoDefault) {
+ return;
+ }
+ }
+ }
+
+ // Now fire a runnable to do the same on the parent's thread if we can.
+ if (aWorkerPrivate) {
+ RefPtr<ReportErrorRunnable> runnable =
+ new ReportErrorRunnable(aWorkerPrivate, aMessage, aFilename, aLine,
+ aLineNumber, aColumnNumber, aFlags,
+ aErrorNumber, aExnType, aMutedError);
+ runnable->Dispatch();
+ return;
+ }
+
+ // Otherwise log an error to the error console.
+ LogErrorToConsole(aMessage, aFilename, aLine, aLineNumber, aColumnNumber,
+ aFlags, aInnerWindowId);
+ }
+
+private:
+ ReportErrorRunnable(WorkerPrivate* aWorkerPrivate, const nsString& aMessage,
+ const nsString& aFilename, const nsString& aLine,
+ uint32_t aLineNumber, uint32_t aColumnNumber,
+ uint32_t aFlags, uint32_t aErrorNumber,
+ JSExnType aExnType, bool aMutedError)
+ : WorkerRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount),
+ mMessage(aMessage), mFilename(aFilename), mLine(aLine),
+ mLineNumber(aLineNumber), mColumnNumber(aColumnNumber), mFlags(aFlags),
+ mErrorNumber(aErrorNumber), mExnType(aExnType), mMutedError(aMutedError)
+ { }
+
+ virtual void
+ PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
+ {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ // Dispatch may fail if the worker was canceled, no need to report that as
+ // an error, so don't call base class PostDispatch.
+ }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ JS::Rooted<JSObject*> target(aCx, aWorkerPrivate->GetWrapper());
+
+ uint64_t innerWindowId;
+ bool fireAtScope = true;
+
+ bool workerIsAcceptingEvents = aWorkerPrivate->IsAcceptingEvents();
+
+ WorkerPrivate* parent = aWorkerPrivate->GetParent();
+ if (parent) {
+ innerWindowId = 0;
+ }
+ else {
+ AssertIsOnMainThread();
+
+ if (aWorkerPrivate->IsFrozen() ||
+ aWorkerPrivate->IsParentWindowPaused()) {
+ MOZ_ASSERT(!IsDebuggerRunnable());
+ aWorkerPrivate->QueueRunnable(this);
+ return true;
+ }
+
+ if (aWorkerPrivate->IsSharedWorker()) {
+ aWorkerPrivate->BroadcastErrorToSharedWorkers(aCx, mMessage, mFilename,
+ mLine, mLineNumber,
+ mColumnNumber, mFlags);
+ return true;
+ }
+
+ // Service workers do not have a main thread parent global, so normal
+ // worker error reporting will crash. Instead, pass the error to
+ // the ServiceWorkerManager to report on any controlled documents.
+ if (aWorkerPrivate->IsServiceWorker()) {
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (swm) {
+ swm->HandleError(aCx, aWorkerPrivate->GetPrincipal(),
+ aWorkerPrivate->WorkerName(),
+ aWorkerPrivate->ScriptURL(),
+ mMessage,
+ mFilename, mLine, mLineNumber,
+ mColumnNumber, mFlags, mExnType);
+ }
+ return true;
+ }
+
+ // The innerWindowId is only required if we are going to ReportError
+ // below, which is gated on this condition. The inner window correctness
+ // check is only going to succeed when the worker is accepting events.
+ if (workerIsAcceptingEvents) {
+ aWorkerPrivate->AssertInnerWindowIsCorrect();
+ innerWindowId = aWorkerPrivate->WindowID();
+ }
+ }
+
+ // Don't fire this event if the JS object has been disconnected from the
+ // private object.
+ if (!workerIsAcceptingEvents) {
+ return true;
+ }
+
+ ReportError(aCx, parent, fireAtScope, aWorkerPrivate, mMessage,
+ mFilename, mLine, mLineNumber, mColumnNumber, mFlags,
+ mErrorNumber, mExnType, mMutedError, innerWindowId);
+ return true;
+ }
+};
+
+class TimerRunnable final : public WorkerRunnable,
+ public nsITimerCallback
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ explicit TimerRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+ { }
+
+private:
+ ~TimerRunnable() {}
+
+ virtual bool
+ PreDispatch(WorkerPrivate* aWorkerPrivate) override
+ {
+ // Silence bad assertions.
+ return true;
+ }
+
+ virtual void
+ PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
+ {
+ // Silence bad assertions.
+ }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ return aWorkerPrivate->RunExpiredTimeouts(aCx);
+ }
+
+ NS_IMETHOD
+ Notify(nsITimer* aTimer) override
+ {
+ return Run();
+ }
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(TimerRunnable, WorkerRunnable, nsITimerCallback)
+
+class DebuggerImmediateRunnable : public WorkerRunnable
+{
+ RefPtr<dom::Function> mHandler;
+
+public:
+ explicit DebuggerImmediateRunnable(WorkerPrivate* aWorkerPrivate,
+ dom::Function& aHandler)
+ : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mHandler(&aHandler)
+ { }
+
+private:
+ virtual bool
+ IsDebuggerRunnable() const override
+ {
+ return true;
+ }
+
+ virtual bool
+ PreDispatch(WorkerPrivate* aWorkerPrivate) override
+ {
+ // Silence bad assertions.
+ return true;
+ }
+
+ virtual void
+ PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
+ {
+ // Silence bad assertions.
+ }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
+ JS::Rooted<JS::Value> callable(aCx, JS::ObjectValue(*mHandler->Callable()));
+ JS::HandleValueArray args = JS::HandleValueArray::empty();
+ JS::Rooted<JS::Value> rval(aCx);
+ if (!JS_CallFunctionValue(aCx, global, callable, args, &rval)) {
+ // Just return false; WorkerRunnable::Run will report the exception.
+ return false;
+ }
+
+ return true;
+ }
+};
+
+void
+DummyCallback(nsITimer* aTimer, void* aClosure)
+{
+ // Nothing!
+}
+
+class UpdateContextOptionsRunnable final : public WorkerControlRunnable
+{
+ JS::ContextOptions mContextOptions;
+
+public:
+ UpdateContextOptionsRunnable(WorkerPrivate* aWorkerPrivate,
+ const JS::ContextOptions& aContextOptions)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mContextOptions(aContextOptions)
+ { }
+
+private:
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ aWorkerPrivate->UpdateContextOptionsInternal(aCx, mContextOptions);
+ return true;
+ }
+};
+
+class UpdatePreferenceRunnable final : public WorkerControlRunnable
+{
+ WorkerPreference mPref;
+ bool mValue;
+
+public:
+ UpdatePreferenceRunnable(WorkerPrivate* aWorkerPrivate,
+ WorkerPreference aPref,
+ bool aValue)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mPref(aPref),
+ mValue(aValue)
+ { }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ aWorkerPrivate->UpdatePreferenceInternal(mPref, mValue);
+ return true;
+ }
+};
+
+class UpdateLanguagesRunnable final : public WorkerRunnable
+{
+ nsTArray<nsString> mLanguages;
+
+public:
+ UpdateLanguagesRunnable(WorkerPrivate* aWorkerPrivate,
+ const nsTArray<nsString>& aLanguages)
+ : WorkerRunnable(aWorkerPrivate),
+ mLanguages(aLanguages)
+ { }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ aWorkerPrivate->UpdateLanguagesInternal(mLanguages);
+ return true;
+ }
+};
+
+class UpdateJSWorkerMemoryParameterRunnable final :
+ public WorkerControlRunnable
+{
+ uint32_t mValue;
+ JSGCParamKey mKey;
+
+public:
+ UpdateJSWorkerMemoryParameterRunnable(WorkerPrivate* aWorkerPrivate,
+ JSGCParamKey aKey,
+ uint32_t aValue)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mValue(aValue), mKey(aKey)
+ { }
+
+private:
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ aWorkerPrivate->UpdateJSWorkerMemoryParameterInternal(aCx, mKey, mValue);
+ return true;
+ }
+};
+
+#ifdef JS_GC_ZEAL
+class UpdateGCZealRunnable final : public WorkerControlRunnable
+{
+ uint8_t mGCZeal;
+ uint32_t mFrequency;
+
+public:
+ UpdateGCZealRunnable(WorkerPrivate* aWorkerPrivate,
+ uint8_t aGCZeal,
+ uint32_t aFrequency)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mGCZeal(aGCZeal), mFrequency(aFrequency)
+ { }
+
+private:
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ aWorkerPrivate->UpdateGCZealInternal(aCx, mGCZeal, mFrequency);
+ return true;
+ }
+};
+#endif
+
+class GarbageCollectRunnable final : public WorkerControlRunnable
+{
+ bool mShrinking;
+ bool mCollectChildren;
+
+public:
+ GarbageCollectRunnable(WorkerPrivate* aWorkerPrivate, bool aShrinking,
+ bool aCollectChildren)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mShrinking(aShrinking), mCollectChildren(aCollectChildren)
+ { }
+
+private:
+ virtual bool
+ PreDispatch(WorkerPrivate* aWorkerPrivate) override
+ {
+ // Silence bad assertions, this can be dispatched from either the main
+ // thread or the timer thread..
+ return true;
+ }
+
+ virtual void
+ PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
+ {
+ // Silence bad assertions, this can be dispatched from either the main
+ // thread or the timer thread..
+ }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ aWorkerPrivate->GarbageCollectInternal(aCx, mShrinking, mCollectChildren);
+ return true;
+ }
+};
+
+class CycleCollectRunnable : public WorkerControlRunnable
+{
+ bool mCollectChildren;
+
+public:
+ CycleCollectRunnable(WorkerPrivate* aWorkerPrivate, bool aCollectChildren)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mCollectChildren(aCollectChildren)
+ { }
+
+ bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+ {
+ aWorkerPrivate->CycleCollectInternal(mCollectChildren);
+ return true;
+ }
+};
+
+class OfflineStatusChangeRunnable : public WorkerRunnable
+{
+public:
+ OfflineStatusChangeRunnable(WorkerPrivate* aWorkerPrivate, bool aIsOffline)
+ : WorkerRunnable(aWorkerPrivate),
+ mIsOffline(aIsOffline)
+ {
+ }
+
+ bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+ {
+ aWorkerPrivate->OfflineStatusChangeEventInternal(mIsOffline);
+ return true;
+ }
+
+private:
+ bool mIsOffline;
+};
+
+class MemoryPressureRunnable : public WorkerControlRunnable
+{
+public:
+ explicit MemoryPressureRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+ {}
+
+ bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+ {
+ aWorkerPrivate->MemoryPressureInternal();
+ return true;
+ }
+};
+
+#ifdef DEBUG
+static bool
+StartsWithExplicit(nsACString& s)
+{
+ return StringBeginsWith(s, NS_LITERAL_CSTRING("explicit/"));
+}
+#endif
+
+class MessagePortRunnable final : public WorkerRunnable
+{
+ MessagePortIdentifier mPortIdentifier;
+
+public:
+ MessagePortRunnable(WorkerPrivate* aWorkerPrivate, MessagePort* aPort)
+ : WorkerRunnable(aWorkerPrivate)
+ {
+ MOZ_ASSERT(aPort);
+ // In order to move the port from one thread to another one, we have to
+ // close and disentangle it. The output will be a MessagePortIdentifier that
+ // will be used to recreate a new MessagePort on the other thread.
+ aPort->CloneAndDisentangle(mPortIdentifier);
+ }
+
+private:
+ ~MessagePortRunnable()
+ { }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ return aWorkerPrivate->ConnectMessagePort(aCx, mPortIdentifier);
+ }
+
+ nsresult
+ Cancel() override
+ {
+ MessagePort::ForceClose(mPortIdentifier);
+ return WorkerRunnable::Cancel();
+ }
+};
+
+class DummyRunnable final
+ : public WorkerRunnable
+{
+public:
+ explicit
+ DummyRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+ {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+private:
+ ~DummyRunnable()
+ {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ virtual bool
+ PreDispatch(WorkerPrivate* aWorkerPrivate) override
+ {
+ MOZ_ASSERT_UNREACHABLE("Should never call Dispatch on this!");
+ return true;
+ }
+
+ virtual void
+ PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
+ {
+ MOZ_ASSERT_UNREACHABLE("Should never call Dispatch on this!");
+ }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ // Do nothing.
+ return true;
+ }
+};
+
+PRThread*
+PRThreadFromThread(nsIThread* aThread)
+{
+ MOZ_ASSERT(aThread);
+
+ PRThread* result;
+ MOZ_ALWAYS_SUCCEEDS(aThread->GetPRThread(&result));
+ MOZ_ASSERT(result);
+
+ return result;
+}
+
+class SimpleWorkerHolder final : public WorkerHolder
+{
+public:
+ virtual bool Notify(Status aStatus) { return true; }
+};
+
+} /* anonymous namespace */
+
+NS_IMPL_ISUPPORTS_INHERITED0(MainThreadReleaseRunnable, Runnable)
+
+NS_IMPL_ISUPPORTS_INHERITED0(TopLevelWorkerFinishedRunnable, Runnable)
+
+TimerThreadEventTarget::TimerThreadEventTarget(WorkerPrivate* aWorkerPrivate,
+ WorkerRunnable* aWorkerRunnable)
+ : mWorkerPrivate(aWorkerPrivate), mWorkerRunnable(aWorkerRunnable)
+{
+ MOZ_ASSERT(aWorkerPrivate);
+ MOZ_ASSERT(aWorkerRunnable);
+}
+
+TimerThreadEventTarget::~TimerThreadEventTarget()
+{
+}
+
+NS_IMETHODIMP
+TimerThreadEventTarget::DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags)
+{
+ nsCOMPtr<nsIRunnable> runnable(aRunnable);
+ return Dispatch(runnable.forget(), aFlags);
+}
+
+NS_IMETHODIMP
+TimerThreadEventTarget::Dispatch(already_AddRefed<nsIRunnable> aRunnable, uint32_t aFlags)
+{
+ // This should only happen on the timer thread.
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aFlags == nsIEventTarget::DISPATCH_NORMAL);
+
+ RefPtr<TimerThreadEventTarget> kungFuDeathGrip = this;
+
+ // Run the runnable we're given now (should just call DummyCallback()),
+ // otherwise the timer thread will leak it... If we run this after
+ // dispatch running the event can race against resetting the timer.
+ nsCOMPtr<nsIRunnable> runnable(aRunnable);
+ runnable->Run();
+
+ // This can fail if we're racing to terminate or cancel, should be handled
+ // by the terminate or cancel code.
+ mWorkerRunnable->Dispatch();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TimerThreadEventTarget::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TimerThreadEventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread)
+{
+ MOZ_ASSERT(aIsOnCurrentThread);
+
+ nsresult rv = mWorkerPrivate->IsOnCurrentThread(aIsOnCurrentThread);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(TimerThreadEventTarget, nsIEventTarget)
+
+WorkerLoadInfo::WorkerLoadInfo()
+ : mWindowID(UINT64_MAX)
+ , mServiceWorkerID(0)
+ , mReferrerPolicy(net::RP_Default)
+ , mFromWindow(false)
+ , mEvalAllowed(false)
+ , mReportCSPViolations(false)
+ , mXHRParamsAllowed(false)
+ , mPrincipalIsSystem(false)
+ , mStorageAllowed(false)
+ , mServiceWorkersTestingInWindow(false)
+{
+ MOZ_COUNT_CTOR(WorkerLoadInfo);
+}
+
+WorkerLoadInfo::~WorkerLoadInfo()
+{
+ MOZ_COUNT_DTOR(WorkerLoadInfo);
+}
+
+void
+WorkerLoadInfo::StealFrom(WorkerLoadInfo& aOther)
+{
+ MOZ_ASSERT(!mBaseURI);
+ aOther.mBaseURI.swap(mBaseURI);
+
+ MOZ_ASSERT(!mResolvedScriptURI);
+ aOther.mResolvedScriptURI.swap(mResolvedScriptURI);
+
+ MOZ_ASSERT(!mPrincipal);
+ aOther.mPrincipal.swap(mPrincipal);
+
+ MOZ_ASSERT(!mScriptContext);
+ aOther.mScriptContext.swap(mScriptContext);
+
+ MOZ_ASSERT(!mWindow);
+ aOther.mWindow.swap(mWindow);
+
+ MOZ_ASSERT(!mCSP);
+ aOther.mCSP.swap(mCSP);
+
+ MOZ_ASSERT(!mChannel);
+ aOther.mChannel.swap(mChannel);
+
+ MOZ_ASSERT(!mLoadGroup);
+ aOther.mLoadGroup.swap(mLoadGroup);
+
+ MOZ_ASSERT(!mLoadFailedAsyncRunnable);
+ aOther.mLoadFailedAsyncRunnable.swap(mLoadFailedAsyncRunnable);
+
+ MOZ_ASSERT(!mInterfaceRequestor);
+ aOther.mInterfaceRequestor.swap(mInterfaceRequestor);
+
+ MOZ_ASSERT(!mPrincipalInfo);
+ mPrincipalInfo = aOther.mPrincipalInfo.forget();
+
+ mDomain = aOther.mDomain;
+ mServiceWorkerCacheName = aOther.mServiceWorkerCacheName;
+ mWindowID = aOther.mWindowID;
+ mServiceWorkerID = aOther.mServiceWorkerID;
+ mReferrerPolicy = aOther.mReferrerPolicy;
+ mFromWindow = aOther.mFromWindow;
+ mEvalAllowed = aOther.mEvalAllowed;
+ mReportCSPViolations = aOther.mReportCSPViolations;
+ mXHRParamsAllowed = aOther.mXHRParamsAllowed;
+ mPrincipalIsSystem = aOther.mPrincipalIsSystem;
+ mStorageAllowed = aOther.mStorageAllowed;
+ mServiceWorkersTestingInWindow = aOther.mServiceWorkersTestingInWindow;
+ mOriginAttributes = aOther.mOriginAttributes;
+}
+
+template <class Derived>
+class WorkerPrivateParent<Derived>::EventTarget final
+ : public nsIEventTarget
+{
+ // This mutex protects mWorkerPrivate and must be acquired *before* the
+ // WorkerPrivate's mutex whenever they must both be held.
+ mozilla::Mutex mMutex;
+ WorkerPrivate* mWorkerPrivate;
+ nsIEventTarget* mWeakNestedEventTarget;
+ nsCOMPtr<nsIEventTarget> mNestedEventTarget;
+
+public:
+ explicit EventTarget(WorkerPrivate* aWorkerPrivate)
+ : mMutex("WorkerPrivateParent::EventTarget::mMutex"),
+ mWorkerPrivate(aWorkerPrivate), mWeakNestedEventTarget(nullptr)
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ }
+
+ EventTarget(WorkerPrivate* aWorkerPrivate, nsIEventTarget* aNestedEventTarget)
+ : mMutex("WorkerPrivateParent::EventTarget::mMutex"),
+ mWorkerPrivate(aWorkerPrivate), mWeakNestedEventTarget(aNestedEventTarget),
+ mNestedEventTarget(aNestedEventTarget)
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ MOZ_ASSERT(aNestedEventTarget);
+ }
+
+ void
+ Disable()
+ {
+ nsCOMPtr<nsIEventTarget> nestedEventTarget;
+ {
+ MutexAutoLock lock(mMutex);
+
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate = nullptr;
+ mNestedEventTarget.swap(nestedEventTarget);
+ }
+ }
+
+ nsIEventTarget*
+ GetWeakNestedEventTarget() const
+ {
+ MOZ_ASSERT(mWeakNestedEventTarget);
+ return mWeakNestedEventTarget;
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIEVENTTARGET
+
+private:
+ ~EventTarget()
+ { }
+};
+
+WorkerLoadInfo::
+InterfaceRequestor::InterfaceRequestor(nsIPrincipal* aPrincipal,
+ nsILoadGroup* aLoadGroup)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPrincipal);
+
+ // Look for an existing LoadContext. This is optional and it's ok if
+ // we don't find one.
+ nsCOMPtr<nsILoadContext> baseContext;
+ if (aLoadGroup) {
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (callbacks) {
+ callbacks->GetInterface(NS_GET_IID(nsILoadContext),
+ getter_AddRefs(baseContext));
+ }
+ mOuterRequestor = callbacks;
+ }
+
+ mLoadContext = new LoadContext(aPrincipal, baseContext);
+}
+
+void
+WorkerLoadInfo::
+InterfaceRequestor::MaybeAddTabChild(nsILoadGroup* aLoadGroup)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aLoadGroup) {
+ return;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (!callbacks) {
+ return;
+ }
+
+ nsCOMPtr<nsITabChild> tabChild;
+ callbacks->GetInterface(NS_GET_IID(nsITabChild), getter_AddRefs(tabChild));
+ if (!tabChild) {
+ return;
+ }
+
+ // Use weak references to the tab child. Holding a strong reference will
+ // not prevent an ActorDestroy() from being called on the TabChild.
+ // Therefore, we should let the TabChild destroy itself as soon as possible.
+ mTabChildList.AppendElement(do_GetWeakReference(tabChild));
+}
+
+NS_IMETHODIMP
+WorkerLoadInfo::
+InterfaceRequestor::GetInterface(const nsIID& aIID, void** aSink)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mLoadContext);
+
+ if (aIID.Equals(NS_GET_IID(nsILoadContext))) {
+ nsCOMPtr<nsILoadContext> ref = mLoadContext;
+ ref.forget(aSink);
+ return NS_OK;
+ }
+
+ // If we still have an active nsITabChild, then return it. Its possible,
+ // though, that all of the TabChild objects have been destroyed. In that
+ // case we return NS_NOINTERFACE.
+ if (aIID.Equals(NS_GET_IID(nsITabChild))) {
+ nsCOMPtr<nsITabChild> tabChild = GetAnyLiveTabChild();
+ if (!tabChild) {
+ return NS_NOINTERFACE;
+ }
+ tabChild.forget(aSink);
+ return NS_OK;
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsINetworkInterceptController)) &&
+ mOuterRequestor) {
+ // If asked for the network intercept controller, ask the outer requestor,
+ // which could be the docshell.
+ return mOuterRequestor->GetInterface(aIID, aSink);
+ }
+
+ return NS_NOINTERFACE;
+}
+
+already_AddRefed<nsITabChild>
+WorkerLoadInfo::
+InterfaceRequestor::GetAnyLiveTabChild()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Search our list of known TabChild objects for one that still exists.
+ while (!mTabChildList.IsEmpty()) {
+ nsCOMPtr<nsITabChild> tabChild =
+ do_QueryReferent(mTabChildList.LastElement());
+
+ // Does this tab child still exist? If so, return it. We are done. If the
+ // PBrowser actor is no longer useful, don't bother returning this tab.
+ if (tabChild && !static_cast<TabChild*>(tabChild.get())->IsDestroyed()) {
+ return tabChild.forget();
+ }
+
+ // Otherwise remove the stale weak reference and check the next one
+ mTabChildList.RemoveElementAt(mTabChildList.Length() - 1);
+ }
+
+ return nullptr;
+}
+
+NS_IMPL_ADDREF(WorkerLoadInfo::InterfaceRequestor)
+NS_IMPL_RELEASE(WorkerLoadInfo::InterfaceRequestor)
+NS_IMPL_QUERY_INTERFACE(WorkerLoadInfo::InterfaceRequestor, nsIInterfaceRequestor)
+
+struct WorkerPrivate::TimeoutInfo
+{
+ TimeoutInfo()
+ : mId(0), mIsInterval(false), mCanceled(false)
+ {
+ MOZ_COUNT_CTOR(mozilla::dom::workers::WorkerPrivate::TimeoutInfo);
+ }
+
+ ~TimeoutInfo()
+ {
+ MOZ_COUNT_DTOR(mozilla::dom::workers::WorkerPrivate::TimeoutInfo);
+ }
+
+ bool operator==(const TimeoutInfo& aOther)
+ {
+ return mTargetTime == aOther.mTargetTime;
+ }
+
+ bool operator<(const TimeoutInfo& aOther)
+ {
+ return mTargetTime < aOther.mTargetTime;
+ }
+
+ nsCOMPtr<nsIScriptTimeoutHandler> mHandler;
+ mozilla::TimeStamp mTargetTime;
+ mozilla::TimeDuration mInterval;
+ int32_t mId;
+ bool mIsInterval;
+ bool mCanceled;
+};
+
+class WorkerJSContextStats final : public JS::RuntimeStats
+{
+ const nsCString mRtPath;
+
+public:
+ explicit WorkerJSContextStats(const nsACString& aRtPath)
+ : JS::RuntimeStats(JsWorkerMallocSizeOf), mRtPath(aRtPath)
+ { }
+
+ ~WorkerJSContextStats()
+ {
+ for (size_t i = 0; i != zoneStatsVector.length(); i++) {
+ delete static_cast<xpc::ZoneStatsExtras*>(zoneStatsVector[i].extra);
+ }
+
+ for (size_t i = 0; i != compartmentStatsVector.length(); i++) {
+ delete static_cast<xpc::CompartmentStatsExtras*>(compartmentStatsVector[i].extra);
+ }
+ }
+
+ const nsCString& Path() const
+ {
+ return mRtPath;
+ }
+
+ virtual void
+ initExtraZoneStats(JS::Zone* aZone,
+ JS::ZoneStats* aZoneStats)
+ override
+ {
+ MOZ_ASSERT(!aZoneStats->extra);
+
+ // ReportJSRuntimeExplicitTreeStats expects that
+ // aZoneStats->extra is a xpc::ZoneStatsExtras pointer.
+ xpc::ZoneStatsExtras* extras = new xpc::ZoneStatsExtras;
+ extras->pathPrefix = mRtPath;
+ extras->pathPrefix += nsPrintfCString("zone(0x%p)/", (void *)aZone);
+
+ MOZ_ASSERT(StartsWithExplicit(extras->pathPrefix));
+
+ aZoneStats->extra = extras;
+ }
+
+ virtual void
+ initExtraCompartmentStats(JSCompartment* aCompartment,
+ JS::CompartmentStats* aCompartmentStats)
+ override
+ {
+ MOZ_ASSERT(!aCompartmentStats->extra);
+
+ // ReportJSRuntimeExplicitTreeStats expects that
+ // aCompartmentStats->extra is a xpc::CompartmentStatsExtras pointer.
+ xpc::CompartmentStatsExtras* extras = new xpc::CompartmentStatsExtras;
+
+ // This is the |jsPathPrefix|. Each worker has exactly two compartments:
+ // one for atoms, and one for everything else.
+ extras->jsPathPrefix.Assign(mRtPath);
+ extras->jsPathPrefix += nsPrintfCString("zone(0x%p)/",
+ (void *)js::GetCompartmentZone(aCompartment));
+ extras->jsPathPrefix += js::IsAtomsCompartment(aCompartment)
+ ? NS_LITERAL_CSTRING("compartment(web-worker-atoms)/")
+ : NS_LITERAL_CSTRING("compartment(web-worker)/");
+
+ // This should never be used when reporting with workers (hence the "?!").
+ extras->domPathPrefix.AssignLiteral("explicit/workers/?!/");
+
+ MOZ_ASSERT(StartsWithExplicit(extras->jsPathPrefix));
+ MOZ_ASSERT(StartsWithExplicit(extras->domPathPrefix));
+
+ extras->location = nullptr;
+
+ aCompartmentStats->extra = extras;
+ }
+};
+
+class WorkerPrivate::MemoryReporter final : public nsIMemoryReporter
+{
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ friend class WorkerPrivate;
+
+ SharedMutex mMutex;
+ WorkerPrivate* mWorkerPrivate;
+ bool mAlreadyMappedToAddon;
+
+public:
+ explicit MemoryReporter(WorkerPrivate* aWorkerPrivate)
+ : mMutex(aWorkerPrivate->mMutex), mWorkerPrivate(aWorkerPrivate),
+ mAlreadyMappedToAddon(false)
+ {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ NS_IMETHOD
+ CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override;
+
+private:
+ class FinishCollectRunnable;
+
+ class CollectReportsRunnable final : public MainThreadWorkerControlRunnable
+ {
+ RefPtr<FinishCollectRunnable> mFinishCollectRunnable;
+ const bool mAnonymize;
+
+ public:
+ CollectReportsRunnable(
+ WorkerPrivate* aWorkerPrivate,
+ nsIHandleReportCallback* aHandleReport,
+ nsISupports* aHandlerData,
+ bool aAnonymize,
+ const nsACString& aPath);
+
+ private:
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
+
+ ~CollectReportsRunnable()
+ {
+ if (NS_IsMainThread()) {
+ mFinishCollectRunnable->Run();
+ return;
+ }
+
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+ MOZ_ALWAYS_SUCCEEDS(
+ workerPrivate->DispatchToMainThread(mFinishCollectRunnable.forget()));
+ }
+ };
+
+ class FinishCollectRunnable final : public Runnable
+ {
+ nsCOMPtr<nsIHandleReportCallback> mHandleReport;
+ nsCOMPtr<nsISupports> mHandlerData;
+ const bool mAnonymize;
+ bool mSuccess;
+
+ public:
+ WorkerJSContextStats mCxStats;
+
+ explicit FinishCollectRunnable(
+ nsIHandleReportCallback* aHandleReport,
+ nsISupports* aHandlerData,
+ bool aAnonymize,
+ const nsACString& aPath);
+
+ NS_IMETHOD Run() override;
+
+ void SetSuccess(bool success)
+ {
+ mSuccess = success;
+ }
+
+ private:
+ ~FinishCollectRunnable()
+ {
+ // mHandleReport and mHandlerData are released on the main thread.
+ AssertIsOnMainThread();
+ }
+
+ FinishCollectRunnable(const FinishCollectRunnable&) = delete;
+ FinishCollectRunnable& operator=(const FinishCollectRunnable&) = delete;
+ FinishCollectRunnable& operator=(const FinishCollectRunnable&&) = delete;
+ };
+
+ ~MemoryReporter()
+ {
+ }
+
+ void
+ Disable()
+ {
+ // Called from WorkerPrivate::DisableMemoryReporter.
+ mMutex.AssertCurrentThreadOwns();
+
+ NS_ASSERTION(mWorkerPrivate, "Disabled more than once!");
+ mWorkerPrivate = nullptr;
+ }
+
+ // Only call this from the main thread and under mMutex lock.
+ void
+ TryToMapAddon(nsACString &path);
+};
+
+NS_IMPL_ISUPPORTS(WorkerPrivate::MemoryReporter, nsIMemoryReporter)
+
+NS_IMETHODIMP
+WorkerPrivate::MemoryReporter::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData,
+ bool aAnonymize)
+{
+ AssertIsOnMainThread();
+
+ RefPtr<CollectReportsRunnable> runnable;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (!mWorkerPrivate) {
+ // This will effectively report 0 memory.
+ nsCOMPtr<nsIMemoryReporterManager> manager =
+ do_GetService("@mozilla.org/memory-reporter-manager;1");
+ if (manager) {
+ manager->EndReport();
+ }
+ return NS_OK;
+ }
+
+ nsAutoCString path;
+ path.AppendLiteral("explicit/workers/workers(");
+ if (aAnonymize && !mWorkerPrivate->Domain().IsEmpty()) {
+ path.AppendLiteral("<anonymized-domain>)/worker(<anonymized-url>");
+ } else {
+ nsAutoCString escapedDomain(mWorkerPrivate->Domain());
+ if (escapedDomain.IsEmpty()) {
+ escapedDomain += "chrome";
+ } else {
+ escapedDomain.ReplaceChar('/', '\\');
+ }
+ path.Append(escapedDomain);
+ path.AppendLiteral(")/worker(");
+ NS_ConvertUTF16toUTF8 escapedURL(mWorkerPrivate->ScriptURL());
+ escapedURL.ReplaceChar('/', '\\');
+ path.Append(escapedURL);
+ }
+ path.AppendPrintf(", 0x%p)/", static_cast<void*>(mWorkerPrivate));
+
+ TryToMapAddon(path);
+
+ runnable =
+ new CollectReportsRunnable(mWorkerPrivate, aHandleReport, aData, aAnonymize, path);
+ }
+
+ if (!runnable->Dispatch()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+void
+WorkerPrivate::MemoryReporter::TryToMapAddon(nsACString &path)
+{
+ AssertIsOnMainThread();
+ mMutex.AssertCurrentThreadOwns();
+
+ if (mAlreadyMappedToAddon || !mWorkerPrivate) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> scriptURI;
+ if (NS_FAILED(NS_NewURI(getter_AddRefs(scriptURI),
+ mWorkerPrivate->ScriptURL()))) {
+ return;
+ }
+
+ mAlreadyMappedToAddon = true;
+
+ if (!XRE_IsParentProcess()) {
+ // Only try to access the service from the main process.
+ return;
+ }
+
+ nsAutoCString addonId;
+ bool ok;
+ nsCOMPtr<amIAddonManager> addonManager =
+ do_GetService("@mozilla.org/addons/integration;1");
+
+ if (!addonManager ||
+ NS_FAILED(addonManager->MapURIToAddonID(scriptURI, addonId, &ok)) ||
+ !ok) {
+ return;
+ }
+
+ static const size_t explicitLength = strlen("explicit/");
+ addonId.Insert(NS_LITERAL_CSTRING("add-ons/"), 0);
+ addonId += "/";
+ path.Insert(addonId, explicitLength);
+}
+
+WorkerPrivate::MemoryReporter::CollectReportsRunnable::CollectReportsRunnable(
+ WorkerPrivate* aWorkerPrivate,
+ nsIHandleReportCallback* aHandleReport,
+ nsISupports* aHandlerData,
+ bool aAnonymize,
+ const nsACString& aPath)
+ : MainThreadWorkerControlRunnable(aWorkerPrivate),
+ mFinishCollectRunnable(
+ new FinishCollectRunnable(aHandleReport, aHandlerData, aAnonymize, aPath)),
+ mAnonymize(aAnonymize)
+{ }
+
+bool
+WorkerPrivate::MemoryReporter::CollectReportsRunnable::WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate)
+{
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ mFinishCollectRunnable->SetSuccess(
+ aWorkerPrivate->CollectRuntimeStats(&mFinishCollectRunnable->mCxStats, mAnonymize));
+
+ return true;
+}
+
+WorkerPrivate::MemoryReporter::FinishCollectRunnable::FinishCollectRunnable(
+ nsIHandleReportCallback* aHandleReport,
+ nsISupports* aHandlerData,
+ bool aAnonymize,
+ const nsACString& aPath)
+ : mHandleReport(aHandleReport),
+ mHandlerData(aHandlerData),
+ mAnonymize(aAnonymize),
+ mSuccess(false),
+ mCxStats(aPath)
+{ }
+
+NS_IMETHODIMP
+WorkerPrivate::MemoryReporter::FinishCollectRunnable::Run()
+{
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsIMemoryReporterManager> manager =
+ do_GetService("@mozilla.org/memory-reporter-manager;1");
+
+ if (!manager)
+ return NS_OK;
+
+ if (mSuccess) {
+ xpc::ReportJSRuntimeExplicitTreeStats(mCxStats, mCxStats.Path(),
+ mHandleReport, mHandlerData,
+ mAnonymize);
+ }
+
+ manager->EndReport();
+
+ return NS_OK;
+}
+
+WorkerPrivate::SyncLoopInfo::SyncLoopInfo(EventTarget* aEventTarget)
+: mEventTarget(aEventTarget), mCompleted(false), mResult(false)
+#ifdef DEBUG
+ , mHasRun(false)
+#endif
+{
+}
+
+template <class Derived>
+nsIDocument*
+WorkerPrivateParent<Derived>::GetDocument() const
+{
+ AssertIsOnMainThread();
+ if (mLoadInfo.mWindow) {
+ return mLoadInfo.mWindow->GetExtantDoc();
+ }
+ // if we don't have a document, we should query the document
+ // from the parent in case of a nested worker
+ WorkerPrivate* parent = mParent;
+ while (parent) {
+ if (parent->mLoadInfo.mWindow) {
+ return parent->mLoadInfo.mWindow->GetExtantDoc();
+ }
+ parent = parent->GetParent();
+ }
+ // couldn't query a document, give up and return nullptr
+ return nullptr;
+}
+
+
+// Can't use NS_IMPL_CYCLE_COLLECTION_CLASS(WorkerPrivateParent) because of the
+// templates.
+template <class Derived>
+typename WorkerPrivateParent<Derived>::cycleCollection
+ WorkerPrivateParent<Derived>::_cycleCollectorGlobal =
+ WorkerPrivateParent<Derived>::cycleCollection();
+
+template <class Derived>
+WorkerPrivateParent<Derived>::WorkerPrivateParent(
+ WorkerPrivate* aParent,
+ const nsAString& aScriptURL,
+ bool aIsChromeWorker,
+ WorkerType aWorkerType,
+ const nsACString& aWorkerName,
+ WorkerLoadInfo& aLoadInfo)
+: mMutex("WorkerPrivateParent Mutex"),
+ mCondVar(mMutex, "WorkerPrivateParent CondVar"),
+ mParent(aParent), mScriptURL(aScriptURL),
+ mWorkerName(aWorkerName), mLoadingWorkerScript(false),
+ mBusyCount(0), mParentWindowPausedDepth(0), mParentStatus(Pending),
+ mParentFrozen(false), mIsChromeWorker(aIsChromeWorker),
+ mMainThreadObjectsForgotten(false), mIsSecureContext(false),
+ mWorkerType(aWorkerType),
+ mCreationTimeStamp(TimeStamp::Now()),
+ mCreationTimeHighRes((double)PR_Now() / PR_USEC_PER_MSEC)
+{
+ MOZ_ASSERT_IF(!IsDedicatedWorker(),
+ !aWorkerName.IsVoid() && NS_IsMainThread());
+ MOZ_ASSERT_IF(IsDedicatedWorker(), aWorkerName.IsEmpty());
+
+ if (aLoadInfo.mWindow) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aLoadInfo.mWindow->IsInnerWindow(),
+ "Should have inner window here!");
+ BindToOwner(aLoadInfo.mWindow);
+ }
+
+ mLoadInfo.StealFrom(aLoadInfo);
+
+ if (aParent) {
+ aParent->AssertIsOnWorkerThread();
+
+ // Note that this copies our parent's secure context state into mJSSettings.
+ aParent->CopyJSSettings(mJSSettings);
+
+ // And manually set our mIsSecureContext, though it's not really relevant to
+ // dedicated workers...
+ mIsSecureContext = aParent->IsSecureContext();
+ MOZ_ASSERT_IF(mIsChromeWorker, mIsSecureContext);
+
+ MOZ_ASSERT(IsDedicatedWorker());
+ mNowBaseTimeStamp = aParent->NowBaseTimeStamp();
+ mNowBaseTimeHighRes = aParent->NowBaseTime();
+
+ if (aParent->mParentFrozen) {
+ Freeze(nullptr);
+ }
+ }
+ else {
+ AssertIsOnMainThread();
+
+ RuntimeService::GetDefaultJSSettings(mJSSettings);
+
+ // Our secure context state depends on the kind of worker we have.
+ if (UsesSystemPrincipal() || IsServiceWorker()) {
+ mIsSecureContext = true;
+ } else if (mLoadInfo.mWindow) {
+ // Shared and dedicated workers both inherit the loading window's secure
+ // context state. Shared workers then prevent windows with a different
+ // secure context state from attaching to them.
+ mIsSecureContext = mLoadInfo.mWindow->IsSecureContext();
+ } else {
+ MOZ_ASSERT_UNREACHABLE("non-chrome worker that is not a service worker "
+ "that has no parent and no associated window");
+ }
+
+ if (mIsSecureContext) {
+ mJSSettings.chrome.compartmentOptions
+ .creationOptions().setSecureContext(true);
+ mJSSettings.content.compartmentOptions
+ .creationOptions().setSecureContext(true);
+ }
+
+ if (IsDedicatedWorker() && mLoadInfo.mWindow &&
+ mLoadInfo.mWindow->GetPerformance()) {
+ mNowBaseTimeStamp = mLoadInfo.mWindow->GetPerformance()->GetDOMTiming()->
+ GetNavigationStartTimeStamp();
+ mNowBaseTimeHighRes =
+ mLoadInfo.mWindow->GetPerformance()->GetDOMTiming()->
+ GetNavigationStartHighRes();
+ } else {
+ mNowBaseTimeStamp = CreationTimeStamp();
+ mNowBaseTimeHighRes = CreationTime();
+ }
+
+ // Our parent can get suspended after it initiates the async creation
+ // of a new worker thread. In this case suspend the new worker as well.
+ if (mLoadInfo.mWindow && mLoadInfo.mWindow->IsSuspended()) {
+ ParentWindowPaused();
+ }
+
+ if (mLoadInfo.mWindow && mLoadInfo.mWindow->IsFrozen()) {
+ Freeze(mLoadInfo.mWindow);
+ }
+ }
+}
+
+template <class Derived>
+WorkerPrivateParent<Derived>::~WorkerPrivateParent()
+{
+ DropJSObjects(this);
+}
+
+template <class Derived>
+JSObject*
+WorkerPrivateParent<Derived>::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ MOZ_ASSERT(!IsSharedWorker(),
+ "We should never wrap a WorkerPrivate for a SharedWorker");
+
+ AssertIsOnParentThread();
+
+ // XXXkhuey this should not need to be rooted, the analysis is dumb.
+ // See bug 980181.
+ JS::Rooted<JSObject*> wrapper(aCx,
+ WorkerBinding::Wrap(aCx, ParentAsWorkerPrivate(), aGivenProto));
+ if (wrapper) {
+ MOZ_ALWAYS_TRUE(TryPreserveWrapper(wrapper));
+ }
+
+ return wrapper;
+}
+
+template <class Derived>
+nsresult
+WorkerPrivateParent<Derived>::DispatchPrivate(already_AddRefed<WorkerRunnable> aRunnable,
+ nsIEventTarget* aSyncLoopTarget)
+{
+ // May be called on any thread!
+ RefPtr<WorkerRunnable> runnable(aRunnable);
+
+ WorkerPrivate* self = ParentAsWorkerPrivate();
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ MOZ_ASSERT_IF(aSyncLoopTarget, self->mThread);
+
+ if (!self->mThread) {
+ if (ParentStatus() == Pending || self->mStatus == Pending) {
+ mPreStartRunnables.AppendElement(runnable);
+ return NS_OK;
+ }
+
+ NS_WARNING("Using a worker event target after the thread has already"
+ "been released!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (self->mStatus == Dead ||
+ (!aSyncLoopTarget && ParentStatus() > Running)) {
+ NS_WARNING("A runnable was posted to a worker that is already shutting "
+ "down!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv;
+ if (aSyncLoopTarget) {
+ rv = aSyncLoopTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
+ } else {
+ rv = self->mThread->DispatchAnyThread(WorkerThreadFriendKey(), runnable.forget());
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mCondVar.Notify();
+ }
+
+ return NS_OK;
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::EnableDebugger()
+{
+ AssertIsOnParentThread();
+
+ WorkerPrivate* self = ParentAsWorkerPrivate();
+
+ if (NS_FAILED(RegisterWorkerDebugger(self))) {
+ NS_WARNING("Failed to register worker debugger!");
+ return;
+ }
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::DisableDebugger()
+{
+ AssertIsOnParentThread();
+
+ WorkerPrivate* self = ParentAsWorkerPrivate();
+
+ if (NS_FAILED(UnregisterWorkerDebugger(self))) {
+ NS_WARNING("Failed to unregister worker debugger!");
+ }
+}
+
+template <class Derived>
+nsresult
+WorkerPrivateParent<Derived>::DispatchControlRunnable(
+ already_AddRefed<WorkerControlRunnable> aWorkerControlRunnable)
+{
+ // May be called on any thread!
+ RefPtr<WorkerControlRunnable> runnable(aWorkerControlRunnable);
+ MOZ_ASSERT(runnable);
+
+ WorkerPrivate* self = ParentAsWorkerPrivate();
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (self->mStatus == Dead) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Transfer ownership to the control queue.
+ self->mControlQueue.Push(runnable.forget().take());
+
+ if (JSContext* cx = self->mJSContext) {
+ MOZ_ASSERT(self->mThread);
+ JS_RequestInterruptCallback(cx);
+ }
+
+ mCondVar.Notify();
+ }
+
+ return NS_OK;
+}
+
+template <class Derived>
+nsresult
+WorkerPrivateParent<Derived>::DispatchDebuggerRunnable(
+ already_AddRefed<WorkerRunnable> aDebuggerRunnable)
+{
+ // May be called on any thread!
+
+ RefPtr<WorkerRunnable> runnable(aDebuggerRunnable);
+
+ MOZ_ASSERT(runnable);
+
+ WorkerPrivate* self = ParentAsWorkerPrivate();
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (self->mStatus == Dead) {
+ NS_WARNING("A debugger runnable was posted to a worker that is already "
+ "shutting down!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Transfer ownership to the debugger queue.
+ self->mDebuggerQueue.Push(runnable.forget().take());
+
+ mCondVar.Notify();
+ }
+
+ return NS_OK;
+}
+
+template <class Derived>
+already_AddRefed<WorkerRunnable>
+WorkerPrivateParent<Derived>::MaybeWrapAsWorkerRunnable(already_AddRefed<nsIRunnable> aRunnable)
+{
+ // May be called on any thread!
+
+ nsCOMPtr<nsIRunnable> runnable(aRunnable);
+ MOZ_ASSERT(runnable);
+
+ RefPtr<WorkerRunnable> workerRunnable =
+ WorkerRunnable::FromRunnable(runnable);
+ if (workerRunnable) {
+ return workerRunnable.forget();
+ }
+
+ nsCOMPtr<nsICancelableRunnable> cancelable = do_QueryInterface(runnable);
+ if (!cancelable) {
+ MOZ_CRASH("All runnables destined for a worker thread must be cancelable!");
+ }
+
+ workerRunnable =
+ new ExternalRunnableWrapper(ParentAsWorkerPrivate(), runnable);
+ return workerRunnable.forget();
+}
+
+template <class Derived>
+already_AddRefed<nsIEventTarget>
+WorkerPrivateParent<Derived>::GetEventTarget()
+{
+ WorkerPrivate* self = ParentAsWorkerPrivate();
+
+ nsCOMPtr<nsIEventTarget> target;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (!mEventTarget &&
+ ParentStatus() <= Running &&
+ self->mStatus <= Running) {
+ mEventTarget = new EventTarget(self);
+ }
+
+ target = mEventTarget;
+ }
+
+ NS_WARNING_ASSERTION(
+ target,
+ "Requested event target for a worker that is already shutting down!");
+
+ return target.forget();
+}
+
+template <class Derived>
+bool
+WorkerPrivateParent<Derived>::Start()
+{
+ // May be called on any thread!
+ {
+ MutexAutoLock lock(mMutex);
+
+ NS_ASSERTION(mParentStatus != Running, "How can this be?!");
+
+ if (mParentStatus == Pending) {
+ mParentStatus = Running;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// aCx is null when called from the finalizer
+template <class Derived>
+bool
+WorkerPrivateParent<Derived>::NotifyPrivate(Status aStatus)
+{
+ AssertIsOnParentThread();
+
+ bool pending;
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mParentStatus >= aStatus) {
+ return true;
+ }
+
+ pending = mParentStatus == Pending;
+ mParentStatus = aStatus;
+ }
+
+ if (IsSharedWorker()) {
+ RuntimeService* runtime = RuntimeService::GetService();
+ MOZ_ASSERT(runtime);
+
+ runtime->ForgetSharedWorker(ParentAsWorkerPrivate());
+ }
+
+ if (pending) {
+ WorkerPrivate* self = ParentAsWorkerPrivate();
+
+#ifdef DEBUG
+ {
+ // Fake a thread here just so that our assertions don't go off for no
+ // reason.
+ nsIThread* currentThread = NS_GetCurrentThread();
+ MOZ_ASSERT(currentThread);
+
+ MOZ_ASSERT(!self->mPRThread);
+ self->mPRThread = PRThreadFromThread(currentThread);
+ MOZ_ASSERT(self->mPRThread);
+ }
+#endif
+
+ // Worker never got a chance to run, go ahead and delete it.
+ self->ScheduleDeletion(WorkerPrivate::WorkerNeverRan);
+ return true;
+ }
+
+ NS_ASSERTION(aStatus != Terminating || mQueuedRunnables.IsEmpty(),
+ "Shouldn't have anything queued!");
+
+ // Anything queued will be discarded.
+ mQueuedRunnables.Clear();
+
+ RefPtr<NotifyRunnable> runnable =
+ new NotifyRunnable(ParentAsWorkerPrivate(), aStatus);
+ return runnable->Dispatch();
+}
+
+template <class Derived>
+bool
+WorkerPrivateParent<Derived>::Freeze(nsPIDOMWindowInner* aWindow)
+{
+ AssertIsOnParentThread();
+
+ // Shared workers are only frozen if all of their owning documents are
+ // frozen. It can happen that mSharedWorkers is empty but this thread has
+ // not been unregistered yet.
+ if ((IsSharedWorker() || IsServiceWorker()) && !mSharedWorkers.IsEmpty()) {
+ AssertIsOnMainThread();
+
+ bool allFrozen = true;
+
+ for (uint32_t i = 0; i < mSharedWorkers.Length(); ++i) {
+ if (aWindow && mSharedWorkers[i]->GetOwner() == aWindow) {
+ // Calling Freeze() may change the refcount, ensure that the worker
+ // outlives this call.
+ RefPtr<SharedWorker> kungFuDeathGrip = mSharedWorkers[i];
+
+ kungFuDeathGrip->Freeze();
+ } else {
+ MOZ_ASSERT_IF(mSharedWorkers[i]->GetOwner() && aWindow,
+ !SameCOMIdentity(mSharedWorkers[i]->GetOwner(),
+ aWindow));
+ if (!mSharedWorkers[i]->IsFrozen()) {
+ allFrozen = false;
+ }
+ }
+ }
+
+ if (!allFrozen || mParentFrozen) {
+ return true;
+ }
+ }
+
+ mParentFrozen = true;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mParentStatus >= Terminating) {
+ return true;
+ }
+ }
+
+ DisableDebugger();
+
+ RefPtr<FreezeRunnable> runnable =
+ new FreezeRunnable(ParentAsWorkerPrivate());
+ if (!runnable->Dispatch()) {
+ return false;
+ }
+
+ return true;
+}
+
+template <class Derived>
+bool
+WorkerPrivateParent<Derived>::Thaw(nsPIDOMWindowInner* aWindow)
+{
+ AssertIsOnParentThread();
+
+ MOZ_ASSERT(mParentFrozen);
+
+ // Shared workers are resumed if any of their owning documents are thawed.
+ // It can happen that mSharedWorkers is empty but this thread has not been
+ // unregistered yet.
+ if ((IsSharedWorker() || IsServiceWorker()) && !mSharedWorkers.IsEmpty()) {
+ AssertIsOnMainThread();
+
+ bool anyRunning = false;
+
+ for (uint32_t i = 0; i < mSharedWorkers.Length(); ++i) {
+ if (aWindow && mSharedWorkers[i]->GetOwner() == aWindow) {
+ // Calling Thaw() may change the refcount, ensure that the worker
+ // outlives this call.
+ RefPtr<SharedWorker> kungFuDeathGrip = mSharedWorkers[i];
+
+ kungFuDeathGrip->Thaw();
+ anyRunning = true;
+ } else {
+ MOZ_ASSERT_IF(mSharedWorkers[i]->GetOwner() && aWindow,
+ !SameCOMIdentity(mSharedWorkers[i]->GetOwner(),
+ aWindow));
+ if (!mSharedWorkers[i]->IsFrozen()) {
+ anyRunning = true;
+ }
+ }
+ }
+
+ if (!anyRunning || !mParentFrozen) {
+ return true;
+ }
+ }
+
+ MOZ_ASSERT(mParentFrozen);
+
+ mParentFrozen = false;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mParentStatus >= Terminating) {
+ return true;
+ }
+ }
+
+ EnableDebugger();
+
+ // Execute queued runnables before waking up the worker, otherwise the worker
+ // could post new messages before we run those that have been queued.
+ if (!IsParentWindowPaused() && !mQueuedRunnables.IsEmpty()) {
+ MOZ_ASSERT(IsDedicatedWorker());
+
+ nsTArray<nsCOMPtr<nsIRunnable>> runnables;
+ mQueuedRunnables.SwapElements(runnables);
+
+ for (uint32_t index = 0; index < runnables.Length(); index++) {
+ runnables[index]->Run();
+ }
+ }
+
+ RefPtr<ThawRunnable> runnable =
+ new ThawRunnable(ParentAsWorkerPrivate());
+ if (!runnable->Dispatch()) {
+ return false;
+ }
+
+ return true;
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::ParentWindowPaused()
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT_IF(IsDedicatedWorker(), mParentWindowPausedDepth == 0);
+ mParentWindowPausedDepth += 1;
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::ParentWindowResumed()
+{
+ AssertIsOnMainThread();
+
+ MOZ_ASSERT(mParentWindowPausedDepth > 0);
+ MOZ_ASSERT_IF(IsDedicatedWorker(), mParentWindowPausedDepth == 1);
+ mParentWindowPausedDepth -= 1;
+ if (mParentWindowPausedDepth > 0) {
+ return;
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mParentStatus >= Terminating) {
+ return;
+ }
+ }
+
+ // Execute queued runnables before waking up, otherwise the worker could post
+ // new messages before we run those that have been queued.
+ if (!IsFrozen() && !mQueuedRunnables.IsEmpty()) {
+ MOZ_ASSERT(IsDedicatedWorker());
+
+ nsTArray<nsCOMPtr<nsIRunnable>> runnables;
+ mQueuedRunnables.SwapElements(runnables);
+
+ for (uint32_t index = 0; index < runnables.Length(); index++) {
+ runnables[index]->Run();
+ }
+ }
+}
+
+template <class Derived>
+bool
+WorkerPrivateParent<Derived>::Close()
+{
+ AssertIsOnParentThread();
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mParentStatus < Closing) {
+ mParentStatus = Closing;
+ }
+ }
+
+ return true;
+}
+
+template <class Derived>
+bool
+WorkerPrivateParent<Derived>::ModifyBusyCount(bool aIncrease)
+{
+ AssertIsOnParentThread();
+
+ NS_ASSERTION(aIncrease || mBusyCount, "Mismatched busy count mods!");
+
+ if (aIncrease) {
+ mBusyCount++;
+ return true;
+ }
+
+ if (--mBusyCount == 0) {
+
+ bool shouldCancel;
+ {
+ MutexAutoLock lock(mMutex);
+ shouldCancel = mParentStatus == Terminating;
+ }
+
+ if (shouldCancel && !Cancel()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::ForgetOverridenLoadGroup(
+ nsCOMPtr<nsILoadGroup>& aLoadGroupOut)
+{
+ AssertIsOnParentThread();
+
+ // If we're not overriden, then do nothing here. Let the load group get
+ // handled in ForgetMainThreadObjects().
+ if (!mLoadInfo.mInterfaceRequestor) {
+ return;
+ }
+
+ mLoadInfo.mLoadGroup.swap(aLoadGroupOut);
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::ForgetMainThreadObjects(
+ nsTArray<nsCOMPtr<nsISupports> >& aDoomed)
+{
+ AssertIsOnParentThread();
+ MOZ_ASSERT(!mMainThreadObjectsForgotten);
+
+ static const uint32_t kDoomedCount = 10;
+
+ aDoomed.SetCapacity(kDoomedCount);
+
+ SwapToISupportsArray(mLoadInfo.mWindow, aDoomed);
+ SwapToISupportsArray(mLoadInfo.mScriptContext, aDoomed);
+ SwapToISupportsArray(mLoadInfo.mBaseURI, aDoomed);
+ SwapToISupportsArray(mLoadInfo.mResolvedScriptURI, aDoomed);
+ SwapToISupportsArray(mLoadInfo.mPrincipal, aDoomed);
+ SwapToISupportsArray(mLoadInfo.mChannel, aDoomed);
+ SwapToISupportsArray(mLoadInfo.mCSP, aDoomed);
+ SwapToISupportsArray(mLoadInfo.mLoadGroup, aDoomed);
+ SwapToISupportsArray(mLoadInfo.mLoadFailedAsyncRunnable, aDoomed);
+ SwapToISupportsArray(mLoadInfo.mInterfaceRequestor, aDoomed);
+ // Before adding anything here update kDoomedCount above!
+
+ MOZ_ASSERT(aDoomed.Length() == kDoomedCount);
+
+ mMainThreadObjectsForgotten = true;
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::PostMessageInternal(
+ JSContext* aCx,
+ JS::Handle<JS::Value> aMessage,
+ const Optional<Sequence<JS::Value>>& aTransferable,
+ UniquePtr<ServiceWorkerClientInfo>&& aClientInfo,
+ PromiseNativeHandler* aHandler,
+ ErrorResult& aRv)
+{
+ AssertIsOnParentThread();
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mParentStatus > Running) {
+ return;
+ }
+ }
+
+ JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedValue());
+ if (aTransferable.WasPassed()) {
+ const Sequence<JS::Value>& realTransferable = aTransferable.Value();
+
+ // The input sequence only comes from the generated bindings code, which
+ // ensures it is rooted.
+ JS::HandleValueArray elements =
+ JS::HandleValueArray::fromMarkedLocation(realTransferable.Length(),
+ realTransferable.Elements());
+
+ JSObject* array =
+ JS_NewArrayObject(aCx, elements);
+ if (!array) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ transferable.setObject(*array);
+ }
+
+ RefPtr<MessageEventRunnable> runnable =
+ new MessageEventRunnable(ParentAsWorkerPrivate(),
+ WorkerRunnable::WorkerThreadModifyBusyCount);
+
+ UniquePtr<AbstractTimelineMarker> start;
+ UniquePtr<AbstractTimelineMarker> end;
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+ bool isTimelineRecording = timelines && !timelines->IsEmpty();
+
+ if (isTimelineRecording) {
+ start = MakeUnique<WorkerTimelineMarker>(NS_IsMainThread()
+ ? ProfileTimelineWorkerOperationType::SerializeDataOnMainThread
+ : ProfileTimelineWorkerOperationType::SerializeDataOffMainThread,
+ MarkerTracingType::START);
+ }
+
+ runnable->Write(aCx, aMessage, transferable, JS::CloneDataPolicy(), aRv);
+
+ if (isTimelineRecording) {
+ end = MakeUnique<WorkerTimelineMarker>(NS_IsMainThread()
+ ? ProfileTimelineWorkerOperationType::SerializeDataOnMainThread
+ : ProfileTimelineWorkerOperationType::SerializeDataOffMainThread,
+ MarkerTracingType::END);
+ timelines->AddMarkerForAllObservedDocShells(start);
+ timelines->AddMarkerForAllObservedDocShells(end);
+ }
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ runnable->SetServiceWorkerData(Move(aClientInfo), aHandler);
+
+ if (!runnable->Dispatch()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ }
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::PostMessage(
+ JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const Optional<Sequence<JS::Value>>& aTransferable,
+ ErrorResult& aRv)
+{
+ PostMessageInternal(aCx, aMessage, aTransferable, nullptr, nullptr, aRv);
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::PostMessageToServiceWorker(
+ JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const Optional<Sequence<JS::Value>>& aTransferable,
+ UniquePtr<ServiceWorkerClientInfo>&& aClientInfo,
+ PromiseNativeHandler* aHandler,
+ ErrorResult& aRv)
+{
+ AssertIsOnMainThread();
+ PostMessageInternal(aCx, aMessage, aTransferable, Move(aClientInfo),
+ aHandler, aRv);
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::UpdateContextOptions(
+ const JS::ContextOptions& aContextOptions)
+{
+ AssertIsOnParentThread();
+
+ {
+ MutexAutoLock lock(mMutex);
+ mJSSettings.contextOptions = aContextOptions;
+ }
+
+ RefPtr<UpdateContextOptionsRunnable> runnable =
+ new UpdateContextOptionsRunnable(ParentAsWorkerPrivate(), aContextOptions);
+ if (!runnable->Dispatch()) {
+ NS_WARNING("Failed to update worker context options!");
+ }
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::UpdatePreference(WorkerPreference aPref, bool aValue)
+{
+ AssertIsOnParentThread();
+ MOZ_ASSERT(aPref >= 0 && aPref < WORKERPREF_COUNT);
+
+ RefPtr<UpdatePreferenceRunnable> runnable =
+ new UpdatePreferenceRunnable(ParentAsWorkerPrivate(), aPref, aValue);
+ if (!runnable->Dispatch()) {
+ NS_WARNING("Failed to update worker preferences!");
+ }
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::UpdateLanguages(const nsTArray<nsString>& aLanguages)
+{
+ AssertIsOnParentThread();
+
+ RefPtr<UpdateLanguagesRunnable> runnable =
+ new UpdateLanguagesRunnable(ParentAsWorkerPrivate(), aLanguages);
+ if (!runnable->Dispatch()) {
+ NS_WARNING("Failed to update worker languages!");
+ }
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::UpdateJSWorkerMemoryParameter(JSGCParamKey aKey,
+ uint32_t aValue)
+{
+ AssertIsOnParentThread();
+
+ bool found = false;
+
+ {
+ MutexAutoLock lock(mMutex);
+ found = mJSSettings.ApplyGCSetting(aKey, aValue);
+ }
+
+ if (found) {
+ RefPtr<UpdateJSWorkerMemoryParameterRunnable> runnable =
+ new UpdateJSWorkerMemoryParameterRunnable(ParentAsWorkerPrivate(), aKey,
+ aValue);
+ if (!runnable->Dispatch()) {
+ NS_WARNING("Failed to update memory parameter!");
+ }
+ }
+}
+
+#ifdef JS_GC_ZEAL
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::UpdateGCZeal(uint8_t aGCZeal, uint32_t aFrequency)
+{
+ AssertIsOnParentThread();
+
+ {
+ MutexAutoLock lock(mMutex);
+ mJSSettings.gcZeal = aGCZeal;
+ mJSSettings.gcZealFrequency = aFrequency;
+ }
+
+ RefPtr<UpdateGCZealRunnable> runnable =
+ new UpdateGCZealRunnable(ParentAsWorkerPrivate(), aGCZeal, aFrequency);
+ if (!runnable->Dispatch()) {
+ NS_WARNING("Failed to update worker gczeal!");
+ }
+}
+#endif
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::GarbageCollect(bool aShrinking)
+{
+ AssertIsOnParentThread();
+
+ RefPtr<GarbageCollectRunnable> runnable =
+ new GarbageCollectRunnable(ParentAsWorkerPrivate(), aShrinking,
+ /* collectChildren = */ true);
+ if (!runnable->Dispatch()) {
+ NS_WARNING("Failed to GC worker!");
+ }
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::CycleCollect(bool aDummy)
+{
+ AssertIsOnParentThread();
+
+ RefPtr<CycleCollectRunnable> runnable =
+ new CycleCollectRunnable(ParentAsWorkerPrivate(),
+ /* collectChildren = */ true);
+ if (!runnable->Dispatch()) {
+ NS_WARNING("Failed to CC worker!");
+ }
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::OfflineStatusChangeEvent(bool aIsOffline)
+{
+ AssertIsOnParentThread();
+
+ RefPtr<OfflineStatusChangeRunnable> runnable =
+ new OfflineStatusChangeRunnable(ParentAsWorkerPrivate(), aIsOffline);
+ if (!runnable->Dispatch()) {
+ NS_WARNING("Failed to dispatch offline status change event!");
+ }
+}
+
+void
+WorkerPrivate::OfflineStatusChangeEventInternal(bool aIsOffline)
+{
+ AssertIsOnWorkerThread();
+
+ // The worker is already in this state. No need to dispatch an event.
+ if (mOnLine == !aIsOffline) {
+ return;
+ }
+
+ for (uint32_t index = 0; index < mChildWorkers.Length(); ++index) {
+ mChildWorkers[index]->OfflineStatusChangeEvent(aIsOffline);
+ }
+
+ mOnLine = !aIsOffline;
+ WorkerGlobalScope* globalScope = GlobalScope();
+ RefPtr<WorkerNavigator> nav = globalScope->GetExistingNavigator();
+ if (nav) {
+ nav->SetOnLine(mOnLine);
+ }
+
+ nsString eventType;
+ if (aIsOffline) {
+ eventType.AssignLiteral("offline");
+ } else {
+ eventType.AssignLiteral("online");
+ }
+
+ RefPtr<Event> event = NS_NewDOMEvent(globalScope, nullptr, nullptr);
+
+ event->InitEvent(eventType, false, false);
+ event->SetTrusted(true);
+
+ globalScope->DispatchDOMEvent(nullptr, event, nullptr, nullptr);
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::MemoryPressure(bool aDummy)
+{
+ AssertIsOnParentThread();
+
+ RefPtr<MemoryPressureRunnable> runnable =
+ new MemoryPressureRunnable(ParentAsWorkerPrivate());
+ Unused << NS_WARN_IF(!runnable->Dispatch());
+}
+
+template <class Derived>
+bool
+WorkerPrivateParent<Derived>::RegisterSharedWorker(SharedWorker* aSharedWorker,
+ MessagePort* aPort)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aSharedWorker);
+ MOZ_ASSERT(IsSharedWorker());
+ MOZ_ASSERT(!mSharedWorkers.Contains(aSharedWorker));
+
+ if (IsSharedWorker()) {
+ RefPtr<MessagePortRunnable> runnable =
+ new MessagePortRunnable(ParentAsWorkerPrivate(), aPort);
+ if (!runnable->Dispatch()) {
+ return false;
+ }
+ }
+
+ mSharedWorkers.AppendElement(aSharedWorker);
+
+ // If there were other SharedWorker objects attached to this worker then they
+ // may all have been frozen and this worker would need to be thawed.
+ if (mSharedWorkers.Length() > 1 && IsFrozen() && !Thaw(nullptr)) {
+ return false;
+ }
+
+ return true;
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::BroadcastErrorToSharedWorkers(
+ JSContext* aCx,
+ const nsAString& aMessage,
+ const nsAString& aFilename,
+ const nsAString& aLine,
+ uint32_t aLineNumber,
+ uint32_t aColumnNumber,
+ uint32_t aFlags)
+{
+ AssertIsOnMainThread();
+
+ if (JSREPORT_IS_WARNING(aFlags)) {
+ // Don't fire any events anywhere. Just log to console.
+ // XXXbz should we log to all the consoles of all the relevant windows?
+ LogErrorToConsole(aMessage, aFilename, aLine, aLineNumber, aColumnNumber,
+ aFlags, 0);
+ return;
+ }
+
+ AutoTArray<RefPtr<SharedWorker>, 10> sharedWorkers;
+ GetAllSharedWorkers(sharedWorkers);
+
+ if (sharedWorkers.IsEmpty()) {
+ return;
+ }
+
+ AutoTArray<WindowAction, 10> windowActions;
+ nsresult rv;
+
+ // First fire the error event at all SharedWorker objects. This may include
+ // multiple objects in a single window as well as objects in different
+ // windows.
+ for (size_t index = 0; index < sharedWorkers.Length(); index++) {
+ RefPtr<SharedWorker>& sharedWorker = sharedWorkers[index];
+
+ // May be null.
+ nsPIDOMWindowInner* window = sharedWorker->GetOwner();
+
+ RootedDictionary<ErrorEventInit> errorInit(aCx);
+ errorInit.mBubbles = false;
+ errorInit.mCancelable = true;
+ errorInit.mMessage = aMessage;
+ errorInit.mFilename = aFilename;
+ errorInit.mLineno = aLineNumber;
+ errorInit.mColno = aColumnNumber;
+
+ RefPtr<ErrorEvent> errorEvent =
+ ErrorEvent::Constructor(sharedWorker, NS_LITERAL_STRING("error"),
+ errorInit);
+ if (!errorEvent) {
+ ThrowAndReport(window, NS_ERROR_UNEXPECTED);
+ continue;
+ }
+
+ errorEvent->SetTrusted(true);
+
+ bool defaultActionEnabled;
+ nsresult rv = sharedWorker->DispatchEvent(errorEvent, &defaultActionEnabled);
+ if (NS_FAILED(rv)) {
+ ThrowAndReport(window, rv);
+ continue;
+ }
+
+ if (defaultActionEnabled) {
+ // Add the owning window to our list so that we will fire an error event
+ // at it later.
+ if (!windowActions.Contains(window)) {
+ windowActions.AppendElement(WindowAction(window));
+ }
+ } else {
+ size_t actionsIndex = windowActions.LastIndexOf(WindowAction(window));
+ if (actionsIndex != windowActions.NoIndex) {
+ // Any listener that calls preventDefault() will prevent the window from
+ // receiving the error event.
+ windowActions[actionsIndex].mDefaultAction = false;
+ }
+ }
+ }
+
+ // If there are no windows to consider further then we're done.
+ if (windowActions.IsEmpty()) {
+ return;
+ }
+
+ bool shouldLogErrorToConsole = true;
+
+ // Now fire error events at all the windows remaining.
+ for (uint32_t index = 0; index < windowActions.Length(); index++) {
+ WindowAction& windowAction = windowActions[index];
+
+ // If there is no window or the script already called preventDefault then
+ // skip this window.
+ if (!windowAction.mWindow || !windowAction.mDefaultAction) {
+ continue;
+ }
+
+ nsCOMPtr<nsIScriptGlobalObject> sgo =
+ do_QueryInterface(windowAction.mWindow);
+ MOZ_ASSERT(sgo);
+
+ MOZ_ASSERT(NS_IsMainThread());
+ RootedDictionary<ErrorEventInit> init(aCx);
+ init.mLineno = aLineNumber;
+ init.mFilename = aFilename;
+ init.mMessage = aMessage;
+ init.mCancelable = true;
+ init.mBubbles = true;
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ rv = sgo->HandleScriptError(init, &status);
+ if (NS_FAILED(rv)) {
+ ThrowAndReport(windowAction.mWindow, rv);
+ continue;
+ }
+
+ if (status == nsEventStatus_eConsumeNoDefault) {
+ shouldLogErrorToConsole = false;
+ }
+ }
+
+ // Finally log a warning in the console if no window tried to prevent it.
+ if (shouldLogErrorToConsole) {
+ LogErrorToConsole(aMessage, aFilename, aLine, aLineNumber, aColumnNumber,
+ aFlags, 0);
+ }
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::GetAllSharedWorkers(
+ nsTArray<RefPtr<SharedWorker>>& aSharedWorkers)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(IsSharedWorker() || IsServiceWorker());
+
+ if (!aSharedWorkers.IsEmpty()) {
+ aSharedWorkers.Clear();
+ }
+
+ for (uint32_t i = 0; i < mSharedWorkers.Length(); ++i) {
+ aSharedWorkers.AppendElement(mSharedWorkers[i]);
+ }
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::CloseSharedWorkersForWindow(
+ nsPIDOMWindowInner* aWindow)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(IsSharedWorker() || IsServiceWorker());
+ MOZ_ASSERT(aWindow);
+
+ bool someRemoved = false;
+
+ for (uint32_t i = 0; i < mSharedWorkers.Length();) {
+ if (mSharedWorkers[i]->GetOwner() == aWindow) {
+ mSharedWorkers[i]->Close();
+ mSharedWorkers.RemoveElementAt(i);
+ someRemoved = true;
+ } else {
+ MOZ_ASSERT(!SameCOMIdentity(mSharedWorkers[i]->GetOwner(),
+ aWindow));
+ ++i;
+ }
+ }
+
+ if (!someRemoved) {
+ return;
+ }
+
+ // If there are still SharedWorker objects attached to this worker then they
+ // may all be frozen and this worker would need to be frozen. Otherwise,
+ // if that was the last SharedWorker then it's time to cancel this worker.
+
+ if (!mSharedWorkers.IsEmpty()) {
+ Freeze(nullptr);
+ } else {
+ Cancel();
+ }
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::CloseAllSharedWorkers()
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(IsSharedWorker() || IsServiceWorker());
+
+ for (uint32_t i = 0; i < mSharedWorkers.Length(); ++i) {
+ mSharedWorkers[i]->Close();
+ }
+
+ mSharedWorkers.Clear();
+
+ Cancel();
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::WorkerScriptLoaded()
+{
+ AssertIsOnMainThread();
+
+ if (IsSharedWorker() || IsServiceWorker()) {
+ // No longer need to hold references to the window or document we came from.
+ mLoadInfo.mWindow = nullptr;
+ mLoadInfo.mScriptContext = nullptr;
+ }
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::SetBaseURI(nsIURI* aBaseURI)
+{
+ AssertIsOnMainThread();
+
+ if (!mLoadInfo.mBaseURI) {
+ NS_ASSERTION(GetParent(), "Shouldn't happen without a parent!");
+ mLoadInfo.mResolvedScriptURI = aBaseURI;
+ }
+
+ mLoadInfo.mBaseURI = aBaseURI;
+
+ if (NS_FAILED(aBaseURI->GetSpec(mLocationInfo.mHref))) {
+ mLocationInfo.mHref.Truncate();
+ }
+
+ mLocationInfo.mHostname.Truncate();
+ nsContentUtils::GetHostOrIPv6WithBrackets(aBaseURI, mLocationInfo.mHostname);
+
+ nsCOMPtr<nsIURL> url(do_QueryInterface(aBaseURI));
+ if (!url || NS_FAILED(url->GetFilePath(mLocationInfo.mPathname))) {
+ mLocationInfo.mPathname.Truncate();
+ }
+
+ nsCString temp;
+
+ if (url && NS_SUCCEEDED(url->GetQuery(temp)) && !temp.IsEmpty()) {
+ mLocationInfo.mSearch.Assign('?');
+ mLocationInfo.mSearch.Append(temp);
+ }
+
+ if (NS_SUCCEEDED(aBaseURI->GetRef(temp)) && !temp.IsEmpty()) {
+ nsCOMPtr<nsITextToSubURI> converter =
+ do_GetService(NS_ITEXTTOSUBURI_CONTRACTID);
+ if (converter && nsContentUtils::GettersDecodeURLHash()) {
+ nsCString charset;
+ nsAutoString unicodeRef;
+ if (NS_SUCCEEDED(aBaseURI->GetOriginCharset(charset)) &&
+ NS_SUCCEEDED(converter->UnEscapeURIForUI(charset, temp,
+ unicodeRef))) {
+ mLocationInfo.mHash.Assign('#');
+ mLocationInfo.mHash.Append(NS_ConvertUTF16toUTF8(unicodeRef));
+ }
+ }
+
+ if (mLocationInfo.mHash.IsEmpty()) {
+ mLocationInfo.mHash.Assign('#');
+ mLocationInfo.mHash.Append(temp);
+ }
+ }
+
+ if (NS_SUCCEEDED(aBaseURI->GetScheme(mLocationInfo.mProtocol))) {
+ mLocationInfo.mProtocol.Append(':');
+ }
+ else {
+ mLocationInfo.mProtocol.Truncate();
+ }
+
+ int32_t port;
+ if (NS_SUCCEEDED(aBaseURI->GetPort(&port)) && port != -1) {
+ mLocationInfo.mPort.AppendInt(port);
+
+ nsAutoCString host(mLocationInfo.mHostname);
+ host.Append(':');
+ host.Append(mLocationInfo.mPort);
+
+ mLocationInfo.mHost.Assign(host);
+ }
+ else {
+ mLocationInfo.mHost.Assign(mLocationInfo.mHostname);
+ }
+
+ nsContentUtils::GetUTFOrigin(aBaseURI, mLocationInfo.mOrigin);
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::SetPrincipal(nsIPrincipal* aPrincipal,
+ nsILoadGroup* aLoadGroup)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(aLoadGroup, aPrincipal));
+ MOZ_ASSERT(!mLoadInfo.mPrincipalInfo);
+
+ mLoadInfo.mPrincipal = aPrincipal;
+ mLoadInfo.mPrincipalIsSystem = nsContentUtils::IsSystemPrincipal(aPrincipal);
+
+ aPrincipal->GetCsp(getter_AddRefs(mLoadInfo.mCSP));
+
+ if (mLoadInfo.mCSP) {
+ mLoadInfo.mCSP->GetAllowsEval(&mLoadInfo.mReportCSPViolations,
+ &mLoadInfo.mEvalAllowed);
+ // Set ReferrerPolicy
+ bool hasReferrerPolicy = false;
+ uint32_t rp = mozilla::net::RP_Default;
+
+ nsresult rv = mLoadInfo.mCSP->GetReferrerPolicy(&rp, &hasReferrerPolicy);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ if (hasReferrerPolicy) {
+ mLoadInfo.mReferrerPolicy = static_cast<net::ReferrerPolicy>(rp);
+ }
+ } else {
+ mLoadInfo.mEvalAllowed = true;
+ mLoadInfo.mReportCSPViolations = false;
+ }
+
+ mLoadInfo.mLoadGroup = aLoadGroup;
+
+ mLoadInfo.mPrincipalInfo = new PrincipalInfo();
+ mLoadInfo.mOriginAttributes = nsContentUtils::GetOriginAttributes(aLoadGroup);
+
+ MOZ_ALWAYS_SUCCEEDS(
+ PrincipalToPrincipalInfo(aPrincipal, mLoadInfo.mPrincipalInfo));
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::UpdateOverridenLoadGroup(nsILoadGroup* aBaseLoadGroup)
+{
+ AssertIsOnMainThread();
+
+ // The load group should have been overriden at init time.
+ mLoadInfo.mInterfaceRequestor->MaybeAddTabChild(aBaseLoadGroup);
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::FlushReportsToSharedWorkers(
+ nsIConsoleReportCollector* aReporter)
+{
+ AssertIsOnMainThread();
+
+ AutoTArray<RefPtr<SharedWorker>, 10> sharedWorkers;
+ AutoTArray<WindowAction, 10> windowActions;
+ GetAllSharedWorkers(sharedWorkers);
+
+ // First find out all the shared workers' window.
+ for (size_t index = 0; index < sharedWorkers.Length(); index++) {
+ RefPtr<SharedWorker>& sharedWorker = sharedWorkers[index];
+
+ // May be null.
+ nsPIDOMWindowInner* window = sharedWorker->GetOwner();
+
+ // Add the owning window to our list so that we will flush the reports later.
+ if (window && !windowActions.Contains(window)) {
+ windowActions.AppendElement(WindowAction(window));
+ }
+ }
+
+ bool reportErrorToBrowserConsole = true;
+
+ // Flush the reports.
+ for (uint32_t index = 0; index < windowActions.Length(); index++) {
+ WindowAction& windowAction = windowActions[index];
+
+ aReporter->FlushConsoleReports(windowAction.mWindow->GetExtantDoc(),
+ nsIConsoleReportCollector::ReportAction::Save);
+ reportErrorToBrowserConsole = false;
+ }
+
+ // Finally report to broswer console if there is no any window or shared
+ // worker.
+ if (reportErrorToBrowserConsole) {
+ aReporter->FlushConsoleReports((nsIDocument*)nullptr);
+ return;
+ }
+
+ aReporter->ClearConsoleReports();
+}
+
+template <class Derived>
+NS_IMPL_ADDREF_INHERITED(WorkerPrivateParent<Derived>, DOMEventTargetHelper)
+
+template <class Derived>
+NS_IMPL_RELEASE_INHERITED(WorkerPrivateParent<Derived>, DOMEventTargetHelper)
+
+template <class Derived>
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(WorkerPrivateParent<Derived>)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+template <class Derived>
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WorkerPrivateParent<Derived>,
+ DOMEventTargetHelper)
+ tmp->AssertIsOnParentThread();
+
+ // The WorkerPrivate::mSelfRef has a reference to itself, which is really
+ // held by the worker thread. We traverse this reference if and only if our
+ // busy count is zero and we have not released the main thread reference.
+ // We do not unlink it. This allows the CC to break cycles involving the
+ // WorkerPrivate and begin shutting it down (which does happen in unlink) but
+ // ensures that the WorkerPrivate won't be deleted before we're done shutting
+ // down the thread.
+
+ if (!tmp->mBusyCount && !tmp->mMainThreadObjectsForgotten) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelfRef)
+ }
+
+ // The various strong references in LoadInfo are managed manually and cannot
+ // be cycle collected.
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+template <class Derived>
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WorkerPrivateParent<Derived>,
+ DOMEventTargetHelper)
+ tmp->Terminate();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+template <class Derived>
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(WorkerPrivateParent<Derived>,
+ DOMEventTargetHelper)
+ tmp->AssertIsOnParentThread();
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+#ifdef DEBUG
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::AssertIsOnParentThread() const
+{
+ if (GetParent()) {
+ GetParent()->AssertIsOnWorkerThread();
+ }
+ else {
+ AssertIsOnMainThread();
+ }
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::AssertInnerWindowIsCorrect() const
+{
+ AssertIsOnParentThread();
+
+ // Only care about top level workers from windows.
+ if (mParent || !mLoadInfo.mWindow) {
+ return;
+ }
+
+ AssertIsOnMainThread();
+
+ nsPIDOMWindowOuter* outer = mLoadInfo.mWindow->GetOuterWindow();
+ NS_ASSERTION(outer && outer->GetCurrentInnerWindow() == mLoadInfo.mWindow,
+ "Inner window no longer correct!");
+}
+
+#endif
+
+class PostDebuggerMessageRunnable final : public Runnable
+{
+ WorkerDebugger *mDebugger;
+ nsString mMessage;
+
+public:
+ PostDebuggerMessageRunnable(WorkerDebugger* aDebugger,
+ const nsAString& aMessage)
+ : mDebugger(aDebugger),
+ mMessage(aMessage)
+ {
+ }
+
+private:
+ ~PostDebuggerMessageRunnable()
+ { }
+
+ NS_IMETHOD
+ Run() override
+ {
+ mDebugger->PostMessageToDebuggerOnMainThread(mMessage);
+
+ return NS_OK;
+ }
+};
+
+class ReportDebuggerErrorRunnable final : public Runnable
+{
+ WorkerDebugger *mDebugger;
+ nsString mFilename;
+ uint32_t mLineno;
+ nsString mMessage;
+
+public:
+ ReportDebuggerErrorRunnable(WorkerDebugger* aDebugger,
+ const nsAString& aFilename, uint32_t aLineno,
+ const nsAString& aMessage)
+ : mDebugger(aDebugger),
+ mFilename(aFilename),
+ mLineno(aLineno),
+ mMessage(aMessage)
+ {
+ }
+
+private:
+ ~ReportDebuggerErrorRunnable()
+ { }
+
+ NS_IMETHOD
+ Run() override
+ {
+ mDebugger->ReportErrorToDebuggerOnMainThread(mFilename, mLineno, mMessage);
+
+ return NS_OK;
+ }
+};
+
+WorkerDebugger::WorkerDebugger(WorkerPrivate* aWorkerPrivate)
+: mWorkerPrivate(aWorkerPrivate),
+ mIsInitialized(false)
+{
+ AssertIsOnMainThread();
+}
+
+WorkerDebugger::~WorkerDebugger()
+{
+ MOZ_ASSERT(!mWorkerPrivate);
+
+ if (!NS_IsMainThread()) {
+ for (size_t index = 0; index < mListeners.Length(); ++index) {
+ NS_ReleaseOnMainThread(mListeners[index].forget());
+ }
+ }
+}
+
+NS_IMPL_ISUPPORTS(WorkerDebugger, nsIWorkerDebugger)
+
+NS_IMETHODIMP
+WorkerDebugger::GetIsClosed(bool* aResult)
+{
+ AssertIsOnMainThread();
+
+ *aResult = !mWorkerPrivate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::GetIsChrome(bool* aResult)
+{
+ AssertIsOnMainThread();
+
+ if (!mWorkerPrivate) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ *aResult = mWorkerPrivate->IsChromeWorker();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::GetIsInitialized(bool* aResult)
+{
+ AssertIsOnMainThread();
+
+ if (!mWorkerPrivate) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ *aResult = mIsInitialized;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::GetParent(nsIWorkerDebugger** aResult)
+{
+ AssertIsOnMainThread();
+
+ if (!mWorkerPrivate) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ WorkerPrivate* parent = mWorkerPrivate->GetParent();
+ if (!parent) {
+ *aResult = nullptr;
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mWorkerPrivate->IsDedicatedWorker());
+
+ nsCOMPtr<nsIWorkerDebugger> debugger = parent->Debugger();
+ debugger.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::GetType(uint32_t* aResult)
+{
+ AssertIsOnMainThread();
+
+ if (!mWorkerPrivate) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ *aResult = mWorkerPrivate->Type();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::GetUrl(nsAString& aResult)
+{
+ AssertIsOnMainThread();
+
+ if (!mWorkerPrivate) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ aResult = mWorkerPrivate->ScriptURL();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::GetWindow(mozIDOMWindow** aResult)
+{
+ AssertIsOnMainThread();
+
+ if (!mWorkerPrivate) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mWorkerPrivate->GetParent() || !mWorkerPrivate->IsDedicatedWorker()) {
+ *aResult = nullptr;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> window = mWorkerPrivate->GetWindow();
+ window.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::GetPrincipal(nsIPrincipal** aResult)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aResult);
+
+ if (!mWorkerPrivate) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIPrincipal> prin = mWorkerPrivate->GetPrincipal();
+ prin.forget(aResult);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::GetServiceWorkerID(uint32_t* aResult)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aResult);
+
+ if (!mWorkerPrivate || !mWorkerPrivate->IsServiceWorker()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ *aResult = mWorkerPrivate->ServiceWorkerID();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::Initialize(const nsAString& aURL)
+{
+ AssertIsOnMainThread();
+
+ if (!mWorkerPrivate) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (!mIsInitialized) {
+ RefPtr<CompileDebuggerScriptRunnable> runnable =
+ new CompileDebuggerScriptRunnable(mWorkerPrivate, aURL);
+ if (!runnable->Dispatch()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mIsInitialized = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::PostMessageMoz(const nsAString& aMessage)
+{
+ AssertIsOnMainThread();
+
+ if (!mWorkerPrivate || !mIsInitialized) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ RefPtr<DebuggerMessageEventRunnable> runnable =
+ new DebuggerMessageEventRunnable(mWorkerPrivate, aMessage);
+ if (!runnable->Dispatch()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::AddListener(nsIWorkerDebuggerListener* aListener)
+{
+ AssertIsOnMainThread();
+
+ if (mListeners.Contains(aListener)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mListeners.AppendElement(aListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::RemoveListener(nsIWorkerDebuggerListener* aListener)
+{
+ AssertIsOnMainThread();
+
+ if (!mListeners.Contains(aListener)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mListeners.RemoveElement(aListener);
+ return NS_OK;
+}
+
+void
+WorkerDebugger::Close()
+{
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate = nullptr;
+
+ nsTArray<nsCOMPtr<nsIWorkerDebuggerListener>> listeners(mListeners);
+ for (size_t index = 0; index < listeners.Length(); ++index) {
+ listeners[index]->OnClose();
+ }
+}
+
+void
+WorkerDebugger::PostMessageToDebugger(const nsAString& aMessage)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<PostDebuggerMessageRunnable> runnable =
+ new PostDebuggerMessageRunnable(this, aMessage);
+ if (NS_FAILED(mWorkerPrivate->DispatchToMainThread(runnable.forget()))) {
+ NS_WARNING("Failed to post message to debugger on main thread!");
+ }
+}
+
+void
+WorkerDebugger::PostMessageToDebuggerOnMainThread(const nsAString& aMessage)
+{
+ AssertIsOnMainThread();
+
+ nsTArray<nsCOMPtr<nsIWorkerDebuggerListener>> listeners(mListeners);
+ for (size_t index = 0; index < listeners.Length(); ++index) {
+ listeners[index]->OnMessage(aMessage);
+ }
+}
+
+void
+WorkerDebugger::ReportErrorToDebugger(const nsAString& aFilename,
+ uint32_t aLineno,
+ const nsAString& aMessage)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<ReportDebuggerErrorRunnable> runnable =
+ new ReportDebuggerErrorRunnable(this, aFilename, aLineno, aMessage);
+ if (NS_FAILED(mWorkerPrivate->DispatchToMainThread(runnable.forget()))) {
+ NS_WARNING("Failed to report error to debugger on main thread!");
+ }
+}
+
+void
+WorkerDebugger::ReportErrorToDebuggerOnMainThread(const nsAString& aFilename,
+ uint32_t aLineno,
+ const nsAString& aMessage)
+{
+ AssertIsOnMainThread();
+
+ nsTArray<nsCOMPtr<nsIWorkerDebuggerListener>> listeners(mListeners);
+ for (size_t index = 0; index < listeners.Length(); ++index) {
+ listeners[index]->OnError(aFilename, aLineno, aMessage);
+ }
+
+ LogErrorToConsole(aMessage, aFilename, nsString(), aLineno, 0, 0, 0);
+}
+
+WorkerPrivate::WorkerPrivate(WorkerPrivate* aParent,
+ const nsAString& aScriptURL,
+ bool aIsChromeWorker, WorkerType aWorkerType,
+ const nsACString& aWorkerName,
+ WorkerLoadInfo& aLoadInfo)
+ : WorkerPrivateParent<WorkerPrivate>(aParent, aScriptURL,
+ aIsChromeWorker, aWorkerType,
+ aWorkerName, aLoadInfo)
+ , mDebuggerRegistered(false)
+ , mDebugger(nullptr)
+ , mJSContext(nullptr)
+ , mPRThread(nullptr)
+ , mDebuggerEventLoopLevel(0)
+ , mMainThreadEventTarget(do_GetMainThread())
+ , mErrorHandlerRecursionCount(0)
+ , mNextTimeoutId(1)
+ , mStatus(Pending)
+ , mFrozen(false)
+ , mTimerRunning(false)
+ , mRunningExpiredTimeouts(false)
+ , mPendingEventQueueClearing(false)
+ , mCancelAllPendingRunnables(false)
+ , mPeriodicGCTimerRunning(false)
+ , mIdleGCTimerRunning(false)
+ , mWorkerScriptExecutedSuccessfully(false)
+ , mOnLine(false)
+{
+ MOZ_ASSERT_IF(!IsDedicatedWorker(), !aWorkerName.IsVoid());
+ MOZ_ASSERT_IF(IsDedicatedWorker(), aWorkerName.IsEmpty());
+
+ if (aParent) {
+ aParent->AssertIsOnWorkerThread();
+ aParent->GetAllPreferences(mPreferences);
+ mOnLine = aParent->OnLine();
+ }
+ else {
+ AssertIsOnMainThread();
+ RuntimeService::GetDefaultPreferences(mPreferences);
+ mOnLine = !NS_IsOffline();
+ }
+
+ nsCOMPtr<nsIEventTarget> target;
+
+ // A child worker just inherits the parent workers ThrottledEventQueue
+ // and main thread target for now. This is mainly due to the restriction
+ // that ThrottledEventQueue can only be created on the main thread at the
+ // moment.
+ if (aParent) {
+ mMainThreadThrottledEventQueue = aParent->mMainThreadThrottledEventQueue;
+ mMainThreadEventTarget = aParent->mMainThreadEventTarget;
+ return;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+ target = GetWindow() ? GetWindow()->GetThrottledEventQueue() : nullptr;
+
+ if (!target) {
+ nsCOMPtr<nsIThread> mainThread;
+ NS_GetMainThread(getter_AddRefs(mainThread));
+ MOZ_DIAGNOSTIC_ASSERT(mainThread);
+ target = mainThread;
+ }
+
+ // Throttle events to the main thread using a ThrottledEventQueue specific to
+ // this worker thread. This may return nullptr during shutdown.
+ mMainThreadThrottledEventQueue = ThrottledEventQueue::Create(target);
+
+ // If we were able to creat the throttled event queue, then use it for
+ // dispatching our main thread runnables. Otherwise use our underlying
+ // base target.
+ if (mMainThreadThrottledEventQueue) {
+ mMainThreadEventTarget = mMainThreadThrottledEventQueue;
+ } else {
+ mMainThreadEventTarget = target.forget();
+ }
+}
+
+WorkerPrivate::~WorkerPrivate()
+{
+}
+
+// static
+already_AddRefed<WorkerPrivate>
+WorkerPrivate::Constructor(const GlobalObject& aGlobal,
+ const nsAString& aScriptURL,
+ ErrorResult& aRv)
+{
+ return WorkerPrivate::Constructor(aGlobal, aScriptURL, false,
+ WorkerTypeDedicated, EmptyCString(),
+ nullptr, aRv);
+}
+
+// static
+bool
+WorkerPrivate::WorkerAvailable(JSContext* /* unused */, JSObject* /* unused */)
+{
+ // If we're already on a worker workers are clearly enabled.
+ if (!NS_IsMainThread()) {
+ return true;
+ }
+
+ // If our caller is chrome, workers are always available.
+ if (nsContentUtils::IsCallerChrome()) {
+ return true;
+ }
+
+ // Else check the pref.
+ return Preferences::GetBool(PREF_WORKERS_ENABLED);
+}
+
+// static
+already_AddRefed<ChromeWorkerPrivate>
+ChromeWorkerPrivate::Constructor(const GlobalObject& aGlobal,
+ const nsAString& aScriptURL,
+ ErrorResult& aRv)
+{
+ return WorkerPrivate::Constructor(aGlobal, aScriptURL, true,
+ WorkerTypeDedicated, EmptyCString(),
+ nullptr, aRv)
+ .downcast<ChromeWorkerPrivate>();
+}
+
+// static
+bool
+ChromeWorkerPrivate::WorkerAvailable(JSContext* aCx, JSObject* /* unused */)
+{
+ // Chrome is always allowed to use workers, and content is never
+ // allowed to use ChromeWorker, so all we have to check is the
+ // caller. However, chrome workers apparently might not have a
+ // system principal, so we have to check for them manually.
+ if (NS_IsMainThread()) {
+ return nsContentUtils::IsCallerChrome();
+ }
+
+ return GetWorkerPrivateFromContext(aCx)->IsChromeWorker();
+}
+
+// static
+already_AddRefed<WorkerPrivate>
+WorkerPrivate::Constructor(const GlobalObject& aGlobal,
+ const nsAString& aScriptURL,
+ bool aIsChromeWorker, WorkerType aWorkerType,
+ const nsACString& aWorkerName,
+ WorkerLoadInfo* aLoadInfo, ErrorResult& aRv)
+{
+ JSContext* cx = aGlobal.Context();
+ return Constructor(cx, aScriptURL, aIsChromeWorker, aWorkerType,
+ aWorkerName, aLoadInfo, aRv);
+}
+
+// static
+already_AddRefed<WorkerPrivate>
+WorkerPrivate::Constructor(JSContext* aCx,
+ const nsAString& aScriptURL,
+ bool aIsChromeWorker, WorkerType aWorkerType,
+ const nsACString& aWorkerName,
+ WorkerLoadInfo* aLoadInfo, ErrorResult& aRv)
+{
+ // If this is a sub-worker, we need to keep the parent worker alive until this
+ // one is registered.
+ UniquePtr<SimpleWorkerHolder> holder;
+
+ WorkerPrivate* parent = NS_IsMainThread() ?
+ nullptr :
+ GetCurrentThreadWorkerPrivate();
+ if (parent) {
+ parent->AssertIsOnWorkerThread();
+
+ holder.reset(new SimpleWorkerHolder());
+ if (!holder->HoldWorker(parent, Canceling)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+ } else {
+ AssertIsOnMainThread();
+ }
+
+ // Only service and shared workers can have names.
+ MOZ_ASSERT_IF(aWorkerType != WorkerTypeDedicated,
+ !aWorkerName.IsVoid());
+ MOZ_ASSERT_IF(aWorkerType == WorkerTypeDedicated,
+ aWorkerName.IsEmpty());
+
+ Maybe<WorkerLoadInfo> stackLoadInfo;
+ if (!aLoadInfo) {
+ stackLoadInfo.emplace();
+
+ nsresult rv = GetLoadInfo(aCx, nullptr, parent, aScriptURL,
+ aIsChromeWorker, InheritLoadGroup,
+ aWorkerType, stackLoadInfo.ptr());
+ aRv.MightThrowJSException();
+ if (NS_FAILED(rv)) {
+ scriptloader::ReportLoadError(aRv, rv, aScriptURL);
+ return nullptr;
+ }
+
+ aLoadInfo = stackLoadInfo.ptr();
+ }
+
+ // NB: This has to be done before creating the WorkerPrivate, because it will
+ // attempt to use static variables that are initialized in the RuntimeService
+ // constructor.
+ RuntimeService* runtimeService;
+
+ if (!parent) {
+ runtimeService = RuntimeService::GetOrCreateService();
+ if (!runtimeService) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ }
+ else {
+ runtimeService = RuntimeService::GetService();
+ }
+
+ MOZ_ASSERT(runtimeService);
+
+ RefPtr<WorkerPrivate> worker =
+ new WorkerPrivate(parent, aScriptURL, aIsChromeWorker,
+ aWorkerType, aWorkerName, *aLoadInfo);
+
+ // Gecko contexts always have an explicitly-set default locale (set by
+ // XPJSRuntime::Initialize for the main thread, set by
+ // WorkerThreadPrimaryRunnable::Run for workers just before running worker
+ // code), so this is never SpiderMonkey's builtin default locale.
+ JS::UniqueChars defaultLocale = JS_GetDefaultLocale(aCx);
+ if (NS_WARN_IF(!defaultLocale)) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ worker->mDefaultLocale = Move(defaultLocale);
+
+ if (!runtimeService->RegisterWorker(worker)) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ worker->EnableDebugger();
+
+ RefPtr<CompileScriptRunnable> compiler =
+ new CompileScriptRunnable(worker, aScriptURL);
+ if (!compiler->Dispatch()) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ worker->mSelfRef = worker;
+
+ return worker.forget();
+}
+
+// static
+nsresult
+WorkerPrivate::GetLoadInfo(JSContext* aCx, nsPIDOMWindowInner* aWindow,
+ WorkerPrivate* aParent, const nsAString& aScriptURL,
+ bool aIsChromeWorker,
+ LoadGroupBehavior aLoadGroupBehavior,
+ WorkerType aWorkerType,
+ WorkerLoadInfo* aLoadInfo)
+{
+ using namespace mozilla::dom::workers::scriptloader;
+
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT_IF(NS_IsMainThread(), aCx == nsContentUtils::GetCurrentJSContext());
+
+ if (aWindow) {
+ AssertIsOnMainThread();
+ }
+
+ WorkerLoadInfo loadInfo;
+ nsresult rv;
+
+ if (aParent) {
+ aParent->AssertIsOnWorkerThread();
+
+ // If the parent is going away give up now.
+ Status parentStatus;
+ {
+ MutexAutoLock lock(aParent->mMutex);
+ parentStatus = aParent->mStatus;
+ }
+
+ if (parentStatus > Running) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // StartAssignment() is used instead getter_AddRefs because, getter_AddRefs
+ // does QI in debug build and, if this worker runs in a child process,
+ // HttpChannelChild will crash because it's not thread-safe.
+ rv = ChannelFromScriptURLWorkerThread(aCx, aParent, aScriptURL,
+ loadInfo.mChannel.StartAssignment());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now that we've spun the loop there's no guarantee that our parent is
+ // still alive. We may have received control messages initiating shutdown.
+ {
+ MutexAutoLock lock(aParent->mMutex);
+ parentStatus = aParent->mStatus;
+ }
+
+ if (parentStatus > Running) {
+ NS_ReleaseOnMainThread(loadInfo.mChannel.forget());
+ return NS_ERROR_FAILURE;
+ }
+
+ loadInfo.mDomain = aParent->Domain();
+ loadInfo.mFromWindow = aParent->IsFromWindow();
+ loadInfo.mWindowID = aParent->WindowID();
+ loadInfo.mStorageAllowed = aParent->IsStorageAllowed();
+ loadInfo.mOriginAttributes = aParent->GetOriginAttributes();
+ loadInfo.mServiceWorkersTestingInWindow =
+ aParent->ServiceWorkersTestingInWindow();
+ } else {
+ AssertIsOnMainThread();
+
+ // Make sure that the IndexedDatabaseManager is set up
+ Unused << NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate());
+
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ MOZ_ASSERT(ssm);
+
+ bool isChrome = nsContentUtils::IsCallerChrome();
+
+ // First check to make sure the caller has permission to make a privileged
+ // worker if they called the ChromeWorker/ChromeSharedWorker constructor.
+ if (aIsChromeWorker && !isChrome) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ // Chrome callers (whether creating a ChromeWorker or Worker) always get the
+ // system principal here as they're allowed to load anything. The script
+ // loader will refuse to run any script that does not also have the system
+ // principal.
+ if (isChrome) {
+ rv = ssm->GetSystemPrincipal(getter_AddRefs(loadInfo.mPrincipal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ loadInfo.mPrincipalIsSystem = true;
+ }
+
+ // See if we're being called from a window.
+ nsCOMPtr<nsPIDOMWindowInner> globalWindow = aWindow;
+ if (!globalWindow) {
+ nsCOMPtr<nsIScriptGlobalObject> scriptGlobal =
+ nsJSUtils::GetStaticScriptGlobal(JS::CurrentGlobalOrNull(aCx));
+ if (scriptGlobal) {
+ globalWindow = do_QueryInterface(scriptGlobal);
+ MOZ_ASSERT(globalWindow);
+ }
+ }
+
+ nsCOMPtr<nsIDocument> document;
+
+ if (globalWindow) {
+ // Only use the current inner window, and only use it if the caller can
+ // access it.
+ if (nsPIDOMWindowOuter* outerWindow = globalWindow->GetOuterWindow()) {
+ loadInfo.mWindow = outerWindow->GetCurrentInnerWindow();
+ // TODO: fix this for SharedWorkers with multiple documents (bug 1177935)
+ loadInfo.mServiceWorkersTestingInWindow =
+ outerWindow->GetServiceWorkersTestingEnabled();
+ }
+
+ if (!loadInfo.mWindow ||
+ (globalWindow != loadInfo.mWindow &&
+ !nsContentUtils::CanCallerAccess(loadInfo.mWindow))) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(loadInfo.mWindow);
+ MOZ_ASSERT(sgo);
+
+ loadInfo.mScriptContext = sgo->GetContext();
+ NS_ENSURE_TRUE(loadInfo.mScriptContext, NS_ERROR_FAILURE);
+
+ // If we're called from a window then we can dig out the principal and URI
+ // from the document.
+ document = loadInfo.mWindow->GetExtantDoc();
+ NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
+
+ loadInfo.mBaseURI = document->GetDocBaseURI();
+ loadInfo.mLoadGroup = document->GetDocumentLoadGroup();
+
+ // Use the document's NodePrincipal as our principal if we're not being
+ // called from chrome.
+ if (!loadInfo.mPrincipal) {
+ loadInfo.mPrincipal = document->NodePrincipal();
+ NS_ENSURE_TRUE(loadInfo.mPrincipal, NS_ERROR_FAILURE);
+
+ // We use the document's base domain to limit the number of workers
+ // each domain can create. For sandboxed documents, we use the domain
+ // of their first non-sandboxed document, walking up until we find
+ // one. If we can't find one, we fall back to using the GUID of the
+ // null principal as the base domain.
+ if (document->GetSandboxFlags() & SANDBOXED_ORIGIN) {
+ nsCOMPtr<nsIDocument> tmpDoc = document;
+ do {
+ tmpDoc = tmpDoc->GetParentDocument();
+ } while (tmpDoc && tmpDoc->GetSandboxFlags() & SANDBOXED_ORIGIN);
+
+ if (tmpDoc) {
+ // There was an unsandboxed ancestor, yay!
+ nsCOMPtr<nsIPrincipal> tmpPrincipal = tmpDoc->NodePrincipal();
+ rv = tmpPrincipal->GetBaseDomain(loadInfo.mDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // No unsandboxed ancestor, use our GUID.
+ rv = loadInfo.mPrincipal->GetBaseDomain(loadInfo.mDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } else {
+ // Document creating the worker is not sandboxed.
+ rv = loadInfo.mPrincipal->GetBaseDomain(loadInfo.mDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ nsCOMPtr<nsIPermissionManager> permMgr =
+ do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t perm;
+ rv = permMgr->TestPermissionFromPrincipal(loadInfo.mPrincipal, "systemXHR",
+ &perm);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ loadInfo.mXHRParamsAllowed = perm == nsIPermissionManager::ALLOW_ACTION;
+
+ loadInfo.mFromWindow = true;
+ loadInfo.mWindowID = globalWindow->WindowID();
+ nsContentUtils::StorageAccess access =
+ nsContentUtils::StorageAllowedForWindow(globalWindow);
+ loadInfo.mStorageAllowed = access > nsContentUtils::StorageAccess::eDeny;
+ loadInfo.mOriginAttributes = nsContentUtils::GetOriginAttributes(document);
+ } else {
+ // Not a window
+ MOZ_ASSERT(isChrome);
+
+ // We're being created outside of a window. Need to figure out the script
+ // that is creating us in order for us to use relative URIs later on.
+ JS::AutoFilename fileName;
+ if (JS::DescribeScriptedCaller(aCx, &fileName)) {
+ // In most cases, fileName is URI. In a few other cases
+ // (e.g. xpcshell), fileName is a file path. Ideally, we would
+ // prefer testing whether fileName parses as an URI and fallback
+ // to file path in case of error, but Windows file paths have
+ // the interesting property that they can be parsed as bogus
+ // URIs (e.g. C:/Windows/Tmp is interpreted as scheme "C",
+ // hostname "Windows", path "Tmp"), which defeats this algorithm.
+ // Therefore, we adopt the opposite convention.
+ nsCOMPtr<nsIFile> scriptFile =
+ do_CreateInstance("@mozilla.org/file/local;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = scriptFile->InitWithPath(NS_ConvertUTF8toUTF16(fileName.get()));
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_NewFileURI(getter_AddRefs(loadInfo.mBaseURI),
+ scriptFile);
+ }
+ if (NS_FAILED(rv)) {
+ // As expected, fileName is not a path, so proceed with
+ // a uri.
+ rv = NS_NewURI(getter_AddRefs(loadInfo.mBaseURI),
+ fileName.get());
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ loadInfo.mXHRParamsAllowed = true;
+ loadInfo.mFromWindow = false;
+ loadInfo.mWindowID = UINT64_MAX;
+ loadInfo.mStorageAllowed = true;
+ loadInfo.mOriginAttributes = PrincipalOriginAttributes();
+ }
+
+ MOZ_ASSERT(loadInfo.mPrincipal);
+ MOZ_ASSERT(isChrome || !loadInfo.mDomain.IsEmpty());
+
+ if (!loadInfo.mLoadGroup || aLoadGroupBehavior == OverrideLoadGroup) {
+ OverrideLoadInfoLoadGroup(loadInfo);
+ }
+ MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(loadInfo.mLoadGroup,
+ loadInfo.mPrincipal));
+
+ // Top level workers' main script use the document charset for the script
+ // uri encoding.
+ bool useDefaultEncoding = false;
+ rv = ChannelFromScriptURLMainThread(loadInfo.mPrincipal, loadInfo.mBaseURI,
+ document, loadInfo.mLoadGroup,
+ aScriptURL,
+ ContentPolicyType(aWorkerType),
+ useDefaultEncoding,
+ getter_AddRefs(loadInfo.mChannel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_GetFinalChannelURI(loadInfo.mChannel,
+ getter_AddRefs(loadInfo.mResolvedScriptURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ aLoadInfo->StealFrom(loadInfo);
+ return NS_OK;
+}
+
+// static
+void
+WorkerPrivate::OverrideLoadInfoLoadGroup(WorkerLoadInfo& aLoadInfo)
+{
+ MOZ_ASSERT(!aLoadInfo.mInterfaceRequestor);
+
+ aLoadInfo.mInterfaceRequestor =
+ new WorkerLoadInfo::InterfaceRequestor(aLoadInfo.mPrincipal,
+ aLoadInfo.mLoadGroup);
+ aLoadInfo.mInterfaceRequestor->MaybeAddTabChild(aLoadInfo.mLoadGroup);
+
+ // NOTE: this defaults the load context to:
+ // - private browsing = false
+ // - content = true
+ // - use remote tabs = false
+ nsCOMPtr<nsILoadGroup> loadGroup =
+ do_CreateInstance(NS_LOADGROUP_CONTRACTID);
+
+ nsresult rv =
+ loadGroup->SetNotificationCallbacks(aLoadInfo.mInterfaceRequestor);
+ MOZ_ALWAYS_SUCCEEDS(rv);
+
+ aLoadInfo.mLoadGroup = loadGroup.forget();
+}
+
+void
+WorkerPrivate::DoRunLoop(JSContext* aCx)
+{
+ AssertIsOnWorkerThread();
+ MOZ_ASSERT(mThread);
+
+ {
+ MutexAutoLock lock(mMutex);
+ mJSContext = aCx;
+
+ MOZ_ASSERT(mStatus == Pending);
+ mStatus = Running;
+ }
+
+ // Now that we've done that, we can go ahead and set up our AutoJSAPI. We
+ // can't before this point, because it can't find the right JSContext before
+ // then, since it gets it from our mJSContext.
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ MOZ_ASSERT(jsapi.cx() == aCx);
+
+ EnableMemoryReporter();
+
+ InitializeGCTimers();
+
+ Maybe<JSAutoCompartment> workerCompartment;
+
+ for (;;) {
+ Status currentStatus, previousStatus;
+ bool debuggerRunnablesPending = false;
+ bool normalRunnablesPending = false;
+
+ {
+ MutexAutoLock lock(mMutex);
+ previousStatus = mStatus;
+
+ while (mControlQueue.IsEmpty() &&
+ !(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty()) &&
+ !(normalRunnablesPending = NS_HasPendingEvents(mThread))) {
+ WaitForWorkerEvents();
+ }
+
+ auto result = ProcessAllControlRunnablesLocked();
+ if (result != ProcessAllControlRunnablesResult::Nothing) {
+ // NB: There's no JS on the stack here, so Abort vs MayContinue is
+ // irrelevant
+
+ // The state of the world may have changed, recheck it.
+ normalRunnablesPending = NS_HasPendingEvents(mThread);
+ // The debugger queue doesn't get cleared, so we can ignore that.
+ }
+
+ currentStatus = mStatus;
+ }
+
+ // if all holders are done then we can kill this thread.
+ if (currentStatus != Running && !HasActiveHolders()) {
+
+ // If we just changed status, we must schedule the current runnables.
+ if (previousStatus != Running && currentStatus != Killing) {
+ NotifyInternal(aCx, Killing);
+ MOZ_ASSERT(!JS_IsExceptionPending(aCx));
+
+#ifdef DEBUG
+ {
+ MutexAutoLock lock(mMutex);
+ currentStatus = mStatus;
+ }
+ MOZ_ASSERT(currentStatus == Killing);
+#else
+ currentStatus = Killing;
+#endif
+ }
+
+ // If we're supposed to die then we should exit the loop.
+ if (currentStatus == Killing) {
+ // Flush uncaught rejections immediately, without
+ // waiting for a next tick.
+ PromiseDebugging::FlushUncaughtRejections();
+
+ ShutdownGCTimers();
+
+ DisableMemoryReporter();
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ mStatus = Dead;
+ mJSContext = nullptr;
+ }
+
+ // After mStatus is set to Dead there can be no more
+ // WorkerControlRunnables so no need to lock here.
+ if (!mControlQueue.IsEmpty()) {
+ WorkerControlRunnable* runnable;
+ while (mControlQueue.Pop(runnable)) {
+ runnable->Cancel();
+ runnable->Release();
+ }
+ }
+
+ // Unroot the globals
+ mScope = nullptr;
+ mDebuggerScope = nullptr;
+
+ return;
+ }
+ }
+
+ if (debuggerRunnablesPending || normalRunnablesPending) {
+ // Start the periodic GC timer if it is not already running.
+ SetGCTimerMode(PeriodicTimer);
+ }
+
+ if (debuggerRunnablesPending) {
+ WorkerRunnable* runnable;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ mDebuggerQueue.Pop(runnable);
+ debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
+ }
+
+ MOZ_ASSERT(runnable);
+ static_cast<nsIRunnable*>(runnable)->Run();
+ runnable->Release();
+
+ // Flush the promise queue.
+ Promise::PerformWorkerDebuggerMicroTaskCheckpoint();
+
+ if (debuggerRunnablesPending) {
+ WorkerDebuggerGlobalScope* globalScope = DebuggerGlobalScope();
+ MOZ_ASSERT(globalScope);
+
+ // Now *might* be a good time to GC. Let the JS engine make the decision.
+ JSAutoCompartment ac(aCx, globalScope->GetGlobalJSObject());
+ JS_MaybeGC(aCx);
+ }
+ } else if (normalRunnablesPending) {
+ // Process a single runnable from the main queue.
+ NS_ProcessNextEvent(mThread, false);
+
+ normalRunnablesPending = NS_HasPendingEvents(mThread);
+ if (normalRunnablesPending && GlobalScope()) {
+ // Now *might* be a good time to GC. Let the JS engine make the decision.
+ JSAutoCompartment ac(aCx, GlobalScope()->GetGlobalJSObject());
+ JS_MaybeGC(aCx);
+ }
+ }
+
+ if (!debuggerRunnablesPending && !normalRunnablesPending) {
+ // Both the debugger event queue and the normal event queue has been
+ // exhausted, cancel the periodic GC timer and schedule the idle GC timer.
+ SetGCTimerMode(IdleTimer);
+ }
+
+ // If the worker thread is spamming the main thread faster than it can
+ // process the work, then pause the worker thread until the MT catches
+ // up.
+ if (mMainThreadThrottledEventQueue &&
+ mMainThreadThrottledEventQueue->Length() > 5000) {
+ mMainThreadThrottledEventQueue->AwaitIdle();
+ }
+ }
+
+ MOZ_CRASH("Shouldn't get here!");
+}
+
+void
+WorkerPrivate::OnProcessNextEvent()
+{
+ AssertIsOnWorkerThread();
+
+ uint32_t recursionDepth = CycleCollectedJSContext::Get()->RecursionDepth();
+ MOZ_ASSERT(recursionDepth);
+
+ // Normally we process control runnables in DoRunLoop or RunCurrentSyncLoop.
+ // However, it's possible that non-worker C++ could spin its own nested event
+ // loop, and in that case we must ensure that we continue to process control
+ // runnables here.
+ if (recursionDepth > 1 &&
+ mSyncLoopStack.Length() < recursionDepth - 1) {
+ Unused << ProcessAllControlRunnables();
+ // There's no running JS, and no state to revalidate, so we can ignore the
+ // return value.
+ }
+}
+
+void
+WorkerPrivate::AfterProcessNextEvent()
+{
+ AssertIsOnWorkerThread();
+ MOZ_ASSERT(CycleCollectedJSContext::Get()->RecursionDepth());
+}
+
+void
+WorkerPrivate::MaybeDispatchLoadFailedRunnable()
+{
+ AssertIsOnWorkerThread();
+
+ nsCOMPtr<nsIRunnable> runnable = StealLoadFailedAsyncRunnable();
+ if (!runnable) {
+ return;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(DispatchToMainThread(runnable.forget()));
+}
+
+nsIEventTarget*
+WorkerPrivate::MainThreadEventTarget()
+{
+ return mMainThreadEventTarget;
+}
+
+nsresult
+WorkerPrivate::DispatchToMainThread(nsIRunnable* aRunnable, uint32_t aFlags)
+{
+ nsCOMPtr<nsIRunnable> r = aRunnable;
+ return DispatchToMainThread(r.forget(), aFlags);
+}
+
+nsresult
+WorkerPrivate::DispatchToMainThread(already_AddRefed<nsIRunnable> aRunnable,
+ uint32_t aFlags)
+{
+ return mMainThreadEventTarget->Dispatch(Move(aRunnable), aFlags);
+}
+
+void
+WorkerPrivate::InitializeGCTimers()
+{
+ AssertIsOnWorkerThread();
+
+ // We need a timer for GC. The basic plan is to run a non-shrinking GC
+ // periodically (PERIODIC_GC_TIMER_DELAY_SEC) while the worker is running.
+ // Once the worker goes idle we set a short (IDLE_GC_TIMER_DELAY_SEC) timer to
+ // run a shrinking GC. If the worker receives more messages then the short
+ // timer is canceled and the periodic timer resumes.
+ mGCTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ MOZ_ASSERT(mGCTimer);
+
+ RefPtr<GarbageCollectRunnable> runnable =
+ new GarbageCollectRunnable(this, false, false);
+ mPeriodicGCTimerTarget = new TimerThreadEventTarget(this, runnable);
+
+ runnable = new GarbageCollectRunnable(this, true, false);
+ mIdleGCTimerTarget = new TimerThreadEventTarget(this, runnable);
+
+ mPeriodicGCTimerRunning = false;
+ mIdleGCTimerRunning = false;
+}
+
+void
+WorkerPrivate::SetGCTimerMode(GCTimerMode aMode)
+{
+ AssertIsOnWorkerThread();
+ MOZ_ASSERT(mGCTimer);
+ MOZ_ASSERT(mPeriodicGCTimerTarget);
+ MOZ_ASSERT(mIdleGCTimerTarget);
+
+ if ((aMode == PeriodicTimer && mPeriodicGCTimerRunning) ||
+ (aMode == IdleTimer && mIdleGCTimerRunning)) {
+ return;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(mGCTimer->Cancel());
+
+ mPeriodicGCTimerRunning = false;
+ mIdleGCTimerRunning = false;
+ LOG(WorkerLog(),
+ ("Worker %p canceled GC timer because %s\n", this,
+ aMode == PeriodicTimer ?
+ "periodic" :
+ aMode == IdleTimer ? "idle" : "none"));
+
+ if (aMode == NoTimer) {
+ return;
+ }
+
+ MOZ_ASSERT(aMode == PeriodicTimer || aMode == IdleTimer);
+
+ nsIEventTarget* target;
+ uint32_t delay;
+ int16_t type;
+
+ if (aMode == PeriodicTimer) {
+ target = mPeriodicGCTimerTarget;
+ delay = PERIODIC_GC_TIMER_DELAY_SEC * 1000;
+ type = nsITimer::TYPE_REPEATING_SLACK;
+ }
+ else {
+ target = mIdleGCTimerTarget;
+ delay = IDLE_GC_TIMER_DELAY_SEC * 1000;
+ type = nsITimer::TYPE_ONE_SHOT;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(mGCTimer->SetTarget(target));
+ MOZ_ALWAYS_SUCCEEDS(
+ mGCTimer->InitWithNamedFuncCallback(DummyCallback, nullptr, delay, type,
+ "dom::workers::DummyCallback(2)"));
+
+ if (aMode == PeriodicTimer) {
+ LOG(WorkerLog(), ("Worker %p scheduled periodic GC timer\n", this));
+ mPeriodicGCTimerRunning = true;
+ }
+ else {
+ LOG(WorkerLog(), ("Worker %p scheduled idle GC timer\n", this));
+ mIdleGCTimerRunning = true;
+ }
+}
+
+void
+WorkerPrivate::ShutdownGCTimers()
+{
+ AssertIsOnWorkerThread();
+
+ MOZ_ASSERT(mGCTimer);
+
+ // Always make sure the timer is canceled.
+ MOZ_ALWAYS_SUCCEEDS(mGCTimer->Cancel());
+
+ LOG(WorkerLog(), ("Worker %p killed the GC timer\n", this));
+
+ mGCTimer = nullptr;
+ mPeriodicGCTimerTarget = nullptr;
+ mIdleGCTimerTarget = nullptr;
+ mPeriodicGCTimerRunning = false;
+ mIdleGCTimerRunning = false;
+}
+
+bool
+WorkerPrivate::InterruptCallback(JSContext* aCx)
+{
+ AssertIsOnWorkerThread();
+
+ MOZ_ASSERT(!JS_IsExceptionPending(aCx));
+
+ bool mayContinue = true;
+ bool scheduledIdleGC = false;
+
+ for (;;) {
+ // Run all control events now.
+ auto result = ProcessAllControlRunnables();
+ if (result == ProcessAllControlRunnablesResult::Abort) {
+ mayContinue = false;
+ }
+
+ bool mayFreeze = mFrozen;
+ if (mayFreeze) {
+ MutexAutoLock lock(mMutex);
+ mayFreeze = mStatus <= Running;
+ }
+
+ if (!mayContinue || !mayFreeze) {
+ break;
+ }
+
+ // Cancel the periodic GC timer here before freezing. The idle GC timer
+ // will clean everything up once it runs.
+ if (!scheduledIdleGC) {
+ SetGCTimerMode(IdleTimer);
+ scheduledIdleGC = true;
+ }
+
+ while ((mayContinue = MayContinueRunning())) {
+ MutexAutoLock lock(mMutex);
+ if (!mControlQueue.IsEmpty()) {
+ break;
+ }
+
+ WaitForWorkerEvents(PR_MillisecondsToInterval(UINT32_MAX));
+ }
+ }
+
+ if (!mayContinue) {
+ // We want only uncatchable exceptions here.
+ NS_ASSERTION(!JS_IsExceptionPending(aCx),
+ "Should not have an exception set here!");
+ return false;
+ }
+
+ // Make sure the periodic timer gets turned back on here.
+ SetGCTimerMode(PeriodicTimer);
+
+ return true;
+}
+
+nsresult
+WorkerPrivate::IsOnCurrentThread(bool* aIsOnCurrentThread)
+{
+ // May be called on any thread!
+
+ MOZ_ASSERT(aIsOnCurrentThread);
+ MOZ_ASSERT(mPRThread);
+
+ *aIsOnCurrentThread = PR_GetCurrentThread() == mPRThread;
+ return NS_OK;
+}
+
+void
+WorkerPrivate::ScheduleDeletion(WorkerRanOrNot aRanOrNot)
+{
+ AssertIsOnWorkerThread();
+ MOZ_ASSERT(mChildWorkers.IsEmpty());
+ MOZ_ASSERT(mSyncLoopStack.IsEmpty());
+ MOZ_ASSERT(!mPendingEventQueueClearing);
+
+ ClearMainEventQueue(aRanOrNot);
+#ifdef DEBUG
+ if (WorkerRan == aRanOrNot) {
+ nsIThread* currentThread = NS_GetCurrentThread();
+ MOZ_ASSERT(currentThread);
+ MOZ_ASSERT(!NS_HasPendingEvents(currentThread));
+ }
+#endif
+
+ if (WorkerPrivate* parent = GetParent()) {
+ RefPtr<WorkerFinishedRunnable> runnable =
+ new WorkerFinishedRunnable(parent, this);
+ if (!runnable->Dispatch()) {
+ NS_WARNING("Failed to dispatch runnable!");
+ }
+ }
+ else {
+ RefPtr<TopLevelWorkerFinishedRunnable> runnable =
+ new TopLevelWorkerFinishedRunnable(this);
+ if (NS_FAILED(DispatchToMainThread(runnable.forget()))) {
+ NS_WARNING("Failed to dispatch runnable!");
+ }
+ }
+}
+
+bool
+WorkerPrivate::CollectRuntimeStats(JS::RuntimeStats* aRtStats,
+ bool aAnonymize)
+{
+ AssertIsOnWorkerThread();
+ NS_ASSERTION(aRtStats, "Null RuntimeStats!");
+ NS_ASSERTION(mJSContext, "This must never be null!");
+
+ return JS::CollectRuntimeStats(mJSContext, aRtStats, nullptr, aAnonymize);
+}
+
+void
+WorkerPrivate::EnableMemoryReporter()
+{
+ AssertIsOnWorkerThread();
+ MOZ_ASSERT(!mMemoryReporter);
+
+ // No need to lock here since the main thread can't race until we've
+ // successfully registered the reporter.
+ mMemoryReporter = new MemoryReporter(this);
+
+ if (NS_FAILED(RegisterWeakAsyncMemoryReporter(mMemoryReporter))) {
+ NS_WARNING("Failed to register memory reporter!");
+ // No need to lock here since a failed registration means our memory
+ // reporter can't start running. Just clean up.
+ mMemoryReporter = nullptr;
+ }
+}
+
+void
+WorkerPrivate::DisableMemoryReporter()
+{
+ AssertIsOnWorkerThread();
+
+ RefPtr<MemoryReporter> memoryReporter;
+ {
+ // Mutex protectes MemoryReporter::mWorkerPrivate which is cleared by
+ // MemoryReporter::Disable() below.
+ MutexAutoLock lock(mMutex);
+
+ // There is nothing to do here if the memory reporter was never successfully
+ // registered.
+ if (!mMemoryReporter) {
+ return;
+ }
+
+ // We don't need this set any longer. Swap it out so that we can unregister
+ // below.
+ mMemoryReporter.swap(memoryReporter);
+
+ // Next disable the memory reporter so that the main thread stops trying to
+ // signal us.
+ memoryReporter->Disable();
+ }
+
+ // Finally unregister the memory reporter.
+ if (NS_FAILED(UnregisterWeakMemoryReporter(memoryReporter))) {
+ NS_WARNING("Failed to unregister memory reporter!");
+ }
+}
+
+void
+WorkerPrivate::WaitForWorkerEvents(PRIntervalTime aInterval)
+{
+ AssertIsOnWorkerThread();
+ mMutex.AssertCurrentThreadOwns();
+
+ // Wait for a worker event.
+ mCondVar.Wait(aInterval);
+}
+
+WorkerPrivate::ProcessAllControlRunnablesResult
+WorkerPrivate::ProcessAllControlRunnablesLocked()
+{
+ AssertIsOnWorkerThread();
+ mMutex.AssertCurrentThreadOwns();
+
+ auto result = ProcessAllControlRunnablesResult::Nothing;
+
+ for (;;) {
+ WorkerControlRunnable* event;
+ if (!mControlQueue.Pop(event)) {
+ break;
+ }
+
+ MutexAutoUnlock unlock(mMutex);
+
+ MOZ_ASSERT(event);
+ if (NS_FAILED(static_cast<nsIRunnable*>(event)->Run())) {
+ result = ProcessAllControlRunnablesResult::Abort;
+ }
+
+ if (result == ProcessAllControlRunnablesResult::Nothing) {
+ // We ran at least one thing.
+ result = ProcessAllControlRunnablesResult::MayContinue;
+ }
+ event->Release();
+ }
+
+ return result;
+}
+
+void
+WorkerPrivate::ClearMainEventQueue(WorkerRanOrNot aRanOrNot)
+{
+ AssertIsOnWorkerThread();
+
+ MOZ_ASSERT(mSyncLoopStack.IsEmpty());
+ MOZ_ASSERT(!mCancelAllPendingRunnables);
+ mCancelAllPendingRunnables = true;
+
+ if (WorkerNeverRan == aRanOrNot) {
+ for (uint32_t count = mPreStartRunnables.Length(), index = 0;
+ index < count;
+ index++) {
+ RefPtr<WorkerRunnable> runnable = mPreStartRunnables[index].forget();
+ static_cast<nsIRunnable*>(runnable.get())->Run();
+ }
+ } else {
+ nsIThread* currentThread = NS_GetCurrentThread();
+ MOZ_ASSERT(currentThread);
+
+ NS_ProcessPendingEvents(currentThread);
+ }
+
+ MOZ_ASSERT(mCancelAllPendingRunnables);
+ mCancelAllPendingRunnables = false;
+}
+
+void
+WorkerPrivate::ClearDebuggerEventQueue()
+{
+ while (!mDebuggerQueue.IsEmpty()) {
+ WorkerRunnable* runnable;
+ mDebuggerQueue.Pop(runnable);
+ // It should be ok to simply release the runnable, without running it.
+ runnable->Release();
+ }
+}
+
+bool
+WorkerPrivate::FreezeInternal()
+{
+ AssertIsOnWorkerThread();
+
+ NS_ASSERTION(!mFrozen, "Already frozen!");
+
+ mFrozen = true;
+
+ for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
+ mChildWorkers[index]->Freeze(nullptr);
+ }
+
+ return true;
+}
+
+bool
+WorkerPrivate::ThawInternal()
+{
+ AssertIsOnWorkerThread();
+
+ NS_ASSERTION(mFrozen, "Not yet frozen!");
+
+ for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
+ mChildWorkers[index]->Thaw(nullptr);
+ }
+
+ mFrozen = false;
+ return true;
+}
+
+void
+WorkerPrivate::TraverseTimeouts(nsCycleCollectionTraversalCallback& cb)
+{
+ for (uint32_t i = 0; i < mTimeouts.Length(); ++i) {
+ TimeoutInfo* tmp = mTimeouts[i];
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHandler)
+ }
+}
+
+void
+WorkerPrivate::UnlinkTimeouts()
+{
+ mTimeouts.Clear();
+}
+
+bool
+WorkerPrivate::ModifyBusyCountFromWorker(bool aIncrease)
+{
+ AssertIsOnWorkerThread();
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ // If we're in shutdown then the busy count is no longer being considered so
+ // just return now.
+ if (mStatus >= Killing) {
+ return true;
+ }
+ }
+
+ RefPtr<ModifyBusyCountRunnable> runnable =
+ new ModifyBusyCountRunnable(this, aIncrease);
+ return runnable->Dispatch();
+}
+
+bool
+WorkerPrivate::AddChildWorker(ParentType* aChildWorker)
+{
+ AssertIsOnWorkerThread();
+
+#ifdef DEBUG
+ {
+ Status currentStatus;
+ {
+ MutexAutoLock lock(mMutex);
+ currentStatus = mStatus;
+ }
+
+ MOZ_ASSERT(currentStatus == Running);
+ }
+#endif
+
+ NS_ASSERTION(!mChildWorkers.Contains(aChildWorker),
+ "Already know about this one!");
+ mChildWorkers.AppendElement(aChildWorker);
+
+ return mChildWorkers.Length() == 1 ?
+ ModifyBusyCountFromWorker(true) :
+ true;
+}
+
+void
+WorkerPrivate::RemoveChildWorker(ParentType* aChildWorker)
+{
+ AssertIsOnWorkerThread();
+
+ NS_ASSERTION(mChildWorkers.Contains(aChildWorker),
+ "Didn't know about this one!");
+ mChildWorkers.RemoveElement(aChildWorker);
+
+ if (mChildWorkers.IsEmpty() && !ModifyBusyCountFromWorker(false)) {
+ NS_WARNING("Failed to modify busy count!");
+ }
+}
+
+bool
+WorkerPrivate::AddHolder(WorkerHolder* aHolder, Status aFailStatus)
+{
+ AssertIsOnWorkerThread();
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mStatus >= aFailStatus) {
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(!mHolders.Contains(aHolder), "Already know about this one!");
+
+ if (mHolders.IsEmpty() && !ModifyBusyCountFromWorker(true)) {
+ return false;
+ }
+
+ mHolders.AppendElement(aHolder);
+ return true;
+}
+
+void
+WorkerPrivate::RemoveHolder(WorkerHolder* aHolder)
+{
+ AssertIsOnWorkerThread();
+
+ MOZ_ASSERT(mHolders.Contains(aHolder), "Didn't know about this one!");
+ mHolders.RemoveElement(aHolder);
+
+ if (mHolders.IsEmpty() && !ModifyBusyCountFromWorker(false)) {
+ NS_WARNING("Failed to modify busy count!");
+ }
+}
+
+void
+WorkerPrivate::NotifyHolders(JSContext* aCx, Status aStatus)
+{
+ AssertIsOnWorkerThread();
+ MOZ_ASSERT(!JS_IsExceptionPending(aCx));
+
+ NS_ASSERTION(aStatus > Running, "Bad status!");
+
+ if (aStatus >= Closing) {
+ CancelAllTimeouts();
+ }
+
+ nsTObserverArray<WorkerHolder*>::ForwardIterator iter(mHolders);
+ while (iter.HasMore()) {
+ WorkerHolder* holder = iter.GetNext();
+ if (!holder->Notify(aStatus)) {
+ NS_WARNING("Failed to notify holder!");
+ }
+ MOZ_ASSERT(!JS_IsExceptionPending(aCx));
+ }
+
+ AutoTArray<ParentType*, 10> children;
+ children.AppendElements(mChildWorkers);
+
+ for (uint32_t index = 0; index < children.Length(); index++) {
+ if (!children[index]->Notify(aStatus)) {
+ NS_WARNING("Failed to notify child worker!");
+ }
+ }
+}
+
+void
+WorkerPrivate::CancelAllTimeouts()
+{
+ AssertIsOnWorkerThread();
+
+ LOG(TimeoutsLog(), ("Worker %p CancelAllTimeouts.\n", this));
+
+ if (mTimerRunning) {
+ NS_ASSERTION(mTimer && mTimerRunnable, "Huh?!");
+ NS_ASSERTION(!mTimeouts.IsEmpty(), "Huh?!");
+
+ if (NS_FAILED(mTimer->Cancel())) {
+ NS_WARNING("Failed to cancel timer!");
+ }
+
+ for (uint32_t index = 0; index < mTimeouts.Length(); index++) {
+ mTimeouts[index]->mCanceled = true;
+ }
+
+ // If mRunningExpiredTimeouts, then the fact that they are all canceled now
+ // means that the currently executing RunExpiredTimeouts will deal with
+ // them. Otherwise, we need to clean them up ourselves.
+ if (!mRunningExpiredTimeouts) {
+ mTimeouts.Clear();
+ ModifyBusyCountFromWorker(false);
+ }
+
+ // Set mTimerRunning false even if mRunningExpiredTimeouts is true, so that
+ // if we get reentered under this same RunExpiredTimeouts call we don't
+ // assert above that !mTimeouts().IsEmpty(), because that's clearly false
+ // now.
+ mTimerRunning = false;
+ }
+#ifdef DEBUG
+ else if (!mRunningExpiredTimeouts) {
+ NS_ASSERTION(mTimeouts.IsEmpty(), "Huh?!");
+ }
+#endif
+
+ mTimer = nullptr;
+ mTimerRunnable = nullptr;
+}
+
+already_AddRefed<nsIEventTarget>
+WorkerPrivate::CreateNewSyncLoop()
+{
+ AssertIsOnWorkerThread();
+
+ nsCOMPtr<nsIThreadInternal> thread = do_QueryInterface(NS_GetCurrentThread());
+ MOZ_ASSERT(thread);
+
+ nsCOMPtr<nsIEventTarget> realEventTarget;
+ MOZ_ALWAYS_SUCCEEDS(thread->PushEventQueue(getter_AddRefs(realEventTarget)));
+
+ RefPtr<EventTarget> workerEventTarget =
+ new EventTarget(this, realEventTarget);
+
+ {
+ // Modifications must be protected by mMutex in DEBUG builds, see comment
+ // about mSyncLoopStack in WorkerPrivate.h.
+#ifdef DEBUG
+ MutexAutoLock lock(mMutex);
+#endif
+
+ mSyncLoopStack.AppendElement(new SyncLoopInfo(workerEventTarget));
+ }
+
+ return workerEventTarget.forget();
+}
+
+bool
+WorkerPrivate::RunCurrentSyncLoop()
+{
+ AssertIsOnWorkerThread();
+
+ JSContext* cx = GetJSContext();
+ MOZ_ASSERT(cx);
+
+ // This should not change between now and the time we finish running this sync
+ // loop.
+ uint32_t currentLoopIndex = mSyncLoopStack.Length() - 1;
+
+ SyncLoopInfo* loopInfo = mSyncLoopStack[currentLoopIndex];
+
+ MOZ_ASSERT(loopInfo);
+ MOZ_ASSERT(!loopInfo->mHasRun);
+ MOZ_ASSERT(!loopInfo->mCompleted);
+
+#ifdef DEBUG
+ loopInfo->mHasRun = true;
+#endif
+
+ while (!loopInfo->mCompleted) {
+ bool normalRunnablesPending = false;
+
+ // Don't block with the periodic GC timer running.
+ if (!NS_HasPendingEvents(mThread)) {
+ SetGCTimerMode(IdleTimer);
+ }
+
+ // Wait for something to do.
+ {
+ MutexAutoLock lock(mMutex);
+
+ for (;;) {
+ while (mControlQueue.IsEmpty() &&
+ !normalRunnablesPending &&
+ !(normalRunnablesPending = NS_HasPendingEvents(mThread))) {
+ WaitForWorkerEvents();
+ }
+
+ auto result = ProcessAllControlRunnablesLocked();
+ if (result != ProcessAllControlRunnablesResult::Nothing) {
+ // XXXkhuey how should we handle Abort here? See Bug 1003730.
+
+ // The state of the world may have changed. Recheck it.
+ normalRunnablesPending = NS_HasPendingEvents(mThread);
+
+ // NB: If we processed a NotifyRunnable, we might have run
+ // non-control runnables, one of which may have shut down the
+ // sync loop.
+ if (loopInfo->mCompleted) {
+ break;
+ }
+ }
+
+ // If we *didn't* run any control runnables, this should be unchanged.
+ MOZ_ASSERT(!loopInfo->mCompleted);
+
+ if (normalRunnablesPending) {
+ break;
+ }
+ }
+ }
+
+ if (normalRunnablesPending) {
+ // Make sure the periodic timer is running before we continue.
+ SetGCTimerMode(PeriodicTimer);
+
+ MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(mThread, false));
+
+ // Now *might* be a good time to GC. Let the JS engine make the decision.
+ if (JS::CurrentGlobalOrNull(cx)) {
+ JS_MaybeGC(cx);
+ }
+ }
+ }
+
+ // Make sure that the stack didn't change underneath us.
+ MOZ_ASSERT(mSyncLoopStack[currentLoopIndex] == loopInfo);
+
+ return DestroySyncLoop(currentLoopIndex);
+}
+
+bool
+WorkerPrivate::DestroySyncLoop(uint32_t aLoopIndex, nsIThreadInternal* aThread)
+{
+ MOZ_ASSERT(!mSyncLoopStack.IsEmpty());
+ MOZ_ASSERT(mSyncLoopStack.Length() - 1 == aLoopIndex);
+
+ if (!aThread) {
+ aThread = mThread;
+ }
+
+ // We're about to delete the loop, stash its event target and result.
+ SyncLoopInfo* loopInfo = mSyncLoopStack[aLoopIndex];
+ nsIEventTarget* nestedEventTarget =
+ loopInfo->mEventTarget->GetWeakNestedEventTarget();
+ MOZ_ASSERT(nestedEventTarget);
+
+ bool result = loopInfo->mResult;
+
+ {
+ // Modifications must be protected by mMutex in DEBUG builds, see comment
+ // about mSyncLoopStack in WorkerPrivate.h.
+#ifdef DEBUG
+ MutexAutoLock lock(mMutex);
+#endif
+
+ // This will delete |loopInfo|!
+ mSyncLoopStack.RemoveElementAt(aLoopIndex);
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(aThread->PopEventQueue(nestedEventTarget));
+
+ if (mSyncLoopStack.IsEmpty() && mPendingEventQueueClearing) {
+ mPendingEventQueueClearing = false;
+ ClearMainEventQueue(WorkerRan);
+ }
+
+ return result;
+}
+
+void
+WorkerPrivate::StopSyncLoop(nsIEventTarget* aSyncLoopTarget, bool aResult)
+{
+ AssertIsOnWorkerThread();
+ AssertValidSyncLoop(aSyncLoopTarget);
+
+ MOZ_ASSERT(!mSyncLoopStack.IsEmpty());
+
+ for (uint32_t index = mSyncLoopStack.Length(); index > 0; index--) {
+ nsAutoPtr<SyncLoopInfo>& loopInfo = mSyncLoopStack[index - 1];
+ MOZ_ASSERT(loopInfo);
+ MOZ_ASSERT(loopInfo->mEventTarget);
+
+ if (loopInfo->mEventTarget == aSyncLoopTarget) {
+ // Can't assert |loop->mHasRun| here because dispatch failures can cause
+ // us to bail out early.
+ MOZ_ASSERT(!loopInfo->mCompleted);
+
+ loopInfo->mResult = aResult;
+ loopInfo->mCompleted = true;
+
+ loopInfo->mEventTarget->Disable();
+
+ return;
+ }
+
+ MOZ_ASSERT(!SameCOMIdentity(loopInfo->mEventTarget, aSyncLoopTarget));
+ }
+
+ MOZ_CRASH("Unknown sync loop!");
+}
+
+#ifdef DEBUG
+void
+WorkerPrivate::AssertValidSyncLoop(nsIEventTarget* aSyncLoopTarget)
+{
+ MOZ_ASSERT(aSyncLoopTarget);
+
+ EventTarget* workerTarget;
+ nsresult rv =
+ aSyncLoopTarget->QueryInterface(kDEBUGWorkerEventTargetIID,
+ reinterpret_cast<void**>(&workerTarget));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_ASSERT(workerTarget);
+
+ bool valid = false;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ for (uint32_t index = 0; index < mSyncLoopStack.Length(); index++) {
+ nsAutoPtr<SyncLoopInfo>& loopInfo = mSyncLoopStack[index];
+ MOZ_ASSERT(loopInfo);
+ MOZ_ASSERT(loopInfo->mEventTarget);
+
+ if (loopInfo->mEventTarget == aSyncLoopTarget) {
+ valid = true;
+ break;
+ }
+
+ MOZ_ASSERT(!SameCOMIdentity(loopInfo->mEventTarget, aSyncLoopTarget));
+ }
+ }
+
+ MOZ_ASSERT(valid);
+}
+#endif
+
+void
+WorkerPrivate::PostMessageToParentInternal(
+ JSContext* aCx,
+ JS::Handle<JS::Value> aMessage,
+ const Optional<Sequence<JS::Value>>& aTransferable,
+ ErrorResult& aRv)
+{
+ AssertIsOnWorkerThread();
+
+ JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedValue());
+ if (aTransferable.WasPassed()) {
+ const Sequence<JS::Value>& realTransferable = aTransferable.Value();
+
+ // The input sequence only comes from the generated bindings code, which
+ // ensures it is rooted.
+ JS::HandleValueArray elements =
+ JS::HandleValueArray::fromMarkedLocation(realTransferable.Length(),
+ realTransferable.Elements());
+
+ JSObject* array = JS_NewArrayObject(aCx, elements);
+ if (!array) {
+ aRv = NS_ERROR_OUT_OF_MEMORY;
+ return;
+ }
+ transferable.setObject(*array);
+ }
+
+ RefPtr<MessageEventRunnable> runnable =
+ new MessageEventRunnable(this,
+ WorkerRunnable::ParentThreadUnchangedBusyCount);
+
+ UniquePtr<AbstractTimelineMarker> start;
+ UniquePtr<AbstractTimelineMarker> end;
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+ bool isTimelineRecording = timelines && !timelines->IsEmpty();
+
+ if (isTimelineRecording) {
+ start = MakeUnique<WorkerTimelineMarker>(NS_IsMainThread()
+ ? ProfileTimelineWorkerOperationType::SerializeDataOnMainThread
+ : ProfileTimelineWorkerOperationType::SerializeDataOffMainThread,
+ MarkerTracingType::START);
+ }
+
+ runnable->Write(aCx, aMessage, transferable, JS::CloneDataPolicy(), aRv);
+
+ if (isTimelineRecording) {
+ end = MakeUnique<WorkerTimelineMarker>(NS_IsMainThread()
+ ? ProfileTimelineWorkerOperationType::SerializeDataOnMainThread
+ : ProfileTimelineWorkerOperationType::SerializeDataOffMainThread,
+ MarkerTracingType::END);
+ timelines->AddMarkerForAllObservedDocShells(start);
+ timelines->AddMarkerForAllObservedDocShells(end);
+ }
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ if (!runnable->Dispatch()) {
+ aRv = NS_ERROR_FAILURE;
+ }
+}
+
+void
+WorkerPrivate::EnterDebuggerEventLoop()
+{
+ AssertIsOnWorkerThread();
+
+ JSContext* cx = GetJSContext();
+ MOZ_ASSERT(cx);
+
+ uint32_t currentEventLoopLevel = ++mDebuggerEventLoopLevel;
+
+ while (currentEventLoopLevel <= mDebuggerEventLoopLevel) {
+ bool debuggerRunnablesPending = false;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
+ }
+
+ // Don't block with the periodic GC timer running.
+ if (!debuggerRunnablesPending) {
+ SetGCTimerMode(IdleTimer);
+ }
+
+ // Wait for something to do
+ {
+ MutexAutoLock lock(mMutex);
+
+ while (mControlQueue.IsEmpty() &&
+ !(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty())) {
+ WaitForWorkerEvents();
+ }
+
+ ProcessAllControlRunnablesLocked();
+
+ // XXXkhuey should we abort JS on the stack here if we got Abort above?
+ }
+
+ if (debuggerRunnablesPending) {
+ // Start the periodic GC timer if it is not already running.
+ SetGCTimerMode(PeriodicTimer);
+
+ WorkerRunnable* runnable;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ mDebuggerQueue.Pop(runnable);
+ }
+
+ MOZ_ASSERT(runnable);
+ static_cast<nsIRunnable*>(runnable)->Run();
+ runnable->Release();
+
+ // Flush the promise queue.
+ Promise::PerformWorkerDebuggerMicroTaskCheckpoint();
+
+ // Now *might* be a good time to GC. Let the JS engine make the decision.
+ if (JS::CurrentGlobalOrNull(cx)) {
+ JS_MaybeGC(cx);
+ }
+ }
+ }
+}
+
+void
+WorkerPrivate::LeaveDebuggerEventLoop()
+{
+ AssertIsOnWorkerThread();
+
+ MutexAutoLock lock(mMutex);
+
+ if (mDebuggerEventLoopLevel > 0) {
+ --mDebuggerEventLoopLevel;
+ }
+}
+
+void
+WorkerPrivate::PostMessageToDebugger(const nsAString& aMessage)
+{
+ mDebugger->PostMessageToDebugger(aMessage);
+}
+
+void
+WorkerPrivate::SetDebuggerImmediate(dom::Function& aHandler, ErrorResult& aRv)
+{
+ AssertIsOnWorkerThread();
+
+ RefPtr<DebuggerImmediateRunnable> runnable =
+ new DebuggerImmediateRunnable(this, aHandler);
+ if (!runnable->Dispatch()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ }
+}
+
+void
+WorkerPrivate::ReportErrorToDebugger(const nsAString& aFilename,
+ uint32_t aLineno,
+ const nsAString& aMessage)
+{
+ mDebugger->ReportErrorToDebugger(aFilename, aLineno, aMessage);
+}
+
+bool
+WorkerPrivate::NotifyInternal(JSContext* aCx, Status aStatus)
+{
+ AssertIsOnWorkerThread();
+
+ NS_ASSERTION(aStatus > Running && aStatus < Dead, "Bad status!");
+
+ RefPtr<EventTarget> eventTarget;
+
+ // Save the old status and set the new status.
+ Status previousStatus;
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mStatus >= aStatus) {
+ MOZ_ASSERT(!mEventTarget);
+ return true;
+ }
+
+ previousStatus = mStatus;
+ mStatus = aStatus;
+
+ mEventTarget.swap(eventTarget);
+ }
+
+ // Now that mStatus > Running, no-one can create a new WorkerEventTarget or
+ // WorkerCrossThreadDispatcher if we don't already have one.
+ if (eventTarget) {
+ // Since we'll no longer process events, make sure we no longer allow anyone
+ // to post them. We have to do this without mMutex held, since our mutex
+ // must be acquired *after* the WorkerEventTarget's mutex when they're both
+ // held.
+ eventTarget->Disable();
+ eventTarget = nullptr;
+ }
+
+ if (mCrossThreadDispatcher) {
+ // Since we'll no longer process events, make sure we no longer allow
+ // anyone to post them. We have to do this without mMutex held, since our
+ // mutex must be acquired *after* mCrossThreadDispatcher's mutex when
+ // they're both held.
+ mCrossThreadDispatcher->Forget();
+ mCrossThreadDispatcher = nullptr;
+ }
+
+ MOZ_ASSERT(previousStatus != Pending);
+
+ // Let all our holders know the new status.
+ NotifyHolders(aCx, aStatus);
+ MOZ_ASSERT(!JS_IsExceptionPending(aCx));
+
+ // If this is the first time our status has changed then we need to clear the
+ // main event queue.
+ if (previousStatus == Running) {
+ // NB: If we're in a sync loop, we can't clear the queue immediately,
+ // because this is the wrong queue. So we have to defer it until later.
+ if (!mSyncLoopStack.IsEmpty()) {
+ mPendingEventQueueClearing = true;
+ } else {
+ ClearMainEventQueue(WorkerRan);
+ }
+ }
+
+ // If the worker script never ran, or failed to compile, we don't need to do
+ // anything else.
+ if (!GlobalScope()) {
+ return true;
+ }
+
+ if (aStatus == Closing) {
+ // Notify parent to stop sending us messages and balance our busy count.
+ RefPtr<CloseRunnable> runnable = new CloseRunnable(this);
+ if (!runnable->Dispatch()) {
+ return false;
+ }
+
+ // Don't abort the script.
+ return true;
+ }
+
+ MOZ_ASSERT(aStatus == Terminating ||
+ aStatus == Canceling ||
+ aStatus == Killing);
+
+ // Always abort the script.
+ return false;
+}
+
+void
+WorkerPrivate::ReportError(JSContext* aCx, JS::ConstUTF8CharsZ aToStringResult,
+ JSErrorReport* aReport)
+{
+ AssertIsOnWorkerThread();
+
+ if (!MayContinueRunning() || mErrorHandlerRecursionCount == 2) {
+ return;
+ }
+
+ NS_ASSERTION(mErrorHandlerRecursionCount == 0 ||
+ mErrorHandlerRecursionCount == 1,
+ "Bad recursion logic!");
+
+ JS::Rooted<JS::Value> exn(aCx);
+ if (!JS_GetPendingException(aCx, &exn)) {
+ // Probably shouldn't actually happen? But let's go ahead and just use null
+ // for lack of anything better.
+ exn.setNull();
+ }
+ JS_ClearPendingException(aCx);
+
+ nsString message, filename, line;
+ uint32_t lineNumber, columnNumber, flags, errorNumber;
+ JSExnType exnType = JSEXN_ERR;
+ bool mutedError = aReport && aReport->isMuted;
+
+ if (aReport) {
+ // We want the same behavior here as xpc::ErrorReport::init here.
+ xpc::ErrorReport::ErrorReportToMessageString(aReport, message);
+
+ filename = NS_ConvertUTF8toUTF16(aReport->filename);
+ line.Assign(aReport->linebuf(), aReport->linebufLength());
+ lineNumber = aReport->lineno;
+ columnNumber = aReport->tokenOffset();
+ flags = aReport->flags;
+ errorNumber = aReport->errorNumber;
+ MOZ_ASSERT(aReport->exnType >= JSEXN_FIRST && aReport->exnType < JSEXN_LIMIT);
+ exnType = JSExnType(aReport->exnType);
+ }
+ else {
+ lineNumber = columnNumber = errorNumber = 0;
+ flags = nsIScriptError::errorFlag | nsIScriptError::exceptionFlag;
+ }
+
+ if (message.IsEmpty() && aToStringResult) {
+ nsDependentCString toStringResult(aToStringResult.c_str());
+ if (!AppendUTF8toUTF16(toStringResult, message, mozilla::fallible)) {
+ // Try again, with only a 1 KB string. Do this infallibly this time.
+ // If the user doesn't have 1 KB to spare we're done anyways.
+ uint32_t index = std::min(uint32_t(1024), toStringResult.Length());
+
+ // Drop the last code point that may be cropped.
+ index = RewindToPriorUTF8Codepoint(toStringResult.BeginReading(), index);
+
+ nsDependentCString truncatedToStringResult(aToStringResult.c_str(),
+ index);
+ AppendUTF8toUTF16(truncatedToStringResult, message);
+ }
+ }
+
+ mErrorHandlerRecursionCount++;
+
+ // Don't want to run the scope's error handler if this is a recursive error or
+ // if we ran out of memory.
+ bool fireAtScope = mErrorHandlerRecursionCount == 1 &&
+ errorNumber != JSMSG_OUT_OF_MEMORY &&
+ JS::CurrentGlobalOrNull(aCx);
+
+ ReportErrorRunnable::ReportError(aCx, this, fireAtScope, nullptr, message,
+ filename, line, lineNumber,
+ columnNumber, flags, errorNumber, exnType,
+ mutedError, 0, exn);
+
+ mErrorHandlerRecursionCount--;
+}
+
+// static
+void
+WorkerPrivate::ReportErrorToConsole(const char* aMessage)
+{
+ WorkerPrivate* wp = nullptr;
+ if (!NS_IsMainThread()) {
+ wp = GetCurrentThreadWorkerPrivate();
+ }
+
+ ReportErrorToConsoleRunnable::Report(wp, aMessage);
+}
+
+int32_t
+WorkerPrivate::SetTimeout(JSContext* aCx,
+ nsIScriptTimeoutHandler* aHandler,
+ int32_t aTimeout, bool aIsInterval,
+ ErrorResult& aRv)
+{
+ AssertIsOnWorkerThread();
+ MOZ_ASSERT(aHandler);
+
+ const int32_t timerId = mNextTimeoutId++;
+
+ Status currentStatus;
+ {
+ MutexAutoLock lock(mMutex);
+ currentStatus = mStatus;
+ }
+
+ // If the worker is trying to call setTimeout/setInterval and the parent
+ // thread has initiated the close process then just silently fail.
+ if (currentStatus >= Closing) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return 0;
+ }
+
+ nsAutoPtr<TimeoutInfo> newInfo(new TimeoutInfo());
+ newInfo->mIsInterval = aIsInterval;
+ newInfo->mId = timerId;
+
+ if (MOZ_UNLIKELY(timerId == INT32_MAX)) {
+ NS_WARNING("Timeout ids overflowed!");
+ mNextTimeoutId = 1;
+ }
+
+ newInfo->mHandler = aHandler;
+
+ // See if any of the optional arguments were passed.
+ aTimeout = std::max(0, aTimeout);
+ newInfo->mInterval = TimeDuration::FromMilliseconds(aTimeout);
+
+ newInfo->mTargetTime = TimeStamp::Now() + newInfo->mInterval;
+
+ nsAutoPtr<TimeoutInfo>* insertedInfo =
+ mTimeouts.InsertElementSorted(newInfo.forget(), GetAutoPtrComparator(mTimeouts));
+
+ LOG(TimeoutsLog(), ("Worker %p has new timeout: delay=%d interval=%s\n",
+ this, aTimeout, aIsInterval ? "yes" : "no"));
+
+ // If the timeout we just made is set to fire next then we need to update the
+ // timer, unless we're currently running timeouts.
+ if (insertedInfo == mTimeouts.Elements() && !mRunningExpiredTimeouts) {
+ nsresult rv;
+
+ if (!mTimer) {
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return 0;
+ }
+
+ mTimerRunnable = new TimerRunnable(this);
+ }
+
+ if (!mTimerRunning) {
+ if (!ModifyBusyCountFromWorker(true)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return 0;
+ }
+ mTimerRunning = true;
+ }
+
+ if (!RescheduleTimeoutTimer(aCx)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return 0;
+ }
+ }
+
+ return timerId;
+}
+
+void
+WorkerPrivate::ClearTimeout(int32_t aId)
+{
+ AssertIsOnWorkerThread();
+
+ if (!mTimeouts.IsEmpty()) {
+ NS_ASSERTION(mTimerRunning, "Huh?!");
+
+ for (uint32_t index = 0; index < mTimeouts.Length(); index++) {
+ nsAutoPtr<TimeoutInfo>& info = mTimeouts[index];
+ if (info->mId == aId) {
+ info->mCanceled = true;
+ break;
+ }
+ }
+ }
+}
+
+bool
+WorkerPrivate::RunExpiredTimeouts(JSContext* aCx)
+{
+ AssertIsOnWorkerThread();
+
+ // We may be called recursively (e.g. close() inside a timeout) or we could
+ // have been canceled while this event was pending, bail out if there is
+ // nothing to do.
+ if (mRunningExpiredTimeouts || !mTimerRunning) {
+ return true;
+ }
+
+ NS_ASSERTION(mTimer && mTimerRunnable, "Must have a timer!");
+ NS_ASSERTION(!mTimeouts.IsEmpty(), "Should have some work to do!");
+
+ bool retval = true;
+
+ AutoPtrComparator<TimeoutInfo> comparator = GetAutoPtrComparator(mTimeouts);
+ JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
+
+ // We want to make sure to run *something*, even if the timer fired a little
+ // early. Fudge the value of now to at least include the first timeout.
+ const TimeStamp actual_now = TimeStamp::Now();
+ const TimeStamp now = std::max(actual_now, mTimeouts[0]->mTargetTime);
+
+ if (now != actual_now) {
+ LOG(TimeoutsLog(), ("Worker %p fudged timeout by %f ms.\n", this,
+ (now - actual_now).ToMilliseconds()));
+ }
+
+ AutoTArray<TimeoutInfo*, 10> expiredTimeouts;
+ for (uint32_t index = 0; index < mTimeouts.Length(); index++) {
+ nsAutoPtr<TimeoutInfo>& info = mTimeouts[index];
+ if (info->mTargetTime > now) {
+ break;
+ }
+ expiredTimeouts.AppendElement(info);
+ }
+
+ // Guard against recursion.
+ mRunningExpiredTimeouts = true;
+
+ // Run expired timeouts.
+ for (uint32_t index = 0; index < expiredTimeouts.Length(); index++) {
+ TimeoutInfo*& info = expiredTimeouts[index];
+
+ if (info->mCanceled) {
+ continue;
+ }
+
+ LOG(TimeoutsLog(), ("Worker %p executing timeout with original delay %f ms.\n",
+ this, info->mInterval.ToMilliseconds()));
+
+ // Always check JS_IsExceptionPending if something fails, and if
+ // JS_IsExceptionPending returns false (i.e. uncatchable exception) then
+ // break out of the loop.
+ const char *reason;
+ if (info->mIsInterval) {
+ reason = "setInterval handler";
+ } else {
+ reason = "setTimeout handler";
+ }
+
+ RefPtr<Function> callback = info->mHandler->GetCallback();
+ if (!callback) {
+ // scope for the AutoEntryScript, so it comes off the stack before we do
+ // Promise::PerformMicroTaskCheckpoint.
+ AutoEntryScript aes(global, reason, false);
+
+ // Evaluate the timeout expression.
+ const nsAString& script = info->mHandler->GetHandlerText();
+
+ const char* filename = nullptr;
+ uint32_t lineNo = 0, dummyColumn = 0;
+ info->mHandler->GetLocation(&filename, &lineNo, &dummyColumn);
+
+ JS::CompileOptions options(aes.cx());
+ options.setFileAndLine(filename, lineNo).setNoScriptRval(true);
+
+ JS::Rooted<JS::Value> unused(aes.cx());
+
+ if (!JS::Evaluate(aes.cx(), options, script.BeginReading(),
+ script.Length(), &unused) &&
+ !JS_IsExceptionPending(aCx)) {
+ retval = false;
+ break;
+ }
+ } else {
+ ErrorResult rv;
+ JS::Rooted<JS::Value> ignoredVal(aCx);
+ callback->Call(GlobalScope(), info->mHandler->GetArgs(), &ignoredVal, rv,
+ reason);
+ if (rv.IsUncatchableException()) {
+ rv.SuppressException();
+ retval = false;
+ break;
+ }
+
+ rv.SuppressException();
+ }
+
+ // Since we might be processing more timeouts, go ahead and flush
+ // the promise queue now before we do that.
+ Promise::PerformWorkerMicroTaskCheckpoint();
+
+ NS_ASSERTION(mRunningExpiredTimeouts, "Someone changed this!");
+ }
+
+ // No longer possible to be called recursively.
+ mRunningExpiredTimeouts = false;
+
+ // Now remove canceled and expired timeouts from the main list.
+ // NB: The timeouts present in expiredTimeouts must have the same order
+ // with respect to each other in mTimeouts. That is, mTimeouts is just
+ // expiredTimeouts with extra elements inserted. There may be unexpired
+ // timeouts that have been inserted between the expired timeouts if the
+ // timeout event handler called setTimeout/setInterval.
+ for (uint32_t index = 0, expiredTimeoutIndex = 0,
+ expiredTimeoutLength = expiredTimeouts.Length();
+ index < mTimeouts.Length(); ) {
+ nsAutoPtr<TimeoutInfo>& info = mTimeouts[index];
+ if ((expiredTimeoutIndex < expiredTimeoutLength &&
+ info == expiredTimeouts[expiredTimeoutIndex] &&
+ ++expiredTimeoutIndex) ||
+ info->mCanceled) {
+ if (info->mIsInterval && !info->mCanceled) {
+ // Reschedule intervals.
+ info->mTargetTime = info->mTargetTime + info->mInterval;
+ // Don't resort the list here, we'll do that at the end.
+ ++index;
+ }
+ else {
+ mTimeouts.RemoveElement(info);
+ }
+ }
+ else {
+ // If info did not match the current entry in expiredTimeouts, it
+ // shouldn't be there at all.
+ NS_ASSERTION(!expiredTimeouts.Contains(info),
+ "Our timeouts are out of order!");
+ ++index;
+ }
+ }
+
+ mTimeouts.Sort(comparator);
+
+ // Either signal the parent that we're no longer using timeouts or reschedule
+ // the timer.
+ if (mTimeouts.IsEmpty()) {
+ if (!ModifyBusyCountFromWorker(false)) {
+ retval = false;
+ }
+ mTimerRunning = false;
+ }
+ else if (retval && !RescheduleTimeoutTimer(aCx)) {
+ retval = false;
+ }
+
+ return retval;
+}
+
+bool
+WorkerPrivate::RescheduleTimeoutTimer(JSContext* aCx)
+{
+ AssertIsOnWorkerThread();
+ MOZ_ASSERT(!mRunningExpiredTimeouts);
+ NS_ASSERTION(!mTimeouts.IsEmpty(), "Should have some timeouts!");
+ NS_ASSERTION(mTimer && mTimerRunnable, "Should have a timer!");
+
+ // NB: This is important! The timer may have already fired, e.g. if a timeout
+ // callback itself calls setTimeout for a short duration and then takes longer
+ // than that to finish executing. If that has happened, it's very important
+ // that we don't execute the event that is now pending in our event queue, or
+ // our code in RunExpiredTimeouts to "fudge" the timeout value will unleash an
+ // early timeout when we execute the event we're about to queue.
+ mTimer->Cancel();
+
+ double delta =
+ (mTimeouts[0]->mTargetTime - TimeStamp::Now()).ToMilliseconds();
+ uint32_t delay = delta > 0 ? std::min(delta, double(UINT32_MAX)) : 0;
+
+ LOG(TimeoutsLog(), ("Worker %p scheduled timer for %d ms, %d pending timeouts\n",
+ this, delay, mTimeouts.Length()));
+
+ nsresult rv = mTimer->InitWithCallback(mTimerRunnable, delay, nsITimer::TYPE_ONE_SHOT);
+ if (NS_FAILED(rv)) {
+ JS_ReportErrorASCII(aCx, "Failed to start timer!");
+ return false;
+ }
+
+ return true;
+}
+
+void
+WorkerPrivate::UpdateContextOptionsInternal(
+ JSContext* aCx,
+ const JS::ContextOptions& aContextOptions)
+{
+ AssertIsOnWorkerThread();
+
+ JS::ContextOptionsRef(aCx) = aContextOptions;
+
+ for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
+ mChildWorkers[index]->UpdateContextOptions(aContextOptions);
+ }
+}
+
+void
+WorkerPrivate::UpdateLanguagesInternal(const nsTArray<nsString>& aLanguages)
+{
+ WorkerGlobalScope* globalScope = GlobalScope();
+ if (globalScope) {
+ RefPtr<WorkerNavigator> nav = globalScope->GetExistingNavigator();
+ if (nav) {
+ nav->SetLanguages(aLanguages);
+ }
+ }
+
+ for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
+ mChildWorkers[index]->UpdateLanguages(aLanguages);
+ }
+}
+
+void
+WorkerPrivate::UpdatePreferenceInternal(WorkerPreference aPref, bool aValue)
+{
+ AssertIsOnWorkerThread();
+ MOZ_ASSERT(aPref >= 0 && aPref < WORKERPREF_COUNT);
+
+ mPreferences[aPref] = aValue;
+
+ for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
+ mChildWorkers[index]->UpdatePreference(aPref, aValue);
+ }
+}
+
+void
+WorkerPrivate::UpdateJSWorkerMemoryParameterInternal(JSContext* aCx,
+ JSGCParamKey aKey,
+ uint32_t aValue)
+{
+ AssertIsOnWorkerThread();
+
+ // XXX aValue might be 0 here (telling us to unset a previous value for child
+ // workers). Calling JS_SetGCParameter with a value of 0 isn't actually
+ // supported though. We really need some way to revert to a default value
+ // here.
+ if (aValue) {
+ JS_SetGCParameter(aCx, aKey, aValue);
+ }
+
+ for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
+ mChildWorkers[index]->UpdateJSWorkerMemoryParameter(aKey, aValue);
+ }
+}
+
+#ifdef JS_GC_ZEAL
+void
+WorkerPrivate::UpdateGCZealInternal(JSContext* aCx, uint8_t aGCZeal,
+ uint32_t aFrequency)
+{
+ AssertIsOnWorkerThread();
+
+ JS_SetGCZeal(aCx, aGCZeal, aFrequency);
+
+ for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
+ mChildWorkers[index]->UpdateGCZeal(aGCZeal, aFrequency);
+ }
+}
+#endif
+
+void
+WorkerPrivate::GarbageCollectInternal(JSContext* aCx, bool aShrinking,
+ bool aCollectChildren)
+{
+ AssertIsOnWorkerThread();
+
+ if (!GlobalScope()) {
+ // We haven't compiled anything yet. Just bail out.
+ return;
+ }
+
+ if (aShrinking || aCollectChildren) {
+ JS::PrepareForFullGC(aCx);
+
+ if (aShrinking) {
+ JS::GCForReason(aCx, GC_SHRINK, JS::gcreason::DOM_WORKER);
+
+ if (!aCollectChildren) {
+ LOG(WorkerLog(), ("Worker %p collected idle garbage\n", this));
+ }
+ }
+ else {
+ JS::GCForReason(aCx, GC_NORMAL, JS::gcreason::DOM_WORKER);
+ LOG(WorkerLog(), ("Worker %p collected garbage\n", this));
+ }
+ }
+ else {
+ JS_MaybeGC(aCx);
+ LOG(WorkerLog(), ("Worker %p collected periodic garbage\n", this));
+ }
+
+ if (aCollectChildren) {
+ for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
+ mChildWorkers[index]->GarbageCollect(aShrinking);
+ }
+ }
+}
+
+void
+WorkerPrivate::CycleCollectInternal(bool aCollectChildren)
+{
+ AssertIsOnWorkerThread();
+
+ nsCycleCollector_collect(nullptr);
+
+ if (aCollectChildren) {
+ for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
+ mChildWorkers[index]->CycleCollect(/* dummy = */ false);
+ }
+ }
+}
+
+void
+WorkerPrivate::MemoryPressureInternal()
+{
+ AssertIsOnWorkerThread();
+
+ RefPtr<Console> console = mScope ? mScope->GetConsoleIfExists() : nullptr;
+ if (console) {
+ console->ClearStorage();
+ }
+
+ console = mDebuggerScope ? mDebuggerScope->GetConsoleIfExists() : nullptr;
+ if (console) {
+ console->ClearStorage();
+ }
+
+ for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
+ mChildWorkers[index]->MemoryPressure(false);
+ }
+}
+
+void
+WorkerPrivate::SetThread(WorkerThread* aThread)
+{
+ if (aThread) {
+#ifdef DEBUG
+ {
+ bool isOnCurrentThread;
+ MOZ_ASSERT(NS_SUCCEEDED(aThread->IsOnCurrentThread(&isOnCurrentThread)));
+ MOZ_ASSERT(isOnCurrentThread);
+ }
+#endif
+
+ MOZ_ASSERT(!mPRThread);
+ mPRThread = PRThreadFromThread(aThread);
+ MOZ_ASSERT(mPRThread);
+ }
+ else {
+ MOZ_ASSERT(mPRThread);
+ }
+
+ const WorkerThreadFriendKey friendKey;
+
+ RefPtr<WorkerThread> doomedThread;
+
+ { // Scope so that |doomedThread| is released without holding the lock.
+ MutexAutoLock lock(mMutex);
+
+ if (aThread) {
+ MOZ_ASSERT(!mThread);
+ MOZ_ASSERT(mStatus == Pending);
+
+ mThread = aThread;
+ mThread->SetWorker(friendKey, this);
+
+ if (!mPreStartRunnables.IsEmpty()) {
+ for (uint32_t index = 0; index < mPreStartRunnables.Length(); index++) {
+ MOZ_ALWAYS_SUCCEEDS(
+ mThread->DispatchAnyThread(friendKey, mPreStartRunnables[index].forget()));
+ }
+ mPreStartRunnables.Clear();
+ }
+ }
+ else {
+ MOZ_ASSERT(mThread);
+
+ mThread->SetWorker(friendKey, nullptr);
+
+ mThread.swap(doomedThread);
+ }
+ }
+}
+
+WorkerCrossThreadDispatcher*
+WorkerPrivate::GetCrossThreadDispatcher()
+{
+ MutexAutoLock lock(mMutex);
+
+ if (!mCrossThreadDispatcher && mStatus <= Running) {
+ mCrossThreadDispatcher = new WorkerCrossThreadDispatcher(this);
+ }
+
+ return mCrossThreadDispatcher;
+}
+
+void
+WorkerPrivate::BeginCTypesCall()
+{
+ AssertIsOnWorkerThread();
+
+ // Don't try to GC while we're blocked in a ctypes call.
+ SetGCTimerMode(NoTimer);
+}
+
+void
+WorkerPrivate::EndCTypesCall()
+{
+ AssertIsOnWorkerThread();
+
+ // Make sure the periodic timer is running before we start running JS again.
+ SetGCTimerMode(PeriodicTimer);
+}
+
+bool
+WorkerPrivate::ConnectMessagePort(JSContext* aCx,
+ MessagePortIdentifier& aIdentifier)
+{
+ AssertIsOnWorkerThread();
+
+ WorkerGlobalScope* globalScope = GlobalScope();
+
+ JS::Rooted<JSObject*> jsGlobal(aCx, globalScope->GetWrapper());
+ MOZ_ASSERT(jsGlobal);
+
+ // This MessagePortIdentifier is used to create a new port, still connected
+ // with the other one, but in the worker thread.
+ ErrorResult rv;
+ RefPtr<MessagePort> port = MessagePort::Create(globalScope, aIdentifier, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return false;
+ }
+
+ GlobalObject globalObject(aCx, jsGlobal);
+ if (globalObject.Failed()) {
+ return false;
+ }
+
+ RootedDictionary<MessageEventInit> init(aCx);
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mSource.SetValue().SetAsMessagePort() = port;
+ if (!init.mPorts.AppendElement(port.forget(), fallible)) {
+ return false;
+ }
+
+ RefPtr<MessageEvent> event =
+ MessageEvent::Constructor(globalObject,
+ NS_LITERAL_STRING("connect"), init, rv);
+
+ event->SetTrusted(true);
+
+ nsCOMPtr<nsIDOMEvent> domEvent = do_QueryObject(event);
+
+ nsEventStatus dummy = nsEventStatus_eIgnore;
+ globalScope->DispatchDOMEvent(nullptr, domEvent, nullptr, &dummy);
+
+ return true;
+}
+
+WorkerGlobalScope*
+WorkerPrivate::GetOrCreateGlobalScope(JSContext* aCx)
+{
+ AssertIsOnWorkerThread();
+
+ if (!mScope) {
+ RefPtr<WorkerGlobalScope> globalScope;
+ if (IsSharedWorker()) {
+ globalScope = new SharedWorkerGlobalScope(this, WorkerName());
+ } else if (IsServiceWorker()) {
+ globalScope = new ServiceWorkerGlobalScope(this, WorkerName());
+ } else {
+ globalScope = new DedicatedWorkerGlobalScope(this);
+ }
+
+ JS::Rooted<JSObject*> global(aCx);
+ NS_ENSURE_TRUE(globalScope->WrapGlobalObject(aCx, &global), nullptr);
+
+ JSAutoCompartment ac(aCx, global);
+
+ // RegisterBindings() can spin a nested event loop so we have to set mScope
+ // before calling it, and we have to make sure to unset mScope if it fails.
+ mScope = Move(globalScope);
+
+ if (!RegisterBindings(aCx, global)) {
+ mScope = nullptr;
+ return nullptr;
+ }
+
+ JS_FireOnNewGlobalObject(aCx, global);
+ }
+
+ return mScope;
+}
+
+WorkerDebuggerGlobalScope*
+WorkerPrivate::CreateDebuggerGlobalScope(JSContext* aCx)
+{
+ AssertIsOnWorkerThread();
+
+ MOZ_ASSERT(!mDebuggerScope);
+
+ RefPtr<WorkerDebuggerGlobalScope> globalScope =
+ new WorkerDebuggerGlobalScope(this);
+
+ JS::Rooted<JSObject*> global(aCx);
+ NS_ENSURE_TRUE(globalScope->WrapGlobalObject(aCx, &global), nullptr);
+
+ JSAutoCompartment ac(aCx, global);
+
+ // RegisterDebuggerBindings() can spin a nested event loop so we have to set
+ // mDebuggerScope before calling it, and we have to make sure to unset
+ // mDebuggerScope if it fails.
+ mDebuggerScope = Move(globalScope);
+
+ if (!RegisterDebuggerBindings(aCx, global)) {
+ mDebuggerScope = nullptr;
+ return nullptr;
+ }
+
+ JS_FireOnNewGlobalObject(aCx, global);
+
+ return mDebuggerScope;
+}
+
+#ifdef DEBUG
+
+void
+WorkerPrivate::AssertIsOnWorkerThread() const
+{
+ // This is much more complicated than it needs to be but we can't use mThread
+ // because it must be protected by mMutex and sometimes this method is called
+ // when mMutex is already locked. This method should always work.
+ MOZ_ASSERT(mPRThread,
+ "AssertIsOnWorkerThread() called before a thread was assigned!");
+
+ nsCOMPtr<nsIThread> thread;
+ nsresult rv =
+ nsThreadManager::get().GetThreadFromPRThread(mPRThread,
+ getter_AddRefs(thread));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_ASSERT(thread);
+
+ bool current;
+ rv = thread->IsOnCurrentThread(&current);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_ASSERT(current, "Wrong thread!");
+}
+
+#endif // DEBUG
+
+NS_IMPL_ISUPPORTS_INHERITED0(ExternalRunnableWrapper, WorkerRunnable)
+
+template <class Derived>
+NS_IMPL_ADDREF(WorkerPrivateParent<Derived>::EventTarget)
+
+template <class Derived>
+NS_IMPL_RELEASE(WorkerPrivateParent<Derived>::EventTarget)
+
+template <class Derived>
+NS_INTERFACE_MAP_BEGIN(WorkerPrivateParent<Derived>::EventTarget)
+ NS_INTERFACE_MAP_ENTRY(nsIEventTarget)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+#ifdef DEBUG
+ // kDEBUGWorkerEventTargetIID is special in that it does not AddRef its
+ // result.
+ if (aIID.Equals(kDEBUGWorkerEventTargetIID)) {
+ *aInstancePtr = this;
+ return NS_OK;
+ }
+ else
+#endif
+NS_INTERFACE_MAP_END
+
+template <class Derived>
+NS_IMETHODIMP
+WorkerPrivateParent<Derived>::
+EventTarget::DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags)
+{
+ nsCOMPtr<nsIRunnable> event(aRunnable);
+ return Dispatch(event.forget(), aFlags);
+}
+
+template <class Derived>
+NS_IMETHODIMP
+WorkerPrivateParent<Derived>::
+EventTarget::Dispatch(already_AddRefed<nsIRunnable> aRunnable, uint32_t aFlags)
+{
+ // May be called on any thread!
+ nsCOMPtr<nsIRunnable> event(aRunnable);
+
+ // Workers only support asynchronous dispatch for now.
+ if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ RefPtr<WorkerRunnable> workerRunnable;
+
+ MutexAutoLock lock(mMutex);
+
+ if (!mWorkerPrivate) {
+ NS_WARNING("A runnable was posted to a worker that is already shutting "
+ "down!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (event) {
+ workerRunnable = mWorkerPrivate->MaybeWrapAsWorkerRunnable(event.forget());
+ }
+
+ nsresult rv =
+ mWorkerPrivate->DispatchPrivate(workerRunnable.forget(), mNestedEventTarget);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+template <class Derived>
+NS_IMETHODIMP
+WorkerPrivateParent<Derived>::
+EventTarget::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+template <class Derived>
+NS_IMETHODIMP
+WorkerPrivateParent<Derived>::
+EventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread)
+{
+ // May be called on any thread!
+
+ MOZ_ASSERT(aIsOnCurrentThread);
+
+ MutexAutoLock lock(mMutex);
+
+ if (!mWorkerPrivate) {
+ NS_WARNING("A worker's event target was used after the worker has !");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv = mWorkerPrivate->IsOnCurrentThread(aIsOnCurrentThread);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+BEGIN_WORKERS_NAMESPACE
+
+WorkerCrossThreadDispatcher*
+GetWorkerCrossThreadDispatcher(JSContext* aCx, const JS::Value& aWorker)
+{
+ if (!aWorker.isObject()) {
+ return nullptr;
+ }
+
+ JS::Rooted<JSObject*> obj(aCx, &aWorker.toObject());
+ WorkerPrivate* w = nullptr;
+ UNWRAP_OBJECT(Worker, &obj, w);
+ MOZ_ASSERT(w);
+ return w->GetCrossThreadDispatcher();
+}
+
+// Force instantiation.
+template class WorkerPrivateParent<WorkerPrivate>;
+
+END_WORKERS_NAMESPACE