/* -*- 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; } }; 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!"); } } } 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); } } 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(¤t); 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