michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: // vim: ft=cpp tw=78 sw=2 et ts=2 michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/dom/ScriptSettings.h" michael@0: #include "mozilla/ThreadLocal.h" michael@0: #include "mozilla/Assertions.h" michael@0: michael@0: #include "jsapi.h" michael@0: #include "xpcpublic.h" michael@0: #include "nsIGlobalObject.h" michael@0: #include "nsIScriptGlobalObject.h" michael@0: #include "nsIScriptContext.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsTArray.h" michael@0: #include "nsJSUtils.h" michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: class ScriptSettingsStack; michael@0: static mozilla::ThreadLocal sScriptSettingsTLS; michael@0: michael@0: ScriptSettingsStackEntry ScriptSettingsStackEntry::NoJSAPISingleton; michael@0: michael@0: class ScriptSettingsStack { michael@0: public: michael@0: static ScriptSettingsStack& Ref() { michael@0: return *sScriptSettingsTLS.get(); michael@0: } michael@0: ScriptSettingsStack() {}; michael@0: michael@0: void Push(ScriptSettingsStackEntry* aSettings) { michael@0: // The bottom-most entry must always be a candidate entry point. michael@0: MOZ_ASSERT_IF(mStack.Length() == 0 || mStack.LastElement()->NoJSAPI(), michael@0: aSettings->mIsCandidateEntryPoint); michael@0: mStack.AppendElement(aSettings); michael@0: } michael@0: michael@0: void PushNoJSAPI() { michael@0: mStack.AppendElement(&ScriptSettingsStackEntry::NoJSAPISingleton); michael@0: } michael@0: michael@0: void Pop() { michael@0: MOZ_ASSERT(mStack.Length() > 0); michael@0: mStack.RemoveElementAt(mStack.Length() - 1); michael@0: } michael@0: michael@0: ScriptSettingsStackEntry* Incumbent() { michael@0: if (!mStack.Length()) { michael@0: return nullptr; michael@0: } michael@0: return mStack.LastElement(); michael@0: } michael@0: michael@0: nsIGlobalObject* IncumbentGlobal() { michael@0: ScriptSettingsStackEntry *entry = Incumbent(); michael@0: return entry ? entry->mGlobalObject : nullptr; michael@0: } michael@0: michael@0: ScriptSettingsStackEntry* EntryPoint() { michael@0: if (!mStack.Length()) michael@0: return nullptr; michael@0: for (int i = mStack.Length() - 1; i >= 0; --i) { michael@0: if (mStack[i]->mIsCandidateEntryPoint) { michael@0: return mStack[i]; michael@0: } michael@0: } michael@0: MOZ_ASSUME_UNREACHABLE("Non-empty stack should always have an entry point"); michael@0: } michael@0: michael@0: nsIGlobalObject* EntryGlobal() { michael@0: ScriptSettingsStackEntry *entry = EntryPoint(); michael@0: return entry ? entry->mGlobalObject : nullptr; michael@0: } michael@0: michael@0: private: michael@0: // These pointers are caller-owned. michael@0: nsTArray mStack; michael@0: }; michael@0: michael@0: void michael@0: InitScriptSettings() michael@0: { michael@0: if (!sScriptSettingsTLS.initialized()) { michael@0: bool success = sScriptSettingsTLS.init(); michael@0: if (!success) { michael@0: MOZ_CRASH(); michael@0: } michael@0: } michael@0: michael@0: ScriptSettingsStack* ptr = new ScriptSettingsStack(); michael@0: sScriptSettingsTLS.set(ptr); michael@0: } michael@0: michael@0: void DestroyScriptSettings() michael@0: { michael@0: ScriptSettingsStack* ptr = sScriptSettingsTLS.get(); michael@0: MOZ_ASSERT(ptr); michael@0: sScriptSettingsTLS.set(nullptr); michael@0: delete ptr; michael@0: } michael@0: michael@0: // This mostly gets the entry global, but doesn't entirely match the spec in michael@0: // certain edge cases. It's good enough for some purposes, but not others. If michael@0: // you want to call this function, ping bholley and describe your use-case. michael@0: nsIGlobalObject* michael@0: BrokenGetEntryGlobal() michael@0: { michael@0: // We need the current JSContext in order to check the JS for michael@0: // scripted frames that may have appeared since anyone last michael@0: // manipulated the stack. If it's null, that means that there michael@0: // must be no entry global on the stack. michael@0: JSContext *cx = nsContentUtils::GetCurrentJSContextForThread(); michael@0: if (!cx) { michael@0: MOZ_ASSERT(ScriptSettingsStack::Ref().EntryGlobal() == nullptr); michael@0: return nullptr; michael@0: } michael@0: michael@0: return nsJSUtils::GetDynamicScriptGlobal(cx); michael@0: } michael@0: michael@0: // Note: When we're ready to expose it, GetEntryGlobal will look similar to michael@0: // GetIncumbentGlobal below. michael@0: michael@0: nsIGlobalObject* michael@0: GetIncumbentGlobal() michael@0: { michael@0: // We need the current JSContext in order to check the JS for michael@0: // scripted frames that may have appeared since anyone last michael@0: // manipulated the stack. If it's null, that means that there michael@0: // must be no entry global on the stack, and therefore no incumbent michael@0: // global either. michael@0: JSContext *cx = nsContentUtils::GetCurrentJSContextForThread(); michael@0: if (!cx) { michael@0: MOZ_ASSERT(ScriptSettingsStack::Ref().EntryGlobal() == nullptr); michael@0: return nullptr; michael@0: } michael@0: michael@0: // See what the JS engine has to say. If we've got a scripted caller michael@0: // override in place, the JS engine will lie to us and pretend that michael@0: // there's nothing on the JS stack, which will cause us to check the michael@0: // incumbent script stack below. michael@0: if (JSObject *global = JS::GetScriptedCallerGlobal(cx)) { michael@0: return xpc::GetNativeForGlobal(global); michael@0: } michael@0: michael@0: // Ok, nothing from the JS engine. Let's use whatever's on the michael@0: // explicit stack. michael@0: return ScriptSettingsStack::Ref().IncumbentGlobal(); michael@0: } michael@0: michael@0: nsIPrincipal* michael@0: GetWebIDLCallerPrincipal() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: ScriptSettingsStackEntry *entry = ScriptSettingsStack::Ref().EntryPoint(); michael@0: michael@0: // If we have an entry point that is not the NoJSAPI singleton, we know it michael@0: // must be an AutoEntryScript. michael@0: if (!entry || entry->NoJSAPI()) { michael@0: return nullptr; michael@0: } michael@0: AutoEntryScript* aes = static_cast(entry); michael@0: michael@0: // We can't yet rely on the Script Settings Stack to properly determine the michael@0: // entry script, because there are still lots of places in the tree where we michael@0: // don't yet use an AutoEntryScript (bug 951991 tracks this work). In the michael@0: // mean time though, we can make some observations to hack around the michael@0: // problem: michael@0: // michael@0: // (1) All calls into JS-implemented WebIDL go through CallSetup, which goes michael@0: // through AutoEntryScript. michael@0: // (2) The top candidate entry point in the Script Settings Stack is the michael@0: // entry point if and only if no other JSContexts have been pushed on michael@0: // top of the push made by that entry's AutoEntryScript. michael@0: // michael@0: // Because of (1), all of the cases where we might return a non-null michael@0: // WebIDL Caller are guaranteed to have put an entry on the Script Settings michael@0: // Stack, so we can restrict our search to that. Moreover, (2) gives us a michael@0: // criterion to determine whether an entry in the Script Setting Stack means michael@0: // that we should return a non-null WebIDL Caller. michael@0: // michael@0: // Once we fix bug 951991, this can all be simplified. michael@0: if (!aes->CxPusherIsStackTop()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return aes->mWebIDLCallerPrincipal; michael@0: } michael@0: michael@0: static JSContext* michael@0: FindJSContext(nsIGlobalObject* aGlobalObject) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: JSContext *cx = nullptr; michael@0: nsCOMPtr sgo = do_QueryInterface(aGlobalObject); michael@0: if (sgo && sgo->GetScriptContext()) { michael@0: cx = sgo->GetScriptContext()->GetNativeContext(); michael@0: } michael@0: if (!cx) { michael@0: cx = nsContentUtils::GetSafeJSContext(); michael@0: } michael@0: return cx; michael@0: } michael@0: michael@0: AutoJSAPI::AutoJSAPI() michael@0: : mCx(nsContentUtils::GetDefaultJSContextForThread()) michael@0: { michael@0: if (NS_IsMainThread()) { michael@0: mCxPusher.construct(mCx); michael@0: } michael@0: michael@0: // Leave the cx in a null compartment. michael@0: mNullAc.construct(mCx); michael@0: } michael@0: michael@0: AutoJSAPI::AutoJSAPI(JSContext *aCx, bool aIsMainThread, bool aSkipNullAc) michael@0: : mCx(aCx) michael@0: { michael@0: MOZ_ASSERT_IF(aIsMainThread, NS_IsMainThread()); michael@0: if (aIsMainThread) { michael@0: mCxPusher.construct(mCx); michael@0: } michael@0: michael@0: // In general we want to leave the cx in a null compartment, but we let michael@0: // subclasses skip this if they plan to immediately enter a compartment. michael@0: if (!aSkipNullAc) { michael@0: mNullAc.construct(mCx); michael@0: } michael@0: } michael@0: michael@0: AutoJSAPIWithErrorsReportedToWindow::AutoJSAPIWithErrorsReportedToWindow(nsIScriptContext* aScx) michael@0: : AutoJSAPI(aScx->GetNativeContext(), /* aIsMainThread = */ true) michael@0: { michael@0: } michael@0: michael@0: AutoJSAPIWithErrorsReportedToWindow::AutoJSAPIWithErrorsReportedToWindow(nsIGlobalObject* aGlobalObject) michael@0: : AutoJSAPI(FindJSContext(aGlobalObject), /* aIsMainThread = */ true) michael@0: { michael@0: } michael@0: michael@0: AutoEntryScript::AutoEntryScript(nsIGlobalObject* aGlobalObject, michael@0: bool aIsMainThread, michael@0: JSContext* aCx) michael@0: : AutoJSAPI(aCx ? aCx : FindJSContext(aGlobalObject), aIsMainThread, /* aSkipNullAc = */ true) michael@0: , ScriptSettingsStackEntry(aGlobalObject, /* aCandidate = */ true) michael@0: , mAc(cx(), aGlobalObject->GetGlobalJSObject()) michael@0: , mStack(ScriptSettingsStack::Ref()) michael@0: { michael@0: MOZ_ASSERT(aGlobalObject); michael@0: MOZ_ASSERT_IF(!aCx, aIsMainThread); // cx is mandatory off-main-thread. michael@0: MOZ_ASSERT_IF(aCx && aIsMainThread, aCx == FindJSContext(aGlobalObject)); michael@0: mStack.Push(this); michael@0: } michael@0: michael@0: AutoEntryScript::~AutoEntryScript() michael@0: { michael@0: MOZ_ASSERT(mStack.Incumbent() == this); michael@0: mStack.Pop(); michael@0: } michael@0: michael@0: AutoIncumbentScript::AutoIncumbentScript(nsIGlobalObject* aGlobalObject) michael@0: : ScriptSettingsStackEntry(aGlobalObject, /* aCandidate = */ false) michael@0: , mStack(ScriptSettingsStack::Ref()) michael@0: , mCallerOverride(nsContentUtils::GetCurrentJSContextForThread()) michael@0: { michael@0: mStack.Push(this); michael@0: } michael@0: michael@0: AutoIncumbentScript::~AutoIncumbentScript() michael@0: { michael@0: MOZ_ASSERT(mStack.Incumbent() == this); michael@0: mStack.Pop(); michael@0: } michael@0: michael@0: AutoNoJSAPI::AutoNoJSAPI(bool aIsMainThread) michael@0: : mStack(ScriptSettingsStack::Ref()) michael@0: { michael@0: MOZ_ASSERT_IF(nsContentUtils::GetCurrentJSContextForThread(), michael@0: !JS_IsExceptionPending(nsContentUtils::GetCurrentJSContextForThread())); michael@0: if (aIsMainThread) { michael@0: mCxPusher.construct(static_cast(nullptr), michael@0: /* aAllowNull = */ true); michael@0: } michael@0: mStack.PushNoJSAPI(); michael@0: } michael@0: michael@0: AutoNoJSAPI::~AutoNoJSAPI() michael@0: { michael@0: mStack.Pop(); michael@0: } michael@0: michael@0: } // namespace dom michael@0: } // namespace mozilla