michael@0: /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "RuntimeService.h" michael@0: michael@0: #include "nsIChannel.h" michael@0: #include "nsIContentSecurityPolicy.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIDOMChromeWindow.h" michael@0: #include "nsIEffectiveTLDService.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsIPrincipal.h" michael@0: #include "nsIScriptContext.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsISupportsPriority.h" michael@0: #include "nsITimer.h" michael@0: #include "nsIURI.h" michael@0: #include "nsPIDOMWindow.h" michael@0: michael@0: #include michael@0: #include "GeckoProfiler.h" michael@0: #include "js/OldDebugAPI.h" michael@0: #include "jsfriendapi.h" michael@0: #include "mozilla/ArrayUtils.h" michael@0: #include "mozilla/CycleCollectedJSRuntime.h" michael@0: #include "mozilla/dom/asmjscache/AsmJSCache.h" michael@0: #include "mozilla/dom/AtomList.h" michael@0: #include "mozilla/dom/BindingUtils.h" michael@0: #include "mozilla/dom/ErrorEventBinding.h" michael@0: #include "mozilla/dom/EventTargetBinding.h" michael@0: #include "mozilla/dom/MessageEventBinding.h" michael@0: #include "mozilla/dom/WorkerBinding.h" michael@0: #include "mozilla/DebugOnly.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/dom/Navigator.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsCycleCollector.h" michael@0: #include "nsDOMJSUtils.h" michael@0: #include "nsISupportsImpl.h" michael@0: #include "nsLayoutStatics.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsThread.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsXPCOM.h" michael@0: #include "nsXPCOMPrivate.h" michael@0: #include "OSFileConstants.h" michael@0: #include "xpcpublic.h" michael@0: michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: #include "ipc/Nuwa.h" michael@0: #endif michael@0: michael@0: #ifdef DEBUG michael@0: #include "nsThreadManager.h" michael@0: #endif michael@0: michael@0: #include "SharedWorker.h" michael@0: #include "WorkerPrivate.h" michael@0: #include "WorkerRunnable.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: USING_WORKERS_NAMESPACE michael@0: michael@0: using mozilla::MutexAutoLock; michael@0: using mozilla::MutexAutoUnlock; michael@0: using mozilla::Preferences; michael@0: michael@0: // The size of the worker runtime heaps in bytes. May be changed via pref. michael@0: #define WORKER_DEFAULT_RUNTIME_HEAPSIZE 32 * 1024 * 1024 michael@0: michael@0: // The size of the worker JS allocation threshold in MB. May be changed via pref. michael@0: #define WORKER_DEFAULT_ALLOCATION_THRESHOLD 30 michael@0: michael@0: // The C stack size. We use the same stack size on all platforms for michael@0: // consistency. michael@0: #define WORKER_STACK_SIZE 256 * sizeof(size_t) * 1024 michael@0: michael@0: // Half the size of the actual C stack, to be safe. michael@0: #define WORKER_CONTEXT_NATIVE_STACK_LIMIT 128 * sizeof(size_t) * 1024 michael@0: michael@0: // The maximum number of threads to use for workers, overridable via pref. michael@0: #define MAX_WORKERS_PER_DOMAIN 10 michael@0: michael@0: static_assert(MAX_WORKERS_PER_DOMAIN >= 1, michael@0: "We should allow at least one worker per domain."); michael@0: michael@0: // The default number of seconds that close handlers will be allowed to run for michael@0: // content workers. michael@0: #define MAX_SCRIPT_RUN_TIME_SEC 10 michael@0: michael@0: // The number of seconds that idle threads can hang around before being killed. michael@0: #define IDLE_THREAD_TIMEOUT_SEC 30 michael@0: michael@0: // The maximum number of threads that can be idle at one time. michael@0: #define MAX_IDLE_THREADS 20 michael@0: michael@0: #define PREF_WORKERS_PREFIX "dom.workers." michael@0: #define PREF_WORKERS_MAX_PER_DOMAIN PREF_WORKERS_PREFIX "maxPerDomain" michael@0: michael@0: #define PREF_MAX_SCRIPT_RUN_TIME_CONTENT "dom.max_script_run_time" michael@0: #define PREF_MAX_SCRIPT_RUN_TIME_CHROME "dom.max_chrome_script_run_time" michael@0: michael@0: #define GC_REQUEST_OBSERVER_TOPIC "child-gc-request" michael@0: #define CC_REQUEST_OBSERVER_TOPIC "child-cc-request" michael@0: #define MEMORY_PRESSURE_OBSERVER_TOPIC "memory-pressure" michael@0: michael@0: #define PREF_GENERAL_APPNAME_OVERRIDE "general.appname.override" michael@0: #define PREF_GENERAL_APPVERSION_OVERRIDE "general.appversion.override" michael@0: #define PREF_GENERAL_PLATFORM_OVERRIDE "general.platform.override" michael@0: michael@0: #define BROADCAST_ALL_WORKERS(_func, ...) \ michael@0: PR_BEGIN_MACRO \ michael@0: AssertIsOnMainThread(); \ michael@0: \ michael@0: nsAutoTArray workers; \ michael@0: { \ michael@0: MutexAutoLock lock(mMutex); \ michael@0: \ michael@0: mDomainMap.EnumerateRead(AddAllTopLevelWorkersToArray, &workers); \ michael@0: } \ michael@0: \ michael@0: if (!workers.IsEmpty()) { \ michael@0: AutoSafeJSContext cx; \ michael@0: JSAutoRequest ar(cx); \ michael@0: for (uint32_t index = 0; index < workers.Length(); index++) { \ michael@0: workers[index]-> _func (cx, __VA_ARGS__); \ michael@0: } \ michael@0: } \ michael@0: PR_END_MACRO michael@0: michael@0: // Prefixes for observing preference changes. michael@0: #define PREF_JS_OPTIONS_PREFIX "javascript.options." michael@0: #define PREF_WORKERS_OPTIONS_PREFIX PREF_WORKERS_PREFIX "options." michael@0: #define PREF_MEM_OPTIONS_PREFIX "mem." michael@0: #define PREF_GCZEAL "gcZeal" michael@0: michael@0: #if !(defined(DEBUG) || defined(MOZ_ENABLE_JS_DUMP)) michael@0: #define DUMP_CONTROLLED_BY_PREF 1 michael@0: #define PREF_DOM_WINDOW_DUMP_ENABLED "browser.dom.window.dump.enabled" michael@0: #endif michael@0: michael@0: #define PREF_WORKERS_LATEST_JS_VERSION "dom.workers.latestJSVersion" michael@0: michael@0: namespace { michael@0: michael@0: const uint32_t kNoIndex = uint32_t(-1); michael@0: michael@0: const JS::ContextOptions kRequiredContextOptions = michael@0: JS::ContextOptions().setDontReportUncaught(true) michael@0: .setNoScriptRval(true); michael@0: michael@0: uint32_t gMaxWorkersPerDomain = MAX_WORKERS_PER_DOMAIN; michael@0: michael@0: // Does not hold an owning reference. michael@0: RuntimeService* gRuntimeService = nullptr; michael@0: michael@0: // Only non-null during the call to Init. michael@0: RuntimeService* gRuntimeServiceDuringInit = nullptr; michael@0: michael@0: enum { michael@0: ID_Worker = 0, michael@0: ID_ChromeWorker, michael@0: ID_Event, michael@0: ID_MessageEvent, michael@0: ID_ErrorEvent, michael@0: michael@0: ID_COUNT michael@0: }; michael@0: michael@0: // These are jsids for the main runtime. Only touched on the main thread. michael@0: jsid gStringIDs[ID_COUNT] = { JSID_VOID }; michael@0: michael@0: const char* gStringChars[] = { michael@0: "Worker", michael@0: "ChromeWorker", michael@0: "Event", michael@0: "MessageEvent", michael@0: "ErrorEvent" michael@0: michael@0: // XXX Don't care about ProgressEvent since it should never leak to the main michael@0: // thread. michael@0: }; michael@0: michael@0: static_assert(MOZ_ARRAY_LENGTH(gStringChars) == ID_COUNT, michael@0: "gStringChars should have the right length."); michael@0: michael@0: class LiteralRebindingCString : public nsDependentCString michael@0: { michael@0: public: michael@0: template michael@0: void RebindLiteral(const char (&aStr)[N]) michael@0: { michael@0: Rebind(aStr, N-1); michael@0: } michael@0: }; michael@0: michael@0: template michael@0: struct PrefTraits; michael@0: michael@0: template <> michael@0: struct PrefTraits michael@0: { michael@0: typedef bool PrefValueType; michael@0: michael@0: static const PrefValueType kDefaultValue = false; michael@0: michael@0: static inline PrefValueType michael@0: Get(const char* aPref) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: return Preferences::GetBool(aPref); michael@0: } michael@0: michael@0: static inline bool michael@0: Exists(const char* aPref) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: return Preferences::GetType(aPref) == nsIPrefBranch::PREF_BOOL; michael@0: } michael@0: }; michael@0: michael@0: template <> michael@0: struct PrefTraits michael@0: { michael@0: typedef int32_t PrefValueType; michael@0: michael@0: static inline PrefValueType michael@0: Get(const char* aPref) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: return Preferences::GetInt(aPref); michael@0: } michael@0: michael@0: static inline bool michael@0: Exists(const char* aPref) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: return Preferences::GetType(aPref) == nsIPrefBranch::PREF_INT; michael@0: } michael@0: }; michael@0: michael@0: template michael@0: T michael@0: GetWorkerPref(const nsACString& aPref, michael@0: const T aDefault = PrefTraits::kDefaultValue) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: typedef PrefTraits PrefHelper; michael@0: michael@0: T result; michael@0: michael@0: nsAutoCString prefName; michael@0: prefName.AssignLiteral(PREF_WORKERS_OPTIONS_PREFIX); michael@0: prefName.Append(aPref); michael@0: michael@0: if (PrefHelper::Exists(prefName.get())) { michael@0: result = PrefHelper::Get(prefName.get()); michael@0: } michael@0: else { michael@0: prefName.AssignLiteral(PREF_JS_OPTIONS_PREFIX); michael@0: prefName.Append(aPref); michael@0: michael@0: if (PrefHelper::Exists(prefName.get())) { michael@0: result = PrefHelper::Get(prefName.get()); michael@0: } michael@0: else { michael@0: result = aDefault; michael@0: } michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: // This function creates a key for a SharedWorker composed by "name|scriptSpec". michael@0: // If the name contains a '|', this will be replaced by '||'. michael@0: void michael@0: GenerateSharedWorkerKey(const nsACString& aScriptSpec, const nsACString& aName, michael@0: nsCString& aKey) michael@0: { michael@0: aKey.Truncate(); michael@0: aKey.SetCapacity(aScriptSpec.Length() + aName.Length() + 1); michael@0: michael@0: nsACString::const_iterator start, end; michael@0: aName.BeginReading(start); michael@0: aName.EndReading(end); michael@0: for (; start != end; ++start) { michael@0: if (*start == '|') { michael@0: aKey.AppendASCII("||"); michael@0: } else { michael@0: aKey.Append(*start); michael@0: } michael@0: } michael@0: michael@0: aKey.Append('|'); michael@0: aKey.Append(aScriptSpec); michael@0: } michael@0: michael@0: void michael@0: LoadRuntimeAndContextOptions(const char* aPrefName, void* /* aClosure */) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: RuntimeService* rts = RuntimeService::GetService(); michael@0: if (!rts && !gRuntimeServiceDuringInit) { michael@0: // May be shutting down, just bail. michael@0: return; michael@0: } michael@0: michael@0: const nsDependentCString prefName(aPrefName); michael@0: michael@0: // Several other pref branches will get included here so bail out if there is michael@0: // another callback that will handle this change. michael@0: if (StringBeginsWith(prefName, michael@0: NS_LITERAL_CSTRING(PREF_JS_OPTIONS_PREFIX michael@0: PREF_MEM_OPTIONS_PREFIX)) || michael@0: StringBeginsWith(prefName, michael@0: NS_LITERAL_CSTRING(PREF_WORKERS_OPTIONS_PREFIX michael@0: PREF_MEM_OPTIONS_PREFIX))) { michael@0: return; michael@0: } michael@0: michael@0: #ifdef JS_GC_ZEAL michael@0: if (prefName.EqualsLiteral(PREF_JS_OPTIONS_PREFIX PREF_GCZEAL) || michael@0: prefName.EqualsLiteral(PREF_WORKERS_OPTIONS_PREFIX PREF_GCZEAL)) { michael@0: return; michael@0: } michael@0: #endif michael@0: michael@0: // Runtime options. michael@0: JS::RuntimeOptions runtimeOptions; michael@0: if (GetWorkerPref(NS_LITERAL_CSTRING("asmjs"))) { michael@0: runtimeOptions.setAsmJS(true); michael@0: } michael@0: if (GetWorkerPref(NS_LITERAL_CSTRING("baselinejit"))) { michael@0: runtimeOptions.setBaseline(true); michael@0: } michael@0: if (GetWorkerPref(NS_LITERAL_CSTRING("ion"))) { michael@0: runtimeOptions.setIon(true); michael@0: } michael@0: michael@0: // Common options. michael@0: JS::ContextOptions commonContextOptions = kRequiredContextOptions; michael@0: if (GetWorkerPref(NS_LITERAL_CSTRING("strict"))) { michael@0: commonContextOptions.setExtraWarnings(true); michael@0: } michael@0: if (GetWorkerPref(NS_LITERAL_CSTRING("werror"))) { michael@0: commonContextOptions.setWerror(true); michael@0: } michael@0: michael@0: // Content options. michael@0: JS::ContextOptions contentContextOptions = commonContextOptions; michael@0: michael@0: // Chrome options. michael@0: JS::ContextOptions chromeContextOptions = commonContextOptions; michael@0: #ifdef DEBUG michael@0: if (GetWorkerPref(NS_LITERAL_CSTRING("strict.debug"))) { michael@0: chromeContextOptions.setExtraWarnings(true); michael@0: } michael@0: #endif michael@0: michael@0: RuntimeService::SetDefaultRuntimeAndContextOptions(runtimeOptions, michael@0: contentContextOptions, michael@0: chromeContextOptions); michael@0: michael@0: if (rts) { michael@0: rts->UpdateAllWorkerRuntimeAndContextOptions(); michael@0: } michael@0: } michael@0: michael@0: #ifdef JS_GC_ZEAL michael@0: void michael@0: LoadGCZealOptions(const char* /* aPrefName */, void* /* aClosure */) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: RuntimeService* rts = RuntimeService::GetService(); michael@0: if (!rts && !gRuntimeServiceDuringInit) { michael@0: // May be shutting down, just bail. michael@0: return; michael@0: } michael@0: michael@0: int32_t gczeal = GetWorkerPref(NS_LITERAL_CSTRING(PREF_GCZEAL), -1); michael@0: if (gczeal < 0) { michael@0: gczeal = 0; michael@0: } michael@0: michael@0: int32_t frequency = michael@0: GetWorkerPref(NS_LITERAL_CSTRING("gcZeal.frequency"), -1); michael@0: if (frequency < 0) { michael@0: frequency = JS_DEFAULT_ZEAL_FREQ; michael@0: } michael@0: michael@0: RuntimeService::SetDefaultGCZeal(uint8_t(gczeal), uint32_t(frequency)); michael@0: michael@0: if (rts) { michael@0: rts->UpdateAllWorkerGCZeal(); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: void michael@0: UpdateCommonJSGCMemoryOption(RuntimeService* aRuntimeService, michael@0: const nsACString& aPrefName, JSGCParamKey aKey) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: NS_ASSERTION(!aPrefName.IsEmpty(), "Empty pref name!"); michael@0: michael@0: int32_t prefValue = GetWorkerPref(aPrefName, -1); michael@0: uint32_t value = michael@0: (prefValue < 0 || prefValue >= 10000) ? 0 : uint32_t(prefValue); michael@0: michael@0: RuntimeService::SetDefaultJSGCSettings(aKey, value); michael@0: michael@0: if (aRuntimeService) { michael@0: aRuntimeService->UpdateAllWorkerMemoryParameter(aKey, value); michael@0: } michael@0: } michael@0: michael@0: void michael@0: UpdatOtherJSGCMemoryOption(RuntimeService* aRuntimeService, michael@0: JSGCParamKey aKey, uint32_t aValue) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: RuntimeService::SetDefaultJSGCSettings(aKey, aValue); michael@0: michael@0: if (aRuntimeService) { michael@0: aRuntimeService->UpdateAllWorkerMemoryParameter(aKey, aValue); michael@0: } michael@0: } michael@0: michael@0: michael@0: void michael@0: LoadJSGCMemoryOptions(const char* aPrefName, void* /* aClosure */) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: RuntimeService* rts = RuntimeService::GetService(); michael@0: michael@0: if (!rts && !gRuntimeServiceDuringInit) { michael@0: // May be shutting down, just bail. michael@0: return; michael@0: } michael@0: michael@0: NS_NAMED_LITERAL_CSTRING(jsPrefix, PREF_JS_OPTIONS_PREFIX); michael@0: NS_NAMED_LITERAL_CSTRING(workersPrefix, PREF_WORKERS_OPTIONS_PREFIX); michael@0: michael@0: const nsDependentCString fullPrefName(aPrefName); michael@0: michael@0: // Pull out the string that actually distinguishes the parameter we need to michael@0: // change. michael@0: nsDependentCSubstring memPrefName; michael@0: if (StringBeginsWith(fullPrefName, jsPrefix)) { michael@0: memPrefName.Rebind(fullPrefName, jsPrefix.Length()); michael@0: } michael@0: else if (StringBeginsWith(fullPrefName, workersPrefix)) { michael@0: memPrefName.Rebind(fullPrefName, workersPrefix.Length()); michael@0: } michael@0: else { michael@0: NS_ERROR("Unknown pref name!"); michael@0: return; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: // During Init() we get called back with a branch string here, so there should michael@0: // be no just a "mem." pref here. michael@0: if (!rts) { michael@0: NS_ASSERTION(memPrefName.EqualsLiteral(PREF_MEM_OPTIONS_PREFIX), "Huh?!"); michael@0: } michael@0: #endif michael@0: michael@0: // If we're running in Init() then do this for every pref we care about. michael@0: // Otherwise we just want to update the parameter that changed. michael@0: for (uint32_t index = rts ? JSSettings::kGCSettingsArraySize - 1 : 0; michael@0: index < JSSettings::kGCSettingsArraySize; michael@0: index++) { michael@0: LiteralRebindingCString matchName; michael@0: michael@0: matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "max"); michael@0: if (memPrefName == matchName || (!rts && index == 0)) { michael@0: int32_t prefValue = GetWorkerPref(matchName, -1); michael@0: uint32_t value = (prefValue <= 0 || prefValue >= 0x1000) ? michael@0: uint32_t(-1) : michael@0: uint32_t(prefValue) * 1024 * 1024; michael@0: UpdatOtherJSGCMemoryOption(rts, JSGC_MAX_BYTES, value); michael@0: continue; michael@0: } michael@0: michael@0: matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "high_water_mark"); michael@0: if (memPrefName == matchName || (!rts && index == 1)) { michael@0: int32_t prefValue = GetWorkerPref(matchName, 128); michael@0: UpdatOtherJSGCMemoryOption(rts, JSGC_MAX_MALLOC_BYTES, michael@0: uint32_t(prefValue) * 1024 * 1024); michael@0: continue; michael@0: } michael@0: michael@0: matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX michael@0: "gc_high_frequency_time_limit_ms"); michael@0: if (memPrefName == matchName || (!rts && index == 2)) { michael@0: UpdateCommonJSGCMemoryOption(rts, matchName, michael@0: JSGC_HIGH_FREQUENCY_TIME_LIMIT); michael@0: continue; michael@0: } michael@0: michael@0: matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX michael@0: "gc_low_frequency_heap_growth"); michael@0: if (memPrefName == matchName || (!rts && index == 3)) { michael@0: UpdateCommonJSGCMemoryOption(rts, matchName, michael@0: JSGC_LOW_FREQUENCY_HEAP_GROWTH); michael@0: continue; michael@0: } michael@0: michael@0: matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX michael@0: "gc_high_frequency_heap_growth_min"); michael@0: if (memPrefName == matchName || (!rts && index == 4)) { michael@0: UpdateCommonJSGCMemoryOption(rts, matchName, michael@0: JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MIN); michael@0: continue; michael@0: } michael@0: michael@0: matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX michael@0: "gc_high_frequency_heap_growth_max"); michael@0: if (memPrefName == matchName || (!rts && index == 5)) { michael@0: UpdateCommonJSGCMemoryOption(rts, matchName, michael@0: JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MAX); michael@0: continue; michael@0: } michael@0: michael@0: matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX michael@0: "gc_high_frequency_low_limit_mb"); michael@0: if (memPrefName == matchName || (!rts && index == 6)) { michael@0: UpdateCommonJSGCMemoryOption(rts, matchName, michael@0: JSGC_HIGH_FREQUENCY_LOW_LIMIT); michael@0: continue; michael@0: } michael@0: michael@0: matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX michael@0: "gc_high_frequency_high_limit_mb"); michael@0: if (memPrefName == matchName || (!rts && index == 7)) { michael@0: UpdateCommonJSGCMemoryOption(rts, matchName, michael@0: JSGC_HIGH_FREQUENCY_HIGH_LIMIT); michael@0: continue; michael@0: } michael@0: michael@0: matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX michael@0: "gc_allocation_threshold_mb"); michael@0: if (memPrefName == matchName || (!rts && index == 8)) { michael@0: UpdateCommonJSGCMemoryOption(rts, matchName, JSGC_ALLOCATION_THRESHOLD); michael@0: continue; michael@0: } michael@0: michael@0: matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "gc_incremental_slice_ms"); michael@0: if (memPrefName == matchName || (!rts && index == 9)) { michael@0: int32_t prefValue = GetWorkerPref(matchName, -1); michael@0: uint32_t value = michael@0: (prefValue <= 0 || prefValue >= 100000) ? 0 : uint32_t(prefValue); michael@0: UpdatOtherJSGCMemoryOption(rts, JSGC_SLICE_TIME_BUDGET, value); michael@0: continue; michael@0: } michael@0: michael@0: matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "gc_dynamic_heap_growth"); michael@0: if (memPrefName == matchName || (!rts && index == 10)) { michael@0: bool prefValue = GetWorkerPref(matchName, false); michael@0: UpdatOtherJSGCMemoryOption(rts, JSGC_DYNAMIC_HEAP_GROWTH, michael@0: prefValue ? 0 : 1); michael@0: continue; michael@0: } michael@0: michael@0: matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "gc_dynamic_mark_slice"); michael@0: if (memPrefName == matchName || (!rts && index == 11)) { michael@0: bool prefValue = GetWorkerPref(matchName, false); michael@0: UpdatOtherJSGCMemoryOption(rts, JSGC_DYNAMIC_MARK_SLICE, michael@0: prefValue ? 0 : 1); michael@0: continue; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: nsAutoCString message("Workers don't support the 'mem."); michael@0: message.Append(memPrefName); michael@0: message.AppendLiteral("' preference!"); michael@0: NS_WARNING(message.get()); michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: void michael@0: ErrorReporter(JSContext* aCx, const char* aMessage, JSErrorReport* aReport) michael@0: { michael@0: WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx); michael@0: MOZ_ASSERT(worker); michael@0: michael@0: return worker->ReportError(aCx, aMessage, aReport); michael@0: } michael@0: michael@0: bool michael@0: InterruptCallback(JSContext* aCx) michael@0: { michael@0: WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx); michael@0: MOZ_ASSERT(worker); michael@0: michael@0: // Now is a good time to turn on profiling if it's pending. michael@0: profiler_js_operation_callback(); michael@0: michael@0: return worker->InterruptCallback(aCx); michael@0: } michael@0: michael@0: class LogViolationDetailsRunnable MOZ_FINAL : public nsRunnable michael@0: { michael@0: WorkerPrivate* mWorkerPrivate; michael@0: nsCOMPtr mSyncLoopTarget; michael@0: nsString mFileName; michael@0: uint32_t mLineNum; michael@0: michael@0: public: michael@0: LogViolationDetailsRunnable(WorkerPrivate* aWorker, michael@0: const nsString& aFileName, michael@0: uint32_t aLineNum) michael@0: : mWorkerPrivate(aWorker), mFileName(aFileName), mLineNum(aLineNum) michael@0: { michael@0: MOZ_ASSERT(aWorker); michael@0: } michael@0: michael@0: NS_DECL_ISUPPORTS_INHERITED michael@0: michael@0: bool michael@0: Dispatch(JSContext* aCx) michael@0: { michael@0: AutoSyncLoopHolder syncLoop(mWorkerPrivate); michael@0: michael@0: mSyncLoopTarget = syncLoop.EventTarget(); michael@0: MOZ_ASSERT(mSyncLoopTarget); michael@0: michael@0: if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) { michael@0: JS_ReportError(aCx, "Failed to dispatch to main thread!"); michael@0: return false; michael@0: } michael@0: michael@0: return syncLoop.Run(); michael@0: } michael@0: michael@0: private: michael@0: NS_DECL_NSIRUNNABLE michael@0: }; michael@0: michael@0: bool michael@0: ContentSecurityPolicyAllows(JSContext* aCx) michael@0: { michael@0: WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx); michael@0: worker->AssertIsOnWorkerThread(); michael@0: michael@0: if (worker->GetReportCSPViolations()) { michael@0: nsString fileName; michael@0: uint32_t lineNum = 0; michael@0: michael@0: JS::AutoFilename file; michael@0: if (JS::DescribeScriptedCaller(aCx, &file, &lineNum) && file.get()) { michael@0: fileName = NS_ConvertUTF8toUTF16(file.get()); michael@0: } else { michael@0: JS_ReportPendingException(aCx); michael@0: } michael@0: michael@0: nsRefPtr runnable = michael@0: new LogViolationDetailsRunnable(worker, fileName, lineNum); michael@0: michael@0: if (!runnable->Dispatch(aCx)) { michael@0: JS_ReportPendingException(aCx); michael@0: } michael@0: } michael@0: michael@0: return worker->IsEvalAllowed(); michael@0: } michael@0: michael@0: void michael@0: CTypesActivityCallback(JSContext* aCx, michael@0: js::CTypesActivityType aType) michael@0: { michael@0: WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx); michael@0: worker->AssertIsOnWorkerThread(); michael@0: michael@0: switch (aType) { michael@0: case js::CTYPES_CALL_BEGIN: michael@0: worker->BeginCTypesCall(); michael@0: break; michael@0: michael@0: case js::CTYPES_CALL_END: michael@0: worker->EndCTypesCall(); michael@0: break; michael@0: michael@0: case js::CTYPES_CALLBACK_BEGIN: michael@0: worker->BeginCTypesCallback(); michael@0: break; michael@0: michael@0: case js::CTYPES_CALLBACK_END: michael@0: worker->EndCTypesCallback(); michael@0: break; michael@0: michael@0: default: michael@0: MOZ_CRASH("Unknown type flag!"); michael@0: } michael@0: } michael@0: michael@0: static nsIPrincipal* michael@0: GetPrincipalForAsmJSCacheOp() michael@0: { michael@0: WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); michael@0: if (!workerPrivate) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // asmjscache::OpenEntryForX guarnatee to only access the given nsIPrincipal michael@0: // from the main thread. michael@0: return workerPrivate->GetPrincipalDontAssertMainThread(); michael@0: } michael@0: michael@0: static bool michael@0: AsmJSCacheOpenEntryForRead(JS::Handle aGlobal, michael@0: const jschar* aBegin, michael@0: const jschar* aLimit, michael@0: size_t* aSize, michael@0: const uint8_t** aMemory, michael@0: intptr_t *aHandle) michael@0: { michael@0: nsIPrincipal* principal = GetPrincipalForAsmJSCacheOp(); michael@0: if (!principal) { michael@0: return false; michael@0: } michael@0: michael@0: return asmjscache::OpenEntryForRead(principal, aBegin, aLimit, aSize, aMemory, michael@0: aHandle); michael@0: } michael@0: michael@0: static bool michael@0: AsmJSCacheOpenEntryForWrite(JS::Handle aGlobal, michael@0: bool aInstalled, michael@0: const jschar* aBegin, michael@0: const jschar* aEnd, michael@0: size_t aSize, michael@0: uint8_t** aMemory, michael@0: intptr_t* aHandle) michael@0: { michael@0: nsIPrincipal* principal = GetPrincipalForAsmJSCacheOp(); michael@0: if (!principal) { michael@0: return false; michael@0: } michael@0: michael@0: return asmjscache::OpenEntryForWrite(principal, aInstalled, aBegin, aEnd, michael@0: aSize, aMemory, aHandle); michael@0: } michael@0: michael@0: struct WorkerThreadRuntimePrivate : public PerThreadAtomCache michael@0: { michael@0: WorkerPrivate* mWorkerPrivate; michael@0: }; michael@0: michael@0: JSContext* michael@0: CreateJSContextForWorker(WorkerPrivate* aWorkerPrivate, JSRuntime* aRuntime) michael@0: { michael@0: aWorkerPrivate->AssertIsOnWorkerThread(); michael@0: NS_ASSERTION(!aWorkerPrivate->GetJSContext(), "Already has a context!"); michael@0: michael@0: JSSettings settings; michael@0: aWorkerPrivate->CopyJSSettings(settings); michael@0: michael@0: JS::RuntimeOptionsRef(aRuntime) = settings.runtimeOptions; michael@0: michael@0: JSSettings::JSGCSettingsArray& gcSettings = settings.gcSettings; michael@0: michael@0: // This is the real place where we set the max memory for the runtime. michael@0: for (uint32_t index = 0; index < ArrayLength(gcSettings); index++) { michael@0: const JSSettings::JSGCSetting& setting = gcSettings[index]; michael@0: if (setting.IsSet()) { michael@0: NS_ASSERTION(setting.value, "Can't handle 0 values!"); michael@0: JS_SetGCParameter(aRuntime, setting.key, setting.value); michael@0: } michael@0: } michael@0: michael@0: JS_SetIsWorkerRuntime(aRuntime); michael@0: michael@0: JS_SetNativeStackQuota(aRuntime, WORKER_CONTEXT_NATIVE_STACK_LIMIT); michael@0: michael@0: // Security policy: michael@0: static JSSecurityCallbacks securityCallbacks = { michael@0: ContentSecurityPolicyAllows michael@0: }; michael@0: JS_SetSecurityCallbacks(aRuntime, &securityCallbacks); michael@0: michael@0: // DOM helpers: michael@0: static js::DOMCallbacks DOMCallbacks = { michael@0: InstanceClassHasProtoAtDepth michael@0: }; michael@0: SetDOMCallbacks(aRuntime, &DOMCallbacks); michael@0: michael@0: // Set up the asm.js cache callbacks michael@0: static JS::AsmJSCacheOps asmJSCacheOps = { michael@0: AsmJSCacheOpenEntryForRead, michael@0: asmjscache::CloseEntryForRead, michael@0: AsmJSCacheOpenEntryForWrite, michael@0: asmjscache::CloseEntryForWrite, michael@0: asmjscache::GetBuildId michael@0: }; michael@0: JS::SetAsmJSCacheOps(aRuntime, &asmJSCacheOps); michael@0: michael@0: JSContext* workerCx = JS_NewContext(aRuntime, 0); michael@0: if (!workerCx) { michael@0: NS_WARNING("Could not create new context!"); michael@0: return nullptr; michael@0: } michael@0: michael@0: auto rtPrivate = new WorkerThreadRuntimePrivate(); michael@0: memset(rtPrivate, 0, sizeof(WorkerThreadRuntimePrivate)); michael@0: rtPrivate->mWorkerPrivate = aWorkerPrivate; michael@0: JS_SetRuntimePrivate(aRuntime, rtPrivate); michael@0: michael@0: JS_SetErrorReporter(workerCx, ErrorReporter); michael@0: michael@0: JS_SetInterruptCallback(aRuntime, InterruptCallback); michael@0: michael@0: js::SetCTypesActivityCallback(aRuntime, CTypesActivityCallback); michael@0: michael@0: JS::ContextOptionsRef(workerCx) = michael@0: aWorkerPrivate->IsChromeWorker() ? settings.chrome.contextOptions michael@0: : settings.content.contextOptions; michael@0: michael@0: #ifdef JS_GC_ZEAL michael@0: JS_SetGCZeal(workerCx, settings.gcZeal, settings.gcZealFrequency); michael@0: #endif michael@0: michael@0: return workerCx; michael@0: } michael@0: michael@0: class WorkerJSRuntime : public mozilla::CycleCollectedJSRuntime michael@0: { michael@0: public: michael@0: // The heap size passed here doesn't matter, we will change it later in the michael@0: // call to JS_SetGCParameter inside CreateJSContextForWorker. michael@0: WorkerJSRuntime(JSRuntime* aParentRuntime, WorkerPrivate* aWorkerPrivate) michael@0: : CycleCollectedJSRuntime(aParentRuntime, michael@0: WORKER_DEFAULT_RUNTIME_HEAPSIZE, michael@0: JS_NO_HELPER_THREADS), michael@0: mWorkerPrivate(aWorkerPrivate) michael@0: { michael@0: } michael@0: michael@0: ~WorkerJSRuntime() michael@0: { michael@0: auto rtPrivate = static_cast(JS_GetRuntimePrivate(Runtime())); michael@0: delete rtPrivate; michael@0: JS_SetRuntimePrivate(Runtime(), nullptr); michael@0: michael@0: // The worker global should be unrooted and the shutdown cycle collection michael@0: // should break all remaining cycles. The superclass destructor will run michael@0: // the GC one final time and finalize any JSObjects that were participating michael@0: // in cycles that were broken during CC shutdown. michael@0: nsCycleCollector_shutdown(); michael@0: michael@0: // The CC is shut down, and the superclass destructor will GC, so make sure michael@0: // we don't try to CC again. michael@0: mWorkerPrivate = nullptr; michael@0: } michael@0: michael@0: virtual void michael@0: PrepareForForgetSkippable() MOZ_OVERRIDE michael@0: { michael@0: } michael@0: michael@0: virtual void michael@0: BeginCycleCollectionCallback() MOZ_OVERRIDE michael@0: { michael@0: } michael@0: michael@0: virtual void michael@0: EndCycleCollectionCallback(CycleCollectorResults &aResults) MOZ_OVERRIDE michael@0: { michael@0: } michael@0: michael@0: void michael@0: DispatchDeferredDeletion(bool aContinuation) MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(!aContinuation); michael@0: michael@0: // Do it immediately, no need for asynchronous behavior here. michael@0: nsCycleCollector_doDeferredDeletion(); michael@0: } michael@0: michael@0: virtual void CustomGCCallback(JSGCStatus aStatus) MOZ_OVERRIDE michael@0: { michael@0: if (!mWorkerPrivate) { michael@0: // We're shutting down, no need to do anything. michael@0: return; michael@0: } michael@0: michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: michael@0: if (aStatus == JSGC_END) { michael@0: nsCycleCollector_collect(nullptr); michael@0: } michael@0: } michael@0: michael@0: private: michael@0: WorkerPrivate* mWorkerPrivate; michael@0: }; michael@0: michael@0: class WorkerThreadPrimaryRunnable MOZ_FINAL : public nsRunnable michael@0: { michael@0: WorkerPrivate* mWorkerPrivate; michael@0: nsRefPtr mThread; michael@0: JSRuntime* mParentRuntime; michael@0: michael@0: class FinishedRunnable MOZ_FINAL : public nsRunnable michael@0: { michael@0: nsRefPtr mThread; michael@0: michael@0: public: michael@0: FinishedRunnable(already_AddRefed aThread) michael@0: : mThread(aThread) michael@0: { michael@0: MOZ_ASSERT(mThread); michael@0: } michael@0: michael@0: NS_DECL_ISUPPORTS_INHERITED michael@0: michael@0: private: michael@0: ~FinishedRunnable() michael@0: { } michael@0: michael@0: NS_DECL_NSIRUNNABLE michael@0: }; michael@0: michael@0: public: michael@0: WorkerThreadPrimaryRunnable(WorkerPrivate* aWorkerPrivate, michael@0: RuntimeService::WorkerThread* aThread, michael@0: JSRuntime* aParentRuntime) michael@0: : mWorkerPrivate(aWorkerPrivate), mThread(aThread), mParentRuntime(aParentRuntime) michael@0: { michael@0: MOZ_ASSERT(aWorkerPrivate); michael@0: MOZ_ASSERT(aThread); michael@0: } michael@0: michael@0: NS_DECL_ISUPPORTS_INHERITED michael@0: michael@0: private: michael@0: ~WorkerThreadPrimaryRunnable() michael@0: { } michael@0: michael@0: NS_DECL_NSIRUNNABLE michael@0: }; michael@0: michael@0: class WorkerTaskRunnable MOZ_FINAL : public WorkerRunnable michael@0: { michael@0: nsRefPtr mTask; michael@0: michael@0: public: michael@0: WorkerTaskRunnable(WorkerPrivate* aWorkerPrivate, WorkerTask* aTask) michael@0: : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), mTask(aTask) michael@0: { michael@0: MOZ_ASSERT(aTask); michael@0: } michael@0: michael@0: private: michael@0: virtual bool michael@0: PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE michael@0: { michael@0: // May be called on any thread! michael@0: return true; michael@0: } michael@0: michael@0: virtual void michael@0: PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, michael@0: bool aDispatchResult) MOZ_OVERRIDE michael@0: { michael@0: // May be called on any thread! michael@0: } michael@0: michael@0: virtual bool michael@0: WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE michael@0: { michael@0: return mTask->RunTask(aCx); michael@0: } michael@0: }; michael@0: michael@0: void michael@0: AppNameOverrideChanged(const char* /* aPrefName */, void* /* aClosure */) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: const nsAdoptingString& override = michael@0: mozilla::Preferences::GetString(PREF_GENERAL_APPNAME_OVERRIDE); michael@0: michael@0: RuntimeService* runtime = RuntimeService::GetService(); michael@0: if (runtime) { michael@0: runtime->UpdateAppNameOverridePreference(override); michael@0: } michael@0: } michael@0: michael@0: void michael@0: AppVersionOverrideChanged(const char* /* aPrefName */, void* /* aClosure */) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: const nsAdoptingString& override = michael@0: mozilla::Preferences::GetString(PREF_GENERAL_APPVERSION_OVERRIDE); michael@0: michael@0: RuntimeService* runtime = RuntimeService::GetService(); michael@0: if (runtime) { michael@0: runtime->UpdateAppVersionOverridePreference(override); michael@0: } michael@0: } michael@0: michael@0: void michael@0: PlatformOverrideChanged(const char* /* aPrefName */, void* /* aClosure */) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: const nsAdoptingString& override = michael@0: mozilla::Preferences::GetString(PREF_GENERAL_PLATFORM_OVERRIDE); michael@0: michael@0: RuntimeService* runtime = RuntimeService::GetService(); michael@0: if (runtime) { michael@0: runtime->UpdatePlatformOverridePreference(override); michael@0: } michael@0: } michael@0: michael@0: } /* anonymous namespace */ michael@0: michael@0: class RuntimeService::WorkerThread MOZ_FINAL : public nsThread michael@0: { michael@0: class Observer MOZ_FINAL : public nsIThreadObserver michael@0: { michael@0: WorkerPrivate* mWorkerPrivate; michael@0: michael@0: public: michael@0: Observer(WorkerPrivate* aWorkerPrivate) michael@0: : mWorkerPrivate(aWorkerPrivate) michael@0: { michael@0: MOZ_ASSERT(aWorkerPrivate); michael@0: aWorkerPrivate->AssertIsOnWorkerThread(); michael@0: } michael@0: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: michael@0: private: michael@0: ~Observer() michael@0: { michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: } michael@0: michael@0: NS_DECL_NSITHREADOBSERVER michael@0: }; michael@0: michael@0: WorkerPrivate* mWorkerPrivate; michael@0: nsRefPtr mObserver; michael@0: michael@0: #ifdef DEBUG michael@0: // Protected by nsThread::mLock. michael@0: bool mAcceptingNonWorkerRunnables; michael@0: #endif michael@0: michael@0: public: michael@0: static already_AddRefed michael@0: Create(); michael@0: michael@0: void michael@0: SetWorker(WorkerPrivate* aWorkerPrivate); michael@0: michael@0: NS_DECL_ISUPPORTS_INHERITED michael@0: michael@0: NS_IMETHOD michael@0: Dispatch(nsIRunnable* aRunnable, uint32_t aFlags) MOZ_OVERRIDE; michael@0: michael@0: #ifdef DEBUG michael@0: bool michael@0: IsAcceptingNonWorkerRunnables() michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: return mAcceptingNonWorkerRunnables; michael@0: } michael@0: michael@0: void michael@0: SetAcceptingNonWorkerRunnables(bool aAcceptingNonWorkerRunnables) michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: mAcceptingNonWorkerRunnables = aAcceptingNonWorkerRunnables; michael@0: } michael@0: #endif michael@0: michael@0: private: michael@0: WorkerThread() michael@0: : nsThread(nsThread::NOT_MAIN_THREAD, WORKER_STACK_SIZE), michael@0: mWorkerPrivate(nullptr) michael@0: #ifdef DEBUG michael@0: , mAcceptingNonWorkerRunnables(true) michael@0: #endif michael@0: { } michael@0: michael@0: ~WorkerThread() michael@0: { } michael@0: }; michael@0: michael@0: BEGIN_WORKERS_NAMESPACE michael@0: michael@0: // Entry point for main thread non-window globals. michael@0: bool michael@0: ResolveWorkerClasses(JSContext* aCx, JS::Handle aObj, JS::Handle aId, michael@0: JS::MutableHandle aObjp) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: MOZ_ASSERT(nsContentUtils::IsCallerChrome()); michael@0: michael@0: // Make sure our strings are interned. michael@0: if (JSID_IS_VOID(gStringIDs[0])) { michael@0: for (uint32_t i = 0; i < ID_COUNT; i++) { michael@0: JSString* str = JS_InternString(aCx, gStringChars[i]); michael@0: if (!str) { michael@0: while (i) { michael@0: gStringIDs[--i] = JSID_VOID; michael@0: } michael@0: return false; michael@0: } michael@0: gStringIDs[i] = INTERNED_STRING_TO_JSID(aCx, str); michael@0: } michael@0: } michael@0: michael@0: bool shouldResolve = false; michael@0: michael@0: for (uint32_t i = 0; i < ID_COUNT; i++) { michael@0: if (gStringIDs[i] == aId) { michael@0: shouldResolve = true; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (!shouldResolve) { michael@0: aObjp.set(nullptr); michael@0: return true; michael@0: } michael@0: michael@0: if (!WorkerBinding::GetConstructorObject(aCx, aObj) || michael@0: !ChromeWorkerBinding::GetConstructorObject(aCx, aObj) || michael@0: !ErrorEventBinding::GetConstructorObject(aCx, aObj) || michael@0: !MessageEventBinding::GetConstructorObject(aCx, aObj)) { michael@0: return false; michael@0: } michael@0: michael@0: aObjp.set(aObj); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: CancelWorkersForWindow(nsPIDOMWindow* aWindow) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: RuntimeService* runtime = RuntimeService::GetService(); michael@0: if (runtime) { michael@0: runtime->CancelWorkersForWindow(aWindow); michael@0: } michael@0: } michael@0: michael@0: void michael@0: SuspendWorkersForWindow(nsPIDOMWindow* aWindow) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: RuntimeService* runtime = RuntimeService::GetService(); michael@0: if (runtime) { michael@0: runtime->SuspendWorkersForWindow(aWindow); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ResumeWorkersForWindow(nsPIDOMWindow* aWindow) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: RuntimeService* runtime = RuntimeService::GetService(); michael@0: if (runtime) { michael@0: runtime->ResumeWorkersForWindow(aWindow); michael@0: } michael@0: } michael@0: michael@0: WorkerCrossThreadDispatcher::WorkerCrossThreadDispatcher( michael@0: WorkerPrivate* aWorkerPrivate) michael@0: : mMutex("WorkerCrossThreadDispatcher::mMutex"), michael@0: mWorkerPrivate(aWorkerPrivate) michael@0: { michael@0: MOZ_ASSERT(aWorkerPrivate); michael@0: } michael@0: michael@0: bool michael@0: WorkerCrossThreadDispatcher::PostTask(WorkerTask* aTask) michael@0: { michael@0: MOZ_ASSERT(aTask); michael@0: michael@0: MutexAutoLock lock(mMutex); michael@0: michael@0: if (!mWorkerPrivate) { michael@0: NS_WARNING("Posted a task to a WorkerCrossThreadDispatcher that is no " michael@0: "longer accepting tasks!"); michael@0: return false; michael@0: } michael@0: michael@0: nsRefPtr runnable = michael@0: new WorkerTaskRunnable(mWorkerPrivate, aTask); michael@0: return runnable->Dispatch(nullptr); michael@0: } michael@0: michael@0: WorkerPrivate* michael@0: GetWorkerPrivateFromContext(JSContext* aCx) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: MOZ_ASSERT(aCx); michael@0: michael@0: JSRuntime* rt = JS_GetRuntime(aCx); michael@0: MOZ_ASSERT(rt); michael@0: michael@0: void* rtPrivate = JS_GetRuntimePrivate(rt); michael@0: MOZ_ASSERT(rtPrivate); michael@0: michael@0: return static_cast(rtPrivate)->mWorkerPrivate; michael@0: } michael@0: michael@0: WorkerPrivate* michael@0: GetCurrentThreadWorkerPrivate() michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: michael@0: CycleCollectedJSRuntime* ccrt = CycleCollectedJSRuntime::Get(); michael@0: if (!ccrt) { michael@0: return nullptr; michael@0: } michael@0: michael@0: JSRuntime* rt = ccrt->Runtime(); michael@0: MOZ_ASSERT(rt); michael@0: michael@0: void* rtPrivate = JS_GetRuntimePrivate(rt); michael@0: MOZ_ASSERT(rtPrivate); michael@0: michael@0: return static_cast(rtPrivate)->mWorkerPrivate; michael@0: } michael@0: michael@0: bool michael@0: IsCurrentThreadRunningChromeWorker() michael@0: { michael@0: return GetCurrentThreadWorkerPrivate()->UsesSystemPrincipal(); michael@0: } michael@0: michael@0: JSContext* michael@0: GetCurrentThreadJSContext() michael@0: { michael@0: return GetCurrentThreadWorkerPrivate()->GetJSContext(); michael@0: } michael@0: michael@0: END_WORKERS_NAMESPACE michael@0: michael@0: // This is only touched on the main thread. Initialized in Init() below. michael@0: JSSettings RuntimeService::sDefaultJSSettings; michael@0: bool RuntimeService::sDefaultPreferences[WORKERPREF_COUNT] = { false }; michael@0: michael@0: RuntimeService::RuntimeService() michael@0: : mMutex("RuntimeService::mMutex"), mObserved(false), michael@0: mShuttingDown(false), mNavigatorPropertiesLoaded(false) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: NS_ASSERTION(!gRuntimeService, "More than one service!"); michael@0: } michael@0: michael@0: RuntimeService::~RuntimeService() michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: // gRuntimeService can be null if Init() fails. michael@0: NS_ASSERTION(!gRuntimeService || gRuntimeService == this, michael@0: "More than one service!"); michael@0: michael@0: gRuntimeService = nullptr; michael@0: } michael@0: michael@0: // static michael@0: RuntimeService* michael@0: RuntimeService::GetOrCreateService() michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: if (!gRuntimeService) { michael@0: nsRefPtr service = new RuntimeService(); michael@0: if (NS_FAILED(service->Init())) { michael@0: NS_WARNING("Failed to initialize!"); michael@0: service->Cleanup(); michael@0: return nullptr; michael@0: } michael@0: michael@0: // The observer service now owns us until shutdown. michael@0: gRuntimeService = service; michael@0: } michael@0: michael@0: return gRuntimeService; michael@0: } michael@0: michael@0: // static michael@0: RuntimeService* michael@0: RuntimeService::GetService() michael@0: { michael@0: return gRuntimeService; michael@0: } michael@0: michael@0: bool michael@0: RuntimeService::RegisterWorker(JSContext* aCx, WorkerPrivate* aWorkerPrivate) michael@0: { michael@0: aWorkerPrivate->AssertIsOnParentThread(); michael@0: michael@0: WorkerPrivate* parent = aWorkerPrivate->GetParent(); michael@0: if (!parent) { michael@0: AssertIsOnMainThread(); michael@0: michael@0: if (mShuttingDown) { michael@0: JS_ReportError(aCx, "Cannot create worker during shutdown!"); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: bool isSharedWorker = aWorkerPrivate->IsSharedWorker(); michael@0: michael@0: const nsCString& sharedWorkerName = aWorkerPrivate->SharedWorkerName(); michael@0: nsCString sharedWorkerScriptSpec; michael@0: michael@0: if (isSharedWorker) { michael@0: AssertIsOnMainThread(); michael@0: michael@0: nsCOMPtr scriptURI = aWorkerPrivate->GetResolvedScriptURI(); michael@0: NS_ASSERTION(scriptURI, "Null script URI!"); michael@0: michael@0: nsresult rv = scriptURI->GetSpec(sharedWorkerScriptSpec); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("GetSpec failed?!"); michael@0: xpc::Throw(aCx, rv); michael@0: return false; michael@0: } michael@0: michael@0: NS_ASSERTION(!sharedWorkerScriptSpec.IsEmpty(), "Empty spec!"); michael@0: } michael@0: michael@0: const nsCString& domain = aWorkerPrivate->Domain(); michael@0: michael@0: WorkerDomainInfo* domainInfo; michael@0: bool queued = false; michael@0: { michael@0: MutexAutoLock lock(mMutex); michael@0: michael@0: if (!mDomainMap.Get(domain, &domainInfo)) { michael@0: NS_ASSERTION(!parent, "Shouldn't have a parent here!"); michael@0: michael@0: domainInfo = new WorkerDomainInfo(); michael@0: domainInfo->mDomain = domain; michael@0: mDomainMap.Put(domain, domainInfo); michael@0: } michael@0: michael@0: queued = gMaxWorkersPerDomain && michael@0: domainInfo->ActiveWorkerCount() >= gMaxWorkersPerDomain && michael@0: !domain.IsEmpty(); michael@0: michael@0: if (queued) { michael@0: domainInfo->mQueuedWorkers.AppendElement(aWorkerPrivate); michael@0: } michael@0: else if (parent) { michael@0: domainInfo->mChildWorkerCount++; michael@0: } michael@0: else { michael@0: domainInfo->mActiveWorkers.AppendElement(aWorkerPrivate); michael@0: } michael@0: michael@0: if (isSharedWorker) { michael@0: nsAutoCString key; michael@0: GenerateSharedWorkerKey(sharedWorkerScriptSpec, sharedWorkerName, key); michael@0: MOZ_ASSERT(!domainInfo->mSharedWorkerInfos.Get(key)); michael@0: michael@0: SharedWorkerInfo* sharedWorkerInfo = michael@0: new SharedWorkerInfo(aWorkerPrivate, sharedWorkerScriptSpec, michael@0: sharedWorkerName); michael@0: domainInfo->mSharedWorkerInfos.Put(key, sharedWorkerInfo); michael@0: } michael@0: } michael@0: michael@0: // From here on out we must call UnregisterWorker if something fails! michael@0: if (parent) { michael@0: if (!parent->AddChildWorker(aCx, aWorkerPrivate)) { michael@0: UnregisterWorker(aCx, aWorkerPrivate); michael@0: return false; michael@0: } michael@0: } michael@0: else { michael@0: if (!mNavigatorPropertiesLoaded) { michael@0: Navigator::AppName(mNavigatorProperties.mAppName, michael@0: false /* aUsePrefOverriddenValue */); michael@0: if (NS_FAILED(Navigator::GetAppVersion(mNavigatorProperties.mAppVersion, michael@0: false /* aUsePrefOverriddenValue */)) || michael@0: NS_FAILED(Navigator::GetPlatform(mNavigatorProperties.mPlatform, michael@0: false /* aUsePrefOverriddenValue */)) || michael@0: NS_FAILED(NS_GetNavigatorUserAgent(mNavigatorProperties.mUserAgent))) { michael@0: JS_ReportError(aCx, "Failed to load navigator strings!"); michael@0: UnregisterWorker(aCx, aWorkerPrivate); michael@0: return false; michael@0: } michael@0: michael@0: mNavigatorProperties.mAppNameOverridden = michael@0: mozilla::Preferences::GetString(PREF_GENERAL_APPNAME_OVERRIDE); michael@0: mNavigatorProperties.mAppVersionOverridden = michael@0: mozilla::Preferences::GetString(PREF_GENERAL_APPVERSION_OVERRIDE); michael@0: mNavigatorProperties.mPlatformOverridden = michael@0: mozilla::Preferences::GetString(PREF_GENERAL_PLATFORM_OVERRIDE); michael@0: michael@0: mNavigatorPropertiesLoaded = true; michael@0: } michael@0: michael@0: nsPIDOMWindow* window = aWorkerPrivate->GetWindow(); michael@0: michael@0: nsTArray* windowArray; michael@0: if (!mWindowMap.Get(window, &windowArray)) { michael@0: windowArray = new nsTArray(1); michael@0: mWindowMap.Put(window, windowArray); michael@0: } michael@0: michael@0: if (!windowArray->Contains(aWorkerPrivate)) { michael@0: windowArray->AppendElement(aWorkerPrivate); michael@0: } else { michael@0: MOZ_ASSERT(aWorkerPrivate->IsSharedWorker()); michael@0: } michael@0: } michael@0: michael@0: if (!queued && !ScheduleWorker(aCx, aWorkerPrivate)) { michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: RuntimeService::UnregisterWorker(JSContext* aCx, WorkerPrivate* aWorkerPrivate) michael@0: { michael@0: aWorkerPrivate->AssertIsOnParentThread(); michael@0: michael@0: WorkerPrivate* parent = aWorkerPrivate->GetParent(); michael@0: if (!parent) { michael@0: AssertIsOnMainThread(); michael@0: } michael@0: michael@0: const nsCString& domain = aWorkerPrivate->Domain(); michael@0: michael@0: WorkerPrivate* queuedWorker = nullptr; michael@0: { michael@0: MutexAutoLock lock(mMutex); michael@0: michael@0: WorkerDomainInfo* domainInfo; michael@0: if (!mDomainMap.Get(domain, &domainInfo)) { michael@0: NS_ERROR("Don't have an entry for this domain!"); michael@0: } michael@0: michael@0: // Remove old worker from everywhere. michael@0: uint32_t index = domainInfo->mQueuedWorkers.IndexOf(aWorkerPrivate); michael@0: if (index != kNoIndex) { michael@0: // Was queued, remove from the list. michael@0: domainInfo->mQueuedWorkers.RemoveElementAt(index); michael@0: } michael@0: else if (parent) { michael@0: NS_ASSERTION(domainInfo->mChildWorkerCount, "Must be non-zero!"); michael@0: domainInfo->mChildWorkerCount--; michael@0: } michael@0: else { michael@0: NS_ASSERTION(domainInfo->mActiveWorkers.Contains(aWorkerPrivate), michael@0: "Don't know about this worker!"); michael@0: domainInfo->mActiveWorkers.RemoveElement(aWorkerPrivate); michael@0: } michael@0: michael@0: if (aWorkerPrivate->IsSharedWorker()) { michael@0: MatchSharedWorkerInfo match(aWorkerPrivate); michael@0: domainInfo->mSharedWorkerInfos.EnumerateRead(FindSharedWorkerInfo, michael@0: &match); michael@0: michael@0: if (match.mSharedWorkerInfo) { michael@0: nsAutoCString key; michael@0: GenerateSharedWorkerKey(match.mSharedWorkerInfo->mScriptSpec, michael@0: match.mSharedWorkerInfo->mName, key); michael@0: domainInfo->mSharedWorkerInfos.Remove(key); michael@0: } michael@0: } michael@0: michael@0: // See if there's a queued worker we can schedule. michael@0: if (domainInfo->ActiveWorkerCount() < gMaxWorkersPerDomain && michael@0: !domainInfo->mQueuedWorkers.IsEmpty()) { michael@0: queuedWorker = domainInfo->mQueuedWorkers[0]; michael@0: domainInfo->mQueuedWorkers.RemoveElementAt(0); michael@0: michael@0: if (queuedWorker->GetParent()) { michael@0: domainInfo->mChildWorkerCount++; michael@0: } michael@0: else { michael@0: domainInfo->mActiveWorkers.AppendElement(queuedWorker); michael@0: } michael@0: } michael@0: michael@0: if (!domainInfo->ActiveWorkerCount()) { michael@0: MOZ_ASSERT(domainInfo->mQueuedWorkers.IsEmpty()); michael@0: mDomainMap.Remove(domain); michael@0: } michael@0: } michael@0: michael@0: if (aWorkerPrivate->IsSharedWorker()) { michael@0: AssertIsOnMainThread(); michael@0: michael@0: nsAutoTArray, 5> sharedWorkersToNotify; michael@0: aWorkerPrivate->GetAllSharedWorkers(sharedWorkersToNotify); michael@0: michael@0: for (uint32_t index = 0; index < sharedWorkersToNotify.Length(); index++) { michael@0: MOZ_ASSERT(sharedWorkersToNotify[index]); michael@0: sharedWorkersToNotify[index]->NoteDeadWorker(aCx); michael@0: } michael@0: } michael@0: michael@0: if (parent) { michael@0: parent->RemoveChildWorker(aCx, aWorkerPrivate); michael@0: } michael@0: else if (aWorkerPrivate->IsSharedWorker()) { michael@0: mWindowMap.Enumerate(RemoveSharedWorkerFromWindowMap, aWorkerPrivate); michael@0: } michael@0: else { michael@0: // May be null. michael@0: nsPIDOMWindow* window = aWorkerPrivate->GetWindow(); michael@0: michael@0: nsTArray* windowArray; michael@0: MOZ_ALWAYS_TRUE(mWindowMap.Get(window, &windowArray)); michael@0: michael@0: MOZ_ALWAYS_TRUE(windowArray->RemoveElement(aWorkerPrivate)); michael@0: michael@0: if (windowArray->IsEmpty()) { michael@0: mWindowMap.Remove(window); michael@0: } michael@0: } michael@0: michael@0: if (queuedWorker && !ScheduleWorker(aCx, queuedWorker)) { michael@0: UnregisterWorker(aCx, queuedWorker); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: RuntimeService::ScheduleWorker(JSContext* aCx, WorkerPrivate* aWorkerPrivate) michael@0: { michael@0: if (!aWorkerPrivate->Start()) { michael@0: // This is ok, means that we didn't need to make a thread for this worker. michael@0: return true; michael@0: } michael@0: michael@0: nsRefPtr thread; michael@0: { michael@0: MutexAutoLock lock(mMutex); michael@0: if (!mIdleThreadArray.IsEmpty()) { michael@0: uint32_t index = mIdleThreadArray.Length() - 1; michael@0: mIdleThreadArray[index].mThread.swap(thread); michael@0: mIdleThreadArray.RemoveElementAt(index); michael@0: } michael@0: } michael@0: michael@0: if (!thread) { michael@0: thread = WorkerThread::Create(); michael@0: if (!thread) { michael@0: UnregisterWorker(aCx, aWorkerPrivate); michael@0: JS_ReportError(aCx, "Could not create new thread!"); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: MOZ_ASSERT(thread->IsAcceptingNonWorkerRunnables()); michael@0: michael@0: int32_t priority = aWorkerPrivate->IsChromeWorker() ? michael@0: nsISupportsPriority::PRIORITY_NORMAL : michael@0: nsISupportsPriority::PRIORITY_LOW; michael@0: michael@0: if (NS_FAILED(thread->SetPriority(priority))) { michael@0: NS_WARNING("Could not set the thread's priority!"); michael@0: } michael@0: michael@0: nsCOMPtr runnable = michael@0: new WorkerThreadPrimaryRunnable(aWorkerPrivate, thread, JS_GetParentRuntime(aCx)); michael@0: if (NS_FAILED(thread->Dispatch(runnable, NS_DISPATCH_NORMAL))) { michael@0: UnregisterWorker(aCx, aWorkerPrivate); michael@0: JS_ReportError(aCx, "Could not dispatch to thread!"); michael@0: return false; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: thread->SetAcceptingNonWorkerRunnables(false); michael@0: #endif michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: RuntimeService::ShutdownIdleThreads(nsITimer* aTimer, void* /* aClosure */) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: RuntimeService* runtime = RuntimeService::GetService(); michael@0: NS_ASSERTION(runtime, "This should never be null!"); michael@0: michael@0: NS_ASSERTION(aTimer == runtime->mIdleThreadTimer, "Wrong timer!"); michael@0: michael@0: // Cheat a little and grab all threads that expire within one second of now. michael@0: TimeStamp now = TimeStamp::Now() + TimeDuration::FromSeconds(1); michael@0: michael@0: TimeStamp nextExpiration; michael@0: michael@0: nsAutoTArray, 20> expiredThreads; michael@0: { michael@0: MutexAutoLock lock(runtime->mMutex); michael@0: michael@0: for (uint32_t index = 0; index < runtime->mIdleThreadArray.Length(); michael@0: index++) { michael@0: IdleThreadInfo& info = runtime->mIdleThreadArray[index]; michael@0: if (info.mExpirationTime > now) { michael@0: nextExpiration = info.mExpirationTime; michael@0: break; michael@0: } michael@0: michael@0: nsRefPtr* thread = expiredThreads.AppendElement(); michael@0: thread->swap(info.mThread); michael@0: } michael@0: michael@0: if (!expiredThreads.IsEmpty()) { michael@0: runtime->mIdleThreadArray.RemoveElementsAt(0, expiredThreads.Length()); michael@0: } michael@0: } michael@0: michael@0: NS_ASSERTION(nextExpiration.IsNull() || !expiredThreads.IsEmpty(), michael@0: "Should have a new time or there should be some threads to shut " michael@0: "down"); michael@0: michael@0: for (uint32_t index = 0; index < expiredThreads.Length(); index++) { michael@0: if (NS_FAILED(expiredThreads[index]->Shutdown())) { michael@0: NS_WARNING("Failed to shutdown thread!"); michael@0: } michael@0: } michael@0: michael@0: if (!nextExpiration.IsNull()) { michael@0: TimeDuration delta = nextExpiration - TimeStamp::Now(); michael@0: uint32_t delay(delta > TimeDuration(0) ? delta.ToMilliseconds() : 0); michael@0: michael@0: // Reschedule the timer. michael@0: if (NS_FAILED(aTimer->InitWithFuncCallback(ShutdownIdleThreads, nullptr, michael@0: delay, michael@0: nsITimer::TYPE_ONE_SHOT))) { michael@0: NS_ERROR("Can't schedule timer!"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: RuntimeService::Init() michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: nsLayoutStatics::AddRef(); michael@0: michael@0: // Initialize JSSettings. michael@0: if (!sDefaultJSSettings.gcSettings[0].IsSet()) { michael@0: sDefaultJSSettings.runtimeOptions = JS::RuntimeOptions(); michael@0: sDefaultJSSettings.chrome.contextOptions = kRequiredContextOptions; michael@0: sDefaultJSSettings.chrome.maxScriptRuntime = -1; michael@0: sDefaultJSSettings.chrome.compartmentOptions.setVersion(JSVERSION_LATEST); michael@0: sDefaultJSSettings.content.contextOptions = kRequiredContextOptions; michael@0: sDefaultJSSettings.content.maxScriptRuntime = MAX_SCRIPT_RUN_TIME_SEC; michael@0: #ifdef JS_GC_ZEAL michael@0: sDefaultJSSettings.gcZealFrequency = JS_DEFAULT_ZEAL_FREQ; michael@0: sDefaultJSSettings.gcZeal = 0; michael@0: #endif michael@0: SetDefaultJSGCSettings(JSGC_MAX_BYTES, WORKER_DEFAULT_RUNTIME_HEAPSIZE); michael@0: SetDefaultJSGCSettings(JSGC_ALLOCATION_THRESHOLD, michael@0: WORKER_DEFAULT_ALLOCATION_THRESHOLD); michael@0: } michael@0: michael@0: // If dump is not controlled by pref, it's set to true. michael@0: #ifndef DUMP_CONTROLLED_BY_PREF michael@0: sDefaultPreferences[WORKERPREF_DUMP] = true; michael@0: #endif michael@0: michael@0: mIdleThreadTimer = do_CreateInstance(NS_TIMER_CONTRACTID); michael@0: NS_ENSURE_STATE(mIdleThreadTimer); michael@0: michael@0: nsCOMPtr obs = services::GetObserverService(); michael@0: NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE); michael@0: michael@0: nsresult rv = michael@0: obs->AddObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mObserved = true; michael@0: michael@0: if (NS_FAILED(obs->AddObserver(this, GC_REQUEST_OBSERVER_TOPIC, false))) { michael@0: NS_WARNING("Failed to register for GC request notifications!"); michael@0: } michael@0: michael@0: if (NS_FAILED(obs->AddObserver(this, CC_REQUEST_OBSERVER_TOPIC, false))) { michael@0: NS_WARNING("Failed to register for CC request notifications!"); michael@0: } michael@0: michael@0: if (NS_FAILED(obs->AddObserver(this, MEMORY_PRESSURE_OBSERVER_TOPIC, michael@0: false))) { michael@0: NS_WARNING("Failed to register for memory pressure notifications!"); michael@0: } michael@0: michael@0: if (NS_FAILED(obs->AddObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, false))) { michael@0: NS_WARNING("Failed to register for offline notification event!"); michael@0: } michael@0: michael@0: NS_ASSERTION(!gRuntimeServiceDuringInit, "This should be null!"); michael@0: gRuntimeServiceDuringInit = this; michael@0: michael@0: if (NS_FAILED(Preferences::RegisterCallback( michael@0: LoadJSGCMemoryOptions, michael@0: PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX, michael@0: nullptr)) || michael@0: NS_FAILED(Preferences::RegisterCallbackAndCall( michael@0: LoadJSGCMemoryOptions, michael@0: PREF_WORKERS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX, michael@0: nullptr)) || michael@0: #ifdef JS_GC_ZEAL michael@0: NS_FAILED(Preferences::RegisterCallback( michael@0: LoadGCZealOptions, michael@0: PREF_JS_OPTIONS_PREFIX PREF_GCZEAL, michael@0: nullptr)) || michael@0: NS_FAILED(Preferences::RegisterCallbackAndCall( michael@0: LoadGCZealOptions, michael@0: PREF_WORKERS_OPTIONS_PREFIX PREF_GCZEAL, michael@0: nullptr)) || michael@0: #endif michael@0: #if DUMP_CONTROLLED_BY_PREF michael@0: NS_FAILED(Preferences::RegisterCallbackAndCall( michael@0: WorkerPrefChanged, michael@0: PREF_DOM_WINDOW_DUMP_ENABLED, michael@0: reinterpret_cast(WORKERPREF_DUMP))) || michael@0: #endif michael@0: NS_FAILED(Preferences::RegisterCallback(LoadRuntimeAndContextOptions, michael@0: PREF_JS_OPTIONS_PREFIX, michael@0: nullptr)) || michael@0: NS_FAILED(Preferences::RegisterCallbackAndCall( michael@0: LoadRuntimeAndContextOptions, michael@0: PREF_WORKERS_OPTIONS_PREFIX, michael@0: nullptr)) || michael@0: NS_FAILED(Preferences::RegisterCallbackAndCall( michael@0: AppNameOverrideChanged, michael@0: PREF_GENERAL_APPNAME_OVERRIDE, michael@0: nullptr)) || michael@0: NS_FAILED(Preferences::RegisterCallbackAndCall( michael@0: AppVersionOverrideChanged, michael@0: PREF_GENERAL_APPVERSION_OVERRIDE, michael@0: nullptr)) || michael@0: NS_FAILED(Preferences::RegisterCallbackAndCall( michael@0: PlatformOverrideChanged, michael@0: PREF_GENERAL_PLATFORM_OVERRIDE, michael@0: nullptr)) || michael@0: NS_FAILED(Preferences::RegisterCallbackAndCall( michael@0: JSVersionChanged, michael@0: PREF_WORKERS_LATEST_JS_VERSION, michael@0: nullptr))) { michael@0: NS_WARNING("Failed to register pref callbacks!"); michael@0: } michael@0: michael@0: NS_ASSERTION(gRuntimeServiceDuringInit == this, "Should be 'this'!"); michael@0: gRuntimeServiceDuringInit = nullptr; michael@0: michael@0: // We assume atomic 32bit reads/writes. If this assumption doesn't hold on michael@0: // some wacky platform then the worst that could happen is that the close michael@0: // handler will run for a slightly different amount of time. michael@0: if (NS_FAILED(Preferences::AddIntVarCache( michael@0: &sDefaultJSSettings.content.maxScriptRuntime, michael@0: PREF_MAX_SCRIPT_RUN_TIME_CONTENT, michael@0: MAX_SCRIPT_RUN_TIME_SEC)) || michael@0: NS_FAILED(Preferences::AddIntVarCache( michael@0: &sDefaultJSSettings.chrome.maxScriptRuntime, michael@0: PREF_MAX_SCRIPT_RUN_TIME_CHROME, -1))) { michael@0: NS_WARNING("Failed to register timeout cache!"); michael@0: } michael@0: michael@0: int32_t maxPerDomain = Preferences::GetInt(PREF_WORKERS_MAX_PER_DOMAIN, michael@0: MAX_WORKERS_PER_DOMAIN); michael@0: gMaxWorkersPerDomain = std::max(0, maxPerDomain); michael@0: michael@0: rv = InitOSFileConstants(); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: RuntimeService::Shutdown() michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: MOZ_ASSERT(!mShuttingDown); michael@0: // That's it, no more workers. michael@0: mShuttingDown = true; michael@0: michael@0: nsCOMPtr obs = services::GetObserverService(); michael@0: NS_WARN_IF_FALSE(obs, "Failed to get observer service?!"); michael@0: michael@0: // Tell anyone that cares that they're about to lose worker support. michael@0: if (obs && NS_FAILED(obs->NotifyObservers(nullptr, WORKERS_SHUTDOWN_TOPIC, michael@0: nullptr))) { michael@0: NS_WARNING("NotifyObservers failed!"); michael@0: } michael@0: michael@0: { michael@0: MutexAutoLock lock(mMutex); michael@0: michael@0: nsAutoTArray workers; michael@0: mDomainMap.EnumerateRead(AddAllTopLevelWorkersToArray, &workers); michael@0: michael@0: if (!workers.IsEmpty()) { michael@0: // Cancel all top-level workers. michael@0: { michael@0: MutexAutoUnlock unlock(mMutex); michael@0: michael@0: AutoSafeJSContext cx; michael@0: JSAutoRequest ar(cx); michael@0: michael@0: for (uint32_t index = 0; index < workers.Length(); index++) { michael@0: if (!workers[index]->Kill(cx)) { michael@0: NS_WARNING("Failed to cancel worker!"); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // This spins the event loop until all workers are finished and their threads michael@0: // have been joined. michael@0: void michael@0: RuntimeService::Cleanup() michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: nsCOMPtr obs = services::GetObserverService(); michael@0: NS_WARN_IF_FALSE(obs, "Failed to get observer service?!"); michael@0: michael@0: if (mIdleThreadTimer) { michael@0: if (NS_FAILED(mIdleThreadTimer->Cancel())) { michael@0: NS_WARNING("Failed to cancel idle timer!"); michael@0: } michael@0: mIdleThreadTimer = nullptr; michael@0: } michael@0: michael@0: { michael@0: MutexAutoLock lock(mMutex); michael@0: michael@0: nsAutoTArray workers; michael@0: mDomainMap.EnumerateRead(AddAllTopLevelWorkersToArray, &workers); michael@0: michael@0: if (!workers.IsEmpty()) { michael@0: nsIThread* currentThread = NS_GetCurrentThread(); michael@0: NS_ASSERTION(currentThread, "This should never be null!"); michael@0: michael@0: // Shut down any idle threads. michael@0: if (!mIdleThreadArray.IsEmpty()) { michael@0: nsAutoTArray, 20> idleThreads; michael@0: michael@0: uint32_t idleThreadCount = mIdleThreadArray.Length(); michael@0: idleThreads.SetLength(idleThreadCount); michael@0: michael@0: for (uint32_t index = 0; index < idleThreadCount; index++) { michael@0: NS_ASSERTION(mIdleThreadArray[index].mThread, "Null thread!"); michael@0: idleThreads[index].swap(mIdleThreadArray[index].mThread); michael@0: } michael@0: michael@0: mIdleThreadArray.Clear(); michael@0: michael@0: MutexAutoUnlock unlock(mMutex); michael@0: michael@0: for (uint32_t index = 0; index < idleThreadCount; index++) { michael@0: if (NS_FAILED(idleThreads[index]->Shutdown())) { michael@0: NS_WARNING("Failed to shutdown thread!"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // And make sure all their final messages have run and all their threads michael@0: // have joined. michael@0: while (mDomainMap.Count()) { michael@0: MutexAutoUnlock unlock(mMutex); michael@0: michael@0: if (!NS_ProcessNextEvent(currentThread)) { michael@0: NS_WARNING("Something bad happened!"); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_ASSERTION(!mWindowMap.Count(), "All windows should have been released!"); michael@0: michael@0: if (mObserved) { michael@0: if (NS_FAILED(Preferences::UnregisterCallback(JSVersionChanged, michael@0: PREF_WORKERS_LATEST_JS_VERSION, michael@0: nullptr)) || michael@0: NS_FAILED(Preferences::UnregisterCallback(AppNameOverrideChanged, michael@0: PREF_GENERAL_APPNAME_OVERRIDE, michael@0: nullptr)) || michael@0: NS_FAILED(Preferences::UnregisterCallback(AppVersionOverrideChanged, michael@0: PREF_GENERAL_APPVERSION_OVERRIDE, michael@0: nullptr)) || michael@0: NS_FAILED(Preferences::UnregisterCallback(PlatformOverrideChanged, michael@0: PREF_GENERAL_PLATFORM_OVERRIDE, michael@0: nullptr)) || michael@0: NS_FAILED(Preferences::UnregisterCallback(LoadRuntimeAndContextOptions, michael@0: PREF_JS_OPTIONS_PREFIX, michael@0: nullptr)) || michael@0: NS_FAILED(Preferences::UnregisterCallback(LoadRuntimeAndContextOptions, michael@0: PREF_WORKERS_OPTIONS_PREFIX, michael@0: nullptr)) || michael@0: #if DUMP_CONTROLLED_BY_PREF michael@0: NS_FAILED(Preferences::UnregisterCallback( michael@0: WorkerPrefChanged, michael@0: PREF_DOM_WINDOW_DUMP_ENABLED, michael@0: reinterpret_cast(WORKERPREF_DUMP))) || michael@0: #endif michael@0: #ifdef JS_GC_ZEAL michael@0: NS_FAILED(Preferences::UnregisterCallback( michael@0: LoadGCZealOptions, michael@0: PREF_JS_OPTIONS_PREFIX PREF_GCZEAL, michael@0: nullptr)) || michael@0: NS_FAILED(Preferences::UnregisterCallback( michael@0: LoadGCZealOptions, michael@0: PREF_WORKERS_OPTIONS_PREFIX PREF_GCZEAL, michael@0: nullptr)) || michael@0: #endif michael@0: NS_FAILED(Preferences::UnregisterCallback( michael@0: LoadJSGCMemoryOptions, michael@0: PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX, michael@0: nullptr)) || michael@0: NS_FAILED(Preferences::UnregisterCallback( michael@0: LoadJSGCMemoryOptions, michael@0: PREF_WORKERS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX, michael@0: nullptr))) { michael@0: NS_WARNING("Failed to unregister pref callbacks!"); michael@0: } michael@0: michael@0: if (obs) { michael@0: if (NS_FAILED(obs->RemoveObserver(this, GC_REQUEST_OBSERVER_TOPIC))) { michael@0: NS_WARNING("Failed to unregister for GC request notifications!"); michael@0: } michael@0: michael@0: if (NS_FAILED(obs->RemoveObserver(this, CC_REQUEST_OBSERVER_TOPIC))) { michael@0: NS_WARNING("Failed to unregister for CC request notifications!"); michael@0: } michael@0: michael@0: if (NS_FAILED(obs->RemoveObserver(this, michael@0: MEMORY_PRESSURE_OBSERVER_TOPIC))) { michael@0: NS_WARNING("Failed to unregister for memory pressure notifications!"); michael@0: } michael@0: michael@0: if (NS_FAILED(obs->RemoveObserver(this, michael@0: NS_IOSERVICE_OFFLINE_STATUS_TOPIC))) { michael@0: NS_WARNING("Failed to unregister for offline notification event!"); michael@0: } michael@0: obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID); michael@0: obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); michael@0: mObserved = false; michael@0: } michael@0: } michael@0: michael@0: CleanupOSFileConstants(); michael@0: nsLayoutStatics::Release(); michael@0: } michael@0: michael@0: // static michael@0: PLDHashOperator michael@0: RuntimeService::AddAllTopLevelWorkersToArray(const nsACString& aKey, michael@0: WorkerDomainInfo* aData, michael@0: void* aUserArg) michael@0: { michael@0: nsTArray* array = michael@0: static_cast*>(aUserArg); michael@0: michael@0: #ifdef DEBUG michael@0: for (uint32_t index = 0; index < aData->mActiveWorkers.Length(); index++) { michael@0: NS_ASSERTION(!aData->mActiveWorkers[index]->GetParent(), michael@0: "Shouldn't have a parent in this list!"); michael@0: } michael@0: #endif michael@0: michael@0: array->AppendElements(aData->mActiveWorkers); michael@0: michael@0: // These might not be top-level workers... michael@0: for (uint32_t index = 0; index < aData->mQueuedWorkers.Length(); index++) { michael@0: WorkerPrivate* worker = aData->mQueuedWorkers[index]; michael@0: if (!worker->GetParent()) { michael@0: array->AppendElement(worker); michael@0: } michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: // static michael@0: PLDHashOperator michael@0: RuntimeService::RemoveSharedWorkerFromWindowMap( michael@0: nsPIDOMWindow* aKey, michael@0: nsAutoPtr >& aData, michael@0: void* aUserArg) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: MOZ_ASSERT(aData.get()); michael@0: MOZ_ASSERT(aUserArg); michael@0: michael@0: auto workerPrivate = static_cast(aUserArg); michael@0: michael@0: MOZ_ASSERT(workerPrivate->IsSharedWorker()); michael@0: michael@0: if (aData->RemoveElement(workerPrivate)) { michael@0: MOZ_ASSERT(!aData->Contains(workerPrivate), "Added worker more than once!"); michael@0: michael@0: if (aData->IsEmpty()) { michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: // static michael@0: PLDHashOperator michael@0: RuntimeService::FindSharedWorkerInfo(const nsACString& aKey, michael@0: SharedWorkerInfo* aData, michael@0: void* aUserArg) michael@0: { michael@0: auto match = static_cast(aUserArg); michael@0: michael@0: if (aData->mWorkerPrivate == match->mWorkerPrivate) { michael@0: match->mSharedWorkerInfo = aData; michael@0: return PL_DHASH_STOP; michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void michael@0: RuntimeService::GetWorkersForWindow(nsPIDOMWindow* aWindow, michael@0: nsTArray& aWorkers) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: nsTArray* workers; michael@0: if (mWindowMap.Get(aWindow, &workers)) { michael@0: NS_ASSERTION(!workers->IsEmpty(), "Should have been removed!"); michael@0: aWorkers.AppendElements(*workers); michael@0: } michael@0: else { michael@0: NS_ASSERTION(aWorkers.IsEmpty(), "Should be empty!"); michael@0: } michael@0: } michael@0: michael@0: void michael@0: RuntimeService::CancelWorkersForWindow(nsPIDOMWindow* aWindow) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: nsAutoTArray workers; michael@0: GetWorkersForWindow(aWindow, workers); michael@0: michael@0: if (!workers.IsEmpty()) { michael@0: nsCOMPtr sgo = do_QueryInterface(aWindow); michael@0: MOZ_ASSERT(sgo); michael@0: michael@0: nsIScriptContext* scx = sgo->GetContext(); michael@0: michael@0: AutoPushJSContext cx(scx ? michael@0: scx->GetNativeContext() : michael@0: nsContentUtils::GetSafeJSContext()); michael@0: michael@0: for (uint32_t index = 0; index < workers.Length(); index++) { michael@0: WorkerPrivate*& worker = workers[index]; michael@0: michael@0: if (worker->IsSharedWorker()) { michael@0: worker->CloseSharedWorkersForWindow(aWindow); michael@0: } else if (!worker->Cancel(cx)) { michael@0: JS_ReportPendingException(cx); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: RuntimeService::SuspendWorkersForWindow(nsPIDOMWindow* aWindow) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: MOZ_ASSERT(aWindow); michael@0: michael@0: nsAutoTArray workers; michael@0: GetWorkersForWindow(aWindow, workers); michael@0: michael@0: if (!workers.IsEmpty()) { michael@0: nsCOMPtr sgo = do_QueryInterface(aWindow); michael@0: MOZ_ASSERT(sgo); michael@0: michael@0: nsIScriptContext* scx = sgo->GetContext(); michael@0: michael@0: AutoPushJSContext cx(scx ? michael@0: scx->GetNativeContext() : michael@0: nsContentUtils::GetSafeJSContext()); michael@0: michael@0: for (uint32_t index = 0; index < workers.Length(); index++) { michael@0: if (!workers[index]->Suspend(cx, aWindow)) { michael@0: JS_ReportPendingException(cx); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: RuntimeService::ResumeWorkersForWindow(nsPIDOMWindow* aWindow) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: MOZ_ASSERT(aWindow); michael@0: michael@0: nsAutoTArray workers; michael@0: GetWorkersForWindow(aWindow, workers); michael@0: michael@0: if (!workers.IsEmpty()) { michael@0: nsCOMPtr sgo = do_QueryInterface(aWindow); michael@0: MOZ_ASSERT(sgo); michael@0: michael@0: nsIScriptContext* scx = sgo->GetContext(); michael@0: michael@0: AutoPushJSContext cx(scx ? michael@0: scx->GetNativeContext() : michael@0: nsContentUtils::GetSafeJSContext()); michael@0: michael@0: for (uint32_t index = 0; index < workers.Length(); index++) { michael@0: if (!workers[index]->SynchronizeAndResume(cx, aWindow, scx)) { michael@0: JS_ReportPendingException(cx); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: RuntimeService::CreateSharedWorker(const GlobalObject& aGlobal, michael@0: const nsAString& aScriptURL, michael@0: const nsACString& aName, michael@0: SharedWorker** aSharedWorker) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: nsCOMPtr window = do_QueryInterface(aGlobal.GetAsSupports()); michael@0: MOZ_ASSERT(window); michael@0: michael@0: JSContext* cx = aGlobal.GetContext(); michael@0: michael@0: WorkerPrivate::LoadInfo loadInfo; michael@0: nsresult rv = WorkerPrivate::GetLoadInfo(cx, window, nullptr, aScriptURL, michael@0: false, &loadInfo); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: MOZ_ASSERT(loadInfo.mResolvedScriptURI); michael@0: michael@0: nsCString scriptSpec; michael@0: rv = loadInfo.mResolvedScriptURI->GetSpec(scriptSpec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsRefPtr workerPrivate; michael@0: { michael@0: MutexAutoLock lock(mMutex); michael@0: michael@0: WorkerDomainInfo* domainInfo; michael@0: SharedWorkerInfo* sharedWorkerInfo; michael@0: michael@0: nsAutoCString key; michael@0: GenerateSharedWorkerKey(scriptSpec, aName, key); michael@0: michael@0: if (mDomainMap.Get(loadInfo.mDomain, &domainInfo) && michael@0: domainInfo->mSharedWorkerInfos.Get(key, &sharedWorkerInfo)) { michael@0: workerPrivate = sharedWorkerInfo->mWorkerPrivate; michael@0: } michael@0: } michael@0: michael@0: bool created = false; michael@0: michael@0: if (!workerPrivate) { michael@0: ErrorResult rv; michael@0: workerPrivate = michael@0: WorkerPrivate::Constructor(aGlobal, aScriptURL, false, michael@0: WorkerPrivate::WorkerTypeShared, aName, michael@0: &loadInfo, rv); michael@0: NS_ENSURE_TRUE(workerPrivate, rv.ErrorCode()); michael@0: michael@0: created = true; michael@0: } michael@0: michael@0: MOZ_ASSERT(workerPrivate->IsSharedWorker()); michael@0: michael@0: nsRefPtr sharedWorker = michael@0: new SharedWorker(window, workerPrivate); michael@0: michael@0: if (!workerPrivate->RegisterSharedWorker(cx, sharedWorker)) { michael@0: NS_WARNING("Worker is unreachable, this shouldn't happen!"); michael@0: sharedWorker->Close(); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // This is normally handled in RegisterWorker, but that wasn't called if the michael@0: // worker already existed. michael@0: if (!created) { michael@0: nsTArray* windowArray; michael@0: if (!mWindowMap.Get(window, &windowArray)) { michael@0: windowArray = new nsTArray(1); michael@0: mWindowMap.Put(window, windowArray); michael@0: } michael@0: michael@0: if (!windowArray->Contains(workerPrivate)) { michael@0: windowArray->AppendElement(workerPrivate); michael@0: } michael@0: } michael@0: michael@0: sharedWorker.forget(aSharedWorker); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: RuntimeService::ForgetSharedWorker(WorkerPrivate* aWorkerPrivate) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: MOZ_ASSERT(aWorkerPrivate); michael@0: MOZ_ASSERT(aWorkerPrivate->IsSharedWorker()); michael@0: michael@0: MutexAutoLock lock(mMutex); michael@0: michael@0: WorkerDomainInfo* domainInfo; michael@0: if (mDomainMap.Get(aWorkerPrivate->Domain(), &domainInfo)) { michael@0: MatchSharedWorkerInfo match(aWorkerPrivate); michael@0: domainInfo->mSharedWorkerInfos.EnumerateRead(FindSharedWorkerInfo, michael@0: &match); michael@0: michael@0: if (match.mSharedWorkerInfo) { michael@0: nsAutoCString key; michael@0: GenerateSharedWorkerKey(match.mSharedWorkerInfo->mScriptSpec, michael@0: match.mSharedWorkerInfo->mName, key); michael@0: domainInfo->mSharedWorkerInfos.Remove(key); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: RuntimeService::NoteIdleThread(WorkerThread* aThread) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: MOZ_ASSERT(aThread); michael@0: michael@0: #ifdef DEBUG michael@0: aThread->SetAcceptingNonWorkerRunnables(true); michael@0: #endif michael@0: michael@0: static TimeDuration timeout = michael@0: TimeDuration::FromSeconds(IDLE_THREAD_TIMEOUT_SEC); michael@0: michael@0: TimeStamp expirationTime = TimeStamp::Now() + timeout; michael@0: michael@0: bool shutdown; michael@0: if (mShuttingDown) { michael@0: shutdown = true; michael@0: } michael@0: else { michael@0: MutexAutoLock lock(mMutex); michael@0: michael@0: if (mIdleThreadArray.Length() < MAX_IDLE_THREADS) { michael@0: IdleThreadInfo* info = mIdleThreadArray.AppendElement(); michael@0: info->mThread = aThread; michael@0: info->mExpirationTime = expirationTime; michael@0: shutdown = false; michael@0: } michael@0: else { michael@0: shutdown = true; michael@0: } michael@0: } michael@0: michael@0: // Too many idle threads, just shut this one down. michael@0: if (shutdown) { michael@0: MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aThread->Shutdown())); michael@0: return; michael@0: } michael@0: michael@0: // Schedule timer. michael@0: MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mIdleThreadTimer->InitWithFuncCallback( michael@0: ShutdownIdleThreads, nullptr, michael@0: IDLE_THREAD_TIMEOUT_SEC * 1000, michael@0: nsITimer::TYPE_ONE_SHOT))); michael@0: } michael@0: michael@0: void michael@0: RuntimeService::UpdateAllWorkerRuntimeAndContextOptions() michael@0: { michael@0: BROADCAST_ALL_WORKERS(UpdateRuntimeAndContextOptions, michael@0: sDefaultJSSettings.runtimeOptions, michael@0: sDefaultJSSettings.content.contextOptions, michael@0: sDefaultJSSettings.chrome.contextOptions); michael@0: } michael@0: michael@0: void michael@0: RuntimeService::UpdateAppNameOverridePreference(const nsAString& aValue) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: mNavigatorProperties.mAppNameOverridden = aValue; michael@0: } michael@0: michael@0: void michael@0: RuntimeService::UpdateAppVersionOverridePreference(const nsAString& aValue) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: mNavigatorProperties.mAppVersionOverridden = aValue; michael@0: } michael@0: michael@0: void michael@0: RuntimeService::UpdatePlatformOverridePreference(const nsAString& aValue) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: mNavigatorProperties.mPlatformOverridden = aValue; michael@0: } michael@0: michael@0: void michael@0: RuntimeService::UpdateAllWorkerPreference(WorkerPreference aPref, bool aValue) michael@0: { michael@0: BROADCAST_ALL_WORKERS(UpdatePreference, aPref, aValue); michael@0: } michael@0: michael@0: void michael@0: RuntimeService::UpdateAllWorkerMemoryParameter(JSGCParamKey aKey, michael@0: uint32_t aValue) michael@0: { michael@0: BROADCAST_ALL_WORKERS(UpdateJSWorkerMemoryParameter, aKey, aValue); michael@0: } michael@0: michael@0: #ifdef JS_GC_ZEAL michael@0: void michael@0: RuntimeService::UpdateAllWorkerGCZeal() michael@0: { michael@0: BROADCAST_ALL_WORKERS(UpdateGCZeal, sDefaultJSSettings.gcZeal, michael@0: sDefaultJSSettings.gcZealFrequency); michael@0: } michael@0: #endif michael@0: michael@0: void michael@0: RuntimeService::GarbageCollectAllWorkers(bool aShrinking) michael@0: { michael@0: BROADCAST_ALL_WORKERS(GarbageCollect, aShrinking); michael@0: } michael@0: michael@0: void michael@0: RuntimeService::CycleCollectAllWorkers() michael@0: { michael@0: BROADCAST_ALL_WORKERS(CycleCollect, /* dummy = */ false); michael@0: } michael@0: michael@0: void michael@0: RuntimeService::SendOfflineStatusChangeEventToAllWorkers(bool aIsOffline) michael@0: { michael@0: BROADCAST_ALL_WORKERS(OfflineStatusChangeEvent, aIsOffline); michael@0: } michael@0: michael@0: // nsISupports michael@0: NS_IMPL_ISUPPORTS(RuntimeService, nsIObserver) michael@0: michael@0: // nsIObserver michael@0: NS_IMETHODIMP michael@0: RuntimeService::Observe(nsISupports* aSubject, const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { michael@0: Shutdown(); michael@0: return NS_OK; michael@0: } michael@0: if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID)) { michael@0: Cleanup(); michael@0: return NS_OK; michael@0: } michael@0: if (!strcmp(aTopic, GC_REQUEST_OBSERVER_TOPIC)) { michael@0: GarbageCollectAllWorkers(/* shrinking = */ false); michael@0: return NS_OK; michael@0: } michael@0: if (!strcmp(aTopic, CC_REQUEST_OBSERVER_TOPIC)) { michael@0: CycleCollectAllWorkers(); michael@0: return NS_OK; michael@0: } michael@0: if (!strcmp(aTopic, MEMORY_PRESSURE_OBSERVER_TOPIC)) { michael@0: GarbageCollectAllWorkers(/* shrinking = */ true); michael@0: CycleCollectAllWorkers(); michael@0: return NS_OK; michael@0: } michael@0: if (!strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC)) { michael@0: SendOfflineStatusChangeEventToAllWorkers(NS_IsOffline()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_NOTREACHED("Unknown observer topic!"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* static */ void michael@0: RuntimeService::WorkerPrefChanged(const char* aPrefName, void* aClosure) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: uintptr_t tmp = reinterpret_cast(aClosure); michael@0: MOZ_ASSERT(tmp < WORKERPREF_COUNT); michael@0: WorkerPreference key = static_cast(tmp); michael@0: michael@0: #ifdef DUMP_CONTROLLED_BY_PREF michael@0: if (key == WORKERPREF_DUMP) { michael@0: key = WORKERPREF_DUMP; michael@0: sDefaultPreferences[WORKERPREF_DUMP] = michael@0: Preferences::GetBool(PREF_DOM_WINDOW_DUMP_ENABLED, false); michael@0: } michael@0: #endif michael@0: michael@0: // This function should never be registered as a callback for a preference it michael@0: // does not handle. michael@0: MOZ_ASSERT(key != WORKERPREF_COUNT); michael@0: michael@0: RuntimeService* rts = RuntimeService::GetService(); michael@0: if (rts) { michael@0: rts->UpdateAllWorkerPreference(key, sDefaultPreferences[key]); michael@0: } michael@0: } michael@0: michael@0: void michael@0: RuntimeService::JSVersionChanged(const char* /* aPrefName */, void* /* aClosure */) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: bool useLatest = Preferences::GetBool(PREF_WORKERS_LATEST_JS_VERSION, false); michael@0: JS::CompartmentOptions& options = sDefaultJSSettings.content.compartmentOptions; michael@0: options.setVersion(useLatest ? JSVERSION_LATEST : JSVERSION_DEFAULT); michael@0: } michael@0: michael@0: // static michael@0: already_AddRefed michael@0: RuntimeService::WorkerThread::Create() michael@0: { michael@0: MOZ_ASSERT(nsThreadManager::get()); michael@0: michael@0: nsRefPtr thread = new WorkerThread(); michael@0: if (NS_FAILED(thread->Init())) { michael@0: NS_WARNING("Failed to create new thread!"); michael@0: return nullptr; michael@0: } michael@0: michael@0: NS_SetThreadName(thread, "DOM Worker"); michael@0: michael@0: return thread.forget(); michael@0: } michael@0: michael@0: void michael@0: RuntimeService::WorkerThread::SetWorker(WorkerPrivate* aWorkerPrivate) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == mThread); michael@0: MOZ_ASSERT_IF(aWorkerPrivate, !mWorkerPrivate); michael@0: MOZ_ASSERT_IF(!aWorkerPrivate, mWorkerPrivate); michael@0: michael@0: // No need to lock here because mWorkerPrivate is only modified on mThread. michael@0: michael@0: if (mWorkerPrivate) { michael@0: MOZ_ASSERT(mObserver); michael@0: michael@0: MOZ_ALWAYS_TRUE(NS_SUCCEEDED(RemoveObserver(mObserver))); michael@0: michael@0: mObserver = nullptr; michael@0: mWorkerPrivate->SetThread(nullptr); michael@0: } michael@0: michael@0: mWorkerPrivate = aWorkerPrivate; michael@0: michael@0: if (mWorkerPrivate) { michael@0: mWorkerPrivate->SetThread(this); michael@0: michael@0: nsRefPtr observer = new Observer(mWorkerPrivate); michael@0: michael@0: MOZ_ALWAYS_TRUE(NS_SUCCEEDED(AddObserver(observer))); michael@0: michael@0: mObserver.swap(observer); michael@0: } michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED0(RuntimeService::WorkerThread, nsThread) michael@0: michael@0: NS_IMETHODIMP michael@0: RuntimeService::WorkerThread::Dispatch(nsIRunnable* aRunnable, uint32_t aFlags) michael@0: { michael@0: // May be called on any thread! michael@0: michael@0: #ifdef DEBUG michael@0: if (PR_GetCurrentThread() == mThread) { michael@0: MOZ_ASSERT(mWorkerPrivate); michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: } michael@0: else if (aRunnable && !IsAcceptingNonWorkerRunnables()) { michael@0: // Only enforce cancelable runnables after we've started the worker loop. michael@0: nsCOMPtr cancelable = do_QueryInterface(aRunnable); michael@0: MOZ_ASSERT(cancelable, michael@0: "Should have been wrapped by the worker's event target!"); michael@0: } michael@0: #endif michael@0: michael@0: // Workers only support asynchronous dispatch for now. michael@0: if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsIRunnable* runnableToDispatch; michael@0: nsRefPtr workerRunnable; michael@0: michael@0: if (aRunnable && PR_GetCurrentThread() == mThread) { michael@0: // No need to lock here because mWorkerPrivate is only modified on mThread. michael@0: workerRunnable = mWorkerPrivate->MaybeWrapAsWorkerRunnable(aRunnable); michael@0: runnableToDispatch = workerRunnable; michael@0: } michael@0: else { michael@0: runnableToDispatch = aRunnable; michael@0: } michael@0: michael@0: nsresult rv = nsThread::Dispatch(runnableToDispatch, NS_DISPATCH_NORMAL); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: return rv; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(RuntimeService::WorkerThread::Observer, nsIThreadObserver) michael@0: michael@0: NS_IMETHODIMP michael@0: RuntimeService::WorkerThread::Observer::OnDispatchedEvent( michael@0: nsIThreadInternal* /*aThread */) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("This should never be called!"); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RuntimeService::WorkerThread::Observer::OnProcessNextEvent( michael@0: nsIThreadInternal* /* aThread */, michael@0: bool aMayWait, michael@0: uint32_t aRecursionDepth) michael@0: { michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: MOZ_ASSERT(!aMayWait); michael@0: michael@0: mWorkerPrivate->OnProcessNextEvent(aRecursionDepth); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RuntimeService::WorkerThread::Observer::AfterProcessNextEvent( michael@0: nsIThreadInternal* /* aThread */, michael@0: uint32_t aRecursionDepth, michael@0: bool /* aEventWasProcessed */) michael@0: { michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: michael@0: mWorkerPrivate->AfterProcessNextEvent(aRecursionDepth); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED0(LogViolationDetailsRunnable, nsRunnable) michael@0: michael@0: NS_IMETHODIMP michael@0: LogViolationDetailsRunnable::Run() michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: nsIContentSecurityPolicy* csp = mWorkerPrivate->GetCSP(); michael@0: if (csp) { michael@0: NS_NAMED_LITERAL_STRING(scriptSample, michael@0: "Call to eval() or related function blocked by CSP."); michael@0: if (mWorkerPrivate->GetReportCSPViolations()) { michael@0: csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL, michael@0: mFileName, scriptSample, mLineNum, michael@0: EmptyString(), EmptyString()); michael@0: } michael@0: } michael@0: michael@0: nsRefPtr response = michael@0: new MainThreadStopSyncLoopRunnable(mWorkerPrivate, mSyncLoopTarget.forget(), michael@0: true); michael@0: MOZ_ALWAYS_TRUE(response->Dispatch(nullptr)); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED0(WorkerThreadPrimaryRunnable, nsRunnable) michael@0: michael@0: NS_IMETHODIMP michael@0: WorkerThreadPrimaryRunnable::Run() michael@0: { michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: if (IsNuwaProcess()) { michael@0: NS_ASSERTION(NuwaMarkCurrentThread != nullptr, michael@0: "NuwaMarkCurrentThread is undefined!"); michael@0: NuwaMarkCurrentThread(nullptr, nullptr); michael@0: NuwaFreezeCurrentThread(); michael@0: } michael@0: #endif michael@0: michael@0: char stackBaseGuess; michael@0: michael@0: nsAutoCString threadName; michael@0: threadName.AssignLiteral("WebWorker '"); michael@0: threadName.Append(NS_LossyConvertUTF16toASCII(mWorkerPrivate->ScriptURL())); michael@0: threadName.Append('\''); michael@0: michael@0: profiler_register_thread(threadName.get(), &stackBaseGuess); michael@0: michael@0: mThread->SetWorker(mWorkerPrivate); michael@0: michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: michael@0: { michael@0: nsCycleCollector_startup(); michael@0: michael@0: WorkerJSRuntime runtime(mParentRuntime, mWorkerPrivate); michael@0: JSRuntime* rt = runtime.Runtime(); michael@0: michael@0: JSContext* cx = CreateJSContextForWorker(mWorkerPrivate, rt); michael@0: if (!cx) { michael@0: // XXX need to fire an error at parent. michael@0: NS_ERROR("Failed to create runtime and context!"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: { michael@0: #ifdef MOZ_ENABLE_PROFILER_SPS michael@0: PseudoStack* stack = mozilla_get_pseudo_stack(); michael@0: if (stack) { michael@0: stack->sampleRuntime(rt); michael@0: } michael@0: #endif michael@0: michael@0: { michael@0: JSAutoRequest ar(cx); michael@0: michael@0: mWorkerPrivate->DoRunLoop(cx); michael@0: michael@0: JS_ReportPendingException(cx); michael@0: } michael@0: michael@0: #ifdef MOZ_ENABLE_PROFILER_SPS michael@0: if (stack) { michael@0: stack->sampleRuntime(nullptr); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: // Destroy the main context. This will unroot the main worker global and michael@0: // GC. This is not the last JSContext (WorkerJSRuntime maintains an michael@0: // internal JSContext). michael@0: JS_DestroyContext(cx); michael@0: michael@0: // Now WorkerJSRuntime goes out of scope and its destructor will shut michael@0: // down the cycle collector and destroy the final JSContext. This michael@0: // breaks any remaining cycles and collects the C++ and JS objects michael@0: // participating. michael@0: } michael@0: michael@0: mThread->SetWorker(nullptr); michael@0: michael@0: mWorkerPrivate->ScheduleDeletion(WorkerPrivate::WorkerRan); michael@0: michael@0: // It is no longer safe to touch mWorkerPrivate. michael@0: mWorkerPrivate = nullptr; michael@0: michael@0: // Now recycle this thread. michael@0: nsCOMPtr mainThread = do_GetMainThread(); michael@0: MOZ_ASSERT(mainThread); michael@0: michael@0: nsRefPtr finishedRunnable = michael@0: new FinishedRunnable(mThread.forget()); michael@0: MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mainThread->Dispatch(finishedRunnable, michael@0: NS_DISPATCH_NORMAL))); michael@0: michael@0: profiler_unregister_thread(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED0(WorkerThreadPrimaryRunnable::FinishedRunnable, michael@0: nsRunnable) michael@0: michael@0: NS_IMETHODIMP michael@0: WorkerThreadPrimaryRunnable::FinishedRunnable::Run() michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: nsRefPtr thread; michael@0: mThread.swap(thread); michael@0: michael@0: RuntimeService* rts = RuntimeService::GetService(); michael@0: if (rts) { michael@0: rts->NoteIdleThread(thread); michael@0: } michael@0: else if (thread->ShutdownRequired()) { michael@0: MOZ_ALWAYS_TRUE(NS_SUCCEEDED(thread->Shutdown())); michael@0: } michael@0: michael@0: return NS_OK; michael@0: }