summaryrefslogtreecommitdiffstats
path: root/js/xpconnect/src/XPCJSContext.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/xpconnect/src/XPCJSContext.cpp')
-rw-r--r--js/xpconnect/src/XPCJSContext.cpp1482
1 files changed, 1482 insertions, 0 deletions
diff --git a/js/xpconnect/src/XPCJSContext.cpp b/js/xpconnect/src/XPCJSContext.cpp
new file mode 100644
index 0000000000..af2d57405b
--- /dev/null
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -0,0 +1,1482 @@
+/* -*- 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 <algorithm>
+# include <sys/resource.h>
+#endif
+
+#ifdef XP_WIN
+// For min.
+# include <algorithm>
+# include <windows.h>
+#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<nsIDebug2> 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<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();
+
+#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<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();
+ 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<nsGlobalWindowInner> 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<JSObject*> 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<nsIObserverService> 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<nsISupports> 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<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 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<nsIXULRuntime> 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<XPCJSContext*>(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<nsIXULRuntime> 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*>(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);
+}
+
+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<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 (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);
+}
+
+bool XPCJSContext::IsSystemCaller() const {
+ return nsContentUtils::IsSystemCaller(Context());
+}