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