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