/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* Per JSContext object */ #include "mozilla/MemoryReporting.h" #include "mozilla/UniquePtr.h" #include "xpcprivate.h" #include "xpcpublic.h" #include "XPCWrapper.h" #include "XPCJSMemoryReporter.h" #include "WrapperFactory.h" #include "mozJSComponentLoader.h" #include "nsNetUtil.h" #include "nsThreadUtils.h" #include "nsIObserverService.h" #include "nsIDebug2.h" #include "nsPIDOMWindow.h" #include "nsPrintfCString.h" #include "mozilla/Preferences.h" #include "mozilla/Telemetry.h" #include "mozilla/Services.h" #ifdef FUZZING # include "mozilla/StaticPrefs_fuzzing.h" #endif #include "mozilla/StaticPrefs_dom.h" #include "mozilla/StaticPrefs_browser.h" #include "mozilla/StaticPrefs_javascript.h" #include "mozilla/dom/ScriptSettings.h" #include "nsContentUtils.h" #include "nsCCUncollectableMarker.h" #include "nsCycleCollectionNoteRootCallback.h" #include "nsCycleCollector.h" #include "jsapi.h" #include "js/ContextOptions.h" #include "js/MemoryMetrics.h" #include "js/OffThreadScriptCompilation.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/ScriptLoader.h" #include "mozilla/dom/WindowBinding.h" #include "mozilla/extensions/WebExtensionPolicy.h" #include "mozilla/Atomics.h" #include "mozilla/Attributes.h" #include "mozilla/ProcessHangMonitor.h" #include "mozilla/Sprintf.h" #include "mozilla/SystemPrincipal.h" #include "mozilla/ThreadLocal.h" #include "mozilla/UniquePtrExtensions.h" #include "mozilla/Unused.h" #include "AccessCheck.h" #include "nsGlobalWindow.h" #include "nsAboutProtocolUtils.h" #include "GeckoProfiler.h" #include "nsIXULRuntime.h" #include "nsJSPrincipals.h" #include "ExpandedPrincipal.h" #if defined(XP_LINUX) && !defined(ANDROID) // For getrlimit and min/max. # include # include #endif #ifdef XP_WIN // For min. # include # include #endif using namespace mozilla; using namespace xpc; using namespace JS; using mozilla::dom::AutoEntryScript; // The watchdog thread loop is pretty trivial, and should not require much stack // space to do its job. So only give it 32KiB or the platform minimum. #if !defined(PTHREAD_STACK_MIN) # define PTHREAD_STACK_MIN 0 #endif static constexpr size_t kWatchdogStackSize = PTHREAD_STACK_MIN < 32 * 1024 ? 32 * 1024 : PTHREAD_STACK_MIN; static void WatchdogMain(void* arg); class Watchdog; class WatchdogManager; class MOZ_RAII AutoLockWatchdog final { Watchdog* const mWatchdog; public: explicit AutoLockWatchdog(Watchdog* aWatchdog); ~AutoLockWatchdog(); }; class Watchdog { public: explicit Watchdog(WatchdogManager* aManager) : mManager(aManager), mLock(nullptr), mWakeup(nullptr), mThread(nullptr), mHibernating(false), mInitialized(false), mShuttingDown(false), mMinScriptRunTimeSeconds(1) {} ~Watchdog() { MOZ_ASSERT(!Initialized()); } WatchdogManager* Manager() { return mManager; } bool Initialized() { return mInitialized; } bool ShuttingDown() { return mShuttingDown; } PRLock* GetLock() { return mLock; } bool Hibernating() { return mHibernating; } void WakeUp() { MOZ_ASSERT(Initialized()); MOZ_ASSERT(Hibernating()); mHibernating = false; PR_NotifyCondVar(mWakeup); } // // Invoked by the main thread only. // void Init() { MOZ_ASSERT(NS_IsMainThread()); mLock = PR_NewLock(); if (!mLock) { MOZ_CRASH("PR_NewLock failed."); } mWakeup = PR_NewCondVar(mLock); if (!mWakeup) { MOZ_CRASH("PR_NewCondVar failed."); } { // Make sure the debug service is instantiated before we create the // watchdog thread, since we intentionally try to keep the thread's stack // segment as small as possible. It isn't always large enough to // instantiate a new service, and even when it is, we don't want fault in // extra pages if we can avoid it. nsCOMPtr dbg = do_GetService("@mozilla.org/xpcom/debug;1"); Unused << dbg; } { AutoLockWatchdog lock(this); // Gecko uses thread private for accounting and has to clean up at thread // exit. Therefore, even though we don't have a return value from the // watchdog, we need to join it on shutdown. mThread = PR_CreateThread(PR_USER_THREAD, WatchdogMain, this, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, kWatchdogStackSize); if (!mThread) { MOZ_CRASH("PR_CreateThread failed!"); } // WatchdogMain acquires the lock and then asserts mInitialized. So // make sure to set mInitialized before releasing the lock here so // that it's atomic with the creation of the thread. mInitialized = true; } } void Shutdown() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(Initialized()); { // Scoped lock. AutoLockWatchdog lock(this); // Signal to the watchdog thread that it's time to shut down. mShuttingDown = true; // Wake up the watchdog, and wait for it to call us back. PR_NotifyCondVar(mWakeup); } PR_JoinThread(mThread); // The thread sets mShuttingDown to false as it exits. MOZ_ASSERT(!mShuttingDown); // Destroy state. mThread = nullptr; PR_DestroyCondVar(mWakeup); mWakeup = nullptr; PR_DestroyLock(mLock); mLock = nullptr; // All done. mInitialized = false; } void SetMinScriptRunTimeSeconds(int32_t seconds) { // This variable is atomic, and is set from the main thread without // locking. MOZ_ASSERT(seconds > 0); mMinScriptRunTimeSeconds = seconds; } // // Invoked by the watchdog thread only. // void Hibernate() { MOZ_ASSERT(!NS_IsMainThread()); mHibernating = true; Sleep(PR_INTERVAL_NO_TIMEOUT); } void Sleep(PRIntervalTime timeout) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ALWAYS_TRUE(PR_WaitCondVar(mWakeup, timeout) == PR_SUCCESS); } void Finished() { MOZ_ASSERT(!NS_IsMainThread()); mShuttingDown = false; } int32_t MinScriptRunTimeSeconds() { return mMinScriptRunTimeSeconds; } private: WatchdogManager* mManager; PRLock* mLock; PRCondVar* mWakeup; PRThread* mThread; bool mHibernating; bool mInitialized; bool mShuttingDown; mozilla::Atomic mMinScriptRunTimeSeconds; }; #define PREF_MAX_SCRIPT_RUN_TIME_CONTENT "dom.max_script_run_time" #define PREF_MAX_SCRIPT_RUN_TIME_CHROME "dom.max_chrome_script_run_time" #define PREF_MAX_SCRIPT_RUN_TIME_EXT_CONTENT \ "dom.max_ext_content_script_run_time" static const char* gCallbackPrefs[] = { "dom.use_watchdog", PREF_MAX_SCRIPT_RUN_TIME_CONTENT, PREF_MAX_SCRIPT_RUN_TIME_CHROME, PREF_MAX_SCRIPT_RUN_TIME_EXT_CONTENT, nullptr, }; class WatchdogManager { public: explicit WatchdogManager() { // All the timestamps start at zero. PodArrayZero(mTimestamps); // Register ourselves as an observer to get updates on the pref. Preferences::RegisterCallbacks(PrefsChanged, gCallbackPrefs, this); } virtual ~WatchdogManager() { // Shutting down the watchdog requires context-switching to the watchdog // thread, which isn't great to do in a destructor. So we require // consumers to shut it down manually before releasing it. MOZ_ASSERT(!mWatchdog); } private: static void PrefsChanged(const char* aPref, void* aSelf) { static_cast(aSelf)->RefreshWatchdog(); } public: void Shutdown() { Preferences::UnregisterCallbacks(PrefsChanged, gCallbackPrefs, this); } void RegisterContext(XPCJSContext* aContext) { MOZ_ASSERT(NS_IsMainThread()); AutoLockWatchdog lock(mWatchdog.get()); if (aContext->mActive == XPCJSContext::CONTEXT_ACTIVE) { mActiveContexts.insertBack(aContext); } else { mInactiveContexts.insertBack(aContext); } // Enable the watchdog, if appropriate. RefreshWatchdog(); } void UnregisterContext(XPCJSContext* aContext) { MOZ_ASSERT(NS_IsMainThread()); AutoLockWatchdog lock(mWatchdog.get()); // aContext must be in one of our two lists, simply remove it. aContext->LinkedListElement::remove(); #ifdef DEBUG // If this was the last context, we should have already shut down // the watchdog. if (mActiveContexts.isEmpty() && mInactiveContexts.isEmpty()) { MOZ_ASSERT(!mWatchdog); } #endif } // Context statistics. These live on the watchdog manager, are written // from the main thread, and are read from the watchdog thread (holding // the lock in each case). void RecordContextActivity(XPCJSContext* aContext, bool active) { // The watchdog reads this state, so acquire the lock before writing it. MOZ_ASSERT(NS_IsMainThread()); AutoLockWatchdog lock(mWatchdog.get()); // Write state. aContext->mLastStateChange = PR_Now(); aContext->mActive = active ? XPCJSContext::CONTEXT_ACTIVE : XPCJSContext::CONTEXT_INACTIVE; UpdateContextLists(aContext); // The watchdog may be hibernating, waiting for the context to go // active. Wake it up if necessary. if (active && mWatchdog && mWatchdog->Hibernating()) { mWatchdog->WakeUp(); } } bool IsAnyContextActive() { return !mActiveContexts.isEmpty(); } PRTime TimeSinceLastActiveContext() { // Must be called on the watchdog thread with the lock held. MOZ_ASSERT(!NS_IsMainThread()); PR_ASSERT_CURRENT_THREAD_OWNS_LOCK(mWatchdog->GetLock()); MOZ_ASSERT(mActiveContexts.isEmpty()); MOZ_ASSERT(!mInactiveContexts.isEmpty()); // We store inactive contexts with the most recently added inactive // context at the end of the list. return PR_Now() - mInactiveContexts.getLast()->mLastStateChange; } void RecordTimestamp(WatchdogTimestampCategory aCategory) { // Must be called on the watchdog thread with the lock held. MOZ_ASSERT(!NS_IsMainThread()); PR_ASSERT_CURRENT_THREAD_OWNS_LOCK(mWatchdog->GetLock()); MOZ_ASSERT(aCategory != TimestampContextStateChange, "Use RecordContextActivity to update this"); mTimestamps[aCategory] = PR_Now(); } PRTime GetContextTimestamp(XPCJSContext* aContext, const AutoLockWatchdog& aProofOfLock) { return aContext->mLastStateChange; } PRTime GetTimestamp(WatchdogTimestampCategory aCategory, const AutoLockWatchdog& aProofOfLock) { MOZ_ASSERT(aCategory != TimestampContextStateChange, "Use GetContextTimestamp to retrieve this"); return mTimestamps[aCategory]; } Watchdog* GetWatchdog() { return mWatchdog.get(); } void RefreshWatchdog() { bool wantWatchdog = Preferences::GetBool("dom.use_watchdog", true); if (wantWatchdog != !!mWatchdog) { if (wantWatchdog) { StartWatchdog(); } else { StopWatchdog(); } } if (mWatchdog) { int32_t contentTime = StaticPrefs::dom_max_script_run_time(); if (contentTime <= 0) { contentTime = INT32_MAX; } int32_t chromeTime = StaticPrefs::dom_max_chrome_script_run_time(); if (chromeTime <= 0) { chromeTime = INT32_MAX; } int32_t extTime = StaticPrefs::dom_max_ext_content_script_run_time(); if (extTime <= 0) { extTime = INT32_MAX; } mWatchdog->SetMinScriptRunTimeSeconds( std::min({contentTime, chromeTime, extTime})); } } void StartWatchdog() { MOZ_ASSERT(!mWatchdog); mWatchdog = mozilla::MakeUnique(this); mWatchdog->Init(); } void StopWatchdog() { MOZ_ASSERT(mWatchdog); mWatchdog->Shutdown(); mWatchdog = nullptr; } template void ForAllActiveContexts(Callback&& aCallback) { // This function must be called on the watchdog thread with the lock held. MOZ_ASSERT(!NS_IsMainThread()); PR_ASSERT_CURRENT_THREAD_OWNS_LOCK(mWatchdog->GetLock()); for (auto* context = mActiveContexts.getFirst(); context; context = context->LinkedListElement::getNext()) { if (!aCallback(context)) { return; } } } private: void UpdateContextLists(XPCJSContext* aContext) { // Given aContext whose activity state or timestamp has just changed, // put it back in the proper position in the proper list. aContext->LinkedListElement::remove(); auto& list = aContext->mActive == XPCJSContext::CONTEXT_ACTIVE ? mActiveContexts : mInactiveContexts; // Either the new list is empty or aContext must be more recent than // the existing last element. MOZ_ASSERT_IF(!list.isEmpty(), list.getLast()->mLastStateChange < aContext->mLastStateChange); list.insertBack(aContext); } LinkedList mActiveContexts; LinkedList mInactiveContexts; mozilla::UniquePtr mWatchdog; // We store ContextStateChange on the contexts themselves. PRTime mTimestamps[kWatchdogTimestampCategoryCount - 1]; }; AutoLockWatchdog::AutoLockWatchdog(Watchdog* aWatchdog) : mWatchdog(aWatchdog) { if (mWatchdog) { PR_Lock(mWatchdog->GetLock()); } } AutoLockWatchdog::~AutoLockWatchdog() { if (mWatchdog) { PR_Unlock(mWatchdog->GetLock()); } } static void WatchdogMain(void* arg) { AUTO_PROFILER_REGISTER_THREAD("JS Watchdog"); // Create an nsThread wrapper for the thread and register it with the thread // manager. Unused << NS_GetCurrentThread(); NS_SetCurrentThreadName("JS Watchdog"); Watchdog* self = static_cast(arg); WatchdogManager* manager = self->Manager(); // Lock lasts until we return AutoLockWatchdog lock(self); MOZ_ASSERT(self->Initialized()); while (!self->ShuttingDown()) { // Sleep only 1 second if recently (or currently) active; otherwise, // hibernate if (manager->IsAnyContextActive() || manager->TimeSinceLastActiveContext() <= PRTime(2 * PR_USEC_PER_SEC)) { self->Sleep(PR_TicksPerSecond()); } else { manager->RecordTimestamp(TimestampWatchdogHibernateStart); self->Hibernate(); manager->RecordTimestamp(TimestampWatchdogHibernateStop); } // Rise and shine. manager->RecordTimestamp(TimestampWatchdogWakeup); // Don't request an interrupt callback unless the current script has // been running long enough that we might show the slow script dialog. // Triggering the callback from off the main thread can be expensive. // We want to avoid showing the slow script dialog if the user's laptop // goes to sleep in the middle of running a script. To ensure this, we // invoke the interrupt callback after only half the timeout has // elapsed. The callback simply records the fact that it was called in // the mSlowScriptSecondHalf flag. Then we wait another (timeout/2) // seconds and invoke the callback again. This time around it sees // mSlowScriptSecondHalf is set and so it shows the slow script // dialog. If the computer is put to sleep during one of the (timeout/2) // periods, the script still has the other (timeout/2) seconds to // finish. if (!self->ShuttingDown() && manager->IsAnyContextActive()) { bool debuggerAttached = false; nsCOMPtr dbg = do_GetService("@mozilla.org/xpcom/debug;1"); if (dbg) { dbg->GetIsDebuggerAttached(&debuggerAttached); } if (debuggerAttached) { // We won't be interrupting these scripts anyway. continue; } PRTime usecs = self->MinScriptRunTimeSeconds() * PR_USEC_PER_SEC / 2; manager->ForAllActiveContexts([usecs, manager, &lock](XPCJSContext* aContext) -> bool { auto timediff = PR_Now() - manager->GetContextTimestamp(aContext, lock); if (timediff > usecs) { JS_RequestInterruptCallback(aContext->Context()); return true; } return false; }); } } // Tell the manager that we've shut down. self->Finished(); } PRTime XPCJSContext::GetWatchdogTimestamp(WatchdogTimestampCategory aCategory) { AutoLockWatchdog lock(mWatchdogManager->GetWatchdog()); return aCategory == TimestampContextStateChange ? mWatchdogManager->GetContextTimestamp(this, lock) : mWatchdogManager->GetTimestamp(aCategory, lock); } // static bool XPCJSContext::RecordScriptActivity(bool aActive) { MOZ_ASSERT(NS_IsMainThread()); XPCJSContext* xpccx = XPCJSContext::Get(); if (!xpccx) { // mozilla::SpinEventLoopUntil may use AutoScriptActivity(false) after // we destroyed the XPCJSContext. MOZ_ASSERT(!aActive); return false; } bool oldValue = xpccx->SetHasScriptActivity(aActive); if (aActive == oldValue) { // Nothing to do. return oldValue; } if (!aActive) { ProcessHangMonitor::ClearHang(); } xpccx->mWatchdogManager->RecordContextActivity(xpccx, aActive); return oldValue; } AutoScriptActivity::AutoScriptActivity(bool aActive) : mActive(aActive), mOldValue(XPCJSContext::RecordScriptActivity(aActive)) {} AutoScriptActivity::~AutoScriptActivity() { MOZ_ALWAYS_TRUE(mActive == XPCJSContext::RecordScriptActivity(mOldValue)); } static const double sChromeSlowScriptTelemetryCutoff(10.0); static bool sTelemetryEventEnabled(false); // static bool XPCJSContext::InterruptCallback(JSContext* cx) { XPCJSContext* self = XPCJSContext::Get(); // Now is a good time to turn on profiling if it's pending. PROFILER_JS_INTERRUPT_CALLBACK(); #ifdef MOZ_GECKO_PROFILER nsDependentCString filename("unknown file"); JS::AutoFilename scriptFilename; // Computing the line number can be very expensive (see bug 1330231 for // example), so don't request it here. if (JS::DescribeScriptedCaller(cx, &scriptFilename)) { if (const char* file = scriptFilename.get()) { filename.Assign(file, strlen(file)); } PROFILER_MARKER_TEXT("JS::InterruptCallback", JS, {}, filename); } #endif // Normally we record mSlowScriptCheckpoint when we start to process an // event. However, we can run JS outside of event handlers. This code takes // care of that case. if (self->mSlowScriptCheckpoint.IsNull()) { self->mSlowScriptCheckpoint = TimeStamp::NowLoRes(); self->mSlowScriptSecondHalf = false; self->mSlowScriptActualWait = mozilla::TimeDuration(); self->mTimeoutAccumulated = false; self->mExecutedChromeScript = false; return true; } // Sometimes we get called back during XPConnect initialization, before Gecko // has finished bootstrapping. Avoid crashing in nsContentUtils below. if (!nsContentUtils::IsInitialized()) { return true; } // This is at least the second interrupt callback we've received since // returning to the event loop. See how long it's been, and what the limit // is. TimeStamp now = TimeStamp::NowLoRes(); TimeDuration duration = now - self->mSlowScriptCheckpoint; int32_t limit; nsString addonId; const char* prefName; bool runningContentJS = false; auto principal = BasePrincipal::Cast(nsContentUtils::SubjectPrincipal(cx)); bool chrome = principal->Is(); if (chrome) { prefName = PREF_MAX_SCRIPT_RUN_TIME_CHROME; limit = StaticPrefs::dom_max_chrome_script_run_time(); self->mExecutedChromeScript = true; } else if (auto policy = principal->ContentScriptAddonPolicy()) { policy->GetId(addonId); prefName = PREF_MAX_SCRIPT_RUN_TIME_EXT_CONTENT; limit = StaticPrefs::dom_max_ext_content_script_run_time(); } else { prefName = PREF_MAX_SCRIPT_RUN_TIME_CONTENT; limit = StaticPrefs::dom_max_script_run_time(); runningContentJS = true; } // When the parent process slow script dialog is disabled, we still want // to be able to track things for telemetry, so set `mSlowScriptSecondHalf` // to true in that case: if (limit == 0 && chrome && duration.ToSeconds() > sChromeSlowScriptTelemetryCutoff / 2.0) { self->mSlowScriptSecondHalf = true; return true; } // If there's no limit, or we're within the limit, let it go. if (limit == 0 || duration.ToSeconds() < limit / 2.0) { return true; } self->mSlowScriptCheckpoint = now; self->mSlowScriptActualWait += duration; // In order to guard against time changes or laptops going to sleep, we // don't trigger the slow script warning until (limit/2) seconds have // elapsed twice. if (!self->mSlowScriptSecondHalf) { self->mSlowScriptSecondHalf = true; return true; } int32_t limitWithoutImportantUserInput = StaticPrefs::dom_max_script_run_time_without_important_user_input(); if (runningContentJS && XRE_IsContentProcess() && limit && limitWithoutImportantUserInput > limit && limitWithoutImportantUserInput > self->mSlowScriptActualWait.ToSeconds()) { // Call possibly slow PeekMessages after the other common early returns in // this method. ContentChild* contentChild = ContentChild::GetSingleton(); mozilla::ipc::MessageChannel* channel = contentChild ? contentChild->GetIPCChannel() : nullptr; if (channel) { bool foundInputEvent = false; channel->PeekMessages( [&foundInputEvent](const IPC::Message& aMsg) -> bool { if (nsContentUtils::IsMessageCriticalInputEvent(aMsg)) { foundInputEvent = true; return false; } return true; }); if (!foundInputEvent) { return true; } } } // We use a fixed value of 2 from browser_parent_process_hang_telemetry.js // to check if the telemetry events work. Do not interrupt it with a dialog. if (chrome && limit == 2 && xpc::IsInAutomation()) { return true; } // // This has gone on long enough! Time to take action. ;-) // // Get the DOM window associated with the running script. If the script is // running in a non-DOM scope, we have to just let it keep running. RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); RefPtr win = WindowOrNull(global); if (!win && IsSandbox(global)) { // If this is a sandbox associated with a DOMWindow via a // sandboxPrototype, use that DOMWindow. This supports GreaseMonkey // and JetPack content scripts. JS::Rooted proto(cx); if (!JS_GetPrototype(cx, global, &proto)) { return false; } if (proto && xpc::IsSandboxPrototypeProxy(proto) && (proto = js::CheckedUnwrapDynamic(proto, cx, /* stopAtWindowProxy = */ false))) { win = WindowGlobalOrNull(proto); } } if (!win) { NS_WARNING("No active window"); return true; } if (win->IsDying()) { // The window is being torn down. When that happens we try to prevent // the dispatch of new runnables, so it also makes sense to kill any // long-running script. The user is primarily interested in this page // going away. return false; } // Accumulate slow script invokation delay. if (!chrome && !self->mTimeoutAccumulated) { uint32_t delay = uint32_t(self->mSlowScriptActualWait.ToMilliseconds() - (limit * 1000.0)); Telemetry::Accumulate(Telemetry::SLOW_SCRIPT_NOTIFY_DELAY, delay); self->mTimeoutAccumulated = true; } // Show the prompt to the user, and kill if requested. nsGlobalWindowInner::SlowScriptResponse response = win->ShowSlowScriptDialog( cx, addonId, self->mSlowScriptActualWait.ToMilliseconds()); if (response == nsGlobalWindowInner::KillSlowScript) { if (Preferences::GetBool("dom.global_stop_script", true)) { xpc::Scriptability::Get(global).Block(); } return false; } if (response == nsGlobalWindowInner::KillScriptGlobal) { nsCOMPtr obs = mozilla::services::GetObserverService(); if (!IsSandbox(global) || !obs) { return false; } // Notify the extensions framework that the sandbox should be killed. nsIXPConnect* xpc = nsContentUtils::XPConnect(); JS::RootedObject wrapper(cx, JS_NewPlainObject(cx)); nsCOMPtr supports; // Store the sandbox object on the wrappedJSObject property of the // subject so that JS recipients can access the JS value directly. if (!wrapper || !JS_DefineProperty(cx, wrapper, "wrappedJSObject", global, JSPROP_ENUMERATE) || NS_FAILED(xpc->WrapJS(cx, wrapper, NS_GET_IID(nsISupports), getter_AddRefs(supports)))) { return false; } obs->NotifyObservers(supports, "kill-content-script-sandbox", nullptr); return false; } // The user chose to continue the script. Reset the timer, and disable this // machinery with a pref if the user opted out of future slow-script dialogs. if (response != nsGlobalWindowInner::ContinueSlowScriptAndKeepNotifying) { self->mSlowScriptCheckpoint = TimeStamp::NowLoRes(); } if (response == nsGlobalWindowInner::AlwaysContinueSlowScript) { Preferences::SetInt(prefName, 0); } return true; } #define JS_OPTIONS_DOT_STR "javascript.options." static mozilla::Atomic sDiscardSystemSource(false); bool xpc::ShouldDiscardSystemSource() { return sDiscardSystemSource; } static mozilla::Atomic sSharedMemoryEnabled(false); static mozilla::Atomic sStreamsEnabled(false); static mozilla::Atomic sPropertyErrorMessageFixEnabled(false); static mozilla::Atomic sWeakRefsEnabled(false); static mozilla::Atomic sWeakRefsExposeCleanupSome(false); static mozilla::Atomic sIteratorHelpersEnabled(false); static JS::WeakRefSpecifier GetWeakRefsEnabled() { if (!sWeakRefsEnabled) { return JS::WeakRefSpecifier::Disabled; } if (sWeakRefsExposeCleanupSome) { return JS::WeakRefSpecifier::EnabledWithCleanupSome; } return JS::WeakRefSpecifier::EnabledWithoutCleanupSome; } void xpc::SetPrefableRealmOptions(JS::RealmOptions& options) { options.creationOptions() .setSharedMemoryAndAtomicsEnabled(sSharedMemoryEnabled) .setCoopAndCoepEnabled( StaticPrefs::browser_tabs_remote_useCrossOriginOpenerPolicy() && StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) .setStreamsEnabled(sStreamsEnabled) .setWritableStreamsEnabled( StaticPrefs::javascript_options_writable_streams()) .setPropertyErrorMessageFixEnabled(sPropertyErrorMessageFixEnabled) .setWeakRefsEnabled(GetWeakRefsEnabled()) .setIteratorHelpersEnabled(sIteratorHelpersEnabled); } static void LoadStartupJSPrefs(XPCJSContext* xpccx) { // Prefs that require a restart are handled here. This includes the // process-wide JIT options because toggling these at runtime can easily cause // races or get us into an inconsistent state. // // 'Live' prefs are handled by ReloadPrefsCallback below. JSContext* cx = xpccx->Context(); bool useBaselineInterp = Preferences::GetBool(JS_OPTIONS_DOT_STR "blinterp"); bool useBaselineJit = Preferences::GetBool(JS_OPTIONS_DOT_STR "baselinejit"); bool useIon = Preferences::GetBool(JS_OPTIONS_DOT_STR "ion"); bool useJitForTrustedPrincipals = Preferences::GetBool(JS_OPTIONS_DOT_STR "jit_trustedprincipals"); bool useNativeRegExp = Preferences::GetBool(JS_OPTIONS_DOT_STR "native_regexp"); bool offthreadIonCompilation = Preferences::GetBool(JS_OPTIONS_DOT_STR "ion.offthread_compilation"); bool useBaselineEager = Preferences::GetBool( JS_OPTIONS_DOT_STR "baselinejit.unsafe_eager_compilation"); bool useIonEager = Preferences::GetBool(JS_OPTIONS_DOT_STR "ion.unsafe_eager_compilation"); #ifdef DEBUG bool fullJitDebugChecks = Preferences::GetBool(JS_OPTIONS_DOT_STR "jit.full_debug_checks"); #endif int32_t baselineInterpThreshold = Preferences::GetInt(JS_OPTIONS_DOT_STR "blinterp.threshold", -1); int32_t baselineThreshold = Preferences::GetInt(JS_OPTIONS_DOT_STR "baselinejit.threshold", -1); int32_t normalIonThreshold = Preferences::GetInt(JS_OPTIONS_DOT_STR "ion.threshold", -1); int32_t ionFrequentBailoutThreshold = Preferences::GetInt( JS_OPTIONS_DOT_STR "ion.frequent_bailout_threshold", -1); bool spectreIndexMasking = Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.index_masking"); bool spectreObjectMitigationsBarriers = Preferences::GetBool( JS_OPTIONS_DOT_STR "spectre.object_mitigations.barriers"); bool spectreObjectMitigationsMisc = Preferences::GetBool( JS_OPTIONS_DOT_STR "spectre.object_mitigations.misc"); bool spectreStringMitigations = Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.string_mitigations"); bool spectreValueMasking = Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.value_masking"); bool spectreJitToCxxCalls = Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.jit_to_C++_calls"); bool disableWasmHugeMemory = Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_disable_huge_memory"); bool useOffThreadParseGlobal = Preferences::GetBool(JS_OPTIONS_DOT_STR "off_thread_parse_global"); nsCOMPtr xr = do_GetService("@mozilla.org/xre/runtime;1"); if (xr) { bool safeMode = false; xr->GetInSafeMode(&safeMode); if (safeMode) { useBaselineJit = false; useIon = false; useJitForTrustedPrincipals = false; useNativeRegExp = false; } } JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_BASELINE_INTERPRETER_ENABLE, useBaselineInterp); JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_BASELINE_ENABLE, useBaselineJit); JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_ION_ENABLE, useIon); JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_JIT_TRUSTEDPRINCIPALS_ENABLE, useJitForTrustedPrincipals); JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_NATIVE_REGEXP_ENABLE, useNativeRegExp); JS_SetOffthreadIonCompilationEnabled(cx, offthreadIonCompilation); JS_SetGlobalJitCompilerOption( cx, JSJITCOMPILER_BASELINE_INTERPRETER_WARMUP_TRIGGER, baselineInterpThreshold); JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_BASELINE_WARMUP_TRIGGER, useBaselineEager ? 0 : baselineThreshold); JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_ION_NORMAL_WARMUP_TRIGGER, useIonEager ? 0 : normalIonThreshold); JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_ION_FREQUENT_BAILOUT_THRESHOLD, ionFrequentBailoutThreshold); #ifdef DEBUG JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_FULL_DEBUG_CHECKS, fullJitDebugChecks); #endif JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_SPECTRE_INDEX_MASKING, spectreIndexMasking); JS_SetGlobalJitCompilerOption( cx, JSJITCOMPILER_SPECTRE_OBJECT_MITIGATIONS_BARRIERS, spectreObjectMitigationsBarriers); JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_SPECTRE_OBJECT_MITIGATIONS_MISC, spectreObjectMitigationsMisc); JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_SPECTRE_STRING_MITIGATIONS, spectreStringMitigations); JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_SPECTRE_VALUE_MASKING, spectreValueMasking); JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_SPECTRE_JIT_TO_CXX_CALLS, spectreJitToCxxCalls); if (disableWasmHugeMemory) { bool disabledHugeMemory = JS::DisableWasmHugeMemory(); MOZ_RELEASE_ASSERT(disabledHugeMemory); } JS::SetUseOffThreadParseGlobal(useOffThreadParseGlobal); } static void ReloadPrefsCallback(const char* pref, void* aXpccx) { // Note: Prefs that require a restart are handled in LoadStartupJSPrefs above. auto xpccx = static_cast(aXpccx); JSContext* cx = xpccx->Context(); bool useAsmJS = Preferences::GetBool(JS_OPTIONS_DOT_STR "asmjs"); bool useWasm = Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm"); bool useWasmTrustedPrincipals = Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_trustedprincipals"); bool useWasmOptimizing = Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_optimizingjit"); bool useWasmBaseline = Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_baselinejit"); bool useWasmReftypes = Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_reftypes"); #ifdef ENABLE_WASM_FUNCTION_REFERENCES bool useWasmFunctionReferences = Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_function_references"); #endif #ifdef ENABLE_WASM_GC bool useWasmGc = Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_gc"); #endif #ifdef ENABLE_WASM_MULTI_VALUE bool useWasmMultiValue = Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_multi_value"); #endif #ifdef ENABLE_WASM_SIMD bool useWasmSimd = Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_simd"); #endif #ifdef ENABLE_WASM_SIMD_WORMHOLE bool useWasmSimdWormhole = Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_simd_wormhole"); #endif bool useWasmVerbose = Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_verbose"); bool throwOnAsmJSValidationFailure = Preferences::GetBool( JS_OPTIONS_DOT_STR "throw_on_asmjs_validation_failure"); bool parallelParsing = Preferences::GetBool(JS_OPTIONS_DOT_STR "parallel_parsing"); sDiscardSystemSource = Preferences::GetBool(JS_OPTIONS_DOT_STR "discardSystemSource"); bool useSourcePragmas = Preferences::GetBool(JS_OPTIONS_DOT_STR "source_pragmas"); bool useAsyncStack = Preferences::GetBool(JS_OPTIONS_DOT_STR "asyncstack"); bool useAsyncStackCaptureDebuggeeOnly = Preferences::GetBool( JS_OPTIONS_DOT_STR "asyncstack_capture_debuggee_only"); bool throwOnDebuggeeWouldRun = Preferences::GetBool(JS_OPTIONS_DOT_STR "throw_on_debuggee_would_run"); bool dumpStackOnDebuggeeWouldRun = Preferences::GetBool( JS_OPTIONS_DOT_STR "dump_stack_on_debuggee_would_run"); sSharedMemoryEnabled = Preferences::GetBool(JS_OPTIONS_DOT_STR "shared_memory"); sStreamsEnabled = Preferences::GetBool(JS_OPTIONS_DOT_STR "streams"); sPropertyErrorMessageFixEnabled = Preferences::GetBool(JS_OPTIONS_DOT_STR "property_error_message_fix"); sWeakRefsEnabled = Preferences::GetBool(JS_OPTIONS_DOT_STR "weakrefs"); sWeakRefsExposeCleanupSome = Preferences::GetBool( JS_OPTIONS_DOT_STR "experimental.weakrefs.expose_cleanupSome"); // Require private fields disabled outside of nightly. bool privateFieldsEnabled = false; bool privateMethodsEnabled = false; #ifdef NIGHTLY_BUILD sIteratorHelpersEnabled = Preferences::GetBool(JS_OPTIONS_DOT_STR "experimental.iterator_helpers"); privateFieldsEnabled = Preferences::GetBool(JS_OPTIONS_DOT_STR "experimental.private_fields"); privateMethodsEnabled = Preferences::GetBool(JS_OPTIONS_DOT_STR "experimental.private_methods"); #endif // Require top level await disabled outside of nightly. bool topLevelAwaitEnabled = false; #ifdef NIGHTLY_BUILD topLevelAwaitEnabled = Preferences::GetBool(JS_OPTIONS_DOT_STR "experimental.top_level_await"); #endif #ifdef JS_GC_ZEAL int32_t zeal = Preferences::GetInt(JS_OPTIONS_DOT_STR "gczeal", -1); int32_t zeal_frequency = Preferences::GetInt( JS_OPTIONS_DOT_STR "gczeal.frequency", JS_DEFAULT_ZEAL_FREQ); if (zeal >= 0) { JS_SetGCZeal(cx, (uint8_t)zeal, zeal_frequency); } #endif // JS_GC_ZEAL #ifdef FUZZING bool fuzzingEnabled = StaticPrefs::fuzzing_enabled(); #endif JS::ContextOptionsRef(cx) .setAsmJS(useAsmJS) #ifdef FUZZING .setFuzzing(fuzzingEnabled) #endif .setWasm(useWasm) .setWasmForTrustedPrinciples(useWasmTrustedPrincipals) #ifdef ENABLE_WASM_CRANELIFT .setWasmCranelift(useWasmOptimizing) #else .setWasmIon(useWasmOptimizing) #endif .setWasmBaseline(useWasmBaseline) .setWasmReftypes(useWasmReftypes) #ifdef ENABLE_WASM_FUNCTION_REFERENCES .setWasmFunctionReferences(useWasmFunctionReferences) #endif #ifdef ENABLE_WASM_GC .setWasmGc(useWasmGc) #endif #ifdef ENABLE_WASM_MULTI_VALUE .setWasmMultiValue(useWasmMultiValue) #endif #ifdef ENABLE_WASM_SIMD .setWasmSimd(useWasmSimd) #endif #ifdef ENABLE_WASM_SIMD_WORMHOLE .setWasmSimdWormhole(useWasmSimdWormhole) #endif .setWasmVerbose(useWasmVerbose) .setThrowOnAsmJSValidationFailure(throwOnAsmJSValidationFailure) .setSourcePragmas(useSourcePragmas) .setAsyncStack(useAsyncStack) .setAsyncStackCaptureDebuggeeOnly(useAsyncStackCaptureDebuggeeOnly) .setThrowOnDebuggeeWouldRun(throwOnDebuggeeWouldRun) .setDumpStackOnDebuggeeWouldRun(dumpStackOnDebuggeeWouldRun) .setPrivateClassFields(privateFieldsEnabled) .setPrivateClassMethods(privateMethodsEnabled) .setTopLevelAwait(topLevelAwaitEnabled); nsCOMPtr xr = do_GetService("@mozilla.org/xre/runtime;1"); if (xr) { bool safeMode = false; xr->GetInSafeMode(&safeMode); if (safeMode) { JS::ContextOptionsRef(cx).disableOptionsForSafeMode(); } } JS_SetParallelParsingEnabled(cx, parallelParsing); } XPCJSContext::~XPCJSContext() { MOZ_COUNT_DTOR_INHERITED(XPCJSContext, CycleCollectedJSContext); // Elsewhere we abort immediately if XPCJSContext initialization fails. // Therefore the context must be non-null. MOZ_ASSERT(MaybeContext()); Preferences::UnregisterPrefixCallback(ReloadPrefsCallback, JS_OPTIONS_DOT_STR, this); #ifdef FUZZING Preferences::UnregisterCallback(ReloadPrefsCallback, "fuzzing.enabled", this); #endif // Clear any pending exception. It might be an XPCWrappedJS, and if we try // to destroy it later we will crash. SetPendingException(nullptr); // If we're the last XPCJSContext around, clean up the watchdog manager. if (--sInstanceCount == 0) { if (mWatchdogManager->GetWatchdog()) { mWatchdogManager->StopWatchdog(); } mWatchdogManager->UnregisterContext(this); mWatchdogManager->Shutdown(); sWatchdogInstance = nullptr; } else { // Otherwise, simply remove ourselves from the list. mWatchdogManager->UnregisterContext(this); } if (mCallContext) { mCallContext->SystemIsBeingShutDown(); } PROFILER_CLEAR_JS_CONTEXT(); } XPCJSContext::XPCJSContext() : mCallContext(nullptr), mAutoRoots(nullptr), mResolveName(JSID_VOID), mResolvingWrapper(nullptr), mWatchdogManager(GetWatchdogManager()), mSlowScriptSecondHalf(false), mTimeoutAccumulated(false), mExecutedChromeScript(false), mHasScriptActivity(false), mPendingResult(NS_OK), mActive(CONTEXT_INACTIVE), mLastStateChange(PR_Now()) { MOZ_COUNT_CTOR_INHERITED(XPCJSContext, CycleCollectedJSContext); MOZ_ASSERT(mWatchdogManager); ++sInstanceCount; mWatchdogManager->RegisterContext(this); } /* static */ XPCJSContext* XPCJSContext::Get() { // Do an explicit null check, because this can get called from a process that // does not run JS. nsXPConnect* xpc = static_cast(nsXPConnect::XPConnect()); return xpc ? xpc->GetContext() : nullptr; } #ifdef XP_WIN static size_t GetWindowsStackSize() { // First, get the stack base. Because the stack grows down, this is the top // of the stack. const uint8_t* stackTop; # ifdef _WIN64 PNT_TIB64 pTib = reinterpret_cast(NtCurrentTeb()); stackTop = reinterpret_cast(pTib->StackBase); # else PNT_TIB pTib = reinterpret_cast(NtCurrentTeb()); stackTop = reinterpret_cast(pTib->StackBase); # endif // Now determine the stack bottom. Note that we can't use tib->StackLimit, // because that's the size of the committed area and we're also interested // in the reserved pages below that. MEMORY_BASIC_INFORMATION mbi; if (!VirtualQuery(&mbi, &mbi, sizeof(mbi))) { MOZ_CRASH("VirtualQuery failed"); } const uint8_t* stackBottom = reinterpret_cast(mbi.AllocationBase); // Do some sanity checks. size_t stackSize = size_t(stackTop - stackBottom); MOZ_RELEASE_ASSERT(stackSize >= 1 * 1024 * 1024); MOZ_RELEASE_ASSERT(stackSize <= 32 * 1024 * 1024); // Subtract 40 KB (Win32) or 80 KB (Win64) to account for things like // the guard page and large PGO stack frames. return stackSize - 10 * sizeof(uintptr_t) * 1024; } #endif XPCJSRuntime* XPCJSContext::Runtime() const { return static_cast(CycleCollectedJSContext::Runtime()); } CycleCollectedJSRuntime* XPCJSContext::CreateRuntime(JSContext* aCx) { return new XPCJSRuntime(aCx); } nsresult XPCJSContext::Initialize() { nsresult rv = CycleCollectedJSContext::Initialize(nullptr, JS::DefaultHeapMaxBytes); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(Context()); JSContext* cx = Context(); // The JS engine permits us to set different stack limits for system code, // trusted script, and untrusted script. We have tests that ensure that // we can always execute 10 "heavy" (eval+with) stack frames deeper in // privileged code. Our stack sizes vary greatly in different configurations, // so satisfying those tests requires some care. Manual measurements of the // number of heavy stack frames achievable gives us the following rough data, // ordered by the effective categories in which they are grouped in the // JS_SetNativeStackQuota call (which predates this analysis). // // The following "Stack Frames" numbers come from `chromeLimit` in // js/xpconnect/tests/chrome/test_bug732665.xul // // Platform | Build | Stack Quota | Stack Frames | Stack Frame Size // ------------+-------+-------------+--------------+------------------ // OSX 64 | Opt | 7MB | 1331 | ~5.4k // OSX 64 | Debug | 7MB | 1202 | ~6.0k // ------------+-------+-------------+--------------+------------------ // Linux 32 | Opt | 7.875MB | 2513 | ~3.2k // Linux 32 | Debug | 7.875MB | 2146 | ~3.8k // ------------+-------+-------------+--------------+------------------ // Linux 64 | Opt | 7.875MB | 1360 | ~5.9k // Linux 64 | Debug | 7.875MB | 1180 | ~6.8k // Linux 64 | ASan | 7.875MB | 473 | ~17.0k // ------------+-------+-------------+--------------+------------------ // Windows 32 | Opt | 984k | 188 | ~5.2k // Windows 32 | Debug | 984k | 208 | ~4.7k // ------------+-------+-------------+--------------+------------------ // Windows 64 | Opt | 1.922MB | 189 | ~10.4k // Windows 64 | Debug | 1.922MB | 175 | ~11.2k // // We tune the trusted/untrusted quotas for each configuration to achieve our // invariants while attempting to minimize overhead. In contrast, our buffer // between system code and trusted script is a very unscientific 10k. const size_t kSystemCodeBuffer = 10 * 1024; // Our "default" stack is what we use in configurations where we don't have // a compelling reason to do things differently. This is effectively 512KB // on 32-bit platforms and 1MB on 64-bit platforms. const size_t kDefaultStackQuota = 128 * sizeof(size_t) * 1024; // Set maximum stack size for different configurations. This value is then // capped below because huge stacks are not web-compatible. #if defined(XP_MACOSX) || defined(DARWIN) // MacOS has a gargantuan default stack size of 8MB. Go wild with 7MB, // and give trusted script 180k extra. The stack is huge on mac anyway. const size_t kUncappedStackQuota = 7 * 1024 * 1024; const size_t kTrustedScriptBuffer = 180 * 1024; #elif defined(XP_LINUX) && !defined(ANDROID) // Most Linux distributions set default stack size to 8MB. Use it as the // maximum value. const size_t kStackQuotaMax = 8 * 1024 * 1024; # if defined(MOZ_ASAN) || defined(DEBUG) // Bug 803182: account for the 4x difference in the size of js::Interpret // between optimized and debug builds. We use 2x since the JIT part // doesn't increase much. // See the standalone MOZ_ASAN branch below for the ASan case. const size_t kStackQuotaMin = 2 * kDefaultStackQuota; # else const size_t kStackQuotaMin = kDefaultStackQuota; # endif // Allocate 128kB margin for the safe space. const size_t kStackSafeMargin = 128 * 1024; struct rlimit rlim; const size_t kUncappedStackQuota = getrlimit(RLIMIT_STACK, &rlim) == 0 ? std::max(std::min(size_t(rlim.rlim_cur - kStackSafeMargin), kStackQuotaMax - kStackSafeMargin), kStackQuotaMin) : kStackQuotaMin; # if defined(MOZ_ASAN) // See the standalone MOZ_ASAN branch below for the ASan case. const size_t kTrustedScriptBuffer = 450 * 1024; # else const size_t kTrustedScriptBuffer = 180 * 1024; # endif #elif defined(XP_WIN) // 1MB is the default stack size on Windows. We use the -STACK linker flag // (see WIN32_EXE_LDFLAGS in config/config.mk) to request a larger stack, so // we determine the stack size at runtime. const size_t kUncappedStackQuota = GetWindowsStackSize(); # if defined(MOZ_ASAN) // See the standalone MOZ_ASAN branch below for the ASan case. const size_t kTrustedScriptBuffer = 450 * 1024; # else const size_t kTrustedScriptBuffer = (sizeof(size_t) == 8) ? 180 * 1024 // win64 : 120 * 1024; // win32 # endif #elif defined(MOZ_ASAN) // ASan requires more stack space due to red-zones, so give it double the // default (1MB on 32-bit, 2MB on 64-bit). ASAN stack frame measurements // were not taken at the time of this writing, so we hazard a guess that // ASAN builds have roughly thrice the stack overhead as normal builds. // On normal builds, the largest stack frame size we might encounter is // 9.0k (see above), so let's use a buffer of 9.0 * 5 * 10 = 450k. // // FIXME: Does this branch make sense for Windows and Android? // (See bug 1415195) const size_t kUncappedStackQuota = 2 * kDefaultStackQuota; const size_t kTrustedScriptBuffer = 450 * 1024; #elif defined(ANDROID) // Android appears to have 1MB stacks. Allow the use of 3/4 of that size // (768KB on 32-bit), since otherwise we can crash with a stack overflow // when nearing the 1MB limit. const size_t kUncappedStackQuota = kDefaultStackQuota + kDefaultStackQuota / 2; const size_t kTrustedScriptBuffer = sizeof(size_t) * 12800; #else // Catch-all configuration for other environments. # if defined(DEBUG) const size_t kUncappedStackQuota = 2 * kDefaultStackQuota; # else const size_t kUncappedStackQuota = kDefaultStackQuota; # endif // Given the numbers above, we use 50k and 100k trusted buffers on 32-bit // and 64-bit respectively. const size_t kTrustedScriptBuffer = sizeof(size_t) * 12800; #endif // Avoid an unused variable warning on platforms where we don't use the // default. (void)kDefaultStackQuota; // Large stacks are not web-compatible so cap to a smaller value. // See bug 1537609 and bug 1562700. const size_t kStackQuotaCap = StaticPrefs::javascript_options_main_thread_stack_quota_cap(); const size_t kStackQuota = std::min(kUncappedStackQuota, kStackQuotaCap); JS_SetNativeStackQuota( cx, kStackQuota, kStackQuota - kSystemCodeBuffer, kStackQuota - kSystemCodeBuffer - kTrustedScriptBuffer); PROFILER_SET_JS_CONTEXT(cx); JS_AddInterruptCallback(cx, InterruptCallback); Runtime()->Initialize(cx); LoadStartupJSPrefs(this); // Watch for the JS boolean options. ReloadPrefsCallback(nullptr, this); Preferences::RegisterPrefixCallback(ReloadPrefsCallback, JS_OPTIONS_DOT_STR, this); #ifdef FUZZING Preferences::RegisterCallback(ReloadPrefsCallback, "fuzzing.enabled", this); #endif // Initialize the MIME type used for the bytecode cache, after calling // SetProcessBuildIdOp and loading JS prefs. if (!nsContentUtils::InitJSBytecodeMimeType()) { NS_ABORT_OOM(0); // Size is unknown. } if (!JS::InitSelfHostedCode(cx)) { // Note: If no exception is pending, failure is due to OOM. if (!JS_IsExceptionPending(cx) || JS_IsThrowingOutOfMemory(cx)) { NS_ABORT_OOM(0); // Size is unknown. } // Failed to execute self-hosted JavaScript! Uh oh. MOZ_CRASH("InitSelfHostedCode failed"); } MOZ_RELEASE_ASSERT(Runtime()->InitializeStrings(cx), "InitializeStrings failed"); return NS_OK; } // static uint32_t XPCJSContext::sInstanceCount; // static StaticAutoPtr XPCJSContext::sWatchdogInstance; // static WatchdogManager* XPCJSContext::GetWatchdogManager() { if (sWatchdogInstance) { return sWatchdogInstance; } MOZ_ASSERT(sInstanceCount == 0); sWatchdogInstance = new WatchdogManager(); return sWatchdogInstance; } // static XPCJSContext* XPCJSContext::NewXPCJSContext() { XPCJSContext* self = new XPCJSContext(); nsresult rv = self->Initialize(); if (NS_FAILED(rv)) { MOZ_CRASH("new XPCJSContext failed to initialize."); } if (self->Context()) { return self; } MOZ_CRASH("new XPCJSContext failed to initialize."); } void XPCJSContext::BeforeProcessTask(bool aMightBlock) { MOZ_ASSERT(NS_IsMainThread()); // Start the slow script timer. mSlowScriptCheckpoint = mozilla::TimeStamp::NowLoRes(); mSlowScriptSecondHalf = false; mSlowScriptActualWait = mozilla::TimeDuration(); mTimeoutAccumulated = false; mExecutedChromeScript = false; CycleCollectedJSContext::BeforeProcessTask(aMightBlock); } void XPCJSContext::AfterProcessTask(uint32_t aNewRecursionDepth) { // Record hangs in the parent process for telemetry. if (mSlowScriptSecondHalf && XRE_IsE10sParentProcess()) { double hangDuration = (mozilla::TimeStamp::NowLoRes() - mSlowScriptCheckpoint + mSlowScriptActualWait) .ToSeconds(); // We use the pref to test this code. double limit = sChromeSlowScriptTelemetryCutoff; if (xpc::IsInAutomation()) { double prefLimit = StaticPrefs::dom_max_chrome_script_run_time(); if (prefLimit > 0) { limit = std::min(prefLimit, sChromeSlowScriptTelemetryCutoff); } } if (hangDuration > limit) { if (!sTelemetryEventEnabled) { sTelemetryEventEnabled = true; Telemetry::SetEventRecordingEnabled("slow_script_warning"_ns, true); } auto uriType = mExecutedChromeScript ? "browser"_ns : "content"_ns; // Use AppendFloat to avoid printf-type APIs using locale-specific // decimal separators, when we definitely want a `.`. nsCString durationStr; durationStr.AppendFloat(hangDuration); auto extra = Some>( {Telemetry::EventExtraEntry{"hang_duration"_ns, durationStr}, Telemetry::EventExtraEntry{"uri_type"_ns, uriType}}); Telemetry::RecordEvent( Telemetry::EventID::Slow_script_warning_Shown_Browser, Nothing(), extra); } } // Now that we're back to the event loop, reset the slow script checkpoint. mSlowScriptCheckpoint = mozilla::TimeStamp(); mSlowScriptSecondHalf = false; // Call cycle collector occasionally. MOZ_ASSERT(NS_IsMainThread()); nsJSContext::MaybePokeCC(); CycleCollectedJSContext::AfterProcessTask(aNewRecursionDepth); // This exception might have been set if we called an XPCWrappedJS that threw, // but now we're returning to the event loop, so nothing is going to look at // this value again. Clear it to prevent leaks. SetPendingException(nullptr); } bool XPCJSContext::IsSystemCaller() const { return nsContentUtils::IsSystemCaller(Context()); }