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"
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"
21 #include "nsJSUtils.h"
22 #include "nsDOMJSUtils.h"
23 #include "WorkerPrivate.h"
28 static mozilla::ThreadLocal
<ScriptSettingsStackEntry
*> sScriptSettingsTLS
;
30 class ScriptSettingsStack
{
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();
62 if (entry
->mIsCandidateEntryPoint
)
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;
78 if (!sScriptSettingsTLS
.initialized()) {
79 bool success
= sScriptSettingsTLS
.init();
85 sScriptSettingsTLS
.set(nullptr);
88 void DestroyScriptSettings()
90 MOZ_ASSERT(sScriptSettingsTLS
.get() == nullptr);
93 ScriptSettingsStackEntry::ScriptSettingsStackEntry(nsIGlobalObject
*aGlobal
,
95 : mGlobalObject(aGlobal
)
96 , mIsCandidateEntryPoint(aCandidate
)
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)
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
;
162 return ClampToSubject(ScriptSettingsStack::EntryGlobal());
168 nsCOMPtr
<nsPIDOMWindow
> entryWin
= do_QueryInterface(GetEntryGlobal());
169 return entryWin
? entryWin
->GetExtantDoc() : nullptr;
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
180 JSContext
*cx
= nsContentUtils::GetCurrentJSContextForThread();
182 MOZ_ASSERT(ScriptSettingsStack::EntryGlobal() == 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
196 return ClampToSubject(ScriptSettingsStack::IncumbentGlobal());
202 JSContext
*cx
= nsContentUtils::GetCurrentJSContextForThread();
207 JSObject
*global
= JS::CurrentGlobalOrNull(cx
);
212 return xpc::GetNativeForGlobal(global
);
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
223 if (!entry
|| entry
->NoJSAPI()) {
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
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()) {
251 return aes
->mWebIDLCallerPrincipal
;
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();
264 cx
= nsContentUtils::GetSafeJSContext();
269 AutoJSAPI::AutoJSAPI()
275 AutoJSAPI::InitInternal(JSObject
* aGlobal
, JSContext
* aCx
, bool 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
284 JS::Rooted
<JSObject
*> global(JS_GetRuntime(aCx
), aGlobal
);
285 mCxPusher
.emplace(mCx
);
286 mAutoNullableCompartment
.emplace(mCx
, global
);
288 mAutoNullableCompartment
.emplace(mCx
, aGlobal
);
292 AutoJSAPI::AutoJSAPI(nsIGlobalObject
* aGlobalObject
,
296 MOZ_ASSERT(aGlobalObject
);
297 MOZ_ASSERT(aGlobalObject
->GetGlobalJSObject(), "Must have a JS global");
299 MOZ_ASSERT_IF(aIsMainThread
, NS_IsMainThread());
301 InitInternal(aGlobalObject
->GetGlobalJSObject(), aCx
, aIsMainThread
);
307 MOZ_ASSERT(!mCx
, "An AutoJSAPI should only be initialised once");
309 InitInternal(/* aGlobal */ nullptr,
310 nsContentUtils::GetDefaultJSContextForThread(),
315 AutoJSAPI::Init(nsIGlobalObject
* aGlobalObject
, JSContext
* aCx
)
317 MOZ_ASSERT(!mCx
, "An AutoJSAPI should only be initialised once");
320 if (NS_WARN_IF(!aGlobalObject
)) {
324 JSObject
* global
= aGlobalObject
->GetGlobalJSObject();
325 if (NS_WARN_IF(!global
)) {
329 InitInternal(global
, aCx
, NS_IsMainThread());
334 AutoJSAPI::Init(nsIGlobalObject
* aGlobalObject
)
336 return Init(aGlobalObject
, nsContentUtils::GetDefaultJSContextForThread());
340 AutoJSAPI::InitWithLegacyErrorReporting(nsIGlobalObject
* aGlobalObject
)
342 MOZ_ASSERT(NS_IsMainThread());
344 return Init(aGlobalObject
, FindJSContext(aGlobalObject
));
348 AutoJSAPI::Init(nsPIDOMWindow
* aWindow
, JSContext
* aCx
)
350 return Init(static_cast<nsGlobalWindow
*>(aWindow
), aCx
);
354 AutoJSAPI::Init(nsPIDOMWindow
* aWindow
)
356 return Init(static_cast<nsGlobalWindow
*>(aWindow
));
360 AutoJSAPI::Init(nsGlobalWindow
* aWindow
, JSContext
* aCx
)
362 return Init(static_cast<nsIGlobalObject
*>(aWindow
), aCx
);
366 AutoJSAPI::Init(nsGlobalWindow
* aWindow
)
368 return Init(static_cast<nsIGlobalObject
*>(aWindow
));
372 AutoJSAPI::InitWithLegacyErrorReporting(nsPIDOMWindow
* aWindow
)
374 return InitWithLegacyErrorReporting(static_cast<nsGlobalWindow
*>(aWindow
));
378 AutoJSAPI::InitWithLegacyErrorReporting(nsGlobalWindow
* aWindow
)
380 return InitWithLegacyErrorReporting(static_cast<nsIGlobalObject
*>(aWindow
));
383 AutoEntryScript::AutoEntryScript(nsIGlobalObject
* aGlobalObject
,
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.
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()));
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().
429 mScx
= GetScriptContextFromJSContext(cx
);
431 XPCJSContextStack
*stack
= XPCJSRuntime::Get()->GetJSContextStack();
432 if (!stack
->Push(cx
)) {
435 mStackDepthAfterPush
= stack
->Count();
439 mCompartmentDepthOnEntry
= cx
? js::GetEnterCompartmentDepth(cx
) : 0;
442 // Enter a request and a compartment for the duration that the cx is on the
443 // stack if non-null.
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();
468 danger::AutoCxPusher::IsStackTop() const
470 uint32_t currentDepth
= XPCJSRuntime::Get()->GetJSContextStack()->Count();
471 MOZ_ASSERT(currentDepth
>= mStackDepthAfterPush
);
472 return currentDepth
== mStackDepthAfterPush
;
477 AutoJSContext::AutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL
)
480 Init(false MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT
);
483 AutoJSContext::AutoJSContext(bool aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL
)
486 Init(aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT
);
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();
499 mCx
= xpc
->GetCurrentJSContext();
508 AutoJSContext::operator JSContext
*() const
513 ThreadsafeAutoJSContext::ThreadsafeAutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL
)
515 MOZ_GUARD_OBJECT_NOTIFIER_INIT
;
517 if (NS_IsMainThread()) {
519 mAutoJSContext
.emplace();
521 mCx
= mozilla::dom::workers::GetCurrentThreadJSContext();
522 mRequest
.emplace(mCx
);
526 ThreadsafeAutoJSContext::operator JSContext
*() const
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()) {
547 mAutoSafeJSContext
.emplace();
549 mCx
= mozilla::dom::workers::GetCurrentThreadJSContext();
550 mRequest
.emplace(mCx
);
554 ThreadsafeAutoSafeJSContext::operator JSContext
*() const
559 return *mAutoSafeJSContext
;
563 } // namespace mozilla