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