ScriptSettings.cpp (24001B)
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 #include "mozilla/dom/ScriptSettings.h" 8 9 #include "MainThreadUtils.h" 10 #include "js/CharacterEncoding.h" 11 #include "js/CompilationAndEvaluation.h" 12 #include "js/Conversions.h" 13 #include "js/ErrorReport.h" 14 #include "js/Exception.h" 15 #include "js/GCAPI.h" 16 #include "js/PropertyAndElement.h" // JS_GetProperty 17 #include "js/TypeDecls.h" 18 #include "js/Value.h" 19 #include "js/Warnings.h" 20 #include "js/Wrapper.h" 21 #include "js/friend/ErrorMessages.h" 22 #include "js/loader/LoadedScript.h" 23 #include "js/loader/ScriptLoadRequest.h" 24 #include "jsapi.h" 25 #include "mozilla/Assertions.h" 26 #include "mozilla/BasePrincipal.h" 27 #include "mozilla/CycleCollectedJSContext.h" 28 #include "mozilla/DebugOnly.h" 29 #include "mozilla/RefPtr.h" 30 #include "mozilla/ThreadLocal.h" 31 #include "mozilla/dom/AutoEntryScript.h" 32 #include "mozilla/dom/BindingUtils.h" 33 #include "mozilla/dom/Document.h" 34 #include "mozilla/dom/Element.h" 35 #include "mozilla/dom/WorkerCommon.h" 36 #include "nsContentUtils.h" 37 #include "nsDebug.h" 38 #include "nsGlobalWindowInner.h" 39 #include "nsIGlobalObject.h" 40 #include "nsINode.h" 41 #include "nsIPrincipal.h" 42 #include "nsISupports.h" 43 #include "nsJSUtils.h" 44 #include "nsPIDOMWindow.h" 45 #include "nsString.h" 46 #include "nscore.h" 47 #include "xpcpublic.h" 48 49 namespace mozilla { 50 namespace dom { 51 52 static MOZ_THREAD_LOCAL(ScriptSettingsStackEntry*) sScriptSettingsTLS; 53 54 class ScriptSettingsStack { 55 public: 56 static ScriptSettingsStackEntry* Top() { return sScriptSettingsTLS.get(); } 57 58 static void Push(ScriptSettingsStackEntry* aEntry) { 59 MOZ_ASSERT(!aEntry->mOlder); 60 // Whenever JSAPI use is disabled, the next stack entry pushed must 61 // not be an AutoIncumbentScript. 62 MOZ_ASSERT_IF(!Top() || Top()->NoJSAPI(), !aEntry->IsIncumbentScript()); 63 // Whenever the top entry is not an incumbent canidate, the next stack entry 64 // pushed must not be an AutoIncumbentScript. 65 MOZ_ASSERT_IF(Top() && !Top()->IsIncumbentCandidate(), 66 !aEntry->IsIncumbentScript()); 67 68 aEntry->mOlder = Top(); 69 sScriptSettingsTLS.set(aEntry); 70 } 71 72 static void Pop(ScriptSettingsStackEntry* aEntry) { 73 MOZ_ASSERT(aEntry == Top()); 74 sScriptSettingsTLS.set(aEntry->mOlder); 75 } 76 77 static nsIGlobalObject* IncumbentGlobal() { 78 ScriptSettingsStackEntry* entry = Top(); 79 while (entry) { 80 if (entry->IsIncumbentCandidate()) { 81 return entry->mGlobalObject; 82 } 83 entry = entry->mOlder; 84 } 85 return nullptr; 86 } 87 88 static ScriptSettingsStackEntry* EntryPoint() { 89 ScriptSettingsStackEntry* entry = Top(); 90 while (entry) { 91 if (entry->IsEntryCandidate()) { 92 return entry; 93 } 94 entry = entry->mOlder; 95 } 96 return nullptr; 97 } 98 99 static nsIGlobalObject* EntryGlobal() { 100 ScriptSettingsStackEntry* entry = EntryPoint(); 101 if (!entry) { 102 return nullptr; 103 } 104 return entry->mGlobalObject; 105 } 106 107 #ifdef DEBUG 108 static ScriptSettingsStackEntry* TopNonIncumbentScript() { 109 ScriptSettingsStackEntry* entry = Top(); 110 while (entry) { 111 if (!entry->IsIncumbentScript()) { 112 return entry; 113 } 114 entry = entry->mOlder; 115 } 116 return nullptr; 117 } 118 #endif // DEBUG 119 }; 120 121 void InitScriptSettings() { 122 bool success = sScriptSettingsTLS.init(); 123 if (!success) { 124 MOZ_CRASH(); 125 } 126 127 sScriptSettingsTLS.set(nullptr); 128 } 129 130 void DestroyScriptSettings() { 131 MOZ_ASSERT(sScriptSettingsTLS.get() == nullptr); 132 } 133 134 ScriptSettingsStackEntry::ScriptSettingsStackEntry(nsIGlobalObject* aGlobal, 135 Type aType) 136 : mGlobalObject(aGlobal), mType(aType), mOlder(nullptr) { 137 MOZ_ASSERT_IF(IsIncumbentCandidate() && !NoJSAPI(), mGlobalObject); 138 MOZ_ASSERT(!mGlobalObject || mGlobalObject->HasJSGlobal(), 139 "Must have an actual JS global for the duration on the stack"); 140 MOZ_ASSERT( 141 !mGlobalObject || 142 JS_IsGlobalObject(mGlobalObject->GetGlobalJSObjectPreserveColor()), 143 "No outer windows allowed"); 144 } 145 146 ScriptSettingsStackEntry::~ScriptSettingsStackEntry() { 147 // We must have an actual JS global for the entire time this is on the stack. 148 MOZ_ASSERT_IF(mGlobalObject, mGlobalObject->HasJSGlobal()); 149 } 150 151 // If the entry or incumbent global ends up being something that the subject 152 // principal doesn't subsume, we don't want to use it. This never happens on 153 // the web, but can happen with asymmetric privilege relationships (i.e. 154 // ExpandedPrincipal and System Principal). 155 // 156 // The most correct thing to use instead would be the topmost global on the 157 // callstack whose principal is subsumed by the subject principal. But that's 158 // hard to compute, so we just substitute the global of the current 159 // compartment. In practice, this is fine. 160 // 161 // Note that in particular things like: 162 // 163 // |SpecialPowers.wrap(crossOriginWindow).eval(open())| 164 // 165 // trigger this case. Although both the entry global and the current global 166 // have normal principals, the use of Gecko-specific System-Principaled JS 167 // puts the code from two different origins on the callstack at once, which 168 // doesn't happen normally on the web. 169 static nsIGlobalObject* ClampToSubject(nsIGlobalObject* aGlobalOrNull) { 170 if (!aGlobalOrNull || !NS_IsMainThread()) { 171 return aGlobalOrNull; 172 } 173 174 nsIPrincipal* globalPrin = aGlobalOrNull->PrincipalOrNull(); 175 NS_ENSURE_TRUE(globalPrin, GetCurrentGlobal()); 176 if (!nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller() 177 ->SubsumesConsideringDomain(globalPrin)) { 178 return GetCurrentGlobal(); 179 } 180 181 return aGlobalOrNull; 182 } 183 184 nsIGlobalObject* GetEntryGlobal() { 185 return ClampToSubject(ScriptSettingsStack::EntryGlobal()); 186 } 187 188 Document* GetEntryDocument() { 189 nsIGlobalObject* global = GetEntryGlobal(); 190 nsCOMPtr<nsPIDOMWindowInner> entryWin = do_QueryInterface(global); 191 192 return entryWin ? entryWin->GetExtantDoc() : nullptr; 193 } 194 195 nsIGlobalObject* GetIncumbentGlobal() { 196 // We need the current JSContext in order to check the JS for 197 // scripted frames that may have appeared since anyone last 198 // manipulated the stack. If it's null, that means that there 199 // must be no entry global on the stack, and therefore no incumbent 200 // global either. 201 JSContext* cx = nsContentUtils::GetCurrentJSContext(); 202 if (!cx) { 203 MOZ_ASSERT(ScriptSettingsStack::EntryGlobal() == nullptr); 204 return nullptr; 205 } 206 207 // See what the JS engine has to say. If we've got a scripted caller 208 // override in place, the JS engine will lie to us and pretend that 209 // there's nothing on the JS stack, which will cause us to check the 210 // incumbent script stack below. 211 if (JSObject* global = JS::GetScriptedCallerGlobal(cx)) { 212 return ClampToSubject(xpc::NativeGlobal(global)); 213 } 214 215 // Ok, nothing from the JS engine. Let's use whatever's on the 216 // explicit stack. 217 return ClampToSubject(ScriptSettingsStack::IncumbentGlobal()); 218 } 219 220 nsIGlobalObject* GetCurrentGlobal() { 221 JSContext* cx = nsContentUtils::GetCurrentJSContext(); 222 if (!cx) { 223 return nullptr; 224 } 225 226 JSObject* global = JS::CurrentGlobalOrNull(cx); 227 if (!global) { 228 return nullptr; 229 } 230 231 return xpc::NativeGlobal(global); 232 } 233 234 WebTaskSchedulingState* GetWebTaskSchedulingState() { 235 if (const nsIGlobalObject* global = GetEntryGlobal()) { 236 return global->GetWebTaskSchedulingState(); 237 } 238 return nullptr; 239 } 240 241 nsIPrincipal* GetWebIDLCallerPrincipal() { 242 MOZ_ASSERT(NS_IsMainThread()); 243 ScriptSettingsStackEntry* entry = ScriptSettingsStack::EntryPoint(); 244 245 // If we have an entry point that is not NoJSAPI, we know it must be an 246 // AutoEntryScript. 247 if (!entry || entry->NoJSAPI()) { 248 return nullptr; 249 } 250 AutoEntryScript* aes = static_cast<AutoEntryScript*>(entry); 251 252 return aes->mWebIDLCallerPrincipal; 253 } 254 255 bool IsJSAPIActive() { 256 ScriptSettingsStackEntry* topEntry = ScriptSettingsStack::Top(); 257 return topEntry && !topEntry->NoJSAPI(); 258 } 259 260 namespace danger { 261 JSContext* GetJSContext() { return CycleCollectedJSContext::Get()->Context(); } 262 } // namespace danger 263 264 JS::RootingContext* RootingCx() { 265 return CycleCollectedJSContext::Get()->RootingCx(); 266 } 267 268 AutoJSAPI::AutoJSAPI() 269 : ScriptSettingsStackEntry(nullptr, eJSAPI), 270 mCx(nullptr), 271 mIsMainThread(false) // For lack of anything better 272 {} 273 274 AutoJSAPI::~AutoJSAPI() { 275 if (!mCx) { 276 // No need to do anything here: we never managed to Init, so can't have an 277 // exception on our (nonexistent) JSContext. We also don't need to restore 278 // any state on it. Finally, we never made it to pushing ourselves onto the 279 // ScriptSettingsStack, so shouldn't pop. 280 MOZ_ASSERT(ScriptSettingsStack::Top() != this); 281 return; 282 } 283 284 ReportException(); 285 286 if (mOldWarningReporter.isSome()) { 287 JS::SetWarningReporter(cx(), mOldWarningReporter.value()); 288 } 289 290 ScriptSettingsStack::Pop(this); 291 } 292 293 void WarningOnlyErrorReporter(JSContext* aCx, JSErrorReport* aRep); 294 295 void AutoJSAPI::InitInternal(nsIGlobalObject* aGlobalObject, JSObject* aGlobal, 296 JSContext* aCx, bool aIsMainThread) { 297 MOZ_ASSERT(aCx); 298 MOZ_ASSERT(aCx == danger::GetJSContext()); 299 MOZ_ASSERT(aIsMainThread == NS_IsMainThread()); 300 MOZ_ASSERT(bool(aGlobalObject) == bool(aGlobal)); 301 MOZ_ASSERT_IF(aGlobalObject, 302 aGlobalObject->GetGlobalJSObjectPreserveColor() == aGlobal); 303 #ifdef DEBUG 304 bool haveException = JS_IsExceptionPending(aCx); 305 #endif // DEBUG 306 307 mCx = aCx; 308 mIsMainThread = aIsMainThread; 309 if (aGlobal) { 310 JS::AssertObjectIsNotGray(aGlobal); 311 } 312 mAutoNullableRealm.emplace(mCx, aGlobal); 313 mGlobalObject = aGlobalObject; 314 315 ScriptSettingsStack::Push(this); 316 317 mOldWarningReporter.emplace(JS::GetWarningReporter(aCx)); 318 319 JS::SetWarningReporter(aCx, WarningOnlyErrorReporter); 320 321 #ifdef DEBUG 322 if (haveException) { 323 JS::Rooted<JS::Value> exn(aCx); 324 JS_GetPendingException(aCx, &exn); 325 326 JS_ClearPendingException(aCx); 327 if (exn.isObject()) { 328 JS::Rooted<JSObject*> exnObj(aCx, &exn.toObject()); 329 330 // Make sure we can actually read things from it. This UncheckedUwrap is 331 // safe because we're only getting data for a debug printf. In 332 // particular, we do not expose this data to anyone, which is very 333 // important; otherwise it could be a cross-origin information leak. 334 exnObj = js::UncheckedUnwrap(exnObj); 335 JSAutoRealm ar(aCx, exnObj); 336 337 nsAutoJSString stack, filename, name, message; 338 int32_t line; 339 340 JS::Rooted<JS::Value> tmp(aCx); 341 if (!JS_GetProperty(aCx, exnObj, "filename", &tmp)) { 342 JS_ClearPendingException(aCx); 343 } 344 if (tmp.isUndefined()) { 345 if (!JS_GetProperty(aCx, exnObj, "fileName", &tmp)) { 346 JS_ClearPendingException(aCx); 347 } 348 } 349 350 if (!filename.init(aCx, tmp)) { 351 JS_ClearPendingException(aCx); 352 } 353 354 if (!JS_GetProperty(aCx, exnObj, "stack", &tmp) || 355 !stack.init(aCx, tmp)) { 356 JS_ClearPendingException(aCx); 357 } 358 359 if (!JS_GetProperty(aCx, exnObj, "name", &tmp) || !name.init(aCx, tmp)) { 360 JS_ClearPendingException(aCx); 361 } 362 363 if (!JS_GetProperty(aCx, exnObj, "message", &tmp) || 364 !message.init(aCx, tmp)) { 365 JS_ClearPendingException(aCx); 366 } 367 368 if (!JS_GetProperty(aCx, exnObj, "lineNumber", &tmp) || 369 !JS::ToInt32(aCx, tmp, &line)) { 370 JS_ClearPendingException(aCx); 371 line = 0; 372 } 373 374 printf_stderr("PREEXISTING EXCEPTION OBJECT: '%s: %s'\n%s:%d\n%s\n", 375 NS_ConvertUTF16toUTF8(name).get(), 376 NS_ConvertUTF16toUTF8(message).get(), 377 NS_ConvertUTF16toUTF8(filename).get(), line, 378 NS_ConvertUTF16toUTF8(stack).get()); 379 } else { 380 // It's a primitive... not much we can do other than stringify it. 381 nsAutoJSString exnStr; 382 if (!exnStr.init(aCx, exn)) { 383 JS_ClearPendingException(aCx); 384 } 385 386 printf_stderr("PREEXISTING EXCEPTION PRIMITIVE: %s\n", 387 NS_ConvertUTF16toUTF8(exnStr).get()); 388 } 389 MOZ_ASSERT(false, "We had an exception; we should not have"); 390 } 391 #endif // DEBUG 392 } 393 394 AutoJSAPI::AutoJSAPI(nsIGlobalObject* aGlobalObject, bool aIsMainThread, 395 Type aType) 396 : ScriptSettingsStackEntry(aGlobalObject, aType), 397 mIsMainThread(aIsMainThread) { 398 MOZ_ASSERT(aGlobalObject); 399 MOZ_ASSERT(aGlobalObject->HasJSGlobal(), "Must have a JS global"); 400 MOZ_ASSERT(aIsMainThread == NS_IsMainThread()); 401 402 InitInternal(aGlobalObject, aGlobalObject->GetGlobalJSObject(), 403 danger::GetJSContext(), aIsMainThread); 404 } 405 406 void AutoJSAPI::Init() { 407 MOZ_ASSERT(!mCx, "An AutoJSAPI should only be initialised once"); 408 409 InitInternal(/* aGlobalObject */ nullptr, /* aGlobal */ nullptr, 410 danger::GetJSContext(), NS_IsMainThread()); 411 } 412 413 bool AutoJSAPI::Init(nsIGlobalObject* aGlobalObject, JSContext* aCx) { 414 MOZ_ASSERT(!mCx, "An AutoJSAPI should only be initialised once"); 415 MOZ_ASSERT(aCx); 416 417 if (NS_WARN_IF(!aGlobalObject)) { 418 return false; 419 } 420 421 JSObject* global = aGlobalObject->GetGlobalJSObject(); 422 if (NS_WARN_IF(!global)) { 423 return false; 424 } 425 426 InitInternal(aGlobalObject, global, aCx, NS_IsMainThread()); 427 return true; 428 } 429 430 bool AutoJSAPI::Init(nsIGlobalObject* aGlobalObject) { 431 return Init(aGlobalObject, danger::GetJSContext()); 432 } 433 434 bool AutoJSAPI::Init(JSObject* aObject) { 435 MOZ_ASSERT(!js::IsCrossCompartmentWrapper(aObject)); 436 return Init(xpc::NativeGlobal(aObject)); 437 } 438 439 bool AutoJSAPI::Init(nsPIDOMWindowInner* aWindow, JSContext* aCx) { 440 return Init(nsGlobalWindowInner::Cast(aWindow), aCx); 441 } 442 443 bool AutoJSAPI::Init(nsPIDOMWindowInner* aWindow) { 444 return Init(nsGlobalWindowInner::Cast(aWindow)); 445 } 446 447 bool AutoJSAPI::Init(nsGlobalWindowInner* aWindow, JSContext* aCx) { 448 return Init(static_cast<nsIGlobalObject*>(aWindow), aCx); 449 } 450 451 bool AutoJSAPI::Init(nsGlobalWindowInner* aWindow) { 452 return Init(static_cast<nsIGlobalObject*>(aWindow)); 453 } 454 455 // Even with autoJSAPIOwnsErrorReporting, the JS engine still sends warning 456 // reports to the JSErrorReporter as soon as they are generated. These go 457 // directly to the console, so we can handle them easily here. 458 // 459 // Eventually, SpiderMonkey will have a special-purpose callback for warnings 460 // only. 461 void WarningOnlyErrorReporter(JSContext* aCx, JSErrorReport* aRep) { 462 MOZ_ASSERT(aRep->isWarning()); 463 if (!NS_IsMainThread()) { 464 // Reporting a warning on workers is a bit complicated because we have to 465 // climb our parent chain until we get to the main thread. So go ahead and 466 // just go through the worker or worklet ReportError codepath here. 467 // 468 // That said, it feels like we should be able to short-circuit things a bit 469 // here by posting an appropriate runnable to the main thread directly... 470 // Worth looking into sometime. 471 CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::GetFor(aCx); 472 MOZ_ASSERT(ccjscx); 473 474 ccjscx->ReportError(aRep, JS::ConstUTF8CharsZ()); 475 return; 476 } 477 478 RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport(); 479 nsGlobalWindowInner* win = xpc::CurrentWindowOrNull(aCx); 480 xpcReport->Init(aRep, nullptr, nsContentUtils::IsSystemCaller(aCx), 481 win ? win->WindowID() : 0); 482 xpcReport->LogToConsole(); 483 } 484 485 void AutoJSAPI::ReportException() { 486 if (!HasException()) { 487 return; 488 } 489 490 // AutoJSAPI uses a JSAutoNullableRealm, and may be in a null realm 491 // when the destructor is called. However, the JS engine requires us 492 // to be in a realm when we fetch the pending exception. In this case, 493 // we enter the privileged junk scope and don't dispatch any error events. 494 JS::Rooted<JSObject*> errorGlobal(cx(), JS::CurrentGlobalOrNull(cx())); 495 if (!errorGlobal) { 496 if (mIsMainThread) { 497 errorGlobal = xpc::PrivilegedJunkScope(); 498 } else { 499 errorGlobal = GetCurrentThreadWorkerGlobal(); 500 if (!errorGlobal) { 501 // We might be reporting an error in debugger code that ran before the 502 // worker's global was created. Use the debugger global instead. 503 errorGlobal = GetCurrentThreadWorkerDebuggerGlobal(); 504 if (NS_WARN_IF(!errorGlobal)) { 505 // An exception may have been thrown on attempt to create a global 506 // and now there is no realm from which to fetch the exception. 507 // Give up. 508 ClearException(); 509 return; 510 } 511 } 512 } 513 } 514 MOZ_ASSERT(JS_IsGlobalObject(errorGlobal)); 515 JSAutoRealm ar(cx(), errorGlobal); 516 JS::ExceptionStack exnStack(cx()); 517 JS::ErrorReportBuilder jsReport(cx()); 518 if (StealExceptionAndStack(&exnStack) && 519 jsReport.init(cx(), exnStack, JS::ErrorReportBuilder::WithSideEffects)) { 520 if (mIsMainThread) { 521 RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport(); 522 523 RefPtr<nsGlobalWindowInner> inner = xpc::WindowOrNull(errorGlobal); 524 525 // For WebExtension content script, `WindowOrNull` method will return 526 // null, whereas we would still like to flag the exception with the 527 // related WindowGlobal the content script executed against. So we only 528 // update the `innerWindowID` and not `inner` as we don't want to dispatch 529 // exceptions caused by the content script to the webpage. 530 uint64_t innerWindowID = 0; 531 if (inner) { 532 innerWindowID = inner->WindowID(); 533 } else if (nsGlobalWindowInner* win = xpc::SandboxWindowOrNull( 534 JS::GetNonCCWObjectGlobal(errorGlobal), cx())) { 535 innerWindowID = win->WindowID(); 536 } 537 538 bool isChrome = 539 nsContentUtils::ObjectPrincipal(errorGlobal)->IsSystemPrincipal(); 540 xpcReport->Init(jsReport.report(), jsReport.toStringResult().c_str(), 541 isChrome, innerWindowID); 542 if (inner && jsReport.report()->errorNumber != JSMSG_OUT_OF_MEMORY) { 543 JS::RootingContext* rcx = JS::RootingContext::get(cx()); 544 DispatchScriptErrorEvent(inner, rcx, xpcReport, exnStack.exception(), 545 exnStack.stack()); 546 } else { 547 JS::Rooted<JSObject*> stack(cx()); 548 JS::Rooted<JSObject*> stackGlobal(cx()); 549 xpc::FindExceptionStackForConsoleReport(inner, exnStack.exception(), 550 exnStack.stack(), &stack, 551 &stackGlobal); 552 // This error is not associated with a specific window, 553 // so omit the exception value to mitigate potential leaks. 554 xpcReport->LogToConsoleWithStack(inner, JS::NothingHandleValue, stack, 555 stackGlobal); 556 } 557 } else { 558 // On a worker or worklet, we just use the error reporting mechanism and 559 // don't bother with xpc::ErrorReport. This will ensure that all the 560 // right worker events (which are a lot more complicated than in the 561 // window case) get fired. 562 CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::GetFor(cx()); 563 MOZ_ASSERT(ccjscx); 564 // Before invoking ReportError, put the exception back on the context, 565 // because it may want to put it in its error events and has no other way 566 // to get hold of it. After we invoke ReportError, clear the exception on 567 // cx(), just in case ReportError didn't. 568 JS::SetPendingExceptionStack(cx(), exnStack); 569 ccjscx->ReportError(jsReport.report(), jsReport.toStringResult()); 570 ClearException(); 571 } 572 } else { 573 NS_WARNING("OOMed while acquiring uncaught exception from JSAPI"); 574 ClearException(); 575 } 576 } 577 578 bool AutoJSAPI::PeekException(JS::MutableHandle<JS::Value> aVal) { 579 MOZ_ASSERT_IF(mIsMainThread, IsStackTop()); 580 MOZ_ASSERT(HasException()); 581 MOZ_ASSERT(js::GetContextRealm(cx())); 582 return JS_GetPendingException(cx(), aVal); 583 } 584 585 bool AutoJSAPI::StealException(JS::MutableHandle<JS::Value> aVal) { 586 JS::ExceptionStack exnStack(cx()); 587 if (!StealExceptionAndStack(&exnStack)) { 588 return false; 589 } 590 aVal.set(exnStack.exception()); 591 return true; 592 } 593 594 bool AutoJSAPI::StealExceptionAndStack(JS::ExceptionStack* aExnStack) { 595 MOZ_ASSERT_IF(mIsMainThread, IsStackTop()); 596 MOZ_ASSERT(HasException()); 597 MOZ_ASSERT(js::GetContextRealm(cx())); 598 599 return JS::StealPendingExceptionStack(cx(), aExnStack); 600 } 601 602 #ifdef DEBUG 603 bool AutoJSAPI::IsStackTop() const { 604 return ScriptSettingsStack::TopNonIncumbentScript() == this; 605 } 606 #endif // DEBUG 607 608 AutoIncumbentScript::AutoIncumbentScript(nsIGlobalObject* aGlobalObject) 609 : ScriptSettingsStackEntry(aGlobalObject, eIncumbentScript), 610 mCallerOverride(nsContentUtils::GetCurrentJSContext()) { 611 ScriptSettingsStack::Push(this); 612 } 613 614 AutoIncumbentScript::~AutoIncumbentScript() { ScriptSettingsStack::Pop(this); } 615 616 AutoNoJSAPI::AutoNoJSAPI(JSContext* aCx) 617 : ScriptSettingsStackEntry(nullptr, eNoJSAPI), 618 JSAutoNullableRealm(aCx, nullptr), 619 mCx(aCx) { 620 // Make sure we don't seem to have an incumbent global due to 621 // whatever script is running right now. 622 JS::HideScriptedCaller(aCx); 623 624 // Make sure the fallback GetIncumbentGlobal() behavior and 625 // GetEntryGlobal() both return null. 626 ScriptSettingsStack::Push(this); 627 } 628 629 AutoNoJSAPI::~AutoNoJSAPI() { 630 ScriptSettingsStack::Pop(this); 631 JS::UnhideScriptedCaller(mCx); 632 } 633 634 } // namespace dom 635 636 AutoJSContext::AutoJSContext() : mCx(nullptr) { 637 JS::AutoSuppressGCAnalysis nogc; 638 MOZ_ASSERT(!mCx, "mCx should not be initialized!"); 639 MOZ_ASSERT(NS_IsMainThread()); 640 641 if (dom::IsJSAPIActive()) { 642 mCx = dom::danger::GetJSContext(); 643 } else { 644 mJSAPI.Init(); 645 mCx = mJSAPI.cx(); 646 } 647 } 648 649 AutoJSContext::operator JSContext*() const { return mCx; } 650 651 AutoSafeJSContext::AutoSafeJSContext() { 652 MOZ_ASSERT(NS_IsMainThread()); 653 654 DebugOnly<bool> ok = Init(xpc::UnprivilegedJunkScope()); 655 MOZ_ASSERT(ok, 656 "This is quite odd. We should have crashed in the " 657 "xpc::NativeGlobal() call if xpc::UnprivilegedJunkScope() " 658 "returned null, and inited correctly otherwise!"); 659 } 660 661 AutoSlowOperation::AutoSlowOperation() : mIsMainThread(NS_IsMainThread()) { 662 if (mIsMainThread) { 663 mScriptActivity.emplace(true); 664 } 665 } 666 667 void AutoSlowOperation::CheckForInterrupt() { 668 // For now we support only main thread! 669 if (mIsMainThread) { 670 // JS_CheckForInterrupt expects us to be in a realm, so we use a junk scope. 671 // In principle, it doesn't matter which one we use, since we aren't really 672 // running scripts here, and none of our interrupt callbacks can stop 673 // scripts in a junk scope anyway. In practice, though, the privileged junk 674 // scope is the same as the JSM global, and therefore always exists, while 675 // the unprivileged junk scope is created lazily, and may not exist until we 676 // try to use it. So we use the former for the sake of efficiency. 677 dom::AutoJSAPI jsapi; 678 MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope())); 679 JS_CheckForInterrupt(jsapi.cx()); 680 } 681 } 682 683 AutoAllowLegacyScriptExecution::AutoAllowLegacyScriptExecution() { 684 #ifdef DEBUG 685 // no need to do that dance if we are off the main thread, 686 // because we only assert if we are on the main thread! 687 if (!NS_IsMainThread()) { 688 return; 689 } 690 sAutoAllowLegacyScriptExecution++; 691 #endif 692 } 693 694 AutoAllowLegacyScriptExecution::~AutoAllowLegacyScriptExecution() { 695 #ifdef DEBUG 696 // no need to do that dance if we are off the main thread, 697 // because we only assert if we are on the main thread! 698 if (!NS_IsMainThread()) { 699 return; 700 } 701 sAutoAllowLegacyScriptExecution--; 702 MOZ_ASSERT(sAutoAllowLegacyScriptExecution >= 0, 703 "how can the stack guard produce a value less than 0?"); 704 #endif 705 } 706 707 int AutoAllowLegacyScriptExecution::sAutoAllowLegacyScriptExecution = 0; 708 709 /*static*/ 710 bool AutoAllowLegacyScriptExecution::IsAllowed() { 711 return sAutoAllowLegacyScriptExecution > 0; 712 } 713 714 } // namespace mozilla