Source code

Revision control

Other Tools

/* -*- 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 "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/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 xpc;
using namespace JS;
// 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();
if (profiler_can_accept_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 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);
}
// 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);
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());
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)
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
if (disableWasmHugeMemory) {
bool disabledHugeMemory = JS::DisableWasmHugeMemory();
MOZ_RELEASE_ASSERT(disabledHugeMemory);
}
JS::SetLargeArrayBuffersEnabled(
StaticPrefs::javascript_options_large_arraybuffers_DoNotUseDirectly());
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();
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");
#define WASM_FEATURE(NAME, LOWER_NAME, COMPILE_PRED, COMPILER_PRED, FLAG_PRED, \
SHELL, PREF) \
bool useWasm##NAME = Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_" PREF);
JS_FOR_WASM_FEATURES(WASM_FEATURE, WASM_FEATURE)
#undef WASM_FEATURE
#ifdef ENABLE_WASM_SIMD_WORMHOLE
bool useWasmSimdWormhole = false;
# ifdef EARLY_BETA_OR_EARLIER
// In late beta and release, the functionality is available but only to
// privileged content.
useWasmSimdWormhole =
Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_simd_wormhole");
# endif
#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");
bool topLevelAwaitEnabled =
Preferences::GetBool(JS_OPTIONS_DOT_STR "experimental.top_level_await");
bool privateFieldsEnabled =
Preferences::GetBool(JS_OPTIONS_DOT_STR "experimental.private_fields");
bool privateMethodsEnabled =
Preferences::GetBool(JS_OPTIONS_DOT_STR "experimental.private_methods");
bool ergnomicBrandChecksEnabled = Preferences::GetBool(
JS_OPTIONS_DOT_STR "experimental.ergonomic_brand_checks");
bool classStaticBlocksEnabled = false;
#ifdef NIGHTLY_BUILD
sIteratorHelpersEnabled =
Preferences::GetBool(JS_OPTIONS_DOT_STR "experimental.iterator_helpers");
classStaticBlocksEnabled = Preferences::GetBool(
JS_OPTIONS_DOT_STR "experimental.class_static_blocks");
#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)
.setWasmIon(false)
#else
.setWasmCranelift(false)
.setWasmIon(useWasmOptimizing)
#endif
.setWasmBaseline(useWasmBaseline)
#define WASM_FEATURE(NAME, ...) .setWasm##NAME(useWasm##NAME)
JS_FOR_WASM_FEATURES(WASM_FEATURE, WASM_FEATURE)
#undef WASM_FEATURE
#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)
.setClassStaticBlocks(classStaticBlocksEnabled)
.setErgnomicBrandChecks(ergnomicBrandChecksEnabled)
.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);
}
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.
}
private:
~HelperThreadTaskHandler() = default;
};
static void DispatchOffThreadTask() {
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 (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());
}