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