summaryrefslogtreecommitdiffstats
path: root/js/xpconnect/src/XPCJSContext.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--js/xpconnect/src/XPCJSContext.cpp1500
1 files changed, 1500 insertions, 0 deletions
diff --git a/js/xpconnect/src/XPCJSContext.cpp b/js/xpconnect/src/XPCJSContext.cpp
new file mode 100644
index 0000000000..0f54e00cd5
--- /dev/null
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -0,0 +1,1500 @@
+/* -*- 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 "XPCSelfHostedShmem.h"
+#include "WrapperFactory.h"
+#include "mozJSModuleLoader.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 "nsJSEnvironment.h"
+#include "jsapi.h"
+#include "js/ArrayBuffer.h"
+#include "js/ContextOptions.h"
+#include "js/HelperThreadAPI.h"
+#include "js/Initialization.h"
+#include "js/MemoryMetrics.h"
+#include "js/OffThreadScriptCompilation.h"
+#include "js/WasmFeatures.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/TaskController.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 <algorithm>
+# include <sys/resource.h>
+#endif
+
+#ifdef XP_WIN
+// For min.
+# include <algorithm>
+# include <windows.h>
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace xpc;
+using namespace JS;
+
+// We will clamp to reasonable values if this isn't set.
+#if !defined(PTHREAD_STACK_MIN)
+# define PTHREAD_STACK_MIN 0
+#endif
+
+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<nsIDebug2> dbg = do_GetService("@mozilla.org/xpcom/debug;1");
+ Unused << dbg;
+ }
+
+ {
+ AutoLockWatchdog lock(this);
+
+ // 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. On modern Linux libc this might resolve to
+ // a runtime call.
+ size_t watchdogStackSize = PTHREAD_STACK_MIN;
+ watchdogStackSize = std::max<size_t>(32 * 1024, watchdogStackSize);
+
+ // 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, watchdogStackSize);
+ 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());
+ AUTO_PROFILER_THREAD_SLEEP;
+ 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<int32_t> 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<WatchdogManager*>(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<XPCJSContext>::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<Watchdog>(this);
+ mWatchdog->Init();
+ }
+
+ void StopWatchdog() {
+ MOZ_ASSERT(mWatchdog);
+ mWatchdog->Shutdown();
+ mWatchdog = nullptr;
+ }
+
+ template <class Callback>
+ 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<XPCJSContext>::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<XPCJSContext>::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<XPCJSContext> mActiveContexts;
+ LinkedList<XPCJSContext> mInactiveContexts;
+ mozilla::UniquePtr<Watchdog> 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<Watchdog*>(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<nsIDebug2> 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();
+
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ 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);
+ }
+ }
+
+ // 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;
+ auto principal = BasePrincipal::Cast(nsContentUtils::SubjectPrincipal(cx));
+ bool chrome = principal->Is<SystemPrincipal>();
+ 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();
+ }
+
+ // 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;
+ }
+
+ // For scripts in content processes, we only want to show the slow script
+ // dialogue if the user is actually trying to perform an important
+ // interaction. In theory this could be a chrome script running in the
+ // content process, which we probably don't want to give the user the ability
+ // to terminate. However, if this is the case we won't be able to map the
+ // script global to a window and we'll bail out below.
+ if (XRE_IsContentProcess() &&
+ StaticPrefs::dom_max_script_run_time_require_critical_input()) {
+ // 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<nsGlobalWindowInner> win = WindowOrNull(global);
+ if (!win) {
+ // If this is a sandbox associated with a DOMWindow via a
+ // sandboxPrototype, use that DOMWindow. This supports WebExtension
+ // content scripts.
+ win = SandboxWindowOrNull(global, cx);
+ }
+
+ 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;
+ }
+
+ // 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<bool> sDiscardSystemSource(false);
+
+bool xpc::ShouldDiscardSystemSource() { return sDiscardSystemSource; }
+
+static mozilla::Atomic<bool> sSharedMemoryEnabled(false);
+static mozilla::Atomic<bool> sStreamsEnabled(false);
+
+static mozilla::Atomic<bool> sPropertyErrorMessageFixEnabled(false);
+static mozilla::Atomic<bool> sWeakRefsEnabled(false);
+static mozilla::Atomic<bool> sWeakRefsExposeCleanupSome(false);
+static mozilla::Atomic<bool> sIteratorHelpersEnabled(false);
+static mozilla::Atomic<bool> sShadowRealmsEnabled(false);
+#ifdef NIGHTLY_BUILD
+static mozilla::Atomic<bool> sArrayGroupingEnabled(false);
+static mozilla::Atomic<bool> sWellFormedUnicodeStringsEnabled(false);
+#endif
+static mozilla::Atomic<bool> sChangeArrayByCopyEnabled(false);
+static mozilla::Atomic<bool> sArrayFromAsyncEnabled(true);
+#ifdef ENABLE_NEW_SET_METHODS
+static mozilla::Atomic<bool> sEnableNewSetMethods(false);
+#endif
+
+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())
+ .setPropertyErrorMessageFixEnabled(sPropertyErrorMessageFixEnabled)
+ .setWeakRefsEnabled(GetWeakRefsEnabled())
+ .setIteratorHelpersEnabled(sIteratorHelpersEnabled)
+ .setShadowRealmsEnabled(sShadowRealmsEnabled)
+#ifdef NIGHTLY_BUILD
+ .setArrayGroupingEnabled(sArrayGroupingEnabled)
+ .setWellFormedUnicodeStringsEnabled(sWellFormedUnicodeStringsEnabled)
+#endif
+ .setChangeArrayByCopyEnabled(sChangeArrayByCopyEnabled)
+ .setArrayFromAsyncEnabled(sArrayFromAsyncEnabled)
+#ifdef ENABLE_NEW_SET_METHODS
+ .setNewSetMethodsEnabled(sEnableNewSetMethods)
+#endif
+ ;
+}
+
+void xpc::SetPrefableContextOptions(JS::ContextOptions& options) {
+ options
+ .setAsmJS(Preferences::GetBool(JS_OPTIONS_DOT_STR "asmjs"))
+#ifdef FUZZING
+ .setFuzzing(Preferences::GetBool(JS_OPTIONS_DOT_STR "fuzzing.enabled"))
+#endif
+ .setWasm(Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm"))
+ .setWasmForTrustedPrinciples(
+ Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_trustedprincipals"))
+ .setWasmIon(Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_optimizingjit"))
+ .setWasmBaseline(
+ Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_baselinejit"))
+#define WASM_FEATURE(NAME, LOWER_NAME, COMPILE_PRED, COMPILER_PRED, FLAG_PRED, \
+ SHELL, PREF) \
+ .setWasm##NAME(Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_" PREF))
+ JS_FOR_WASM_FEATURES(WASM_FEATURE, WASM_FEATURE, WASM_FEATURE)
+#undef WASM_FEATURE
+ .setWasmVerbose(Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_verbose"))
+ .setThrowOnAsmJSValidationFailure(Preferences::GetBool(
+ JS_OPTIONS_DOT_STR "throw_on_asmjs_validation_failure"))
+ .setSourcePragmas(
+ Preferences::GetBool(JS_OPTIONS_DOT_STR "source_pragmas"))
+ .setAsyncStack(Preferences::GetBool(JS_OPTIONS_DOT_STR "asyncstack"))
+ .setAsyncStackCaptureDebuggeeOnly(Preferences::GetBool(
+ JS_OPTIONS_DOT_STR "asyncstack_capture_debuggee_only"))
+#ifdef NIGHTLY_BUILD
+ .setImportAssertions(Preferences::GetBool(
+ JS_OPTIONS_DOT_STR "experimental.import_assertions"))
+#endif
+ ;
+}
+
+// Mirrored value of javascript.options.self_hosted.use_shared_memory.
+static bool sSelfHostedUseSharedMemory = false;
+
+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();
+
+ // Some prefs are unlisted in all.js / StaticPrefs (and thus are invisible in
+ // about:config). Make sure we use explicit defaults here.
+ bool useJitForTrustedPrincipals =
+ Preferences::GetBool(JS_OPTIONS_DOT_STR "jit_trustedprincipals", false);
+ bool disableWasmHugeMemory = Preferences::GetBool(
+ JS_OPTIONS_DOT_STR "wasm_disable_huge_memory", false);
+
+ bool safeMode = false;
+ nsCOMPtr<nsIXULRuntime> xr = do_GetService("@mozilla.org/xre/runtime;1");
+ if (xr) {
+ xr->GetInSafeMode(&safeMode);
+ }
+
+ // NOTE: Baseline Interpreter is still used in safe-mode. This gives a big
+ // perf gain and is our simplest JIT so we make a tradeoff.
+ JS_SetGlobalJitCompilerOption(
+ cx, JSJITCOMPILER_BASELINE_INTERPRETER_ENABLE,
+ StaticPrefs::javascript_options_blinterp_DoNotUseDirectly());
+
+ // Disable most JITs in Safe-Mode.
+ if (safeMode) {
+ JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_BASELINE_ENABLE, false);
+ JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_ION_ENABLE, false);
+ JS_SetGlobalJitCompilerOption(
+ cx, JSJITCOMPILER_JIT_TRUSTEDPRINCIPALS_ENABLE, false);
+ JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_NATIVE_REGEXP_ENABLE,
+ false);
+ JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_JIT_HINTS_ENABLE, false);
+ sSelfHostedUseSharedMemory = false;
+ } else {
+ JS_SetGlobalJitCompilerOption(
+ cx, JSJITCOMPILER_BASELINE_ENABLE,
+ StaticPrefs::javascript_options_baselinejit_DoNotUseDirectly());
+ JS_SetGlobalJitCompilerOption(
+ cx, JSJITCOMPILER_ION_ENABLE,
+ StaticPrefs::javascript_options_ion_DoNotUseDirectly());
+ JS_SetGlobalJitCompilerOption(cx,
+ JSJITCOMPILER_JIT_TRUSTEDPRINCIPALS_ENABLE,
+ useJitForTrustedPrincipals);
+ JS_SetGlobalJitCompilerOption(
+ cx, JSJITCOMPILER_NATIVE_REGEXP_ENABLE,
+ StaticPrefs::javascript_options_native_regexp_DoNotUseDirectly());
+ // Only enable the jit hints cache for the content process to avoid
+ // any possible jank or delays on the parent process.
+ JS_SetGlobalJitCompilerOption(
+ cx, JSJITCOMPILER_JIT_HINTS_ENABLE,
+ XRE_IsContentProcess()
+ ? StaticPrefs::javascript_options_jithints_DoNotUseDirectly()
+ : false);
+ sSelfHostedUseSharedMemory = StaticPrefs::
+ javascript_options_self_hosted_use_shared_memory_DoNotUseDirectly();
+ }
+
+ JS_SetOffthreadIonCompilationEnabled(
+ cx, StaticPrefs::
+ javascript_options_ion_offthread_compilation_DoNotUseDirectly());
+
+ JS_SetGlobalJitCompilerOption(
+ cx, JSJITCOMPILER_BASELINE_INTERPRETER_WARMUP_TRIGGER,
+ StaticPrefs::javascript_options_blinterp_threshold_DoNotUseDirectly());
+ JS_SetGlobalJitCompilerOption(
+ cx, JSJITCOMPILER_BASELINE_WARMUP_TRIGGER,
+ StaticPrefs::javascript_options_baselinejit_threshold_DoNotUseDirectly());
+ JS_SetGlobalJitCompilerOption(
+ cx, JSJITCOMPILER_ION_NORMAL_WARMUP_TRIGGER,
+ StaticPrefs::javascript_options_ion_threshold_DoNotUseDirectly());
+ JS_SetGlobalJitCompilerOption(
+ cx, JSJITCOMPILER_ION_FREQUENT_BAILOUT_THRESHOLD,
+ StaticPrefs::
+ javascript_options_ion_frequent_bailout_threshold_DoNotUseDirectly());
+ JS_SetGlobalJitCompilerOption(
+ cx, JSJITCOMPILER_INLINING_BYTECODE_MAX_LENGTH,
+ StaticPrefs::
+ javascript_options_inlining_bytecode_max_length_DoNotUseDirectly());
+
+#ifdef DEBUG
+ JS_SetGlobalJitCompilerOption(
+ cx, JSJITCOMPILER_FULL_DEBUG_CHECKS,
+ StaticPrefs::javascript_options_jit_full_debug_checks_DoNotUseDirectly());
+#endif
+
+#if !defined(JS_CODEGEN_MIPS32) && !defined(JS_CODEGEN_MIPS64) && \
+ !defined(JS_CODEGEN_RISCV64) && !defined(JS_CODEGEN_LOONG64)
+ JS_SetGlobalJitCompilerOption(
+ cx, JSJITCOMPILER_SPECTRE_INDEX_MASKING,
+ StaticPrefs::javascript_options_spectre_index_masking_DoNotUseDirectly());
+ JS_SetGlobalJitCompilerOption(
+ cx, JSJITCOMPILER_SPECTRE_OBJECT_MITIGATIONS,
+ StaticPrefs::
+ javascript_options_spectre_object_mitigations_DoNotUseDirectly());
+ JS_SetGlobalJitCompilerOption(
+ cx, JSJITCOMPILER_SPECTRE_STRING_MITIGATIONS,
+ StaticPrefs::
+ javascript_options_spectre_string_mitigations_DoNotUseDirectly());
+ JS_SetGlobalJitCompilerOption(
+ cx, JSJITCOMPILER_SPECTRE_VALUE_MASKING,
+ StaticPrefs::javascript_options_spectre_value_masking_DoNotUseDirectly());
+ JS_SetGlobalJitCompilerOption(
+ cx, JSJITCOMPILER_SPECTRE_JIT_TO_CXX_CALLS,
+ StaticPrefs::
+ javascript_options_spectre_jit_to_cxx_calls_DoNotUseDirectly());
+#endif
+
+ JS_SetGlobalJitCompilerOption(
+ cx, JSJITCOMPILER_WATCHTOWER_MEGAMORPHIC,
+ StaticPrefs::
+ javascript_options_watchtower_megamorphic_DoNotUseDirectly());
+
+ if (disableWasmHugeMemory) {
+ bool disabledHugeMemory = JS::DisableWasmHugeMemory();
+ MOZ_RELEASE_ASSERT(disabledHugeMemory);
+ }
+
+ JS::SetSiteBasedPretenuringEnabled(
+ StaticPrefs::
+ javascript_options_site_based_pretenuring_DoNotUseDirectly());
+}
+
+static void ReloadPrefsCallback(const char* pref, void* aXpccx) {
+ // Note: Prefs that require a restart are handled in LoadStartupJSPrefs above.
+
+ auto xpccx = static_cast<XPCJSContext*>(aXpccx);
+ JSContext* cx = xpccx->Context();
+
+ sDiscardSystemSource =
+ Preferences::GetBool(JS_OPTIONS_DOT_STR "discardSystemSource");
+ 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");
+ sShadowRealmsEnabled =
+ Preferences::GetBool(JS_OPTIONS_DOT_STR "experimental.shadow_realms");
+#ifdef NIGHTLY_BUILD
+ sIteratorHelpersEnabled =
+ Preferences::GetBool(JS_OPTIONS_DOT_STR "experimental.iterator_helpers");
+ sArrayGroupingEnabled =
+ Preferences::GetBool(JS_OPTIONS_DOT_STR "experimental.array_grouping");
+ sWellFormedUnicodeStringsEnabled = Preferences::GetBool(
+ JS_OPTIONS_DOT_STR "experimental.well_formed_unicode_strings");
+#endif
+ sChangeArrayByCopyEnabled = Preferences::GetBool(
+ JS_OPTIONS_DOT_STR "experimental.enable_change_array_by_copy");
+ sArrayFromAsyncEnabled = Preferences::GetBool(
+ JS_OPTIONS_DOT_STR "experimental.enable_array_from_async");
+#ifdef ENABLE_NEW_SET_METHODS
+ sEnableNewSetMethods =
+ Preferences::GetBool(JS_OPTIONS_DOT_STR "experimental.new_set_methods");
+#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
+
+ auto& contextOptions = JS::ContextOptionsRef(cx);
+ SetPrefableContextOptions(contextOptions);
+
+ // Set options not shared with workers.
+ contextOptions
+ .setThrowOnDebuggeeWouldRun(Preferences::GetBool(
+ JS_OPTIONS_DOT_STR "throw_on_debuggee_would_run"))
+ .setDumpStackOnDebuggeeWouldRun(Preferences::GetBool(
+ JS_OPTIONS_DOT_STR "dump_stack_on_debuggee_would_run"));
+
+ JS::SetUseFdlibmForSinCosTan(
+ Preferences::GetBool(JS_OPTIONS_DOT_STR "use_fdlibm_for_sin_cos_tan"));
+
+ nsCOMPtr<nsIXULRuntime> xr = do_GetService("@mozilla.org/xre/runtime;1");
+ if (xr) {
+ bool safeMode = false;
+ xr->GetInSafeMode(&safeMode);
+ if (safeMode) {
+ contextOptions.disableOptionsForSafeMode();
+ }
+ }
+
+ JS_SetParallelParsingEnabled(
+ cx, Preferences::GetBool(JS_OPTIONS_DOT_STR "parallel_parsing"));
+}
+
+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(JS::PropertyKey::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*>(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<PNT_TIB64>(NtCurrentTeb());
+ stackTop = reinterpret_cast<const uint8_t*>(pTib->StackBase);
+# else
+ PNT_TIB pTib = reinterpret_cast<PNT_TIB>(NtCurrentTeb());
+ stackTop = reinterpret_cast<const uint8_t*>(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<const uint8_t*>(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<XPCJSRuntime*>(CycleCollectedJSContext::Runtime());
+}
+
+CycleCollectedJSRuntime* XPCJSContext::CreateRuntime(JSContext* aCx) {
+ return new XPCJSRuntime(aCx);
+}
+
+class HelperThreadTaskHandler : public Task {
+ public:
+ bool Run() override {
+ JS::RunHelperThreadTask();
+ return true;
+ }
+ explicit HelperThreadTaskHandler() : Task(false, EventQueuePriority::Normal) {
+ // Bug 1703185: Currently all tasks are run at the same priority.
+ }
+
+#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+ bool GetName(nsACString& aName) override {
+ aName.AssignLiteral("HelperThreadTask");
+ return true;
+ }
+#endif
+
+ private:
+ ~HelperThreadTaskHandler() = default;
+};
+
+static void DispatchOffThreadTask(JS::DispatchReason) {
+ TaskController::Get()->AddTask(MakeAndAddRef<HelperThreadTaskHandler>());
+}
+
+static bool CreateSelfHostedSharedMemory(JSContext* aCx,
+ JS::SelfHostedCache aBuf) {
+ auto& shm = xpc::SelfHostedShmem::GetSingleton();
+ MOZ_RELEASE_ASSERT(shm.Content().IsEmpty());
+ // Failures within InitFromParent output warnings but do not cause
+ // unrecoverable failures.
+ shm.InitFromParent(aBuf);
+ return true;
+}
+
+nsresult XPCJSContext::Initialize() {
+ if (StaticPrefs::javascript_options_external_thread_pool_DoNotUseDirectly()) {
+ size_t threadCount = TaskController::GetPoolThreadCount();
+ size_t stackSize = TaskController::GetThreadStackSize();
+ SetHelperThreadTaskCallback(&DispatchOffThreadTask, threadCount, stackSize);
+ }
+
+ 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.
+ }
+
+ // When available, set the self-hosted shared memory to be read, so that we
+ // can decode the self-hosted content instead of parsing it.
+ auto& shm = xpc::SelfHostedShmem::GetSingleton();
+ JS::SelfHostedCache selfHostedContent = shm.Content();
+ JS::SelfHostedWriter writer = nullptr;
+ if (XRE_IsParentProcess() && sSelfHostedUseSharedMemory) {
+ // Only the Parent process has permissions to write to the self-hosted
+ // shared memory.
+ writer = CreateSelfHostedSharedMemory;
+ }
+
+ if (!JS::InitSelfHostedCode(cx, selfHostedContent, writer)) {
+ // 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<WatchdogManager> 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 (rv == NS_ERROR_OUT_OF_MEMORY) {
+ mozalloc_handle_oom(0);
+ } else 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<nsTArray<Telemetry::EventExtraEntry>>(
+ {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);
+}
+
+void XPCJSContext::MaybePokeGC() { nsJSContext::MaybePokeGC(); }
+
+bool XPCJSContext::IsSystemCaller() const {
+ return nsContentUtils::IsSystemCaller(Context());
+}