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