/* -*- 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 "RuntimeService.h" #include "nsContentSecurityUtils.h" #include "nsIContentSecurityPolicy.h" #include "mozilla/dom/Document.h" #include "nsIObserverService.h" #include "nsIScriptContext.h" #include "nsIStreamTransportService.h" #include "nsISupportsPriority.h" #include "nsITimer.h" #include "nsIURI.h" #include "nsIXULRuntime.h" #include "nsPIDOMWindow.h" #include #include "mozilla/ipc/BackgroundChild.h" #include "GeckoProfiler.h" #include "js/experimental/CTypes.h" // JS::CTypesActivityType, JS::SetCTypesActivityCallback #include "jsfriendapi.h" #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* #include "js/ContextOptions.h" #include "js/Initialization.h" #include "js/LocaleSensitive.h" #include "js/WasmFeatures.h" #include "mozilla/ArrayUtils.h" #include "mozilla/Atomics.h" #include "mozilla/Attributes.h" #include "mozilla/CycleCollectedJSContext.h" #include "mozilla/CycleCollectedJSRuntime.h" #include "mozilla/Telemetry.h" #include "mozilla/TimeStamp.h" #include "mozilla/dom/AtomList.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/ErrorEventBinding.h" #include "mozilla/dom/EventTargetBinding.h" #include "mozilla/dom/FetchUtil.h" #include "mozilla/dom/MessageChannel.h" #include "mozilla/dom/MessageEventBinding.h" #include "mozilla/dom/PerformanceService.h" #include "mozilla/dom/RemoteWorkerChild.h" #include "mozilla/dom/WorkerBinding.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/ShadowRealmGlobalScope.h" #include "mozilla/dom/IndexedDatabaseManager.h" #include "mozilla/DebugOnly.h" #include "mozilla/Preferences.h" #include "mozilla/ScopeExit.h" #include "mozilla/dom/Navigator.h" #include "mozilla/Monitor.h" #include "nsContentUtils.h" #include "nsCycleCollector.h" #include "nsDOMJSUtils.h" #include "nsISupportsImpl.h" #include "nsLayoutStatics.h" #include "nsNetUtil.h" #include "nsServiceManagerUtils.h" #include "nsThreadUtils.h" #include "nsXPCOM.h" #include "nsXPCOMPrivate.h" #include "OSFileConstants.h" #include "xpcpublic.h" #include "XPCSelfHostedShmem.h" #if defined(XP_MACOSX) # include "nsMacUtilsImpl.h" #endif #include "WorkerDebuggerManager.h" #include "WorkerError.h" #include "WorkerLoadInfo.h" #include "WorkerRunnable.h" #include "WorkerScope.h" #include "WorkerThread.h" #include "prsystem.h" #ifdef DEBUG # include "nsICookieJarSettings.h" #endif #define WORKERS_SHUTDOWN_TOPIC "web-workers-shutdown" static mozilla::LazyLogModule gWorkerShutdownDumpLog("WorkerShutdownDump"); #ifdef SHUTDOWN_LOG # undef SHUTDOWN_LOG #endif #define SHUTDOWN_LOG(msg) MOZ_LOG(gWorkerShutdownDumpLog, LogLevel::Debug, msg); namespace mozilla { using namespace ipc; namespace dom { using namespace workerinternals; namespace workerinternals { // The size of the worker runtime heaps in bytes. May be changed via pref. #define WORKER_DEFAULT_RUNTIME_HEAPSIZE 32 * 1024 * 1024 // The size of the worker JS allocation threshold in MB. May be changed via // pref. #define WORKER_DEFAULT_ALLOCATION_THRESHOLD 30 // Half the size of the actual C stack, to be safe. #define WORKER_CONTEXT_NATIVE_STACK_LIMIT 128 * sizeof(size_t) * 1024 // The maximum number of threads to use for workers, overridable via pref. #define MAX_WORKERS_PER_DOMAIN 512 static_assert(MAX_WORKERS_PER_DOMAIN >= 1, "We should allow at least one worker per domain."); #define PREF_WORKERS_PREFIX "dom.workers." #define PREF_WORKERS_MAX_PER_DOMAIN PREF_WORKERS_PREFIX "maxPerDomain" #define GC_REQUEST_OBSERVER_TOPIC "child-gc-request" #define CC_REQUEST_OBSERVER_TOPIC "child-cc-request" #define MEMORY_PRESSURE_OBSERVER_TOPIC "memory-pressure" #define LOW_MEMORY_DATA "low-memory" #define LOW_MEMORY_ONGOING_DATA "low-memory-ongoing" #define MEMORY_PRESSURE_STOP_OBSERVER_TOPIC "memory-pressure-stop" // Prefixes for observing preference changes. #define PREF_JS_OPTIONS_PREFIX "javascript.options." #define PREF_MEM_OPTIONS_PREFIX "mem." #define PREF_GCZEAL "gczeal" static NS_DEFINE_CID(kStreamTransportServiceCID, NS_STREAMTRANSPORTSERVICE_CID); namespace { const uint32_t kNoIndex = uint32_t(-1); uint32_t gMaxWorkersPerDomain = MAX_WORKERS_PER_DOMAIN; // Does not hold an owning reference. Atomic gRuntimeService(nullptr); // Only true during the call to Init. bool gRuntimeServiceDuringInit = false; class LiteralRebindingCString : public nsDependentCString { public: template void RebindLiteral(const char (&aStr)[N]) { Rebind(aStr, N - 1); } }; template struct PrefTraits; template <> struct PrefTraits { using PrefValueType = bool; static inline PrefValueType Get(const char* aPref) { AssertIsOnMainThread(); return Preferences::GetBool(aPref); } static inline bool Exists(const char* aPref) { AssertIsOnMainThread(); return Preferences::GetType(aPref) == nsIPrefBranch::PREF_BOOL; } }; template <> struct PrefTraits { using PrefValueType = int32_t; static inline PrefValueType Get(const char* aPref) { AssertIsOnMainThread(); return Preferences::GetInt(aPref); } static inline bool Exists(const char* aPref) { AssertIsOnMainThread(); return Preferences::GetType(aPref) == nsIPrefBranch::PREF_INT; } }; template T GetPref(const char* aFullPref, const T aDefault, bool* aPresent = nullptr) { AssertIsOnMainThread(); using PrefHelper = PrefTraits; T result; bool present = true; if (PrefHelper::Exists(aFullPref)) { result = PrefHelper::Get(aFullPref); } else { result = aDefault; present = false; } if (aPresent) { *aPresent = present; } return result; } void LoadContextOptions(const char* aPrefName, void* /* aClosure */) { AssertIsOnMainThread(); RuntimeService* rts = RuntimeService::GetService(); if (!rts) { // May be shutting down, just bail. return; } const nsDependentCString prefName(aPrefName); // Several other pref branches will get included here so bail out if there is // another callback that will handle this change. if (StringBeginsWith( prefName, nsLiteralCString(PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX))) { return; } #ifdef JS_GC_ZEAL if (prefName.EqualsLiteral(PREF_JS_OPTIONS_PREFIX PREF_GCZEAL)) { return; } #endif JS::ContextOptions contextOptions; xpc::SetPrefableContextOptions(contextOptions); nsCOMPtr xr = do_GetService("@mozilla.org/xre/runtime;1"); if (xr) { bool safeMode = false; xr->GetInSafeMode(&safeMode); if (safeMode) { contextOptions.disableOptionsForSafeMode(); } } RuntimeService::SetDefaultContextOptions(contextOptions); if (rts) { rts->UpdateAllWorkerContextOptions(); } } #ifdef JS_GC_ZEAL void LoadGCZealOptions(const char* /* aPrefName */, void* /* aClosure */) { AssertIsOnMainThread(); RuntimeService* rts = RuntimeService::GetService(); if (!rts) { // May be shutting down, just bail. return; } int32_t gczeal = GetPref(PREF_JS_OPTIONS_PREFIX PREF_GCZEAL, -1); if (gczeal < 0) { gczeal = 0; } int32_t frequency = GetPref(PREF_JS_OPTIONS_PREFIX PREF_GCZEAL ".frequency", -1); if (frequency < 0) { frequency = JS_DEFAULT_ZEAL_FREQ; } RuntimeService::SetDefaultGCZeal(uint8_t(gczeal), uint32_t(frequency)); if (rts) { rts->UpdateAllWorkerGCZeal(); } } #endif void UpdateCommonJSGCMemoryOption(RuntimeService* aRuntimeService, const char* aPrefName, JSGCParamKey aKey) { AssertIsOnMainThread(); NS_ASSERTION(aPrefName, "Null pref name!"); int32_t prefValue = GetPref(aPrefName, -1); Maybe value = (prefValue < 0 || prefValue >= 10000) ? Nothing() : Some(uint32_t(prefValue)); RuntimeService::SetDefaultJSGCSettings(aKey, value); if (aRuntimeService) { aRuntimeService->UpdateAllWorkerMemoryParameter(aKey, value); } } void UpdateOtherJSGCMemoryOption(RuntimeService* aRuntimeService, JSGCParamKey aKey, Maybe aValue) { AssertIsOnMainThread(); RuntimeService::SetDefaultJSGCSettings(aKey, aValue); if (aRuntimeService) { aRuntimeService->UpdateAllWorkerMemoryParameter(aKey, aValue); } } void LoadJSGCMemoryOptions(const char* aPrefName, void* /* aClosure */) { AssertIsOnMainThread(); RuntimeService* rts = RuntimeService::GetService(); if (!rts) { // May be shutting down, just bail. return; } constexpr auto memPrefix = nsLiteralCString{PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX}; const nsDependentCString fullPrefName(aPrefName); // Pull out the string that actually distinguishes the parameter we need to // change. nsDependentCSubstring memPrefName; if (StringBeginsWith(fullPrefName, memPrefix)) { memPrefName.Rebind(fullPrefName, memPrefix.Length()); } else { NS_ERROR("Unknown pref name!"); return; } struct WorkerGCPref { nsLiteralCString memName; const char* fullName; JSGCParamKey key; }; #define PREF(suffix_, key_) \ { \ nsLiteralCString(PREF_MEM_OPTIONS_PREFIX suffix_), \ PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX suffix_, key_ \ } constexpr WorkerGCPref kWorkerPrefs[] = { PREF("max", JSGC_MAX_BYTES), PREF("gc_high_frequency_time_limit_ms", JSGC_HIGH_FREQUENCY_TIME_LIMIT), PREF("gc_low_frequency_heap_growth", JSGC_LOW_FREQUENCY_HEAP_GROWTH), PREF("gc_high_frequency_large_heap_growth", JSGC_HIGH_FREQUENCY_LARGE_HEAP_GROWTH), PREF("gc_high_frequency_small_heap_growth", JSGC_HIGH_FREQUENCY_SMALL_HEAP_GROWTH), PREF("gc_small_heap_size_max_mb", JSGC_SMALL_HEAP_SIZE_MAX), PREF("gc_large_heap_size_min_mb", JSGC_LARGE_HEAP_SIZE_MIN), PREF("gc_balanced_heap_limits", JSGC_BALANCED_HEAP_LIMITS_ENABLED), PREF("gc_heap_growth_factor", JSGC_HEAP_GROWTH_FACTOR), PREF("gc_allocation_threshold_mb", JSGC_ALLOCATION_THRESHOLD), PREF("gc_malloc_threshold_base_mb", JSGC_MALLOC_THRESHOLD_BASE), PREF("gc_small_heap_incremental_limit", JSGC_SMALL_HEAP_INCREMENTAL_LIMIT), PREF("gc_large_heap_incremental_limit", JSGC_LARGE_HEAP_INCREMENTAL_LIMIT), PREF("gc_urgent_threshold_mb", JSGC_URGENT_THRESHOLD_MB), PREF("gc_incremental_slice_ms", JSGC_SLICE_TIME_BUDGET_MS), PREF("gc_min_empty_chunk_count", JSGC_MIN_EMPTY_CHUNK_COUNT), PREF("gc_max_empty_chunk_count", JSGC_MAX_EMPTY_CHUNK_COUNT), PREF("gc_compacting", JSGC_COMPACTING_ENABLED), }; #undef PREF auto pref = kWorkerPrefs; auto end = kWorkerPrefs + ArrayLength(kWorkerPrefs); if (gRuntimeServiceDuringInit) { // During init, we want to update every pref in kWorkerPrefs. MOZ_ASSERT(memPrefName.IsEmpty(), "Pref branch prefix only expected during init"); } else { // Otherwise, find the single pref that changed. while (pref != end) { if (pref->memName == memPrefName) { end = pref + 1; break; } ++pref; } #ifdef DEBUG if (pref == end) { nsAutoCString message("Workers don't support the '"); message.Append(memPrefName); message.AppendLiteral("' preference!"); NS_WARNING(message.get()); } #endif } while (pref != end) { switch (pref->key) { case JSGC_MAX_BYTES: { int32_t prefValue = GetPref(pref->fullName, -1); Maybe value = (prefValue <= 0 || prefValue >= 0x1000) ? Nothing() : Some(uint32_t(prefValue) * 1024 * 1024); UpdateOtherJSGCMemoryOption(rts, pref->key, value); break; } case JSGC_SLICE_TIME_BUDGET_MS: { int32_t prefValue = GetPref(pref->fullName, -1); Maybe value = (prefValue <= 0 || prefValue >= 100000) ? Nothing() : Some(uint32_t(prefValue)); UpdateOtherJSGCMemoryOption(rts, pref->key, value); break; } case JSGC_COMPACTING_ENABLED: case JSGC_PARALLEL_MARKING_ENABLED: case JSGC_BALANCED_HEAP_LIMITS_ENABLED: { bool present; bool prefValue = GetPref(pref->fullName, false, &present); Maybe value = present ? Some(prefValue ? 1 : 0) : Nothing(); UpdateOtherJSGCMemoryOption(rts, pref->key, value); break; } case JSGC_HIGH_FREQUENCY_TIME_LIMIT: case JSGC_LOW_FREQUENCY_HEAP_GROWTH: case JSGC_HIGH_FREQUENCY_LARGE_HEAP_GROWTH: case JSGC_HIGH_FREQUENCY_SMALL_HEAP_GROWTH: case JSGC_SMALL_HEAP_SIZE_MAX: case JSGC_LARGE_HEAP_SIZE_MIN: case JSGC_ALLOCATION_THRESHOLD: case JSGC_MALLOC_THRESHOLD_BASE: case JSGC_SMALL_HEAP_INCREMENTAL_LIMIT: case JSGC_LARGE_HEAP_INCREMENTAL_LIMIT: case JSGC_URGENT_THRESHOLD_MB: case JSGC_MIN_EMPTY_CHUNK_COUNT: case JSGC_MAX_EMPTY_CHUNK_COUNT: case JSGC_HEAP_GROWTH_FACTOR: UpdateCommonJSGCMemoryOption(rts, pref->fullName, pref->key); break; default: MOZ_ASSERT_UNREACHABLE("Unknown JSGCParamKey value"); break; } ++pref; } } bool InterruptCallback(JSContext* aCx) { WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx); MOZ_ASSERT(worker); // Now is a good time to turn on profiling if it's pending. PROFILER_JS_INTERRUPT_CALLBACK(); return worker->InterruptCallback(aCx); } class LogViolationDetailsRunnable final : public WorkerMainThreadRunnable { uint16_t mViolationType; nsString mFileName; uint32_t mLineNum; uint32_t mColumnNum; nsString mScriptSample; public: LogViolationDetailsRunnable(WorkerPrivate* aWorker, uint16_t aViolationType, const nsString& aFileName, uint32_t aLineNum, uint32_t aColumnNum, const nsAString& aScriptSample) : WorkerMainThreadRunnable(aWorker, "RuntimeService :: LogViolationDetails"_ns), mViolationType(aViolationType), mFileName(aFileName), mLineNum(aLineNum), mColumnNum(aColumnNum), mScriptSample(aScriptSample) { MOZ_ASSERT(aWorker); } virtual bool MainThreadRun() override; private: ~LogViolationDetailsRunnable() = default; }; bool ContentSecurityPolicyAllows(JSContext* aCx, JS::RuntimeCode aKind, JS::Handle aCode) { WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx); worker->AssertIsOnWorkerThread(); bool evalOK; bool reportViolation; uint16_t violationType; nsAutoJSString scriptSample; if (aKind == JS::RuntimeCode::JS) { if (NS_WARN_IF(!scriptSample.init(aCx, aCode))) { JS_ClearPendingException(aCx); return false; } if (!nsContentSecurityUtils::IsEvalAllowed( aCx, worker->UsesSystemPrincipal(), scriptSample)) { return false; } evalOK = worker->IsEvalAllowed(); reportViolation = worker->GetReportEvalCSPViolations(); violationType = nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL; } else { evalOK = worker->IsWasmEvalAllowed(); reportViolation = worker->GetReportWasmEvalCSPViolations(); violationType = nsIContentSecurityPolicy::VIOLATION_TYPE_WASM_EVAL; } if (reportViolation) { nsString fileName; uint32_t lineNum = 0; uint32_t columnNum = 0; JS::AutoFilename file; if (JS::DescribeScriptedCaller(aCx, &file, &lineNum, &columnNum) && file.get()) { CopyUTF8toUTF16(MakeStringSpan(file.get()), fileName); } else { MOZ_ASSERT(!JS_IsExceptionPending(aCx)); } RefPtr runnable = new LogViolationDetailsRunnable(worker, violationType, fileName, lineNum, columnNum, scriptSample); ErrorResult rv; runnable->Dispatch(Killing, rv); if (NS_WARN_IF(rv.Failed())) { rv.SuppressException(); } } return evalOK; } void CTypesActivityCallback(JSContext* aCx, JS::CTypesActivityType aType) { WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx); worker->AssertIsOnWorkerThread(); switch (aType) { case JS::CTypesActivityType::BeginCall: worker->BeginCTypesCall(); break; case JS::CTypesActivityType::EndCall: worker->EndCTypesCall(); break; case JS::CTypesActivityType::BeginCallback: worker->BeginCTypesCallback(); break; case JS::CTypesActivityType::EndCallback: worker->EndCTypesCallback(); break; default: MOZ_CRASH("Unknown type flag!"); } } // JSDispatchableRunnables are WorkerRunnables used to dispatch JS::Dispatchable // back to their worker thread. A WorkerRunnable is used for two reasons: // // 1. The JS::Dispatchable::run() callback may run JS so we cannot use a control // runnable since they use async interrupts and break JS run-to-completion. // // 2. The DispatchToEventLoopCallback interface is *required* to fail during // shutdown (see jsapi.h) which is exactly what WorkerRunnable::Dispatch() will // do. Moreover, JS_DestroyContext() does *not* block on JS::Dispatchable::run // being called, DispatchToEventLoopCallback failure is expected to happen // during shutdown. class JSDispatchableRunnable final : public WorkerRunnable { JS::Dispatchable* mDispatchable; ~JSDispatchableRunnable() { MOZ_ASSERT(!mDispatchable); } // Disable the usual pre/post-dispatch thread assertions since we are // dispatching from some random JS engine internal thread: bool PreDispatch(WorkerPrivate* aWorkerPrivate) override { return true; } void PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override { // For the benefit of the destructor assert. if (!aDispatchResult) { mDispatchable = nullptr; } } public: JSDispatchableRunnable(WorkerPrivate* aWorkerPrivate, JS::Dispatchable* aDispatchable) : WorkerRunnable(aWorkerPrivate, WorkerRunnable::WorkerThreadUnchangedBusyCount), mDispatchable(aDispatchable) { MOZ_ASSERT(mDispatchable); } bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { MOZ_ASSERT(aWorkerPrivate == mWorkerPrivate); MOZ_ASSERT(aCx == mWorkerPrivate->GetJSContext()); MOZ_ASSERT(mDispatchable); AutoJSAPI jsapi; jsapi.Init(); mDispatchable->run(mWorkerPrivate->GetJSContext(), JS::Dispatchable::NotShuttingDown); mDispatchable = nullptr; // mDispatchable may delete itself return true; } nsresult Cancel() override { // We need to check first if cancel is called twice nsresult rv = WorkerRunnable::Cancel(); NS_ENSURE_SUCCESS(rv, rv); MOZ_ASSERT(mDispatchable); AutoJSAPI jsapi; jsapi.Init(); mDispatchable->run(mWorkerPrivate->GetJSContext(), JS::Dispatchable::ShuttingDown); mDispatchable = nullptr; // mDispatchable may delete itself return NS_OK; } }; static bool DispatchToEventLoop(void* aClosure, JS::Dispatchable* aDispatchable) { // This callback may execute either on the worker thread or a random // JS-internal helper thread. // See comment at JS::InitDispatchToEventLoop() below for how we know the // WorkerPrivate is alive. WorkerPrivate* workerPrivate = reinterpret_cast(aClosure); // Dispatch is expected to fail during shutdown for the reasons outlined in // the JSDispatchableRunnable comment above. RefPtr r = new JSDispatchableRunnable(workerPrivate, aDispatchable); return r->Dispatch(); } static bool ConsumeStream(JSContext* aCx, JS::Handle aObj, JS::MimeType aMimeType, JS::StreamConsumer* aConsumer) { WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx); if (!worker) { JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, JSMSG_WASM_ERROR_CONSUMING_RESPONSE); return false; } return FetchUtil::StreamResponseToJS(aCx, aObj, aMimeType, aConsumer, worker); } bool InitJSContextForWorker(WorkerPrivate* aWorkerPrivate, JSContext* aWorkerCx) { aWorkerPrivate->AssertIsOnWorkerThread(); NS_ASSERTION(!aWorkerPrivate->GetJSContext(), "Already has a context!"); JSSettings settings; aWorkerPrivate->CopyJSSettings(settings); JS::ContextOptionsRef(aWorkerCx) = settings.contextOptions; // This is the real place where we set the max memory for the runtime. for (const auto& setting : settings.gcSettings) { if (setting.value) { JS_SetGCParameter(aWorkerCx, setting.key, *setting.value); } else { JS_ResetGCParameter(aWorkerCx, setting.key); } } JS_SetNativeStackQuota(aWorkerCx, WORKER_CONTEXT_NATIVE_STACK_LIMIT); // Security policy: static const JSSecurityCallbacks securityCallbacks = { ContentSecurityPolicyAllows}; JS_SetSecurityCallbacks(aWorkerCx, &securityCallbacks); // A WorkerPrivate lives strictly longer than its JSRuntime so we can safely // store a raw pointer as the callback's closure argument on the JSRuntime. JS::InitDispatchToEventLoop(aWorkerCx, DispatchToEventLoop, (void*)aWorkerPrivate); JS::InitConsumeStreamCallback(aWorkerCx, ConsumeStream, FetchUtil::ReportJSStreamError); // When available, set the self-hosted shared memory to be read, so that we // can decode the self-hosted content instead of parsing it. auto& shm = xpc::SelfHostedShmem::GetSingleton(); JS::SelfHostedCache selfHostedContent = shm.Content(); if (!JS::InitSelfHostedCode(aWorkerCx, selfHostedContent)) { NS_WARNING("Could not init self-hosted code!"); return false; } JS_AddInterruptCallback(aWorkerCx, InterruptCallback); JS::SetCTypesActivityCallback(aWorkerCx, CTypesActivityCallback); #ifdef JS_GC_ZEAL JS_SetGCZeal(aWorkerCx, settings.gcZeal, settings.gcZealFrequency); #endif return true; } static bool PreserveWrapper(JSContext* cx, JS::Handle obj) { MOZ_ASSERT(cx); MOZ_ASSERT(obj); MOZ_ASSERT(mozilla::dom::IsDOMObject(obj)); return mozilla::dom::TryPreserveWrapper(obj); } static bool IsWorkerDebuggerGlobalOrSandbox(JS::Handle aGlobal) { return IsWorkerDebuggerGlobal(aGlobal) || IsWorkerDebuggerSandbox(aGlobal); } JSObject* Wrap(JSContext* cx, JS::Handle existing, JS::Handle obj) { JS::Rooted targetGlobal(cx, JS::CurrentGlobalOrNull(cx)); // Note: the JS engine unwraps CCWs before calling this callback. JS::Rooted originGlobal(cx, JS::GetNonCCWObjectGlobal(obj)); const js::Wrapper* wrapper = nullptr; if (IsWorkerDebuggerGlobalOrSandbox(targetGlobal) && IsWorkerDebuggerGlobalOrSandbox(originGlobal)) { wrapper = &js::CrossCompartmentWrapper::singleton; } else { wrapper = &js::OpaqueCrossCompartmentWrapper::singleton; } if (existing) { js::Wrapper::Renew(existing, obj, wrapper); } return js::Wrapper::New(cx, obj, wrapper); } static const JSWrapObjectCallbacks WrapObjectCallbacks = { Wrap, nullptr, }; class WorkerJSRuntime final : public mozilla::CycleCollectedJSRuntime { public: // The heap size passed here doesn't matter, we will change it later in the // call to JS_SetGCParameter inside InitJSContextForWorker. explicit WorkerJSRuntime(JSContext* aCx, WorkerPrivate* aWorkerPrivate) : CycleCollectedJSRuntime(aCx), mWorkerPrivate(aWorkerPrivate) { MOZ_COUNT_CTOR_INHERITED(WorkerJSRuntime, CycleCollectedJSRuntime); MOZ_ASSERT(aWorkerPrivate); { JS::UniqueChars defaultLocale = aWorkerPrivate->AdoptDefaultLocale(); MOZ_ASSERT(defaultLocale, "failure of a WorkerPrivate to have a default locale should " "have made the worker fail to spawn"); if (!JS_SetDefaultLocale(Runtime(), defaultLocale.get())) { NS_WARNING("failed to set workerCx's default locale"); } } } void Shutdown(JSContext* cx) override { // The CC is shut down, and the superclass destructor will GC, so make sure // we don't try to CC again. mWorkerPrivate = nullptr; CycleCollectedJSRuntime::Shutdown(cx); } ~WorkerJSRuntime() { MOZ_COUNT_DTOR_INHERITED(WorkerJSRuntime, CycleCollectedJSRuntime); } virtual void PrepareForForgetSkippable() override {} virtual void BeginCycleCollectionCallback( mozilla::CCReason aReason) override {} virtual void EndCycleCollectionCallback( CycleCollectorResults& aResults) override {} void DispatchDeferredDeletion(bool aContinuation, bool aPurge) override { MOZ_ASSERT(!aContinuation); // Do it immediately, no need for asynchronous behavior here. nsCycleCollector_doDeferredDeletion(); } virtual void CustomGCCallback(JSGCStatus aStatus) override { if (!mWorkerPrivate) { // We're shutting down, no need to do anything. return; } mWorkerPrivate->AssertIsOnWorkerThread(); if (aStatus == JSGC_END) { bool collectedAnything = nsCycleCollector_collect(CCReason::GC_FINISHED, nullptr); mWorkerPrivate->SetCCCollectedAnything(collectedAnything); } } private: WorkerPrivate* mWorkerPrivate; }; } // anonymous namespace } // namespace workerinternals class WorkerJSContext final : public mozilla::CycleCollectedJSContext { public: // The heap size passed here doesn't matter, we will change it later in the // call to JS_SetGCParameter inside InitJSContextForWorker. explicit WorkerJSContext(WorkerPrivate* aWorkerPrivate) : mWorkerPrivate(aWorkerPrivate) { MOZ_COUNT_CTOR_INHERITED(WorkerJSContext, CycleCollectedJSContext); MOZ_ASSERT(aWorkerPrivate); // Magical number 2. Workers have the base recursion depth 1, and normal // runnables run at level 2, and we don't want to process microtasks // at any other level. SetTargetedMicroTaskRecursionDepth(2); } // MOZ_CAN_RUN_SCRIPT_BOUNDARY because otherwise we have to annotate the // SpiderMonkey JS::JobQueue's destructor as MOZ_CAN_RUN_SCRIPT, which is a // bit of a pain. MOZ_CAN_RUN_SCRIPT_BOUNDARY ~WorkerJSContext() { MOZ_COUNT_DTOR_INHERITED(WorkerJSContext, CycleCollectedJSContext); JSContext* cx = MaybeContext(); if (!cx) { return; // Initialize() must have failed } // We expect to come here with the cycle collector already shut down. // The superclass destructor will run the GC one final time and finalize any // JSObjects that were participating in cycles that were broken during CC // shutdown. // Make sure we don't try to CC again. mWorkerPrivate = nullptr; } WorkerJSContext* GetAsWorkerJSContext() override { return this; } CycleCollectedJSRuntime* CreateRuntime(JSContext* aCx) override { return new WorkerJSRuntime(aCx, mWorkerPrivate); } nsresult Initialize(JSRuntime* aParentRuntime) { nsresult rv = CycleCollectedJSContext::Initialize( aParentRuntime, WORKER_DEFAULT_RUNTIME_HEAPSIZE); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } JSContext* cx = Context(); js::SetPreserveWrapperCallbacks(cx, PreserveWrapper, HasReleasedWrapper); JS_InitDestroyPrincipalsCallback(cx, nsJSPrincipals::Destroy); JS_InitReadPrincipalsCallback(cx, nsJSPrincipals::ReadPrincipals); JS_SetWrapObjectCallbacks(cx, &WrapObjectCallbacks); if (mWorkerPrivate->IsDedicatedWorker()) { JS_SetFutexCanWait(cx); } return NS_OK; } virtual void DispatchToMicroTask( already_AddRefed aRunnable) override { RefPtr runnable(aRunnable); MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(runnable); std::deque>* microTaskQueue = nullptr; JSContext* cx = Context(); NS_ASSERTION(cx, "This should never be null!"); JS::Rooted global(cx, JS::CurrentGlobalOrNull(cx)); NS_ASSERTION(global, "This should never be null!"); // On worker threads, if the current global is the worker global or // ShadowRealm global, we use the main micro task queue. Otherwise, the // current global must be either the debugger global or a debugger sandbox, // and we use the debugger micro task queue instead. if (IsWorkerGlobal(global) || IsShadowRealmGlobal(global)) { microTaskQueue = &GetMicroTaskQueue(); } else { MOZ_ASSERT(IsWorkerDebuggerGlobal(global) || IsWorkerDebuggerSandbox(global)); microTaskQueue = &GetDebuggerMicroTaskQueue(); } JS::JobQueueMayNotBeEmpty(cx); microTaskQueue->push_back(std::move(runnable)); } bool IsSystemCaller() const override { return mWorkerPrivate->UsesSystemPrincipal(); } void ReportError(JSErrorReport* aReport, JS::ConstUTF8CharsZ aToStringResult) override { mWorkerPrivate->ReportError(Context(), aToStringResult, aReport); } WorkerPrivate* GetWorkerPrivate() const { return mWorkerPrivate; } private: WorkerPrivate* mWorkerPrivate; }; namespace workerinternals { namespace { class WorkerThreadPrimaryRunnable final : public Runnable { WorkerPrivate* mWorkerPrivate; SafeRefPtr mThread; JSRuntime* mParentRuntime; class FinishedRunnable final : public Runnable { SafeRefPtr mThread; public: explicit FinishedRunnable(SafeRefPtr aThread) : Runnable("WorkerThreadPrimaryRunnable::FinishedRunnable"), mThread(std::move(aThread)) { MOZ_ASSERT(mThread); } NS_INLINE_DECL_REFCOUNTING_INHERITED(FinishedRunnable, Runnable) private: ~FinishedRunnable() = default; NS_DECL_NSIRUNNABLE }; public: WorkerThreadPrimaryRunnable(WorkerPrivate* aWorkerPrivate, SafeRefPtr aThread, JSRuntime* aParentRuntime) : mozilla::Runnable("WorkerThreadPrimaryRunnable"), mWorkerPrivate(aWorkerPrivate), mThread(std::move(aThread)), mParentRuntime(aParentRuntime) { MOZ_ASSERT(aWorkerPrivate); MOZ_ASSERT(mThread); } NS_INLINE_DECL_REFCOUNTING_INHERITED(WorkerThreadPrimaryRunnable, Runnable) private: ~WorkerThreadPrimaryRunnable() = default; NS_DECL_NSIRUNNABLE }; void PrefLanguagesChanged(const char* /* aPrefName */, void* /* aClosure */) { AssertIsOnMainThread(); nsTArray languages; Navigator::GetAcceptLanguages(languages); RuntimeService* runtime = RuntimeService::GetService(); if (runtime) { runtime->UpdateAllWorkerLanguages(languages); } } void AppNameOverrideChanged(const char* /* aPrefName */, void* /* aClosure */) { AssertIsOnMainThread(); nsAutoString override; Preferences::GetString("general.appname.override", override); RuntimeService* runtime = RuntimeService::GetService(); if (runtime) { runtime->UpdateAppNameOverridePreference(override); } } void AppVersionOverrideChanged(const char* /* aPrefName */, void* /* aClosure */) { AssertIsOnMainThread(); nsAutoString override; Preferences::GetString("general.appversion.override", override); RuntimeService* runtime = RuntimeService::GetService(); if (runtime) { runtime->UpdateAppVersionOverridePreference(override); } } void PlatformOverrideChanged(const char* /* aPrefName */, void* /* aClosure */) { AssertIsOnMainThread(); nsAutoString override; Preferences::GetString("general.platform.override", override); RuntimeService* runtime = RuntimeService::GetService(); if (runtime) { runtime->UpdatePlatformOverridePreference(override); } } } /* anonymous namespace */ // This is only touched on the main thread. Initialized in Init() below. UniquePtr RuntimeService::sDefaultJSSettings; RuntimeService::RuntimeService() : mMutex("RuntimeService::mMutex"), mObserved(false), mShuttingDown(false), mNavigatorPropertiesLoaded(false) { AssertIsOnMainThread(); MOZ_ASSERT(!GetService(), "More than one service!"); } RuntimeService::~RuntimeService() { AssertIsOnMainThread(); // gRuntimeService can be null if Init() fails. MOZ_ASSERT(!GetService() || GetService() == this, "More than one service!"); gRuntimeService = nullptr; } // static RuntimeService* RuntimeService::GetOrCreateService() { AssertIsOnMainThread(); if (!gRuntimeService) { // The observer service now owns us until shutdown. gRuntimeService = new RuntimeService(); if (NS_FAILED((*gRuntimeService).Init())) { NS_WARNING("Failed to initialize!"); (*gRuntimeService).Cleanup(); gRuntimeService = nullptr; return nullptr; } } return gRuntimeService; } // static RuntimeService* RuntimeService::GetService() { return gRuntimeService; } bool RuntimeService::RegisterWorker(WorkerPrivate& aWorkerPrivate) { aWorkerPrivate.AssertIsOnParentThread(); WorkerPrivate* parent = aWorkerPrivate.GetParent(); if (!parent) { AssertIsOnMainThread(); if (mShuttingDown) { return false; } } const bool isServiceWorker = aWorkerPrivate.IsServiceWorker(); const bool isSharedWorker = aWorkerPrivate.IsSharedWorker(); const bool isDedicatedWorker = aWorkerPrivate.IsDedicatedWorker(); if (isServiceWorker) { AssertIsOnMainThread(); Telemetry::Accumulate(Telemetry::SERVICE_WORKER_SPAWN_ATTEMPTS, 1); } nsCString sharedWorkerScriptSpec; if (isSharedWorker) { AssertIsOnMainThread(); nsCOMPtr scriptURI = aWorkerPrivate.GetResolvedScriptURI(); NS_ASSERTION(scriptURI, "Null script URI!"); nsresult rv = scriptURI->GetSpec(sharedWorkerScriptSpec); if (NS_FAILED(rv)) { NS_WARNING("GetSpec failed?!"); return false; } NS_ASSERTION(!sharedWorkerScriptSpec.IsEmpty(), "Empty spec!"); } bool exemptFromPerDomainMax = false; if (isServiceWorker) { AssertIsOnMainThread(); exemptFromPerDomainMax = Preferences::GetBool( "dom.serviceWorkers.exemptFromPerDomainMax", false); } const nsCString& domain = aWorkerPrivate.Domain(); bool queued = false; { MutexAutoLock lock(mMutex); auto* const domainInfo = mDomainMap .LookupOrInsertWith( domain, [&domain, parent] { NS_ASSERTION(!parent, "Shouldn't have a parent here!"); Unused << parent; // silence clang -Wunused-lambda-capture in // opt builds auto wdi = MakeUnique(); wdi->mDomain = domain; return wdi; }) .get(); queued = gMaxWorkersPerDomain && domainInfo->ActiveWorkerCount() >= gMaxWorkersPerDomain && !domain.IsEmpty() && !exemptFromPerDomainMax; if (queued) { domainInfo->mQueuedWorkers.AppendElement(&aWorkerPrivate); // Worker spawn gets queued due to hitting max workers per domain // limit so let's log a warning. WorkerPrivate::ReportErrorToConsole("HittingMaxWorkersPerDomain2"); if (isServiceWorker) { Telemetry::Accumulate(Telemetry::SERVICE_WORKER_SPAWN_GETS_QUEUED, 1); } else if (isSharedWorker) { Telemetry::Accumulate(Telemetry::SHARED_WORKER_SPAWN_GETS_QUEUED, 1); } else if (isDedicatedWorker) { Telemetry::Accumulate(Telemetry::DEDICATED_WORKER_SPAWN_GETS_QUEUED, 1); } } else if (parent) { domainInfo->mChildWorkerCount++; } else if (isServiceWorker) { domainInfo->mActiveServiceWorkers.AppendElement(&aWorkerPrivate); } else { domainInfo->mActiveWorkers.AppendElement(&aWorkerPrivate); } } // From here on out we must call UnregisterWorker if something fails! if (parent) { if (!parent->AddChildWorker(aWorkerPrivate)) { UnregisterWorker(aWorkerPrivate); return false; } } else { if (!mNavigatorPropertiesLoaded) { Navigator::AppName(mNavigatorProperties.mAppName, aWorkerPrivate.GetDocument(), false /* aUsePrefOverriddenValue */); if (NS_FAILED(Navigator::GetAppVersion( mNavigatorProperties.mAppVersion, aWorkerPrivate.GetDocument(), false /* aUsePrefOverriddenValue */)) || NS_FAILED(Navigator::GetPlatform( mNavigatorProperties.mPlatform, aWorkerPrivate.GetDocument(), false /* aUsePrefOverriddenValue */))) { UnregisterWorker(aWorkerPrivate); return false; } // The navigator overridden properties should have already been read. Navigator::GetAcceptLanguages(mNavigatorProperties.mLanguages); mNavigatorPropertiesLoaded = true; } nsPIDOMWindowInner* window = aWorkerPrivate.GetWindow(); if (!isServiceWorker) { // Service workers are excluded since their lifetime is separate from // that of dom windows. if (auto* const windowArray = mWindowMap.GetOrInsertNew(window, 1); !windowArray->Contains(&aWorkerPrivate)) { windowArray->AppendElement(&aWorkerPrivate); } else { MOZ_ASSERT(aWorkerPrivate.IsSharedWorker()); } } } if (!queued && !ScheduleWorker(aWorkerPrivate)) { return false; } if (isServiceWorker) { AssertIsOnMainThread(); Telemetry::Accumulate(Telemetry::SERVICE_WORKER_WAS_SPAWNED, 1); } return true; } void RuntimeService::UnregisterWorker(WorkerPrivate& aWorkerPrivate) { aWorkerPrivate.AssertIsOnParentThread(); WorkerPrivate* parent = aWorkerPrivate.GetParent(); if (!parent) { AssertIsOnMainThread(); } const nsCString& domain = aWorkerPrivate.Domain(); WorkerPrivate* queuedWorker = nullptr; { MutexAutoLock lock(mMutex); WorkerDomainInfo* domainInfo; if (!mDomainMap.Get(domain, &domainInfo)) { NS_ERROR("Don't have an entry for this domain!"); } // Remove old worker from everywhere. uint32_t index = domainInfo->mQueuedWorkers.IndexOf(&aWorkerPrivate); if (index != kNoIndex) { // Was queued, remove from the list. domainInfo->mQueuedWorkers.RemoveElementAt(index); } else if (parent) { MOZ_ASSERT(domainInfo->mChildWorkerCount, "Must be non-zero!"); domainInfo->mChildWorkerCount--; } else if (aWorkerPrivate.IsServiceWorker()) { MOZ_ASSERT(domainInfo->mActiveServiceWorkers.Contains(&aWorkerPrivate), "Don't know about this worker!"); domainInfo->mActiveServiceWorkers.RemoveElement(&aWorkerPrivate); } else { MOZ_ASSERT(domainInfo->mActiveWorkers.Contains(&aWorkerPrivate), "Don't know about this worker!"); domainInfo->mActiveWorkers.RemoveElement(&aWorkerPrivate); } // See if there's a queued worker we can schedule. if (domainInfo->ActiveWorkerCount() < gMaxWorkersPerDomain && !domainInfo->mQueuedWorkers.IsEmpty()) { queuedWorker = domainInfo->mQueuedWorkers[0]; domainInfo->mQueuedWorkers.RemoveElementAt(0); if (queuedWorker->GetParent()) { domainInfo->mChildWorkerCount++; } else if (queuedWorker->IsServiceWorker()) { domainInfo->mActiveServiceWorkers.AppendElement(queuedWorker); } else { domainInfo->mActiveWorkers.AppendElement(queuedWorker); } } if (domainInfo->HasNoWorkers()) { MOZ_ASSERT(domainInfo->mQueuedWorkers.IsEmpty()); mDomainMap.Remove(domain); } } // NB: For Shared Workers we used to call ShutdownOnMainThread on the // RemoteWorkerController; however, that was redundant because // RemoteWorkerChild uses a WeakWorkerRef which notifies at about the // same time as us calling into the code here and would race with us. if (parent) { parent->RemoveChildWorker(aWorkerPrivate); } else if (aWorkerPrivate.IsSharedWorker()) { AssertIsOnMainThread(); mWindowMap.RemoveIf([&aWorkerPrivate](const auto& iter) { const auto& workers = iter.Data(); MOZ_ASSERT(workers); if (workers->RemoveElement(&aWorkerPrivate)) { MOZ_ASSERT(!workers->Contains(&aWorkerPrivate), "Added worker more than once!"); return workers->IsEmpty(); } return false; }); } else if (aWorkerPrivate.IsDedicatedWorker()) { // May be null. nsPIDOMWindowInner* window = aWorkerPrivate.GetWindow(); if (auto entry = mWindowMap.Lookup(window)) { MOZ_ALWAYS_TRUE(entry.Data()->RemoveElement(&aWorkerPrivate)); if (entry.Data()->IsEmpty()) { entry.Remove(); } } else { MOZ_ASSERT_UNREACHABLE("window is not in mWindowMap"); } } if (queuedWorker && !ScheduleWorker(*queuedWorker)) { UnregisterWorker(*queuedWorker); } } bool RuntimeService::ScheduleWorker(WorkerPrivate& aWorkerPrivate) { if (!aWorkerPrivate.Start()) { // This is ok, means that we didn't need to make a thread for this worker. return true; } const WorkerThreadFriendKey friendKey; SafeRefPtr thread = WorkerThread::Create(friendKey); if (!thread) { UnregisterWorker(aWorkerPrivate); return false; } if (NS_FAILED(thread->SetPriority(nsISupportsPriority::PRIORITY_NORMAL))) { NS_WARNING("Could not set the thread's priority!"); } aWorkerPrivate.SetThread(thread.unsafeGetRawPtr()); JSContext* cx = CycleCollectedJSContext::Get()->Context(); nsCOMPtr runnable = new WorkerThreadPrimaryRunnable( &aWorkerPrivate, thread.clonePtr(), JS_GetParentRuntime(cx)); if (NS_FAILED( thread->DispatchPrimaryRunnable(friendKey, runnable.forget()))) { UnregisterWorker(aWorkerPrivate); return false; } return true; } nsresult RuntimeService::Init() { AssertIsOnMainThread(); nsLayoutStatics::AddRef(); // Initialize JSSettings. sDefaultJSSettings = MakeUnique(); SetDefaultJSGCSettings(JSGC_MAX_BYTES, Some(WORKER_DEFAULT_RUNTIME_HEAPSIZE)); SetDefaultJSGCSettings(JSGC_ALLOCATION_THRESHOLD, Some(WORKER_DEFAULT_ALLOCATION_THRESHOLD)); // nsIStreamTransportService is thread-safe but it must be initialized on the // main-thread. FileReader needs it, so, let's initialize it now. nsresult rv; nsCOMPtr sts = do_GetService(kStreamTransportServiceCID, &rv); NS_ENSURE_TRUE(sts, NS_ERROR_FAILURE); nsCOMPtr obs = services::GetObserverService(); NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE); rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false); NS_ENSURE_SUCCESS(rv, rv); rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); NS_ENSURE_SUCCESS(rv, rv); mObserved = true; if (NS_FAILED(obs->AddObserver(this, GC_REQUEST_OBSERVER_TOPIC, false))) { NS_WARNING("Failed to register for GC request notifications!"); } if (NS_FAILED(obs->AddObserver(this, CC_REQUEST_OBSERVER_TOPIC, false))) { NS_WARNING("Failed to register for CC request notifications!"); } if (NS_FAILED( obs->AddObserver(this, MEMORY_PRESSURE_OBSERVER_TOPIC, false))) { NS_WARNING("Failed to register for memory pressure notifications!"); } if (NS_FAILED( obs->AddObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, false))) { NS_WARNING("Failed to register for offline notification event!"); } MOZ_ASSERT(!gRuntimeServiceDuringInit, "This should be false!"); gRuntimeServiceDuringInit = true; #define WORKER_PREF(name, callback) \ NS_FAILED(Preferences::RegisterCallbackAndCall(callback, name)) if (NS_FAILED(Preferences::RegisterPrefixCallbackAndCall( LoadJSGCMemoryOptions, PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX)) || #ifdef JS_GC_ZEAL NS_FAILED(Preferences::RegisterCallback( LoadGCZealOptions, PREF_JS_OPTIONS_PREFIX PREF_GCZEAL)) || #endif WORKER_PREF("intl.accept_languages", PrefLanguagesChanged) || WORKER_PREF("general.appname.override", AppNameOverrideChanged) || WORKER_PREF("general.appversion.override", AppVersionOverrideChanged) || WORKER_PREF("general.platform.override", PlatformOverrideChanged) || NS_FAILED(Preferences::RegisterPrefixCallbackAndCall( LoadContextOptions, PREF_JS_OPTIONS_PREFIX))) { NS_WARNING("Failed to register pref callbacks!"); } #undef WORKER_PREF MOZ_ASSERT(gRuntimeServiceDuringInit, "Should be true!"); gRuntimeServiceDuringInit = false; int32_t maxPerDomain = Preferences::GetInt(PREF_WORKERS_MAX_PER_DOMAIN, MAX_WORKERS_PER_DOMAIN); gMaxWorkersPerDomain = std::max(0, maxPerDomain); RefPtr osFileConstantsService = OSFileConstantsService::GetOrCreate(); if (NS_WARN_IF(!osFileConstantsService)) { return NS_ERROR_FAILURE; } if (NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate())) { return NS_ERROR_UNEXPECTED; } // PerformanceService must be initialized on the main-thread. PerformanceService::GetOrCreate(); return NS_OK; } void RuntimeService::Shutdown() { AssertIsOnMainThread(); MOZ_ASSERT(!mShuttingDown); // That's it, no more workers. mShuttingDown = true; nsCOMPtr obs = services::GetObserverService(); NS_WARNING_ASSERTION(obs, "Failed to get observer service?!"); // Tell anyone that cares that they're about to lose worker support. if (obs && NS_FAILED(obs->NotifyObservers(nullptr, WORKERS_SHUTDOWN_TOPIC, nullptr))) { NS_WARNING("NotifyObservers failed!"); } { AutoTArray workers; { MutexAutoLock lock(mMutex); AddAllTopLevelWorkersToArray(workers); } // Cancel all top-level workers. for (const auto& worker : workers) { if (!worker->Cancel()) { NS_WARNING("Failed to cancel worker!"); } } } sDefaultJSSettings = nullptr; } namespace { class DumpCrashInfoRunnable : public WorkerControlRunnable { public: explicit DumpCrashInfoRunnable(WorkerPrivate* aWorkerPrivate) : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), mMonitor("DumpCrashInfoRunnable::mMonitor") {} bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { MonitorAutoLock lock(mMonitor); if (!mHasMsg) { aWorkerPrivate->DumpCrashInformation(mMsg); mHasMsg.Flip(); } lock.Notify(); return true; } nsresult Cancel() override { // We need to check first if cancel is called twice nsresult rv = WorkerRunnable::Cancel(); NS_ENSURE_SUCCESS(rv, rv); MonitorAutoLock lock(mMonitor); if (!mHasMsg) { mMsg.Assign("Canceled"); mHasMsg.Flip(); } lock.Notify(); return NS_OK; } bool DispatchAndWait() { MonitorAutoLock lock(mMonitor); if (!Dispatch()) { // The worker is already dead but the main thread still didn't remove it // from RuntimeService's registry. return false; } // To avoid any possibility of process hangs we never receive reports on // we give the worker 1sec to react. lock.Wait(TimeDuration::FromMilliseconds(1000)); if (!mHasMsg) { mMsg.Append("NoResponse"); mHasMsg.Flip(); } return true; } const nsCString& MsgData() const { return mMsg; } private: bool PreDispatch(WorkerPrivate* aWorkerPrivate) override { return true; } void PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override {} Monitor mMonitor MOZ_UNANNOTATED; nsCString mMsg; FlippedOnce mHasMsg; }; struct ActiveWorkerStats { template void Update(const nsTArray& aWorkers) { for (const auto worker : aWorkers) { RefPtr runnable = new DumpCrashInfoRunnable(worker); if (runnable->DispatchAndWait()) { ++(this->*Category); // BC: Busy Count mMessage.AppendPrintf("-BC:%d", worker->BusyCount()); mMessage.Append(runnable->MsgData()); } else { mMessage.AppendPrintf("-BC:%d DispatchFailed", worker->BusyCount()); } } } uint32_t mWorkers = 0; uint32_t mServiceWorkers = 0; nsCString mMessage; }; } // namespace void RuntimeService::CrashIfHanging() { MutexAutoLock lock(mMutex); // If we never wanted to shut down we cannot hang. if (!mShuttingDown) { return; } ActiveWorkerStats activeStats; uint32_t inactiveWorkers = 0; for (const auto& aData : mDomainMap.Values()) { activeStats.Update<&ActiveWorkerStats::mWorkers>(aData->mActiveWorkers); activeStats.Update<&ActiveWorkerStats::mServiceWorkers>( aData->mActiveServiceWorkers); // These might not be top-level workers... inactiveWorkers += std::count_if( aData->mQueuedWorkers.begin(), aData->mQueuedWorkers.end(), [](const auto* const worker) { return !worker->GetParent(); }); } if (activeStats.mWorkers + activeStats.mServiceWorkers + inactiveWorkers == 0) { return; } nsCString msg; // A: active Workers | S: active ServiceWorkers | Q: queued Workers msg.AppendPrintf("Workers Hanging - %d|A:%d|S:%d|Q:%d", mShuttingDown ? 1 : 0, activeStats.mWorkers, activeStats.mServiceWorkers, inactiveWorkers); msg.Append(activeStats.mMessage); // This string will be leaked. MOZ_CRASH_UNSAFE(strdup(msg.BeginReading())); } // This spins the event loop until all workers are finished and their threads // have been joined. void RuntimeService::Cleanup() { AssertIsOnMainThread(); if (!mShuttingDown) { Shutdown(); } nsCOMPtr obs = services::GetObserverService(); NS_WARNING_ASSERTION(obs, "Failed to get observer service?!"); { MutexAutoLock lock(mMutex); AutoTArray workers; AddAllTopLevelWorkersToArray(workers); if (!workers.IsEmpty()) { nsIThread* currentThread = NS_GetCurrentThread(); NS_ASSERTION(currentThread, "This should never be null!"); // If the loop below takes too long, we probably have a problematic // worker. MOZ_LOG some info before the parent process forcibly // terminates us so that in the event we are a content process, the log // output can provide useful context about the workers that did not // cleanly shut down. nsCOMPtr timer; RefPtr self = this; nsresult rv = NS_NewTimerWithCallback( getter_AddRefs(timer), [self](nsITimer*) { self->DumpRunningWorkers(); }, TimeDuration::FromSeconds(1), nsITimer::TYPE_ONE_SHOT, "RuntimeService::WorkerShutdownDump"); Unused << NS_WARN_IF(NS_FAILED(rv)); // And make sure all their final messages have run and all their threads // have joined. while (mDomainMap.Count()) { MutexAutoUnlock unlock(mMutex); if (!NS_ProcessNextEvent(currentThread)) { NS_WARNING("Something bad happened!"); break; } } if (NS_SUCCEEDED(rv)) { timer->Cancel(); } } } NS_ASSERTION(!mWindowMap.Count(), "All windows should have been released!"); #define WORKER_PREF(name, callback) \ NS_FAILED(Preferences::UnregisterCallback(callback, name)) if (mObserved) { if (NS_FAILED(Preferences::UnregisterPrefixCallback( LoadContextOptions, PREF_JS_OPTIONS_PREFIX)) || WORKER_PREF("intl.accept_languages", PrefLanguagesChanged) || WORKER_PREF("general.appname.override", AppNameOverrideChanged) || WORKER_PREF("general.appversion.override", AppVersionOverrideChanged) || WORKER_PREF("general.platform.override", PlatformOverrideChanged) || #ifdef JS_GC_ZEAL NS_FAILED(Preferences::UnregisterCallback( LoadGCZealOptions, PREF_JS_OPTIONS_PREFIX PREF_GCZEAL)) || #endif NS_FAILED(Preferences::UnregisterPrefixCallback( LoadJSGCMemoryOptions, PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX))) { NS_WARNING("Failed to unregister pref callbacks!"); } #undef WORKER_PREF if (obs) { if (NS_FAILED(obs->RemoveObserver(this, GC_REQUEST_OBSERVER_TOPIC))) { NS_WARNING("Failed to unregister for GC request notifications!"); } if (NS_FAILED(obs->RemoveObserver(this, CC_REQUEST_OBSERVER_TOPIC))) { NS_WARNING("Failed to unregister for CC request notifications!"); } if (NS_FAILED( obs->RemoveObserver(this, MEMORY_PRESSURE_OBSERVER_TOPIC))) { NS_WARNING("Failed to unregister for memory pressure notifications!"); } if (NS_FAILED( obs->RemoveObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC))) { NS_WARNING("Failed to unregister for offline notification event!"); } obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID); obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); mObserved = false; } } nsLayoutStatics::Release(); } void RuntimeService::AddAllTopLevelWorkersToArray( nsTArray& aWorkers) { for (const auto& aData : mDomainMap.Values()) { #ifdef DEBUG for (const auto& activeWorker : aData->mActiveWorkers) { MOZ_ASSERT(!activeWorker->GetParent(), "Shouldn't have a parent in this list!"); } for (const auto& activeServiceWorker : aData->mActiveServiceWorkers) { MOZ_ASSERT(!activeServiceWorker->GetParent(), "Shouldn't have a parent in this list!"); } #endif aWorkers.AppendElements(aData->mActiveWorkers); aWorkers.AppendElements(aData->mActiveServiceWorkers); // These might not be top-level workers... std::copy_if(aData->mQueuedWorkers.begin(), aData->mQueuedWorkers.end(), MakeBackInserter(aWorkers), [](const auto& worker) { return !worker->GetParent(); }); } } nsTArray RuntimeService::GetWorkersForWindow( const nsPIDOMWindowInner& aWindow) const { AssertIsOnMainThread(); nsTArray result; if (nsTArray* const workers = mWindowMap.Get(&aWindow)) { NS_ASSERTION(!workers->IsEmpty(), "Should have been removed!"); result.AppendElements(*workers); } return result; } void RuntimeService::CancelWorkersForWindow(const nsPIDOMWindowInner& aWindow) { AssertIsOnMainThread(); for (WorkerPrivate* const worker : GetWorkersForWindow(aWindow)) { MOZ_ASSERT(!worker->IsSharedWorker()); worker->Cancel(); } } void RuntimeService::FreezeWorkersForWindow(const nsPIDOMWindowInner& aWindow) { AssertIsOnMainThread(); for (WorkerPrivate* const worker : GetWorkersForWindow(aWindow)) { MOZ_ASSERT(!worker->IsSharedWorker()); worker->Freeze(&aWindow); } } void RuntimeService::ThawWorkersForWindow(const nsPIDOMWindowInner& aWindow) { AssertIsOnMainThread(); for (WorkerPrivate* const worker : GetWorkersForWindow(aWindow)) { MOZ_ASSERT(!worker->IsSharedWorker()); worker->Thaw(&aWindow); } } void RuntimeService::SuspendWorkersForWindow( const nsPIDOMWindowInner& aWindow) { AssertIsOnMainThread(); for (WorkerPrivate* const worker : GetWorkersForWindow(aWindow)) { MOZ_ASSERT(!worker->IsSharedWorker()); worker->ParentWindowPaused(); } } void RuntimeService::ResumeWorkersForWindow(const nsPIDOMWindowInner& aWindow) { AssertIsOnMainThread(); for (WorkerPrivate* const worker : GetWorkersForWindow(aWindow)) { MOZ_ASSERT(!worker->IsSharedWorker()); worker->ParentWindowResumed(); } } void RuntimeService::PropagateStorageAccessPermissionGranted( const nsPIDOMWindowInner& aWindow) { AssertIsOnMainThread(); MOZ_ASSERT_IF(aWindow.GetExtantDoc(), aWindow.GetExtantDoc() ->CookieJarSettings() ->GetRejectThirdPartyContexts()); for (WorkerPrivate* const worker : GetWorkersForWindow(aWindow)) { worker->PropagateStorageAccessPermissionGranted(); } } template void RuntimeService::BroadcastAllWorkers(const Func& aFunc) { AssertIsOnMainThread(); AutoTArray workers; { MutexAutoLock lock(mMutex); AddAllTopLevelWorkersToArray(workers); } for (const auto& worker : workers) { aFunc(*worker); } } void RuntimeService::UpdateAllWorkerContextOptions() { BroadcastAllWorkers([](auto& worker) { worker.UpdateContextOptions(sDefaultJSSettings->contextOptions); }); } void RuntimeService::UpdateAppNameOverridePreference(const nsAString& aValue) { AssertIsOnMainThread(); mNavigatorProperties.mAppNameOverridden = aValue; } void RuntimeService::UpdateAppVersionOverridePreference( const nsAString& aValue) { AssertIsOnMainThread(); mNavigatorProperties.mAppVersionOverridden = aValue; } void RuntimeService::UpdatePlatformOverridePreference(const nsAString& aValue) { AssertIsOnMainThread(); mNavigatorProperties.mPlatformOverridden = aValue; } void RuntimeService::UpdateAllWorkerLanguages( const nsTArray& aLanguages) { MOZ_ASSERT(NS_IsMainThread()); mNavigatorProperties.mLanguages = aLanguages.Clone(); BroadcastAllWorkers( [&aLanguages](auto& worker) { worker.UpdateLanguages(aLanguages); }); } void RuntimeService::UpdateAllWorkerMemoryParameter(JSGCParamKey aKey, Maybe aValue) { BroadcastAllWorkers([aKey, aValue](auto& worker) { worker.UpdateJSWorkerMemoryParameter(aKey, aValue); }); } #ifdef JS_GC_ZEAL void RuntimeService::UpdateAllWorkerGCZeal() { BroadcastAllWorkers([](auto& worker) { worker.UpdateGCZeal(sDefaultJSSettings->gcZeal, sDefaultJSSettings->gcZealFrequency); }); } #endif void RuntimeService::SetLowMemoryStateAllWorkers(bool aState) { BroadcastAllWorkers( [aState](auto& worker) { worker.SetLowMemoryState(aState); }); } void RuntimeService::GarbageCollectAllWorkers(bool aShrinking) { BroadcastAllWorkers( [aShrinking](auto& worker) { worker.GarbageCollect(aShrinking); }); } void RuntimeService::CycleCollectAllWorkers() { BroadcastAllWorkers([](auto& worker) { worker.CycleCollect(); }); } void RuntimeService::SendOfflineStatusChangeEventToAllWorkers(bool aIsOffline) { BroadcastAllWorkers([aIsOffline](auto& worker) { worker.OfflineStatusChangeEvent(aIsOffline); }); } void RuntimeService::MemoryPressureAllWorkers() { BroadcastAllWorkers([](auto& worker) { worker.MemoryPressure(); }); } uint32_t RuntimeService::ClampedHardwareConcurrency( bool aShouldResistFingerprinting) const { // The Firefox Hardware Report says 70% of Firefox users have exactly 2 cores. // When the resistFingerprinting pref is set, we want to blend into the crowd // so spoof navigator.hardwareConcurrency = 2 to reduce user uniqueness. if (MOZ_UNLIKELY(aShouldResistFingerprinting)) { return 2; } // This needs to be atomic, because multiple workers, and even mainthread, // could race to initialize it at once. static Atomic unclampedHardwareConcurrency; // No need to loop here: if compareExchange fails, that just means that some // other worker has initialized numberOfProcessors, so we're good to go. if (!unclampedHardwareConcurrency) { int32_t numberOfProcessors = 0; #if defined(XP_MACOSX) if (nsMacUtilsImpl::IsTCSMAvailable()) { // On failure, zero is returned from GetPhysicalCPUCount() // and we fallback to PR_GetNumberOfProcessors below. numberOfProcessors = nsMacUtilsImpl::GetPhysicalCPUCount(); } #endif if (numberOfProcessors == 0) { numberOfProcessors = PR_GetNumberOfProcessors(); } if (numberOfProcessors <= 0) { numberOfProcessors = 1; // Must be one there somewhere } Unused << unclampedHardwareConcurrency.compareExchange(0, numberOfProcessors); } return std::min(uint32_t(unclampedHardwareConcurrency), StaticPrefs::dom_maxHardwareConcurrency()); } // nsISupports NS_IMPL_ISUPPORTS(RuntimeService, nsIObserver) // nsIObserver NS_IMETHODIMP RuntimeService::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { AssertIsOnMainThread(); if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { Shutdown(); return NS_OK; } if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID)) { Cleanup(); return NS_OK; } if (!strcmp(aTopic, GC_REQUEST_OBSERVER_TOPIC)) { GarbageCollectAllWorkers(/* shrinking = */ false); return NS_OK; } if (!strcmp(aTopic, CC_REQUEST_OBSERVER_TOPIC)) { CycleCollectAllWorkers(); return NS_OK; } if (!strcmp(aTopic, MEMORY_PRESSURE_OBSERVER_TOPIC)) { nsDependentString data(aData); // Don't continue to GC/CC if we are in an ongoing low-memory state since // its very slow and it likely won't help us anyway. if (data.EqualsLiteral(LOW_MEMORY_ONGOING_DATA)) { return NS_OK; } if (data.EqualsLiteral(LOW_MEMORY_DATA)) { SetLowMemoryStateAllWorkers(true); } GarbageCollectAllWorkers(/* shrinking = */ true); CycleCollectAllWorkers(); MemoryPressureAllWorkers(); return NS_OK; } if (!strcmp(aTopic, MEMORY_PRESSURE_STOP_OBSERVER_TOPIC)) { SetLowMemoryStateAllWorkers(false); return NS_OK; } if (!strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC)) { SendOfflineStatusChangeEventToAllWorkers(NS_IsOffline()); return NS_OK; } MOZ_ASSERT_UNREACHABLE("Unknown observer topic!"); return NS_OK; } namespace { const char* WorkerKindToString(WorkerKind kind) { switch (kind) { case WorkerKindDedicated: return "dedicated"; case WorkerKindShared: return "shared"; case WorkerKindService: return "service"; default: NS_WARNING("Unknown worker type"); return "unknown worker type"; } } void LogWorker(WorkerPrivate* worker, const char* category) { AssertIsOnMainThread(); SHUTDOWN_LOG(("Found %s (%s): %s", category, WorkerKindToString(worker->Kind()), NS_ConvertUTF16toUTF8(worker->ScriptURL()).get())); if (worker->Kind() == WorkerKindService) { SHUTDOWN_LOG(("Scope: %s", worker->ServiceWorkerScope().get())); } nsCString origin; worker->GetPrincipal()->GetOrigin(origin); SHUTDOWN_LOG(("Principal: %s", origin.get())); nsCString loadingOrigin; worker->GetLoadingPrincipal()->GetOrigin(loadingOrigin); SHUTDOWN_LOG(("LoadingPrincipal: %s", loadingOrigin.get())); SHUTDOWN_LOG(("BusyCount: %d", worker->BusyCount())); RefPtr runnable = new DumpCrashInfoRunnable(worker); if (runnable->DispatchAndWait()) { SHUTDOWN_LOG(("CrashInfo: %s", runnable->MsgData().get())); } else { SHUTDOWN_LOG(("CrashInfo: dispatch failed")); } } } // namespace void RuntimeService::DumpRunningWorkers() { // Temporarily set the LogLevel high enough to be certain the messages are // visible. LogModule* module = gWorkerShutdownDumpLog; LogLevel prevLevel = module->Level(); const auto cleanup = MakeScopeExit([module, prevLevel] { module->SetLevel(prevLevel); }); if (prevLevel < LogLevel::Debug) { module->SetLevel(LogLevel::Debug); } MutexAutoLock lock(mMutex); for (const auto& info : mDomainMap.Values()) { for (WorkerPrivate* worker : info->mActiveWorkers) { LogWorker(worker, "ActiveWorker"); } for (WorkerPrivate* worker : info->mActiveServiceWorkers) { LogWorker(worker, "ActiveServiceWorker"); } for (WorkerPrivate* worker : info->mQueuedWorkers) { LogWorker(worker, "QueuedWorker"); } } } bool LogViolationDetailsRunnable::MainThreadRun() { AssertIsOnMainThread(); nsIContentSecurityPolicy* csp = mWorkerPrivate->GetCsp(); if (csp) { csp->LogViolationDetails(mViolationType, nullptr, // triggering element mWorkerPrivate->CSPEventListener(), mFileName, mScriptSample, mLineNum, mColumnNum, u""_ns, u""_ns); } return true; } // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See // bug 1535398. MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP WorkerThreadPrimaryRunnable::Run() { NS_ConvertUTF16toUTF8 url(mWorkerPrivate->ScriptURL()); AUTO_PROFILER_LABEL_DYNAMIC_CSTR("WorkerThreadPrimaryRunnable::Run", OTHER, url.get()); using mozilla::ipc::BackgroundChild; { auto failureCleanup = MakeScopeExit([&]() { // The creation of threadHelper above is the point at which a worker is // considered to have run, because the `mPreStartRunnables` are all // re-dispatched after `mThread` is set. We need to let the WorkerPrivate // know so it can clean up the various event loops and delete the worker. mWorkerPrivate->RunLoopNeverRan(); }); mWorkerPrivate->SetWorkerPrivateInWorkerThread(mThread.unsafeGetRawPtr()); const auto threadCleanup = MakeScopeExit([&] { // This must be called before ScheduleDeletion, which is either called // from failureCleanup leaving scope, or from the outer scope. mWorkerPrivate->ResetWorkerPrivateInWorkerThread(); }); mWorkerPrivate->AssertIsOnWorkerThread(); // This needs to be initialized on the worker thread before being used on // the main thread and calling BackgroundChild::GetOrCreateForCurrentThread // exposes it to the main thread. mWorkerPrivate->EnsurePerformanceStorage(); if (NS_WARN_IF(!BackgroundChild::GetOrCreateForCurrentThread())) { return NS_ERROR_FAILURE; } nsWeakPtr globalScopeSentinel; nsWeakPtr debuggerScopeSentinel; // Never use the following pointers without checking their corresponding // nsWeakPtr sentinel, defined above and initialized after DoRunLoop ends. WorkerGlobalScopeBase* globalScopeRawPtr = nullptr; WorkerGlobalScopeBase* debuggerScopeRawPtr = nullptr; { nsCycleCollector_startup(); auto context = MakeUnique(mWorkerPrivate); nsresult rv = context->Initialize(mParentRuntime); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } JSContext* cx = context->Context(); if (!InitJSContextForWorker(mWorkerPrivate, cx)) { return NS_ERROR_FAILURE; } failureCleanup.release(); { PROFILER_SET_JS_CONTEXT(cx); { // We're on the worker thread here, and WorkerPrivate's refcounting is // non-threadsafe: you can only do it on the parent thread. What that // means in practice is that we're relying on it being kept alive // while we run. Hopefully. MOZ_KnownLive(mWorkerPrivate)->DoRunLoop(cx); // The AutoJSAPI in DoRunLoop should have reported any exceptions left // on cx. MOZ_ASSERT(!JS_IsExceptionPending(cx)); } mWorkerPrivate->ShutdownModuleLoader(); mWorkerPrivate->RunShutdownTasks(); BackgroundChild::CloseForCurrentThread(); PROFILER_CLEAR_JS_CONTEXT(); } // There may still be runnables on the debugger event queue that hold a // strong reference to the debugger global scope. These runnables are not // visible to the cycle collector, so we need to make sure to clear the // debugger event queue before we try to destroy the context. If we don't, // the garbage collector will crash. // Note that this just releases the runnables and does not execute them. mWorkerPrivate->ClearDebuggerEventQueue(); // Before shutting down the cycle collector we need to do one more pass // through the event loop to clean up any C++ objects that need deferred // cleanup. NS_ProcessPendingEvents(nullptr); // At this point we expect the scopes to be alive if they were ever // created successfully, keep weak references and set up the sentinels. globalScopeRawPtr = mWorkerPrivate->GlobalScope(); if (globalScopeRawPtr) { globalScopeSentinel = do_GetWeakReference(globalScopeRawPtr); } MOZ_ASSERT(!globalScopeRawPtr || globalScopeSentinel); debuggerScopeRawPtr = mWorkerPrivate->DebuggerGlobalScope(); if (debuggerScopeRawPtr) { debuggerScopeSentinel = do_GetWeakReference(debuggerScopeRawPtr); } MOZ_ASSERT(!debuggerScopeRawPtr || debuggerScopeSentinel); // To our best knowledge nobody should need a reference to our globals // now (NS_ProcessPendingEvents is the last expected potential usage) // and we can unroot them. mWorkerPrivate->UnrootGlobalScopes(); // Perform a full GC until we collect the main worker global and CC, // which should break all cycles that touch JS. bool repeatGCCC = true; while (repeatGCCC) { JS::PrepareForFullGC(cx); JS::NonIncrementalGC(cx, JS::GCOptions::Shutdown, JS::GCReason::WORKER_SHUTDOWN); // If we CCed something or got new events as a side effect, repeat. repeatGCCC = mWorkerPrivate->isLastCCCollectedAnything() || NS_HasPendingEvents(nullptr); NS_ProcessPendingEvents(nullptr); } // The worker global should be unrooted and the shutdown of cycle // collection should break all the remaining cycles. nsCycleCollector_shutdown(); // If ever the CC shutdown run caused side effects, process them. NS_ProcessPendingEvents(nullptr); // Now WorkerJSContext goes out of scope. Do not use any cycle // collectable objects nor JS after this point! } // Check sentinels if we actually removed all global scope references. // In case use the earlier set-aside raw pointers to not mess with the // ref counting after the cycle collector has gone away. if (globalScopeSentinel) { MOZ_ASSERT(!globalScopeSentinel->IsAlive()); if (NS_WARN_IF(globalScopeSentinel->IsAlive())) { globalScopeRawPtr->NoteWorkerTerminated(); globalScopeRawPtr = nullptr; } } if (debuggerScopeSentinel) { MOZ_ASSERT(!debuggerScopeSentinel->IsAlive()); if (NS_WARN_IF(debuggerScopeSentinel->IsAlive())) { debuggerScopeRawPtr->NoteWorkerTerminated(); debuggerScopeRawPtr = nullptr; } } } mWorkerPrivate->ScheduleDeletion(WorkerPrivate::WorkerRan); // It is no longer safe to touch mWorkerPrivate. mWorkerPrivate = nullptr; // Now recycle this thread. nsCOMPtr mainTarget = GetMainThreadSerialEventTarget(); MOZ_ASSERT(mainTarget); RefPtr finishedRunnable = new FinishedRunnable(std::move(mThread)); MOZ_ALWAYS_SUCCEEDS( mainTarget->Dispatch(finishedRunnable, NS_DISPATCH_NORMAL)); return NS_OK; } NS_IMETHODIMP WorkerThreadPrimaryRunnable::FinishedRunnable::Run() { AssertIsOnMainThread(); SafeRefPtr thread = std::move(mThread); if (thread->ShutdownRequired()) { MOZ_ALWAYS_SUCCEEDS(thread->Shutdown()); } return NS_OK; } } // namespace workerinternals // This is mostly for invoking within a debugger. void DumpRunningWorkers() { RuntimeService* runtimeService = RuntimeService::GetService(); if (runtimeService) { runtimeService->DumpRunningWorkers(); } else { NS_WARNING("RuntimeService not found"); } } void CancelWorkersForWindow(const nsPIDOMWindowInner& aWindow) { AssertIsOnMainThread(); RuntimeService* runtime = RuntimeService::GetService(); if (runtime) { runtime->CancelWorkersForWindow(aWindow); } } void FreezeWorkersForWindow(const nsPIDOMWindowInner& aWindow) { AssertIsOnMainThread(); RuntimeService* runtime = RuntimeService::GetService(); if (runtime) { runtime->FreezeWorkersForWindow(aWindow); } } void ThawWorkersForWindow(const nsPIDOMWindowInner& aWindow) { AssertIsOnMainThread(); RuntimeService* runtime = RuntimeService::GetService(); if (runtime) { runtime->ThawWorkersForWindow(aWindow); } } void SuspendWorkersForWindow(const nsPIDOMWindowInner& aWindow) { AssertIsOnMainThread(); RuntimeService* runtime = RuntimeService::GetService(); if (runtime) { runtime->SuspendWorkersForWindow(aWindow); } } void ResumeWorkersForWindow(const nsPIDOMWindowInner& aWindow) { AssertIsOnMainThread(); RuntimeService* runtime = RuntimeService::GetService(); if (runtime) { runtime->ResumeWorkersForWindow(aWindow); } } void PropagateStorageAccessPermissionGrantedToWorkers( const nsPIDOMWindowInner& aWindow) { AssertIsOnMainThread(); MOZ_ASSERT_IF(aWindow.GetExtantDoc(), aWindow.GetExtantDoc() ->CookieJarSettings() ->GetRejectThirdPartyContexts()); RuntimeService* runtime = RuntimeService::GetService(); if (runtime) { runtime->PropagateStorageAccessPermissionGranted(aWindow); } } WorkerPrivate* GetWorkerPrivateFromContext(JSContext* aCx) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aCx); CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::GetFor(aCx); if (!ccjscx) { return nullptr; } WorkerJSContext* workerjscx = ccjscx->GetAsWorkerJSContext(); // GetWorkerPrivateFromContext is called only for worker contexts. The // context private is cleared early in ~CycleCollectedJSContext() and so // GetFor() returns null above if called after ccjscx is no longer a // WorkerJSContext. MOZ_ASSERT(workerjscx); return workerjscx->GetWorkerPrivate(); } WorkerPrivate* GetCurrentThreadWorkerPrivate() { if (NS_IsMainThread()) { return nullptr; } CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get(); if (!ccjscx) { return nullptr; } WorkerJSContext* workerjscx = ccjscx->GetAsWorkerJSContext(); // Even when GetCurrentThreadWorkerPrivate() is called on worker // threads, the ccjscx will no longer be a WorkerJSContext if called from // stable state events during ~CycleCollectedJSContext(). if (!workerjscx) { return nullptr; } return workerjscx->GetWorkerPrivate(); } bool IsCurrentThreadRunningWorker() { return !NS_IsMainThread() && !!GetCurrentThreadWorkerPrivate(); } bool IsCurrentThreadRunningChromeWorker() { WorkerPrivate* wp = GetCurrentThreadWorkerPrivate(); return wp && wp->UsesSystemPrincipal(); } JSContext* GetCurrentWorkerThreadJSContext() { WorkerPrivate* wp = GetCurrentThreadWorkerPrivate(); if (!wp) { return nullptr; } return wp->GetJSContext(); } JSObject* GetCurrentThreadWorkerGlobal() { WorkerPrivate* wp = GetCurrentThreadWorkerPrivate(); if (!wp) { return nullptr; } WorkerGlobalScope* scope = wp->GlobalScope(); if (!scope) { return nullptr; } return scope->GetGlobalJSObject(); } JSObject* GetCurrentThreadWorkerDebuggerGlobal() { WorkerPrivate* wp = GetCurrentThreadWorkerPrivate(); if (!wp) { return nullptr; } WorkerDebuggerGlobalScope* scope = wp->DebuggerGlobalScope(); if (!scope) { return nullptr; } return scope->GetGlobalJSObject(); } } // namespace dom } // namespace mozilla