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