Bumping manifests a=b2g-bump
[gecko.git] / dom / base / ScriptSettings.cpp
blob9d87051ca26bfaec7c7dd0a91a3f97d5fae38ca7
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 // vim: ft=cpp tw=78 sw=2 et ts=2
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/. */
7 #include "mozilla/dom/ScriptSettings.h"
8 #include "mozilla/ThreadLocal.h"
9 #include "mozilla/Assertions.h"
11 #include "jsapi.h"
12 #include "xpcprivate.h" // For AutoCxPusher guts
13 #include "xpcpublic.h"
14 #include "nsIGlobalObject.h"
15 #include "nsIScriptGlobalObject.h"
16 #include "nsIScriptContext.h"
17 #include "nsContentUtils.h"
18 #include "nsGlobalWindow.h"
19 #include "nsPIDOMWindow.h"
20 #include "nsTArray.h"
21 #include "nsJSUtils.h"
22 #include "nsDOMJSUtils.h"
23 #include "WorkerPrivate.h"
25 namespace mozilla {
26 namespace dom {
28 static mozilla::ThreadLocal<ScriptSettingsStackEntry*> sScriptSettingsTLS;
30 class ScriptSettingsStack {
31 public:
32 static ScriptSettingsStackEntry* Top() {
33 return sScriptSettingsTLS.get();
36 static void Push(ScriptSettingsStackEntry *aEntry) {
37 MOZ_ASSERT(!aEntry->mOlder);
38 // Whenever JSAPI use is disabled, the next stack entry pushed must
39 // always be a candidate entry point.
40 MOZ_ASSERT_IF(!Top() || Top()->NoJSAPI(), aEntry->mIsCandidateEntryPoint);
42 aEntry->mOlder = Top();
43 sScriptSettingsTLS.set(aEntry);
46 static void Pop(ScriptSettingsStackEntry *aEntry) {
47 MOZ_ASSERT(aEntry == Top());
48 sScriptSettingsTLS.set(aEntry->mOlder);
51 static nsIGlobalObject* IncumbentGlobal() {
52 ScriptSettingsStackEntry *entry = Top();
53 return entry ? entry->mGlobalObject : nullptr;
56 static ScriptSettingsStackEntry* EntryPoint() {
57 ScriptSettingsStackEntry *entry = Top();
58 if (!entry) {
59 return nullptr;
61 while (entry) {
62 if (entry->mIsCandidateEntryPoint)
63 return entry;
64 entry = entry->mOlder;
66 MOZ_CRASH("Non-empty stack should always have an entry point");
69 static nsIGlobalObject* EntryGlobal() {
70 ScriptSettingsStackEntry *entry = EntryPoint();
71 return entry ? entry->mGlobalObject : nullptr;
75 void
76 InitScriptSettings()
78 if (!sScriptSettingsTLS.initialized()) {
79 bool success = sScriptSettingsTLS.init();
80 if (!success) {
81 MOZ_CRASH();
85 sScriptSettingsTLS.set(nullptr);
88 void DestroyScriptSettings()
90 MOZ_ASSERT(sScriptSettingsTLS.get() == nullptr);
93 ScriptSettingsStackEntry::ScriptSettingsStackEntry(nsIGlobalObject *aGlobal,
94 bool aCandidate)
95 : mGlobalObject(aGlobal)
96 , mIsCandidateEntryPoint(aCandidate)
97 , mOlder(nullptr)
99 MOZ_ASSERT(mGlobalObject);
100 MOZ_ASSERT(mGlobalObject->GetGlobalJSObject(),
101 "Must have an actual JS global for the duration on the stack");
102 MOZ_ASSERT(JS_IsGlobalObject(mGlobalObject->GetGlobalJSObject()),
103 "No outer windows allowed");
105 ScriptSettingsStack::Push(this);
108 // This constructor is only for use by AutoNoJSAPI.
109 ScriptSettingsStackEntry::ScriptSettingsStackEntry()
110 : mGlobalObject(nullptr)
111 , mIsCandidateEntryPoint(true)
112 , mOlder(nullptr)
114 ScriptSettingsStack::Push(this);
117 ScriptSettingsStackEntry::~ScriptSettingsStackEntry()
119 // We must have an actual JS global for the entire time this is on the stack.
120 MOZ_ASSERT_IF(mGlobalObject, mGlobalObject->GetGlobalJSObject());
122 ScriptSettingsStack::Pop(this);
125 // If the entry or incumbent global ends up being something that the subject
126 // principal doesn't subsume, we don't want to use it. This never happens on
127 // the web, but can happen with asymmetric privilege relationships (i.e.
128 // nsExpandedPrincipal and System Principal).
130 // The most correct thing to use instead would be the topmost global on the
131 // callstack whose principal is subsumed by the subject principal. But that's
132 // hard to compute, so we just substitute the global of the current
133 // compartment. In practice, this is fine.
135 // Note that in particular things like:
137 // |SpecialPowers.wrap(crossOriginWindow).eval(open())|
139 // trigger this case. Although both the entry global and the current global
140 // have normal principals, the use of Gecko-specific System-Principaled JS
141 // puts the code from two different origins on the callstack at once, which
142 // doesn't happen normally on the web.
143 static nsIGlobalObject*
144 ClampToSubject(nsIGlobalObject* aGlobalOrNull)
146 if (!aGlobalOrNull || !NS_IsMainThread()) {
147 return aGlobalOrNull;
150 nsIPrincipal* globalPrin = aGlobalOrNull->PrincipalOrNull();
151 NS_ENSURE_TRUE(globalPrin, GetCurrentGlobal());
152 if (!nsContentUtils::SubjectPrincipal()->SubsumesConsideringDomain(globalPrin)) {
153 return GetCurrentGlobal();
156 return aGlobalOrNull;
159 nsIGlobalObject*
160 GetEntryGlobal()
162 return ClampToSubject(ScriptSettingsStack::EntryGlobal());
165 nsIDocument*
166 GetEntryDocument()
168 nsCOMPtr<nsPIDOMWindow> entryWin = do_QueryInterface(GetEntryGlobal());
169 return entryWin ? entryWin->GetExtantDoc() : nullptr;
172 nsIGlobalObject*
173 GetIncumbentGlobal()
175 // We need the current JSContext in order to check the JS for
176 // scripted frames that may have appeared since anyone last
177 // manipulated the stack. If it's null, that means that there
178 // must be no entry global on the stack, and therefore no incumbent
179 // global either.
180 JSContext *cx = nsContentUtils::GetCurrentJSContextForThread();
181 if (!cx) {
182 MOZ_ASSERT(ScriptSettingsStack::EntryGlobal() == nullptr);
183 return nullptr;
186 // See what the JS engine has to say. If we've got a scripted caller
187 // override in place, the JS engine will lie to us and pretend that
188 // there's nothing on the JS stack, which will cause us to check the
189 // incumbent script stack below.
190 if (JSObject *global = JS::GetScriptedCallerGlobal(cx)) {
191 return ClampToSubject(xpc::GetNativeForGlobal(global));
194 // Ok, nothing from the JS engine. Let's use whatever's on the
195 // explicit stack.
196 return ClampToSubject(ScriptSettingsStack::IncumbentGlobal());
199 nsIGlobalObject*
200 GetCurrentGlobal()
202 JSContext *cx = nsContentUtils::GetCurrentJSContextForThread();
203 if (!cx) {
204 return nullptr;
207 JSObject *global = JS::CurrentGlobalOrNull(cx);
208 if (!global) {
209 return nullptr;
212 return xpc::GetNativeForGlobal(global);
215 nsIPrincipal*
216 GetWebIDLCallerPrincipal()
218 MOZ_ASSERT(NS_IsMainThread());
219 ScriptSettingsStackEntry *entry = ScriptSettingsStack::EntryPoint();
221 // If we have an entry point that is not NoJSAPI, we know it must be an
222 // AutoEntryScript.
223 if (!entry || entry->NoJSAPI()) {
224 return nullptr;
226 AutoEntryScript* aes = static_cast<AutoEntryScript*>(entry);
228 // We can't yet rely on the Script Settings Stack to properly determine the
229 // entry script, because there are still lots of places in the tree where we
230 // don't yet use an AutoEntryScript (bug 951991 tracks this work). In the
231 // mean time though, we can make some observations to hack around the
232 // problem:
234 // (1) All calls into JS-implemented WebIDL go through CallSetup, which goes
235 // through AutoEntryScript.
236 // (2) The top candidate entry point in the Script Settings Stack is the
237 // entry point if and only if no other JSContexts have been pushed on
238 // top of the push made by that entry's AutoEntryScript.
240 // Because of (1), all of the cases where we might return a non-null
241 // WebIDL Caller are guaranteed to have put an entry on the Script Settings
242 // Stack, so we can restrict our search to that. Moreover, (2) gives us a
243 // criterion to determine whether an entry in the Script Setting Stack means
244 // that we should return a non-null WebIDL Caller.
246 // Once we fix bug 951991, this can all be simplified.
247 if (!aes->CxPusherIsStackTop()) {
248 return nullptr;
251 return aes->mWebIDLCallerPrincipal;
254 static JSContext*
255 FindJSContext(nsIGlobalObject* aGlobalObject)
257 MOZ_ASSERT(NS_IsMainThread());
258 JSContext *cx = nullptr;
259 nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aGlobalObject);
260 if (sgo && sgo->GetScriptContext()) {
261 cx = sgo->GetScriptContext()->GetNativeContext();
263 if (!cx) {
264 cx = nsContentUtils::GetSafeJSContext();
266 return cx;
269 AutoJSAPI::AutoJSAPI()
270 : mCx(nullptr)
274 void
275 AutoJSAPI::InitInternal(JSObject* aGlobal, JSContext* aCx, bool aIsMainThread)
277 MOZ_ASSERT(aCx);
278 mCx = aCx;
279 if (aIsMainThread) {
280 // This Rooted<> is necessary only as long as AutoCxPusher::AutoCxPusher
281 // can GC, which is only possible because XPCJSContextStack::Push calls
282 // nsIPrincipal.Equals. Once that is removed, the Rooted<> will no longer
283 // be necessary.
284 JS::Rooted<JSObject*> global(JS_GetRuntime(aCx), aGlobal);
285 mCxPusher.emplace(mCx);
286 mAutoNullableCompartment.emplace(mCx, global);
287 } else {
288 mAutoNullableCompartment.emplace(mCx, aGlobal);
292 AutoJSAPI::AutoJSAPI(nsIGlobalObject* aGlobalObject,
293 bool aIsMainThread,
294 JSContext* aCx)
296 MOZ_ASSERT(aGlobalObject);
297 MOZ_ASSERT(aGlobalObject->GetGlobalJSObject(), "Must have a JS global");
298 MOZ_ASSERT(aCx);
299 MOZ_ASSERT_IF(aIsMainThread, NS_IsMainThread());
301 InitInternal(aGlobalObject->GetGlobalJSObject(), aCx, aIsMainThread);
304 void
305 AutoJSAPI::Init()
307 MOZ_ASSERT(!mCx, "An AutoJSAPI should only be initialised once");
309 InitInternal(/* aGlobal */ nullptr,
310 nsContentUtils::GetDefaultJSContextForThread(),
311 NS_IsMainThread());
314 bool
315 AutoJSAPI::Init(nsIGlobalObject* aGlobalObject, JSContext* aCx)
317 MOZ_ASSERT(!mCx, "An AutoJSAPI should only be initialised once");
318 MOZ_ASSERT(aCx);
320 if (NS_WARN_IF(!aGlobalObject)) {
321 return false;
324 JSObject* global = aGlobalObject->GetGlobalJSObject();
325 if (NS_WARN_IF(!global)) {
326 return false;
329 InitInternal(global, aCx, NS_IsMainThread());
330 return true;
333 bool
334 AutoJSAPI::Init(nsIGlobalObject* aGlobalObject)
336 return Init(aGlobalObject, nsContentUtils::GetDefaultJSContextForThread());
339 bool
340 AutoJSAPI::InitWithLegacyErrorReporting(nsIGlobalObject* aGlobalObject)
342 MOZ_ASSERT(NS_IsMainThread());
344 return Init(aGlobalObject, FindJSContext(aGlobalObject));
347 bool
348 AutoJSAPI::Init(nsPIDOMWindow* aWindow, JSContext* aCx)
350 return Init(static_cast<nsGlobalWindow*>(aWindow), aCx);
353 bool
354 AutoJSAPI::Init(nsPIDOMWindow* aWindow)
356 return Init(static_cast<nsGlobalWindow*>(aWindow));
359 bool
360 AutoJSAPI::Init(nsGlobalWindow* aWindow, JSContext* aCx)
362 return Init(static_cast<nsIGlobalObject*>(aWindow), aCx);
365 bool
366 AutoJSAPI::Init(nsGlobalWindow* aWindow)
368 return Init(static_cast<nsIGlobalObject*>(aWindow));
371 bool
372 AutoJSAPI::InitWithLegacyErrorReporting(nsPIDOMWindow* aWindow)
374 return InitWithLegacyErrorReporting(static_cast<nsGlobalWindow*>(aWindow));
377 bool
378 AutoJSAPI::InitWithLegacyErrorReporting(nsGlobalWindow* aWindow)
380 return InitWithLegacyErrorReporting(static_cast<nsIGlobalObject*>(aWindow));
383 AutoEntryScript::AutoEntryScript(nsIGlobalObject* aGlobalObject,
384 bool aIsMainThread,
385 JSContext* aCx)
386 : AutoJSAPI(aGlobalObject, aIsMainThread,
387 aCx ? aCx : FindJSContext(aGlobalObject))
388 , ScriptSettingsStackEntry(aGlobalObject, /* aCandidate = */ true)
389 , mWebIDLCallerPrincipal(nullptr)
391 MOZ_ASSERT(aGlobalObject);
392 MOZ_ASSERT_IF(!aCx, aIsMainThread); // cx is mandatory off-main-thread.
393 MOZ_ASSERT_IF(aCx && aIsMainThread, aCx == FindJSContext(aGlobalObject));
396 AutoEntryScript::~AutoEntryScript()
398 // GC when we pop a script entry point. This is a useful heuristic that helps
399 // us out on certain (flawed) benchmarks like sunspider, because it lets us
400 // avoid GCing during the timing loop.
401 JS_MaybeGC(cx());
404 AutoIncumbentScript::AutoIncumbentScript(nsIGlobalObject* aGlobalObject)
405 : ScriptSettingsStackEntry(aGlobalObject, /* aCandidate = */ false)
406 , mCallerOverride(nsContentUtils::GetCurrentJSContextForThread())
410 AutoNoJSAPI::AutoNoJSAPI(bool aIsMainThread)
411 : ScriptSettingsStackEntry()
413 MOZ_ASSERT_IF(nsContentUtils::GetCurrentJSContextForThread(),
414 !JS_IsExceptionPending(nsContentUtils::GetCurrentJSContextForThread()));
415 if (aIsMainThread) {
416 mCxPusher.emplace(static_cast<JSContext*>(nullptr),
417 /* aAllowNull = */ true);
421 danger::AutoCxPusher::AutoCxPusher(JSContext* cx, bool allowNull)
423 MOZ_ASSERT_IF(!allowNull, cx);
425 // Hold a strong ref to the nsIScriptContext, if any. This ensures that we
426 // only destroy the mContext of an nsJSContext when it is not on the cx stack
427 // (and therefore not in use). See nsJSContext::DestroyJSContext().
428 if (cx)
429 mScx = GetScriptContextFromJSContext(cx);
431 XPCJSContextStack *stack = XPCJSRuntime::Get()->GetJSContextStack();
432 if (!stack->Push(cx)) {
433 MOZ_CRASH();
435 mStackDepthAfterPush = stack->Count();
437 #ifdef DEBUG
438 mPushedContext = cx;
439 mCompartmentDepthOnEntry = cx ? js::GetEnterCompartmentDepth(cx) : 0;
440 #endif
442 // Enter a request and a compartment for the duration that the cx is on the
443 // stack if non-null.
444 if (cx) {
445 mAutoRequest.emplace(cx);
449 danger::AutoCxPusher::~AutoCxPusher()
451 // Leave the request before popping.
452 mAutoRequest.reset();
454 // When we push a context, we may save the frame chain and pretend like we
455 // haven't entered any compartment. This gets restored on Pop(), but we can
456 // run into trouble if a Push/Pop are interleaved with a
457 // JSAutoEnterCompartment. Make sure the compartment depth right before we
458 // pop is the same as it was right after we pushed.
459 MOZ_ASSERT_IF(mPushedContext, mCompartmentDepthOnEntry ==
460 js::GetEnterCompartmentDepth(mPushedContext));
461 DebugOnly<JSContext*> stackTop;
462 MOZ_ASSERT(mPushedContext == nsXPConnect::XPConnect()->GetCurrentJSContext());
463 XPCJSRuntime::Get()->GetJSContextStack()->Pop();
464 mScx = nullptr;
467 bool
468 danger::AutoCxPusher::IsStackTop() const
470 uint32_t currentDepth = XPCJSRuntime::Get()->GetJSContextStack()->Count();
471 MOZ_ASSERT(currentDepth >= mStackDepthAfterPush);
472 return currentDepth == mStackDepthAfterPush;
475 } // namespace dom
477 AutoJSContext::AutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
478 : mCx(nullptr)
480 Init(false MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT);
483 AutoJSContext::AutoJSContext(bool aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
484 : mCx(nullptr)
486 Init(aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT);
489 void
490 AutoJSContext::Init(bool aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
492 JS::AutoSuppressGCAnalysis nogc;
493 MOZ_ASSERT(!mCx, "mCx should not be initialized!");
495 MOZ_GUARD_OBJECT_NOTIFIER_INIT;
497 nsXPConnect *xpc = nsXPConnect::XPConnect();
498 if (!aSafe) {
499 mCx = xpc->GetCurrentJSContext();
502 if (!mCx) {
503 mJSAPI.Init();
504 mCx = mJSAPI.cx();
508 AutoJSContext::operator JSContext*() const
510 return mCx;
513 ThreadsafeAutoJSContext::ThreadsafeAutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
515 MOZ_GUARD_OBJECT_NOTIFIER_INIT;
517 if (NS_IsMainThread()) {
518 mCx = nullptr;
519 mAutoJSContext.emplace();
520 } else {
521 mCx = mozilla::dom::workers::GetCurrentThreadJSContext();
522 mRequest.emplace(mCx);
526 ThreadsafeAutoJSContext::operator JSContext*() const
528 if (mCx) {
529 return mCx;
530 } else {
531 return *mAutoJSContext;
535 AutoSafeJSContext::AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
536 : AutoJSContext(true MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT)
537 , mAc(mCx, xpc::UnprivilegedJunkScope())
541 ThreadsafeAutoSafeJSContext::ThreadsafeAutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
543 MOZ_GUARD_OBJECT_NOTIFIER_INIT;
545 if (NS_IsMainThread()) {
546 mCx = nullptr;
547 mAutoSafeJSContext.emplace();
548 } else {
549 mCx = mozilla::dom::workers::GetCurrentThreadJSContext();
550 mRequest.emplace(mCx);
554 ThreadsafeAutoSafeJSContext::operator JSContext*() const
556 if (mCx) {
557 return mCx;
558 } else {
559 return *mAutoSafeJSContext;
563 } // namespace mozilla