XPCJSContext.cpp (54464B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /* Per JSContext object */ 8 9 #include "mozilla/UniquePtr.h" 10 11 #include "xpcprivate.h" 12 #include "xpcpublic.h" 13 #include "XPCWrapper.h" 14 #include "XPCJSMemoryReporter.h" 15 #include "XPCSelfHostedShmem.h" 16 #include "WrapperFactory.h" 17 #include "mozJSModuleLoader.h" 18 #include "nsNetUtil.h" 19 #include "nsThreadUtils.h" 20 #include "ExecutionTracerIntegration.h" 21 22 #include "nsIObserverService.h" 23 #include "nsIDebug2.h" 24 #include "nsPIDOMWindow.h" 25 #include "nsPrintfCString.h" 26 #include "mozilla/AppShutdown.h" 27 #include "mozilla/Preferences.h" 28 #include "mozilla/MemoryTelemetry.h" 29 #include "mozilla/Services.h" 30 #ifdef FUZZING 31 # include "mozilla/StaticPrefs_fuzzing.h" 32 #endif 33 #include "mozilla/StaticPrefs_dom.h" 34 #include "mozilla/StaticPrefs_browser.h" 35 #include "mozilla/StaticPrefs_javascript.h" 36 #include "mozilla/dom/ScriptSettings.h" 37 #include "mozilla/glean/JsXpconnectMetrics.h" 38 #include "mozilla/scache/StartupCache.h" 39 40 #include "nsContentUtils.h" 41 #include "nsCCUncollectableMarker.h" 42 #include "nsCycleCollectionNoteRootCallback.h" 43 #include "nsCycleCollector.h" 44 #include "nsINode.h" 45 #include "nsJSEnvironment.h" 46 #include "jsapi.h" 47 #include "js/ArrayBuffer.h" 48 #include "js/ContextOptions.h" 49 #include "js/DOMEventDispatch.h" 50 #include "js/experimental/LoggingInterface.h" 51 #include "js/HelperThreadAPI.h" 52 #include "js/Initialization.h" 53 #include "js/MemoryMetrics.h" 54 #include "js/Prefs.h" 55 #include "js/WasmFeatures.h" 56 #include "fmt/format.h" 57 #include "mozilla/dom/BindingUtils.h" 58 #include "mozilla/dom/ContentChild.h" 59 #include "mozilla/dom/Document.h" 60 #include "mozilla/dom/Element.h" 61 #include "mozilla/dom/ScriptLoader.h" 62 #include "mozilla/dom/WindowBinding.h" 63 #include "mozilla/dom/WakeLockBinding.h" 64 #include "mozilla/extensions/WebExtensionPolicy.h" 65 #include "mozilla/AsyncEventDispatcher.h" 66 #include "mozilla/Atomics.h" 67 #include "mozilla/Attributes.h" 68 #include "mozilla/ProcessHangMonitor.h" 69 #include "mozilla/Sprintf.h" 70 #include "mozilla/SystemPrincipal.h" 71 #include "mozilla/TaskController.h" 72 #include "mozilla/UniquePtrExtensions.h" 73 #include "AccessCheck.h" 74 #include "nsGlobalWindowInner.h" 75 #include "nsAboutProtocolUtils.h" 76 77 #include "GeckoProfiler.h" 78 #include "nsIXULRuntime.h" 79 #include "nsJSPrincipals.h" 80 #include "ExpandedPrincipal.h" 81 82 #if defined(XP_LINUX) && !defined(ANDROID) 83 // For getrlimit and min/max. 84 # include <algorithm> 85 # include <sys/resource.h> 86 #endif 87 88 #ifdef XP_WIN 89 // For min. 90 # include <algorithm> 91 # include <windows.h> 92 #endif 93 94 using namespace mozilla; 95 using namespace mozilla::dom; 96 using namespace xpc; 97 using namespace JS; 98 99 // Callback for JIT trace events to dispatch DOM events to global target 100 static void DispatchJitEventToDOM(JSContext* cx, const char* eventType) { 101 // Check if test interfaces are enabled 102 if (!StaticPrefs::dom_expose_test_interfaces()) { 103 return; 104 } 105 106 if (!cx) { 107 return; 108 } 109 110 // Get the global object from the context 111 JSObject* globalObj = JS::CurrentGlobalOrNull(cx); 112 if (!globalObj) { 113 return; 114 } 115 116 nsIGlobalObject* global = xpc::NativeGlobal(globalObj); 117 if (!global) { 118 return; 119 } 120 121 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global); 122 if (!window) { 123 return; 124 } 125 126 mozilla::dom::Document* doc = window->GetDoc(); 127 if (!doc) { 128 return; 129 } 130 131 nsCOMPtr<nsINode> target = doc; 132 if (!target) { 133 return; 134 } 135 136 // Convert event type to nsString and dispatch to document 137 NS_ConvertUTF8toUTF16 eventTypeStr(eventType); 138 RefPtr<AsyncEventDispatcher> dispatcher = new AsyncEventDispatcher( 139 target, eventTypeStr, CanBubble::eYes, ChromeOnlyDispatch::eNo); 140 dispatcher->PostDOMEvent(); 141 } 142 143 // We will clamp to reasonable values if this isn't set. 144 #if !defined(PTHREAD_STACK_MIN) 145 # define PTHREAD_STACK_MIN 0 146 #endif 147 148 static void WatchdogMain(void* arg); 149 class Watchdog; 150 class WatchdogManager; 151 class MOZ_RAII AutoLockWatchdog final { 152 Watchdog* const mWatchdog; 153 154 public: 155 explicit AutoLockWatchdog(Watchdog* aWatchdog); 156 ~AutoLockWatchdog(); 157 }; 158 159 class Watchdog { 160 public: 161 explicit Watchdog(WatchdogManager* aManager) 162 : mManager(aManager), 163 mLock(nullptr), 164 mWakeup(nullptr), 165 mThread(nullptr), 166 mHibernating(false), 167 mInitialized(false), 168 mShuttingDown(false), 169 mMinScriptRunTimeSeconds(1) {} 170 ~Watchdog() { MOZ_ASSERT(!Initialized()); } 171 172 WatchdogManager* Manager() { return mManager; } 173 bool Initialized() { return mInitialized; } 174 bool ShuttingDown() { return mShuttingDown; } 175 PRLock* GetLock() { return mLock; } 176 bool Hibernating() { return mHibernating; } 177 void WakeUp() { 178 MOZ_ASSERT(Initialized()); 179 MOZ_ASSERT(Hibernating()); 180 mHibernating = false; 181 PR_NotifyCondVar(mWakeup); 182 } 183 184 // 185 // Invoked by the main thread only. 186 // 187 188 void Init() { 189 MOZ_ASSERT(NS_IsMainThread()); 190 mLock = PR_NewLock(); 191 if (!mLock) { 192 MOZ_CRASH("PR_NewLock failed."); 193 } 194 195 mWakeup = PR_NewCondVar(mLock); 196 if (!mWakeup) { 197 MOZ_CRASH("PR_NewCondVar failed."); 198 } 199 200 { 201 // Make sure the debug service is instantiated before we create the 202 // watchdog thread, since we intentionally try to keep the thread's stack 203 // segment as small as possible. It isn't always large enough to 204 // instantiate a new service, and even when it is, we don't want fault in 205 // extra pages if we can avoid it. 206 nsCOMPtr<nsIDebug2> dbg = do_GetService("@mozilla.org/xpcom/debug;1"); 207 (void)dbg; 208 } 209 210 { 211 AutoLockWatchdog lock(this); 212 213 // The watchdog thread loop is pretty trivial, and should not 214 // require much stack space to do its job. So only give it 32KiB 215 // or the platform minimum. On modern Linux libc this might resolve to 216 // a runtime call. 217 size_t watchdogStackSize = PTHREAD_STACK_MIN; 218 watchdogStackSize = std::max<size_t>(32 * 1024, watchdogStackSize); 219 220 // Gecko uses thread private for accounting and has to clean up at thread 221 // exit. Therefore, even though we don't have a return value from the 222 // watchdog, we need to join it on shutdown. 223 mThread = PR_CreateThread(PR_USER_THREAD, WatchdogMain, this, 224 PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, 225 PR_JOINABLE_THREAD, watchdogStackSize); 226 if (!mThread) { 227 MOZ_CRASH("PR_CreateThread failed!"); 228 } 229 230 // WatchdogMain acquires the lock and then asserts mInitialized. So 231 // make sure to set mInitialized before releasing the lock here so 232 // that it's atomic with the creation of the thread. 233 mInitialized = true; 234 } 235 } 236 237 void Shutdown() { 238 MOZ_ASSERT(NS_IsMainThread()); 239 MOZ_ASSERT(Initialized()); 240 { // Scoped lock. 241 AutoLockWatchdog lock(this); 242 243 // Signal to the watchdog thread that it's time to shut down. 244 mShuttingDown = true; 245 246 // Wake up the watchdog, and wait for it to call us back. 247 PR_NotifyCondVar(mWakeup); 248 } 249 250 PR_JoinThread(mThread); 251 252 // The thread sets mShuttingDown to false as it exits. 253 MOZ_ASSERT(!mShuttingDown); 254 255 // Destroy state. 256 mThread = nullptr; 257 PR_DestroyCondVar(mWakeup); 258 mWakeup = nullptr; 259 PR_DestroyLock(mLock); 260 mLock = nullptr; 261 262 // All done. 263 mInitialized = false; 264 } 265 266 void SetMinScriptRunTimeSeconds(int32_t seconds) { 267 // This variable is atomic, and is set from the main thread without 268 // locking. 269 MOZ_ASSERT(seconds > 0); 270 mMinScriptRunTimeSeconds = seconds; 271 } 272 273 // 274 // Invoked by the watchdog thread only. 275 // 276 277 void Hibernate() { 278 MOZ_ASSERT(!NS_IsMainThread()); 279 mHibernating = true; 280 Sleep(PR_INTERVAL_NO_TIMEOUT); 281 } 282 void Sleep(PRIntervalTime timeout) { 283 MOZ_ASSERT(!NS_IsMainThread()); 284 AUTO_PROFILER_THREAD_SLEEP; 285 MOZ_ALWAYS_TRUE(PR_WaitCondVar(mWakeup, timeout) == PR_SUCCESS); 286 } 287 void Finished() { 288 MOZ_ASSERT(!NS_IsMainThread()); 289 mShuttingDown = false; 290 } 291 292 int32_t MinScriptRunTimeSeconds() { return mMinScriptRunTimeSeconds; } 293 294 private: 295 WatchdogManager* mManager; 296 297 PRLock* mLock; 298 PRCondVar* mWakeup; 299 PRThread* mThread; 300 bool mHibernating; 301 bool mInitialized; 302 bool mShuttingDown; 303 mozilla::Atomic<int32_t> mMinScriptRunTimeSeconds; 304 }; 305 306 #define PREF_MAX_SCRIPT_RUN_TIME_CONTENT "dom.max_script_run_time" 307 #define PREF_MAX_SCRIPT_RUN_TIME_CHROME "dom.max_chrome_script_run_time" 308 #define PREF_MAX_SCRIPT_RUN_TIME_EXT_CONTENT \ 309 "dom.max_ext_content_script_run_time" 310 311 static const char* gCallbackPrefs[] = { 312 "dom.use_watchdog", 313 PREF_MAX_SCRIPT_RUN_TIME_CONTENT, 314 PREF_MAX_SCRIPT_RUN_TIME_CHROME, 315 PREF_MAX_SCRIPT_RUN_TIME_EXT_CONTENT, 316 nullptr, 317 }; 318 319 class WatchdogManager { 320 public: 321 explicit WatchdogManager() { 322 // All the timestamps start at zero. 323 PodArrayZero(mTimestamps); 324 325 // Register ourselves as an observer to get updates on the pref. 326 Preferences::RegisterCallbacks(PrefsChanged, gCallbackPrefs, this); 327 } 328 329 virtual ~WatchdogManager() { 330 // Shutting down the watchdog requires context-switching to the watchdog 331 // thread, which isn't great to do in a destructor. So we require 332 // consumers to shut it down manually before releasing it. 333 MOZ_ASSERT(!mWatchdog); 334 } 335 336 private: 337 static void PrefsChanged(const char* aPref, void* aSelf) { 338 static_cast<WatchdogManager*>(aSelf)->RefreshWatchdog(); 339 } 340 341 public: 342 void Shutdown() { 343 Preferences::UnregisterCallbacks(PrefsChanged, gCallbackPrefs, this); 344 } 345 346 void RegisterContext(XPCJSContext* aContext) { 347 MOZ_ASSERT(NS_IsMainThread()); 348 AutoLockWatchdog lock(mWatchdog.get()); 349 350 if (aContext->mActive == XPCJSContext::CONTEXT_ACTIVE) { 351 mActiveContexts.insertBack(aContext); 352 } else { 353 mInactiveContexts.insertBack(aContext); 354 } 355 356 // Enable the watchdog, if appropriate. 357 RefreshWatchdog(); 358 } 359 360 void UnregisterContext(XPCJSContext* aContext) { 361 MOZ_ASSERT(NS_IsMainThread()); 362 AutoLockWatchdog lock(mWatchdog.get()); 363 364 // aContext must be in one of our two lists, simply remove it. 365 aContext->LinkedListElement<XPCJSContext>::remove(); 366 367 #ifdef DEBUG 368 // If this was the last context, we should have already shut down 369 // the watchdog. 370 if (mActiveContexts.isEmpty() && mInactiveContexts.isEmpty()) { 371 MOZ_ASSERT(!mWatchdog); 372 } 373 #endif 374 } 375 376 // Context statistics. These live on the watchdog manager, are written 377 // from the main thread, and are read from the watchdog thread (holding 378 // the lock in each case). 379 void RecordContextActivity(XPCJSContext* aContext, bool active) { 380 // The watchdog reads this state, so acquire the lock before writing it. 381 MOZ_ASSERT(NS_IsMainThread()); 382 AutoLockWatchdog lock(mWatchdog.get()); 383 384 // Write state. 385 aContext->mLastStateChange = PR_Now(); 386 aContext->mActive = 387 active ? XPCJSContext::CONTEXT_ACTIVE : XPCJSContext::CONTEXT_INACTIVE; 388 UpdateContextLists(aContext); 389 390 // The watchdog may be hibernating, waiting for the context to go 391 // active. Wake it up if necessary. 392 if (active && mWatchdog && mWatchdog->Hibernating()) { 393 mWatchdog->WakeUp(); 394 } 395 } 396 397 bool IsAnyContextActive() { return !mActiveContexts.isEmpty(); } 398 PRTime TimeSinceLastActiveContext() { 399 // Must be called on the watchdog thread with the lock held. 400 MOZ_ASSERT(!NS_IsMainThread()); 401 PR_ASSERT_CURRENT_THREAD_OWNS_LOCK(mWatchdog->GetLock()); 402 MOZ_ASSERT(mActiveContexts.isEmpty()); 403 MOZ_ASSERT(!mInactiveContexts.isEmpty()); 404 405 // We store inactive contexts with the most recently added inactive 406 // context at the end of the list. 407 return PR_Now() - mInactiveContexts.getLast()->mLastStateChange; 408 } 409 410 void RecordTimestamp(WatchdogTimestampCategory aCategory) { 411 // Must be called on the watchdog thread with the lock held. 412 MOZ_ASSERT(!NS_IsMainThread()); 413 PR_ASSERT_CURRENT_THREAD_OWNS_LOCK(mWatchdog->GetLock()); 414 MOZ_ASSERT(aCategory != TimestampContextStateChange, 415 "Use RecordContextActivity to update this"); 416 417 mTimestamps[aCategory] = PR_Now(); 418 } 419 420 PRTime GetContextTimestamp(XPCJSContext* aContext, 421 const AutoLockWatchdog& aProofOfLock) { 422 return aContext->mLastStateChange; 423 } 424 425 PRTime GetTimestamp(WatchdogTimestampCategory aCategory, 426 const AutoLockWatchdog& aProofOfLock) { 427 MOZ_ASSERT(aCategory != TimestampContextStateChange, 428 "Use GetContextTimestamp to retrieve this"); 429 return mTimestamps[aCategory]; 430 } 431 432 Watchdog* GetWatchdog() { return mWatchdog.get(); } 433 434 void RefreshWatchdog() { 435 bool wantWatchdog = Preferences::GetBool("dom.use_watchdog", true); 436 if (wantWatchdog != !!mWatchdog) { 437 if (wantWatchdog) { 438 StartWatchdog(); 439 } else { 440 StopWatchdog(); 441 } 442 } 443 444 if (mWatchdog) { 445 int32_t contentTime = StaticPrefs::dom_max_script_run_time(); 446 if (contentTime <= 0) { 447 contentTime = INT32_MAX; 448 } 449 int32_t chromeTime = StaticPrefs::dom_max_chrome_script_run_time(); 450 if (chromeTime <= 0) { 451 chromeTime = INT32_MAX; 452 } 453 int32_t extTime = StaticPrefs::dom_max_ext_content_script_run_time(); 454 if (extTime <= 0) { 455 extTime = INT32_MAX; 456 } 457 mWatchdog->SetMinScriptRunTimeSeconds( 458 std::min({contentTime, chromeTime, extTime})); 459 } 460 } 461 462 void StartWatchdog() { 463 MOZ_ASSERT(!mWatchdog); 464 mWatchdog = mozilla::MakeUnique<Watchdog>(this); 465 mWatchdog->Init(); 466 } 467 468 void StopWatchdog() { 469 MOZ_ASSERT(mWatchdog); 470 mWatchdog->Shutdown(); 471 mWatchdog = nullptr; 472 } 473 474 template <class Callback> 475 void ForAllActiveContexts(Callback&& aCallback) { 476 // This function must be called on the watchdog thread with the lock held. 477 MOZ_ASSERT(!NS_IsMainThread()); 478 PR_ASSERT_CURRENT_THREAD_OWNS_LOCK(mWatchdog->GetLock()); 479 480 for (auto* context = mActiveContexts.getFirst(); context; 481 context = context->LinkedListElement<XPCJSContext>::getNext()) { 482 if (!aCallback(context)) { 483 return; 484 } 485 } 486 } 487 488 private: 489 void UpdateContextLists(XPCJSContext* aContext) { 490 // Given aContext whose activity state or timestamp has just changed, 491 // put it back in the proper position in the proper list. 492 aContext->LinkedListElement<XPCJSContext>::remove(); 493 auto& list = aContext->mActive == XPCJSContext::CONTEXT_ACTIVE 494 ? mActiveContexts 495 : mInactiveContexts; 496 497 // Either the new list is empty or aContext must be more recent than 498 // the existing last element. 499 MOZ_ASSERT_IF(!list.isEmpty(), list.getLast()->mLastStateChange < 500 aContext->mLastStateChange); 501 list.insertBack(aContext); 502 } 503 504 LinkedList<XPCJSContext> mActiveContexts; 505 LinkedList<XPCJSContext> mInactiveContexts; 506 mozilla::UniquePtr<Watchdog> mWatchdog; 507 508 // We store ContextStateChange on the contexts themselves. 509 PRTime mTimestamps[kWatchdogTimestampCategoryCount - 1]; 510 }; 511 512 AutoLockWatchdog::AutoLockWatchdog(Watchdog* aWatchdog) : mWatchdog(aWatchdog) { 513 if (mWatchdog) { 514 PR_Lock(mWatchdog->GetLock()); 515 } 516 } 517 518 AutoLockWatchdog::~AutoLockWatchdog() { 519 if (mWatchdog) { 520 PR_Unlock(mWatchdog->GetLock()); 521 } 522 } 523 524 static void WatchdogMain(void* arg) { 525 AUTO_PROFILER_REGISTER_THREAD("JS Watchdog"); 526 // Create an nsThread wrapper for the thread and register it with the thread 527 // manager. 528 (void)NS_GetCurrentThread(); 529 NS_SetCurrentThreadName("JS Watchdog"); 530 531 Watchdog* self = static_cast<Watchdog*>(arg); 532 WatchdogManager* manager = self->Manager(); 533 534 // Lock lasts until we return 535 AutoLockWatchdog lock(self); 536 537 MOZ_ASSERT(self->Initialized()); 538 while (!self->ShuttingDown()) { 539 // Sleep only 1 second if recently (or currently) active; otherwise, 540 // hibernate 541 if (manager->IsAnyContextActive() || 542 manager->TimeSinceLastActiveContext() <= PRTime(2 * PR_USEC_PER_SEC)) { 543 self->Sleep(PR_TicksPerSecond()); 544 } else { 545 manager->RecordTimestamp(TimestampWatchdogHibernateStart); 546 self->Hibernate(); 547 manager->RecordTimestamp(TimestampWatchdogHibernateStop); 548 } 549 550 // Rise and shine. 551 manager->RecordTimestamp(TimestampWatchdogWakeup); 552 553 // Don't request an interrupt callback unless the current script has 554 // been running long enough that we might show the slow script dialog. 555 // Triggering the callback from off the main thread can be expensive. 556 557 // We want to avoid showing the slow script dialog if the user's laptop 558 // goes to sleep in the middle of running a script. To ensure this, we 559 // invoke the interrupt callback after only half the timeout has 560 // elapsed. The callback simply records the fact that it was called in 561 // the mSlowScriptSecondHalf flag. Then we wait another (timeout/2) 562 // seconds and invoke the callback again. This time around it sees 563 // mSlowScriptSecondHalf is set and so it shows the slow script 564 // dialog. If the computer is put to sleep during one of the (timeout/2) 565 // periods, the script still has the other (timeout/2) seconds to 566 // finish. 567 if (!self->ShuttingDown() && manager->IsAnyContextActive()) { 568 bool debuggerAttached = false; 569 nsCOMPtr<nsIDebug2> dbg = do_GetService("@mozilla.org/xpcom/debug;1"); 570 if (dbg) { 571 dbg->GetIsDebuggerAttached(&debuggerAttached); 572 } 573 if (debuggerAttached) { 574 // We won't be interrupting these scripts anyway. 575 continue; 576 } 577 578 PRTime usecs = self->MinScriptRunTimeSeconds() * PR_USEC_PER_SEC / 2; 579 manager->ForAllActiveContexts([usecs, manager, 580 &lock](XPCJSContext* aContext) -> bool { 581 auto timediff = PR_Now() - manager->GetContextTimestamp(aContext, lock); 582 if (timediff > usecs) { 583 JS_RequestInterruptCallback(aContext->Context()); 584 return true; 585 } 586 return false; 587 }); 588 } 589 } 590 591 // Tell the manager that we've shut down. 592 self->Finished(); 593 } 594 595 PRTime XPCJSContext::GetWatchdogTimestamp(WatchdogTimestampCategory aCategory) { 596 AutoLockWatchdog lock(mWatchdogManager->GetWatchdog()); 597 return aCategory == TimestampContextStateChange 598 ? mWatchdogManager->GetContextTimestamp(this, lock) 599 : mWatchdogManager->GetTimestamp(aCategory, lock); 600 } 601 602 // static 603 bool XPCJSContext::RecordScriptActivity(bool aActive) { 604 MOZ_ASSERT(NS_IsMainThread()); 605 606 XPCJSContext* xpccx = XPCJSContext::Get(); 607 if (!xpccx) { 608 // mozilla::SpinEventLoopUntil may use AutoScriptActivity(false) after 609 // we destroyed the XPCJSContext. 610 MOZ_ASSERT(!aActive); 611 return false; 612 } 613 614 bool oldValue = xpccx->SetHasScriptActivity(aActive); 615 if (aActive == oldValue) { 616 // Nothing to do. 617 return oldValue; 618 } 619 620 if (!aActive) { 621 ProcessHangMonitor::ClearHang(); 622 } 623 xpccx->mWatchdogManager->RecordContextActivity(xpccx, aActive); 624 625 return oldValue; 626 } 627 628 AutoScriptActivity::AutoScriptActivity(bool aActive) 629 : mActive(aActive), 630 mOldValue(XPCJSContext::RecordScriptActivity(aActive)) {} 631 632 AutoScriptActivity::~AutoScriptActivity() { 633 MOZ_ALWAYS_TRUE(mActive == XPCJSContext::RecordScriptActivity(mOldValue)); 634 } 635 636 static const double sChromeSlowScriptTelemetryCutoff(10.0); 637 638 // static 639 bool XPCJSContext::InterruptCallback(JSContext* cx) { 640 XPCJSContext* self = XPCJSContext::Get(); 641 642 // Now is a good time to turn on profiling if it's pending. 643 PROFILER_JS_INTERRUPT_CALLBACK(); 644 645 if (profiler_thread_is_being_profiled_for_markers()) { 646 nsDependentCString filename("unknown file"); 647 JS::AutoFilename scriptFilename; 648 // Computing the line number can be very expensive (see bug 1330231 for 649 // example), so don't request it here. 650 if (JS::DescribeScriptedCaller(&scriptFilename, cx)) { 651 if (const char* file = scriptFilename.get()) { 652 filename.Assign(file, strlen(file)); 653 } 654 PROFILER_MARKER_TEXT("JS::InterruptCallback", JS, {}, filename); 655 } 656 } 657 658 // Normally we record mSlowScriptCheckpoint when we start to process an 659 // event. However, we can run JS outside of event handlers. This code takes 660 // care of that case. 661 if (self->mSlowScriptCheckpoint.IsNull()) { 662 self->mSlowScriptCheckpoint = TimeStamp::NowLoRes(); 663 self->mSlowScriptSecondHalf = false; 664 self->mSlowScriptActualWait = mozilla::TimeDuration(); 665 self->mTimeoutAccumulated = false; 666 self->mExecutedChromeScript = false; 667 return true; 668 } 669 670 // Sometimes we get called back during XPConnect initialization, before Gecko 671 // has finished bootstrapping. Avoid crashing in nsContentUtils below. 672 if (!nsContentUtils::IsInitialized()) { 673 return true; 674 } 675 676 // This is at least the second interrupt callback we've received since 677 // returning to the event loop. See how long it's been, and what the limit 678 // is. 679 TimeStamp now = TimeStamp::NowLoRes(); 680 TimeDuration duration = now - self->mSlowScriptCheckpoint; 681 int32_t limit; 682 683 nsString addonId; 684 const char* prefName; 685 auto principal = BasePrincipal::Cast(nsContentUtils::SubjectPrincipal(cx)); 686 bool chrome = principal->Is<SystemPrincipal>(); 687 if (chrome) { 688 prefName = PREF_MAX_SCRIPT_RUN_TIME_CHROME; 689 limit = StaticPrefs::dom_max_chrome_script_run_time(); 690 self->mExecutedChromeScript = true; 691 } else if (auto policy = principal->ContentScriptAddonPolicy()) { 692 policy->GetId(addonId); 693 prefName = PREF_MAX_SCRIPT_RUN_TIME_EXT_CONTENT; 694 limit = StaticPrefs::dom_max_ext_content_script_run_time(); 695 } else { 696 prefName = PREF_MAX_SCRIPT_RUN_TIME_CONTENT; 697 limit = StaticPrefs::dom_max_script_run_time(); 698 } 699 700 // When the parent process slow script dialog is disabled, we still want 701 // to be able to track things for telemetry, so set `mSlowScriptSecondHalf` 702 // to true in that case: 703 if (limit == 0 && chrome && 704 duration.ToSeconds() > sChromeSlowScriptTelemetryCutoff / 2.0) { 705 self->mSlowScriptSecondHalf = true; 706 return true; 707 } 708 // If there's no limit, or we're within the limit, let it go. 709 if (limit == 0 || duration.ToSeconds() < limit / 2.0) { 710 return true; 711 } 712 713 self->mSlowScriptCheckpoint = now; 714 self->mSlowScriptActualWait += duration; 715 716 // In order to guard against time changes or laptops going to sleep, we 717 // don't trigger the slow script warning until (limit/2) seconds have 718 // elapsed twice. 719 if (!self->mSlowScriptSecondHalf) { 720 self->mSlowScriptSecondHalf = true; 721 return true; 722 } 723 724 // For scripts in content processes, we only want to show the slow script 725 // dialogue if the user is actually trying to perform an important 726 // interaction. In theory this could be a chrome script running in the 727 // content process, which we probably don't want to give the user the ability 728 // to terminate. However, if this is the case we won't be able to map the 729 // script global to a window and we'll bail out below. 730 if (XRE_IsContentProcess() && 731 StaticPrefs::dom_max_script_run_time_require_critical_input()) { 732 // Call possibly slow PeekMessages after the other common early returns in 733 // this method. 734 ContentChild* contentChild = ContentChild::GetSingleton(); 735 mozilla::ipc::MessageChannel* channel = 736 contentChild ? contentChild->GetIPCChannel() : nullptr; 737 if (channel) { 738 bool foundInputEvent = false; 739 channel->PeekMessages( 740 [&foundInputEvent](const IPC::Message& aMsg) -> bool { 741 if (nsContentUtils::IsMessageCriticalInputEvent(aMsg)) { 742 foundInputEvent = true; 743 return false; 744 } 745 return true; 746 }); 747 if (!foundInputEvent) { 748 return true; 749 } 750 } 751 } 752 753 // We use a fixed value of 2 from browser_parent_process_hang_telemetry.js 754 // to check if the telemetry events work. Do not interrupt it with a dialog. 755 if (chrome && limit == 2 && xpc::IsInAutomation()) { 756 return true; 757 } 758 759 // 760 // This has gone on long enough! Time to take action. ;-) 761 // 762 763 // Get the DOM window associated with the running script. If the script is 764 // running in a non-DOM scope, we have to just let it keep running. 765 RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); 766 RefPtr<nsGlobalWindowInner> win = WindowOrNull(global); 767 if (!win) { 768 // If this is a sandbox associated with a DOMWindow via a 769 // sandboxPrototype, use that DOMWindow. This supports WebExtension 770 // content scripts. 771 win = SandboxWindowOrNull(global, cx); 772 } 773 774 if (!win) { 775 NS_WARNING("No active window"); 776 return true; 777 } 778 779 if (win->IsDying()) { 780 // The window is being torn down. When that happens we try to prevent 781 // the dispatch of new runnables, so it also makes sense to kill any 782 // long-running script. The user is primarily interested in this page 783 // going away. 784 return false; 785 } 786 787 // Accumulate slow script invokation delay. 788 if (!chrome && !self->mTimeoutAccumulated) { 789 TimeDuration delay = 790 self->mSlowScriptActualWait - TimeDuration::FromSeconds(limit); 791 glean::slow_script_warning::notify_delay.AccumulateRawDuration(delay); 792 self->mTimeoutAccumulated = true; 793 } 794 795 // Show the prompt to the user, and kill if requested. 796 nsGlobalWindowInner::SlowScriptResponse response = win->ShowSlowScriptDialog( 797 cx, addonId, self->mSlowScriptActualWait.ToMilliseconds()); 798 if (response == nsGlobalWindowInner::KillSlowScript) { 799 if (Preferences::GetBool("dom.global_stop_script", true)) { 800 xpc::Scriptability::Get(global).Block(); 801 } 802 if (nsCOMPtr<Document> doc = win->GetExtantDoc()) { 803 doc->UnlockAllWakeLocks(WakeLockType::Screen); 804 } 805 return false; 806 } 807 808 // The user chose to continue the script. Reset the timer, and disable this 809 // machinery with a pref if the user opted out of future slow-script dialogs. 810 if (response != nsGlobalWindowInner::ContinueSlowScriptAndKeepNotifying) { 811 self->mSlowScriptCheckpoint = TimeStamp::NowLoRes(); 812 } 813 814 if (response == nsGlobalWindowInner::AlwaysContinueSlowScript) { 815 Preferences::SetInt(prefName, 0); 816 } 817 818 return true; 819 } 820 821 #define JS_OPTIONS_DOT_STR "javascript.options." 822 823 static mozilla::Atomic<bool> sDiscardSystemSource(false); 824 825 bool xpc::ShouldDiscardSystemSource() { return sDiscardSystemSource; } 826 827 static mozilla::Atomic<bool> sSharedMemoryEnabled(false); 828 static mozilla::Atomic<bool> sStreamsEnabled(false); 829 830 void xpc::SetPrefableRealmOptions(JS::RealmOptions& options) { 831 options.creationOptions() 832 .setSharedMemoryAndAtomicsEnabled(sSharedMemoryEnabled) 833 .setCoopAndCoepEnabled( 834 StaticPrefs::browser_tabs_remote_useCrossOriginOpenerPolicy() && 835 StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()); 836 } 837 838 void xpc::SetPrefableCompileOptions(JS::PrefableCompileOptions& options) { 839 options.setSourcePragmas(StaticPrefs::javascript_options_source_pragmas()) 840 .setAsmJS(StaticPrefs::javascript_options_asmjs()) 841 .setThrowOnAsmJSValidationFailure( 842 StaticPrefs::javascript_options_throw_on_asmjs_validation_failure()); 843 } 844 845 void xpc::SetPrefableContextOptions(JS::ContextOptions& options) { 846 options 847 #ifdef FUZZING 848 .setFuzzing(Preferences::GetBool(JS_OPTIONS_DOT_STR "fuzzing.enabled")) 849 #endif 850 .setWasm(Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm")) 851 .setWasmForTrustedPrinciples( 852 Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_trustedprincipals")) 853 .setWasmIon(Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_optimizingjit")) 854 .setWasmBaseline( 855 Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_baselinejit")) 856 .setAsyncStack(Preferences::GetBool(JS_OPTIONS_DOT_STR "asyncstack")) 857 .setAsyncStackCaptureDebuggeeOnly(Preferences::GetBool( 858 JS_OPTIONS_DOT_STR "asyncstack_capture_debuggee_only")); 859 860 SetPrefableCompileOptions(options.compileOptions()); 861 } 862 863 static void LoadStartupJSPrefs(XPCJSContext* xpccx) { 864 // Prefs that require a restart are handled here. This includes the 865 // process-wide JIT options because toggling these at runtime can easily cause 866 // races or get us into an inconsistent state. 867 // 868 // 'Live' prefs are handled by ReloadPrefsCallback below. 869 870 // Note: JS::Prefs are set earlier in startup, in InitializeJS in 871 // XPCOMInit.cpp. 872 873 JSContext* cx = xpccx->Context(); 874 875 // Some prefs are unlisted in all.js / StaticPrefs (and thus are invisible in 876 // about:config). Make sure we use explicit defaults here. 877 bool useJitForTrustedPrincipals = 878 Preferences::GetBool(JS_OPTIONS_DOT_STR "jit_trustedprincipals", false); 879 880 bool safeMode = false; 881 nsCOMPtr<nsIXULRuntime> xr = do_GetService("@mozilla.org/xre/runtime;1"); 882 if (xr) { 883 xr->GetInSafeMode(&safeMode); 884 } 885 886 // NOTE: Baseline Interpreter is still used in safe-mode. This gives a big 887 // perf gain and is our simplest JIT so we make a tradeoff. 888 JS_SetGlobalJitCompilerOption( 889 cx, JSJITCOMPILER_BASELINE_INTERPRETER_ENABLE, 890 StaticPrefs::javascript_options_blinterp_DoNotUseDirectly()); 891 892 // Disable most JITs in Safe-Mode. 893 if (safeMode) { 894 JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_BASELINE_ENABLE, false); 895 JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_ION_ENABLE, false); 896 JS_SetGlobalJitCompilerOption( 897 cx, JSJITCOMPILER_JIT_TRUSTEDPRINCIPALS_ENABLE, false); 898 JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_NATIVE_REGEXP_ENABLE, 899 false); 900 JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_JIT_HINTS_ENABLE, false); 901 xpc::SelfHostedShmem::SetSelfHostedUseSharedMemory(false); 902 } else { 903 JS_SetGlobalJitCompilerOption( 904 cx, JSJITCOMPILER_BASELINE_ENABLE, 905 StaticPrefs::javascript_options_baselinejit_DoNotUseDirectly()); 906 JS_SetGlobalJitCompilerOption( 907 cx, JSJITCOMPILER_ION_ENABLE, 908 StaticPrefs::javascript_options_ion_DoNotUseDirectly()); 909 JS_SetGlobalJitCompilerOption(cx, 910 JSJITCOMPILER_JIT_TRUSTEDPRINCIPALS_ENABLE, 911 useJitForTrustedPrincipals); 912 JS_SetGlobalJitCompilerOption( 913 cx, JSJITCOMPILER_NATIVE_REGEXP_ENABLE, 914 StaticPrefs::javascript_options_native_regexp_DoNotUseDirectly()); 915 // Only enable the jit hints cache for the content process to avoid 916 // any possible jank or delays on the parent process. 917 JS_SetGlobalJitCompilerOption( 918 cx, JSJITCOMPILER_JIT_HINTS_ENABLE, 919 XRE_IsContentProcess() 920 ? StaticPrefs::javascript_options_jithints_DoNotUseDirectly() 921 : false); 922 xpc::SelfHostedShmem::SetSelfHostedUseSharedMemory( 923 StaticPrefs:: 924 javascript_options_self_hosted_use_shared_memory_DoNotUseDirectly()); 925 } 926 927 uint32_t strategyIndex = StaticPrefs:: 928 javascript_options_baselinejit_offthread_compilation_strategy(); 929 bool onDemandOMTBaselineEnabled = strategyIndex == 1 || strategyIndex == 3; 930 JS_SetOffthreadBaselineCompilationEnabled(cx, onDemandOMTBaselineEnabled); 931 932 JS_SetOffthreadIonCompilationEnabled( 933 cx, StaticPrefs:: 934 javascript_options_ion_offthread_compilation_DoNotUseDirectly()); 935 936 JS_SetGlobalJitCompilerOption( 937 cx, JSJITCOMPILER_BASELINE_INTERPRETER_WARMUP_TRIGGER, 938 StaticPrefs::javascript_options_blinterp_threshold_DoNotUseDirectly()); 939 JS_SetGlobalJitCompilerOption( 940 cx, JSJITCOMPILER_BASELINE_WARMUP_TRIGGER, 941 StaticPrefs::javascript_options_baselinejit_threshold_DoNotUseDirectly()); 942 JS_SetGlobalJitCompilerOption( 943 cx, JSJITCOMPILER_ION_NORMAL_WARMUP_TRIGGER, 944 StaticPrefs::javascript_options_ion_threshold_DoNotUseDirectly()); 945 JS_SetGlobalJitCompilerOption( 946 cx, JSJITCOMPILER_ION_FREQUENT_BAILOUT_THRESHOLD, 947 StaticPrefs:: 948 javascript_options_ion_frequent_bailout_threshold_DoNotUseDirectly()); 949 JS_SetGlobalJitCompilerOption( 950 cx, JSJITCOMPILER_INLINING_BYTECODE_MAX_LENGTH, 951 StaticPrefs:: 952 javascript_options_inlining_bytecode_max_length_DoNotUseDirectly()); 953 954 #ifdef DEBUG 955 JS_SetGlobalJitCompilerOption( 956 cx, JSJITCOMPILER_FULL_DEBUG_CHECKS, 957 StaticPrefs::javascript_options_jit_full_debug_checks_DoNotUseDirectly()); 958 #endif 959 960 #if !defined(JS_CODEGEN_MIPS64) && !defined(JS_CODEGEN_RISCV64) && \ 961 !defined(JS_CODEGEN_LOONG64) 962 JS_SetGlobalJitCompilerOption( 963 cx, JSJITCOMPILER_SPECTRE_INDEX_MASKING, 964 StaticPrefs::javascript_options_spectre_index_masking_DoNotUseDirectly()); 965 JS_SetGlobalJitCompilerOption( 966 cx, JSJITCOMPILER_SPECTRE_OBJECT_MITIGATIONS, 967 StaticPrefs:: 968 javascript_options_spectre_object_mitigations_DoNotUseDirectly()); 969 JS_SetGlobalJitCompilerOption( 970 cx, JSJITCOMPILER_SPECTRE_STRING_MITIGATIONS, 971 StaticPrefs:: 972 javascript_options_spectre_string_mitigations_DoNotUseDirectly()); 973 JS_SetGlobalJitCompilerOption( 974 cx, JSJITCOMPILER_SPECTRE_VALUE_MASKING, 975 StaticPrefs::javascript_options_spectre_value_masking_DoNotUseDirectly()); 976 JS_SetGlobalJitCompilerOption( 977 cx, JSJITCOMPILER_SPECTRE_JIT_TO_CXX_CALLS, 978 StaticPrefs:: 979 javascript_options_spectre_jit_to_cxx_calls_DoNotUseDirectly()); 980 #endif 981 982 bool writeProtectCode = true; 983 if (XRE_IsContentProcess()) { 984 writeProtectCode = 985 StaticPrefs::javascript_options_content_process_write_protect_code(); 986 } 987 JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_WRITE_PROTECT_CODE, 988 writeProtectCode); 989 } 990 991 static void ReloadPrefsCallback(const char* pref, void* aXpccx) { 992 // Note: Prefs that require a restart are handled in LoadStartupJSPrefs above. 993 994 // Update all non-startup JS::Prefs. 995 SET_NON_STARTUP_JS_PREFS_FROM_BROWSER_PREFS; 996 997 auto xpccx = static_cast<XPCJSContext*>(aXpccx); 998 JSContext* cx = xpccx->Context(); 999 1000 sDiscardSystemSource = 1001 Preferences::GetBool(JS_OPTIONS_DOT_STR "discardSystemSource"); 1002 sSharedMemoryEnabled = 1003 Preferences::GetBool(JS_OPTIONS_DOT_STR "shared_memory"); 1004 sStreamsEnabled = Preferences::GetBool(JS_OPTIONS_DOT_STR "streams"); 1005 1006 #ifdef JS_GC_ZEAL 1007 int32_t zeal = Preferences::GetInt(JS_OPTIONS_DOT_STR "mem.gc_zeal.mode", -1); 1008 int32_t zeal_frequency = 1009 Preferences::GetInt(JS_OPTIONS_DOT_STR "mem.gc_zeal.frequency", 1010 JS::BrowserDefaultGCZealFrequency); 1011 if (zeal >= 0) { 1012 JS::SetGCZeal(cx, (uint8_t)zeal, zeal_frequency); 1013 } 1014 #endif // JS_GC_ZEAL 1015 1016 auto& contextOptions = JS::ContextOptionsRef(cx); 1017 SetPrefableContextOptions(contextOptions); 1018 1019 // Set options not shared with workers. 1020 contextOptions 1021 .setThrowOnDebuggeeWouldRun(Preferences::GetBool( 1022 JS_OPTIONS_DOT_STR "throw_on_debuggee_would_run")) 1023 .setDumpStackOnDebuggeeWouldRun(Preferences::GetBool( 1024 JS_OPTIONS_DOT_STR "dump_stack_on_debuggee_would_run")); 1025 1026 nsCOMPtr<nsIXULRuntime> xr = do_GetService("@mozilla.org/xre/runtime;1"); 1027 if (xr) { 1028 bool safeMode = false; 1029 xr->GetInSafeMode(&safeMode); 1030 if (safeMode) { 1031 contextOptions.disableOptionsForSafeMode(); 1032 } 1033 } 1034 1035 // Set up the callback for DOM event dispatch 1036 if (StaticPrefs::dom_expose_test_interfaces()) { 1037 JS::SetDispatchDOMEventCallback(cx, DispatchJitEventToDOM); 1038 } else { 1039 JS::SetDispatchDOMEventCallback(cx, nullptr); 1040 } 1041 } 1042 1043 XPCJSContext::~XPCJSContext() { 1044 MOZ_COUNT_DTOR_INHERITED(XPCJSContext, CycleCollectedJSContext); 1045 // Elsewhere we abort immediately if XPCJSContext initialization fails. 1046 // Therefore the context must be non-null. 1047 MOZ_ASSERT(MaybeContext()); 1048 1049 Preferences::UnregisterPrefixCallback(ReloadPrefsCallback, JS_OPTIONS_DOT_STR, 1050 this); 1051 1052 #ifdef FUZZING 1053 Preferences::UnregisterCallback(ReloadPrefsCallback, "fuzzing.enabled", this); 1054 #endif 1055 1056 // Clear any pending exception. It might be an XPCWrappedJS, and if we try 1057 // to destroy it later we will crash. 1058 SetPendingException(nullptr); 1059 1060 // If we're the last XPCJSContext around, clean up the watchdog manager. 1061 if (--sInstanceCount == 0) { 1062 if (mWatchdogManager->GetWatchdog()) { 1063 mWatchdogManager->StopWatchdog(); 1064 } 1065 1066 mWatchdogManager->UnregisterContext(this); 1067 mWatchdogManager->Shutdown(); 1068 sWatchdogInstance = nullptr; 1069 } else { 1070 // Otherwise, simply remove ourselves from the list. 1071 mWatchdogManager->UnregisterContext(this); 1072 } 1073 1074 if (mCallContext) { 1075 mCallContext->SystemIsBeingShutDown(); 1076 } 1077 1078 PROFILER_CLEAR_JS_CONTEXT(); 1079 } 1080 1081 XPCJSContext::XPCJSContext() 1082 : mCallContext(nullptr), 1083 mAutoRoots(nullptr), 1084 mResolveName(JS::PropertyKey::Void()), 1085 mResolvingWrapper(nullptr), 1086 mWatchdogManager(GetWatchdogManager()), 1087 mSlowScriptSecondHalf(false), 1088 mTimeoutAccumulated(false), 1089 mExecutedChromeScript(false), 1090 mHasScriptActivity(false), 1091 mPendingResult(NS_OK), 1092 mActive(CONTEXT_INACTIVE), 1093 mLastStateChange(PR_Now()) { 1094 MOZ_COUNT_CTOR_INHERITED(XPCJSContext, CycleCollectedJSContext); 1095 MOZ_ASSERT(mWatchdogManager); 1096 ++sInstanceCount; 1097 mWatchdogManager->RegisterContext(this); 1098 } 1099 1100 /* static */ 1101 XPCJSContext* XPCJSContext::Get() { 1102 // Do an explicit null check, because this can get called from a process that 1103 // does not run JS. 1104 nsXPConnect* xpc = static_cast<nsXPConnect*>(nsXPConnect::XPConnect()); 1105 return xpc ? xpc->GetContext() : nullptr; 1106 } 1107 1108 #ifdef XP_WIN 1109 static size_t GetWindowsStackSize() { 1110 // First, get the stack base. Because the stack grows down, this is the top 1111 // of the stack. 1112 const uint8_t* stackTop; 1113 # ifdef _WIN64 1114 PNT_TIB64 pTib = reinterpret_cast<PNT_TIB64>(NtCurrentTeb()); 1115 stackTop = reinterpret_cast<const uint8_t*>(pTib->StackBase); 1116 # else 1117 PNT_TIB pTib = reinterpret_cast<PNT_TIB>(NtCurrentTeb()); 1118 stackTop = reinterpret_cast<const uint8_t*>(pTib->StackBase); 1119 # endif 1120 1121 // Now determine the stack bottom. Note that we can't use tib->StackLimit, 1122 // because that's the size of the committed area and we're also interested 1123 // in the reserved pages below that. 1124 MEMORY_BASIC_INFORMATION mbi; 1125 if (!VirtualQuery(&mbi, &mbi, sizeof(mbi))) { 1126 MOZ_CRASH("VirtualQuery failed"); 1127 } 1128 1129 const uint8_t* stackBottom = 1130 reinterpret_cast<const uint8_t*>(mbi.AllocationBase); 1131 1132 // Do some sanity checks. 1133 size_t stackSize = size_t(stackTop - stackBottom); 1134 MOZ_RELEASE_ASSERT(stackSize >= 1 * 1024 * 1024); 1135 MOZ_RELEASE_ASSERT(stackSize <= 32 * 1024 * 1024); 1136 1137 // Subtract 40 KB (Win32) or 80 KB (Win64) to account for things like 1138 // the guard page and large PGO stack frames. 1139 return stackSize - 10 * sizeof(uintptr_t) * 1024; 1140 } 1141 #endif 1142 1143 XPCJSRuntime* XPCJSContext::Runtime() const { 1144 return static_cast<XPCJSRuntime*>(CycleCollectedJSContext::Runtime()); 1145 } 1146 1147 CycleCollectedJSRuntime* XPCJSContext::CreateRuntime(JSContext* aCx) { 1148 return new XPCJSRuntime(aCx); 1149 } 1150 1151 class HelperThreadTaskHandler : public Task { 1152 JS::HelperThreadTask* mTask; 1153 1154 public: 1155 explicit HelperThreadTaskHandler(JS::HelperThreadTask* aTask) 1156 : Task(Kind::OffMainThreadOnly, EventQueuePriority::Normal), 1157 mTask(aTask) { 1158 // Bug 1703185: Currently all tasks are run at the same priority. 1159 } 1160 1161 TaskResult Run() override { 1162 JS::RunHelperThreadTask(mTask); 1163 return TaskResult::Complete; 1164 } 1165 1166 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY 1167 bool GetName(nsACString& aName) override { 1168 const char* taskName = JS::GetHelperThreadTaskName(mTask); 1169 aName.AssignLiteral(taskName, strlen(taskName)); 1170 return true; 1171 } 1172 #endif 1173 1174 private: 1175 ~HelperThreadTaskHandler() = default; 1176 }; 1177 1178 static void DispatchOffThreadTask(JS::HelperThreadTask* aTask) { 1179 TaskController::Get()->AddTask(MakeAndAddRef<HelperThreadTaskHandler>(aTask)); 1180 } 1181 1182 // Name of entry in mozilla::scache::StartupCache to use for SpiderMonkey 1183 // self-hosted JS precompiled bytecode. 1184 static constexpr char kSelfHostCacheKey[] = "js.self-hosted"; 1185 1186 static bool CreateSelfHostedSharedMemory(JSContext* aCx, 1187 JS::SelfHostedCache aBuf) { 1188 // Record the data to the "StartupCache" for future restarts to use to 1189 // initialize the shmem with. 1190 if (auto* sc = scache::StartupCache::GetSingleton()) { 1191 UniqueFreePtr<char[]> copy(static_cast<char*>(malloc(aBuf.LengthBytes()))); 1192 if (copy) { 1193 memcpy(copy.get(), aBuf.Elements(), aBuf.LengthBytes()); 1194 sc->PutBuffer(kSelfHostCacheKey, std::move(copy), aBuf.LengthBytes()); 1195 } 1196 } 1197 1198 auto& shm = xpc::SelfHostedShmem::GetSingleton(); 1199 MOZ_RELEASE_ASSERT(shm.Content().IsEmpty()); 1200 // Failures within InitFromParent output warnings but do not cause 1201 // unrecoverable failures. 1202 shm.InitFromParent(aBuf); 1203 return true; 1204 } 1205 1206 static JS::OpaqueLogger GetLoggerByName(const char* name) { 1207 LogModule* tmp = LogModule::Get(name); 1208 return static_cast<JS::OpaqueLogger>(tmp); 1209 } 1210 1211 MOZ_FORMAT_PRINTF(3, 0) 1212 static void LogPrintVA(JS::OpaqueLogger aLogger, mozilla::LogLevel level, 1213 const char* aFmt, va_list ap) { 1214 LogModule* logmod = static_cast<LogModule*>(aLogger); 1215 1216 logmod->Printv(level, aFmt, ap); 1217 } 1218 1219 static void LogPrintFMT(JS::OpaqueLogger aLogger, mozilla::LogLevel level, 1220 fmt::string_view fmt, fmt::format_args args) { 1221 LogModule* logmod = static_cast<LogModule*>(aLogger); 1222 1223 logmod->PrintvFmt(level, fmt, args); 1224 } 1225 1226 static AtomicLogLevel& GetLevelRef(JS::OpaqueLogger aLogger) { 1227 LogModule* logmod = static_cast<LogModule*>(aLogger); 1228 return logmod->LevelRef(); 1229 } 1230 1231 static JS::LoggingInterface loggingInterface = {GetLoggerByName, LogPrintVA, 1232 LogPrintFMT, GetLevelRef}; 1233 1234 nsresult XPCJSContext::Initialize() { 1235 if (StaticPrefs::javascript_options_external_thread_pool_DoNotUseDirectly()) { 1236 size_t threadCount = TaskController::GetPoolThreadCount(); 1237 size_t stackSize = TaskController::GetThreadStackSize(); 1238 SetHelperThreadTaskCallback(&DispatchOffThreadTask, threadCount, stackSize); 1239 } 1240 1241 if (!JS::SetLoggingInterface(loggingInterface)) { 1242 MOZ_CRASH("Failed to install logging interface"); 1243 } 1244 1245 nsresult rv = 1246 CycleCollectedJSContext::Initialize(nullptr, JS::DefaultHeapMaxBytes); 1247 if (NS_WARN_IF(NS_FAILED(rv))) { 1248 return rv; 1249 } 1250 1251 MOZ_ASSERT(Context()); 1252 JSContext* cx = Context(); 1253 1254 // The JS engine permits us to set different stack limits for system code, 1255 // trusted script, and untrusted script. We have tests that ensure that 1256 // we can always execute 10 "heavy" (eval+with) stack frames deeper in 1257 // privileged code. Our stack sizes vary greatly in different configurations, 1258 // so satisfying those tests requires some care. Manual measurements of the 1259 // number of heavy stack frames achievable gives us the following rough data, 1260 // ordered by the effective categories in which they are grouped in the 1261 // JS_SetNativeStackQuota call (which predates this analysis). 1262 // 1263 // The following "Stack Frames" numbers come from `chromeLimit` in 1264 // js/xpconnect/tests/chrome/test_bug732665.xul 1265 // 1266 // Platform | Build | Stack Quota | Stack Frames | Stack Frame Size 1267 // ------------+-------+-------------+--------------+------------------ 1268 // OSX 64 | Opt | 7MB | 1331 | ~5.4k 1269 // OSX 64 | Debug | 7MB | 1202 | ~6.0k 1270 // ------------+-------+-------------+--------------+------------------ 1271 // Linux 32 | Opt | 7.875MB | 2513 | ~3.2k 1272 // Linux 32 | Debug | 7.875MB | 2146 | ~3.8k 1273 // ------------+-------+-------------+--------------+------------------ 1274 // Linux 64 | Opt | 7.875MB | 1360 | ~5.9k 1275 // Linux 64 | Debug | 7.875MB | 1180 | ~6.8k 1276 // Linux 64 | ASan | 7.875MB | 473 | ~17.0k 1277 // ------------+-------+-------------+--------------+------------------ 1278 // Windows 32 | Opt | 984k | 188 | ~5.2k 1279 // Windows 32 | Debug | 984k | 208 | ~4.7k 1280 // ------------+-------+-------------+--------------+------------------ 1281 // Windows 64 | Opt | 1.922MB | 189 | ~10.4k 1282 // Windows 64 | Debug | 1.922MB | 175 | ~11.2k 1283 // 1284 // We tune the trusted/untrusted quotas for each configuration to achieve our 1285 // invariants while attempting to minimize overhead. In contrast, our buffer 1286 // between system code and trusted script is a very unscientific 10k. 1287 const size_t kSystemCodeBuffer = 10 * 1024; 1288 1289 // Our "default" stack is what we use in configurations where we don't have 1290 // a compelling reason to do things differently. This is effectively 512KB 1291 // on 32-bit platforms and 1MB on 64-bit platforms. 1292 const size_t kDefaultStackQuota = 128 * sizeof(size_t) * 1024; 1293 1294 // Set maximum stack size for different configurations. This value is then 1295 // capped below because huge stacks are not web-compatible. 1296 1297 #if defined(XP_MACOSX) || defined(DARWIN) 1298 // MacOS has a gargantuan default stack size of 8MB. Go wild with 7MB, 1299 // and give trusted script 180k extra. The stack is huge on mac anyway. 1300 const size_t kUncappedStackQuota = 7 * 1024 * 1024; 1301 const size_t kTrustedScriptBuffer = 180 * 1024; 1302 #elif defined(XP_LINUX) && !defined(ANDROID) 1303 // Most Linux distributions set default stack size to 8MB. Use it as the 1304 // maximum value. 1305 const size_t kStackQuotaMax = 8 * 1024 * 1024; 1306 # if defined(MOZ_ASAN) || defined(DEBUG) 1307 // Bug 803182: account for the 4x difference in the size of js::Interpret 1308 // between optimized and debug builds. We use 2x since the JIT part 1309 // doesn't increase much. 1310 // See the standalone MOZ_ASAN branch below for the ASan case. 1311 const size_t kStackQuotaMin = 2 * kDefaultStackQuota; 1312 # else 1313 const size_t kStackQuotaMin = kDefaultStackQuota; 1314 # endif 1315 // Allocate 128kB margin for the safe space. 1316 const size_t kStackSafeMargin = 128 * 1024; 1317 1318 struct rlimit rlim; 1319 const size_t kUncappedStackQuota = 1320 getrlimit(RLIMIT_STACK, &rlim) == 0 1321 ? std::clamp(size_t(rlim.rlim_cur - kStackSafeMargin), kStackQuotaMin, 1322 kStackQuotaMax - kStackSafeMargin) 1323 : kStackQuotaMin; 1324 # if defined(MOZ_ASAN) 1325 // See the standalone MOZ_ASAN branch below for the ASan case. 1326 const size_t kTrustedScriptBuffer = 450 * 1024; 1327 # else 1328 const size_t kTrustedScriptBuffer = 180 * 1024; 1329 # endif 1330 #elif defined(XP_WIN) 1331 // 1MB is the default stack size on Windows. We use the -STACK linker flag 1332 // (see WIN32_EXE_LDFLAGS in config/config.mk) to request a larger stack, so 1333 // we determine the stack size at runtime. 1334 const size_t kUncappedStackQuota = GetWindowsStackSize(); 1335 # if defined(MOZ_ASAN) 1336 // See the standalone MOZ_ASAN branch below for the ASan case. 1337 const size_t kTrustedScriptBuffer = 450 * 1024; 1338 # else 1339 const size_t kTrustedScriptBuffer = (sizeof(size_t) == 8) 1340 ? 180 * 1024 // win64 1341 : 120 * 1024; // win32 1342 # endif 1343 #elif defined(MOZ_ASAN) 1344 // ASan requires more stack space due to red-zones, so give it double the 1345 // default (1MB on 32-bit, 2MB on 64-bit). ASAN stack frame measurements 1346 // were not taken at the time of this writing, so we hazard a guess that 1347 // ASAN builds have roughly thrice the stack overhead as normal builds. 1348 // On normal builds, the largest stack frame size we might encounter is 1349 // 9.0k (see above), so let's use a buffer of 9.0 * 5 * 10 = 450k. 1350 // 1351 // FIXME: Does this branch make sense for Windows and Android? 1352 // (See bug 1415195) 1353 const size_t kUncappedStackQuota = 2 * kDefaultStackQuota; 1354 const size_t kTrustedScriptBuffer = 450 * 1024; 1355 #elif defined(ANDROID) 1356 // Android appears to have 1MB stacks. Allow the use of 3/4 of that size 1357 // (768KB on 32-bit), since otherwise we can crash with a stack overflow 1358 // when nearing the 1MB limit. 1359 const size_t kUncappedStackQuota = 1360 kDefaultStackQuota + kDefaultStackQuota / 2; 1361 const size_t kTrustedScriptBuffer = sizeof(size_t) * 12800; 1362 #else 1363 // Catch-all configuration for other environments. 1364 # if defined(DEBUG) 1365 const size_t kUncappedStackQuota = 2 * kDefaultStackQuota; 1366 # else 1367 const size_t kUncappedStackQuota = kDefaultStackQuota; 1368 # endif 1369 // Given the numbers above, we use 50k and 100k trusted buffers on 32-bit 1370 // and 64-bit respectively. 1371 const size_t kTrustedScriptBuffer = sizeof(size_t) * 12800; 1372 #endif 1373 1374 // Avoid an unused variable warning on platforms where we don't use the 1375 // default. 1376 (void)kDefaultStackQuota; 1377 1378 // Large stacks are not web-compatible so cap to a smaller value. 1379 // See bug 1537609 and bug 1562700. 1380 const size_t kStackQuotaCap = 1381 StaticPrefs::javascript_options_main_thread_stack_quota_cap(); 1382 const size_t kStackQuota = std::min(kUncappedStackQuota, kStackQuotaCap); 1383 1384 JS_SetNativeStackQuota( 1385 cx, kStackQuota, kStackQuota - kSystemCodeBuffer, 1386 kStackQuota - kSystemCodeBuffer - kTrustedScriptBuffer); 1387 1388 PROFILER_SET_JS_CONTEXT(this); 1389 1390 JS_AddInterruptCallback(cx, InterruptCallback); 1391 1392 Runtime()->Initialize(cx); 1393 1394 LoadStartupJSPrefs(this); 1395 1396 // Watch for the JS boolean options. 1397 ReloadPrefsCallback(nullptr, this); 1398 Preferences::RegisterPrefixCallback(ReloadPrefsCallback, JS_OPTIONS_DOT_STR, 1399 this); 1400 1401 #ifdef FUZZING 1402 Preferences::RegisterCallback(ReloadPrefsCallback, "fuzzing.enabled", this); 1403 #endif 1404 1405 // Initialize the MIME type used for the bytecode cache, after calling 1406 // SetProcessBuildIdOp and loading JS prefs. 1407 if (!nsContentUtils::InitJSBytecodeMimeType()) { 1408 NS_ABORT_OOM(0); // Size is unknown. 1409 } 1410 1411 // The self-hosted bytecode can be shared with child processes and also stored 1412 // in startupcache. Only the parent process may initialize the data. 1413 auto& shm = xpc::SelfHostedShmem::GetSingleton(); 1414 JS::SelfHostedWriter writer = nullptr; 1415 if (XRE_IsParentProcess() && 1416 xpc::SelfHostedShmem::SelfHostedUseSharedMemory()) { 1417 // Check the startup cache for a copy of the bytecode. 1418 if (auto* sc = scache::StartupCache::GetSingleton()) { 1419 const char* buf = nullptr; 1420 uint32_t len = 0; 1421 if (NS_SUCCEEDED(sc->GetBuffer(kSelfHostCacheKey, &buf, &len))) { 1422 shm.InitFromParent(AsBytes(mozilla::Span(buf, len))); 1423 } 1424 } 1425 1426 // If we have no data then the InitSelfHostedCode call below will parse from 1427 // scratch and invoke this callback with the results. That callback data can 1428 // then be used in initialize cache and SelfHostedShmem. 1429 if (shm.Content().IsEmpty()) { 1430 writer = CreateSelfHostedSharedMemory; 1431 } 1432 } 1433 1434 if (!JS::InitSelfHostedCode(cx, shm.Content(), writer)) { 1435 // Note: If no exception is pending, failure is due to OOM. 1436 if (!JS_IsExceptionPending(cx) || JS_IsThrowingOutOfMemory(cx)) { 1437 NS_ABORT_OOM(0); // Size is unknown. 1438 } 1439 1440 // Failed to execute self-hosted JavaScript! Uh oh. 1441 MOZ_CRASH("InitSelfHostedCode failed"); 1442 } 1443 1444 #ifdef MOZ_EXECUTION_TRACING 1445 JS_SetCustomObjectSummaryCallback(cx, ExecutionTracerIntegration::Callback); 1446 #endif 1447 1448 MOZ_RELEASE_ASSERT(Runtime()->InitializeStrings(cx), 1449 "InitializeStrings failed"); 1450 1451 return NS_OK; 1452 } 1453 1454 // static 1455 uint32_t XPCJSContext::sInstanceCount; 1456 1457 // static 1458 StaticAutoPtr<WatchdogManager> XPCJSContext::sWatchdogInstance; 1459 1460 // static 1461 WatchdogManager* XPCJSContext::GetWatchdogManager() { 1462 if (sWatchdogInstance) { 1463 return sWatchdogInstance; 1464 } 1465 1466 MOZ_ASSERT(sInstanceCount == 0); 1467 sWatchdogInstance = new WatchdogManager(); 1468 return sWatchdogInstance; 1469 } 1470 1471 // static 1472 XPCJSContext* XPCJSContext::NewXPCJSContext() { 1473 XPCJSContext* self = new XPCJSContext(); 1474 nsresult rv = self->Initialize(); 1475 if (rv == NS_ERROR_OUT_OF_MEMORY) { 1476 mozalloc_handle_oom(0); 1477 } else if (NS_FAILED(rv)) { 1478 MOZ_CRASH("new XPCJSContext failed to initialize."); 1479 } 1480 1481 if (self->Context()) { 1482 return self; 1483 } 1484 1485 MOZ_CRASH("new XPCJSContext failed to initialize."); 1486 } 1487 1488 void XPCJSContext::BeforeProcessTask(bool aMightBlock) { 1489 MOZ_ASSERT(NS_IsMainThread()); 1490 1491 // Start the slow script timer. 1492 mSlowScriptCheckpoint = mozilla::TimeStamp::NowLoRes(); 1493 mSlowScriptSecondHalf = false; 1494 mSlowScriptActualWait = mozilla::TimeDuration(); 1495 mTimeoutAccumulated = false; 1496 mExecutedChromeScript = false; 1497 CycleCollectedJSContext::BeforeProcessTask(aMightBlock); 1498 } 1499 1500 void XPCJSContext::AfterProcessTask(uint32_t aNewRecursionDepth) { 1501 // Record hangs in the parent process for telemetry. 1502 if (mSlowScriptSecondHalf && XRE_IsE10sParentProcess()) { 1503 double hangDuration = (mozilla::TimeStamp::NowLoRes() - 1504 mSlowScriptCheckpoint + mSlowScriptActualWait) 1505 .ToSeconds(); 1506 // We use the pref to test this code. 1507 double limit = sChromeSlowScriptTelemetryCutoff; 1508 if (xpc::IsInAutomation()) { 1509 double prefLimit = StaticPrefs::dom_max_chrome_script_run_time(); 1510 if (prefLimit > 0) { 1511 limit = std::min(prefLimit, sChromeSlowScriptTelemetryCutoff); 1512 } 1513 } 1514 if (hangDuration > limit) { 1515 // Use AppendFloat to avoid printf-type APIs using locale-specific 1516 // decimal separators, when we definitely want a `.`. 1517 nsCString durationStr; 1518 durationStr.AppendFloat(hangDuration); 1519 1520 glean::slow_script_warning::ShownBrowserExtra extra = { 1521 .hangDuration = Some(durationStr), 1522 .uriType = Some(mExecutedChromeScript ? "browser"_ns : "content"_ns), 1523 }; 1524 glean::slow_script_warning::shown_browser.Record(Some(extra)); 1525 } 1526 } 1527 1528 // Now that we're back to the event loop, reset the slow script checkpoint. 1529 mSlowScriptCheckpoint = mozilla::TimeStamp(); 1530 mSlowScriptSecondHalf = false; 1531 1532 // Call cycle collector occasionally. 1533 MOZ_ASSERT(NS_IsMainThread()); 1534 nsJSContext::MaybePokeCC(); 1535 CycleCollectedJSContext::AfterProcessTask(aNewRecursionDepth); 1536 1537 // Poke the memory telemetry reporter 1538 if (AppShutdown::GetCurrentShutdownPhase() == ShutdownPhase::NotInShutdown) { 1539 MemoryTelemetry::Get().Poke(); 1540 } 1541 1542 // This exception might have been set if we called an XPCWrappedJS that threw, 1543 // but now we're returning to the event loop, so nothing is going to look at 1544 // this value again. Clear it to prevent leaks. 1545 SetPendingException(nullptr); 1546 } 1547 1548 void XPCJSContext::MaybePokeGC() { nsJSContext::MaybePokeGC(); } 1549 1550 bool XPCJSContext::IsSystemCaller() const { 1551 return nsContentUtils::IsSystemCaller(Context()); 1552 }