dom/base/ScriptSettings.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     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 "xpcpublic.h"
    13 #include "nsIGlobalObject.h"
    14 #include "nsIScriptGlobalObject.h"
    15 #include "nsIScriptContext.h"
    16 #include "nsContentUtils.h"
    17 #include "nsTArray.h"
    18 #include "nsJSUtils.h"
    20 namespace mozilla {
    21 namespace dom {
    23 class ScriptSettingsStack;
    24 static mozilla::ThreadLocal<ScriptSettingsStack*> sScriptSettingsTLS;
    26 ScriptSettingsStackEntry ScriptSettingsStackEntry::NoJSAPISingleton;
    28 class ScriptSettingsStack {
    29 public:
    30   static ScriptSettingsStack& Ref() {
    31     return *sScriptSettingsTLS.get();
    32   }
    33   ScriptSettingsStack() {};
    35   void Push(ScriptSettingsStackEntry* aSettings) {
    36     // The bottom-most entry must always be a candidate entry point.
    37     MOZ_ASSERT_IF(mStack.Length() == 0 || mStack.LastElement()->NoJSAPI(),
    38                   aSettings->mIsCandidateEntryPoint);
    39     mStack.AppendElement(aSettings);
    40   }
    42   void PushNoJSAPI() {
    43     mStack.AppendElement(&ScriptSettingsStackEntry::NoJSAPISingleton);
    44   }
    46   void Pop() {
    47     MOZ_ASSERT(mStack.Length() > 0);
    48     mStack.RemoveElementAt(mStack.Length() - 1);
    49   }
    51   ScriptSettingsStackEntry* Incumbent() {
    52     if (!mStack.Length()) {
    53       return nullptr;
    54     }
    55     return mStack.LastElement();
    56   }
    58   nsIGlobalObject* IncumbentGlobal() {
    59     ScriptSettingsStackEntry *entry = Incumbent();
    60     return entry ? entry->mGlobalObject : nullptr;
    61   }
    63   ScriptSettingsStackEntry* EntryPoint() {
    64     if (!mStack.Length())
    65       return nullptr;
    66     for (int i = mStack.Length() - 1; i >= 0; --i) {
    67       if (mStack[i]->mIsCandidateEntryPoint) {
    68         return mStack[i];
    69       }
    70     }
    71     MOZ_ASSUME_UNREACHABLE("Non-empty stack should always have an entry point");
    72   }
    74   nsIGlobalObject* EntryGlobal() {
    75     ScriptSettingsStackEntry *entry = EntryPoint();
    76     return entry ? entry->mGlobalObject : nullptr;
    77   }
    79 private:
    80   // These pointers are caller-owned.
    81   nsTArray<ScriptSettingsStackEntry*> mStack;
    82 };
    84 void
    85 InitScriptSettings()
    86 {
    87   if (!sScriptSettingsTLS.initialized()) {
    88     bool success = sScriptSettingsTLS.init();
    89     if (!success) {
    90       MOZ_CRASH();
    91     }
    92   }
    94   ScriptSettingsStack* ptr = new ScriptSettingsStack();
    95   sScriptSettingsTLS.set(ptr);
    96 }
    98 void DestroyScriptSettings()
    99 {
   100   ScriptSettingsStack* ptr = sScriptSettingsTLS.get();
   101   MOZ_ASSERT(ptr);
   102   sScriptSettingsTLS.set(nullptr);
   103   delete ptr;
   104 }
   106 // This mostly gets the entry global, but doesn't entirely match the spec in
   107 // certain edge cases. It's good enough for some purposes, but not others. If
   108 // you want to call this function, ping bholley and describe your use-case.
   109 nsIGlobalObject*
   110 BrokenGetEntryGlobal()
   111 {
   112   // We need the current JSContext in order to check the JS for
   113   // scripted frames that may have appeared since anyone last
   114   // manipulated the stack. If it's null, that means that there
   115   // must be no entry global on the stack.
   116   JSContext *cx = nsContentUtils::GetCurrentJSContextForThread();
   117   if (!cx) {
   118     MOZ_ASSERT(ScriptSettingsStack::Ref().EntryGlobal() == nullptr);
   119     return nullptr;
   120   }
   122   return nsJSUtils::GetDynamicScriptGlobal(cx);
   123 }
   125 // Note: When we're ready to expose it, GetEntryGlobal will look similar to
   126 // GetIncumbentGlobal below.
   128 nsIGlobalObject*
   129 GetIncumbentGlobal()
   130 {
   131   // We need the current JSContext in order to check the JS for
   132   // scripted frames that may have appeared since anyone last
   133   // manipulated the stack. If it's null, that means that there
   134   // must be no entry global on the stack, and therefore no incumbent
   135   // global either.
   136   JSContext *cx = nsContentUtils::GetCurrentJSContextForThread();
   137   if (!cx) {
   138     MOZ_ASSERT(ScriptSettingsStack::Ref().EntryGlobal() == nullptr);
   139     return nullptr;
   140   }
   142   // See what the JS engine has to say. If we've got a scripted caller
   143   // override in place, the JS engine will lie to us and pretend that
   144   // there's nothing on the JS stack, which will cause us to check the
   145   // incumbent script stack below.
   146   if (JSObject *global = JS::GetScriptedCallerGlobal(cx)) {
   147     return xpc::GetNativeForGlobal(global);
   148   }
   150   // Ok, nothing from the JS engine. Let's use whatever's on the
   151   // explicit stack.
   152   return ScriptSettingsStack::Ref().IncumbentGlobal();
   153 }
   155 nsIPrincipal*
   156 GetWebIDLCallerPrincipal()
   157 {
   158   MOZ_ASSERT(NS_IsMainThread());
   159   ScriptSettingsStackEntry *entry = ScriptSettingsStack::Ref().EntryPoint();
   161   // If we have an entry point that is not the NoJSAPI singleton, we know it
   162   // must be an AutoEntryScript.
   163   if (!entry || entry->NoJSAPI()) {
   164     return nullptr;
   165   }
   166   AutoEntryScript* aes = static_cast<AutoEntryScript*>(entry);
   168   // We can't yet rely on the Script Settings Stack to properly determine the
   169   // entry script, because there are still lots of places in the tree where we
   170   // don't yet use an AutoEntryScript (bug 951991 tracks this work). In the
   171   // mean time though, we can make some observations to hack around the
   172   // problem:
   173   //
   174   // (1) All calls into JS-implemented WebIDL go through CallSetup, which goes
   175   //     through AutoEntryScript.
   176   // (2) The top candidate entry point in the Script Settings Stack is the
   177   //     entry point if and only if no other JSContexts have been pushed on
   178   //     top of the push made by that entry's AutoEntryScript.
   179   //
   180   // Because of (1), all of the cases where we might return a non-null
   181   // WebIDL Caller are guaranteed to have put an entry on the Script Settings
   182   // Stack, so we can restrict our search to that. Moreover, (2) gives us a
   183   // criterion to determine whether an entry in the Script Setting Stack means
   184   // that we should return a non-null WebIDL Caller.
   185   //
   186   // Once we fix bug 951991, this can all be simplified.
   187   if (!aes->CxPusherIsStackTop()) {
   188     return nullptr;
   189   }
   191   return aes->mWebIDLCallerPrincipal;
   192 }
   194 static JSContext*
   195 FindJSContext(nsIGlobalObject* aGlobalObject)
   196 {
   197   MOZ_ASSERT(NS_IsMainThread());
   198   JSContext *cx = nullptr;
   199   nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aGlobalObject);
   200   if (sgo && sgo->GetScriptContext()) {
   201     cx = sgo->GetScriptContext()->GetNativeContext();
   202   }
   203   if (!cx) {
   204     cx = nsContentUtils::GetSafeJSContext();
   205   }
   206   return cx;
   207 }
   209 AutoJSAPI::AutoJSAPI()
   210   : mCx(nsContentUtils::GetDefaultJSContextForThread())
   211 {
   212   if (NS_IsMainThread()) {
   213     mCxPusher.construct(mCx);
   214   }
   216   // Leave the cx in a null compartment.
   217   mNullAc.construct(mCx);
   218 }
   220 AutoJSAPI::AutoJSAPI(JSContext *aCx, bool aIsMainThread, bool aSkipNullAc)
   221   : mCx(aCx)
   222 {
   223   MOZ_ASSERT_IF(aIsMainThread, NS_IsMainThread());
   224   if (aIsMainThread) {
   225     mCxPusher.construct(mCx);
   226   }
   228   // In general we want to leave the cx in a null compartment, but we let
   229   // subclasses skip this if they plan to immediately enter a compartment.
   230   if (!aSkipNullAc) {
   231     mNullAc.construct(mCx);
   232   }
   233 }
   235 AutoJSAPIWithErrorsReportedToWindow::AutoJSAPIWithErrorsReportedToWindow(nsIScriptContext* aScx)
   236   : AutoJSAPI(aScx->GetNativeContext(), /* aIsMainThread = */ true)
   237 {
   238 }
   240 AutoJSAPIWithErrorsReportedToWindow::AutoJSAPIWithErrorsReportedToWindow(nsIGlobalObject* aGlobalObject)
   241   : AutoJSAPI(FindJSContext(aGlobalObject), /* aIsMainThread = */ true)
   242 {
   243 }
   245 AutoEntryScript::AutoEntryScript(nsIGlobalObject* aGlobalObject,
   246                                  bool aIsMainThread,
   247                                  JSContext* aCx)
   248   : AutoJSAPI(aCx ? aCx : FindJSContext(aGlobalObject), aIsMainThread, /* aSkipNullAc = */ true)
   249   , ScriptSettingsStackEntry(aGlobalObject, /* aCandidate = */ true)
   250   , mAc(cx(), aGlobalObject->GetGlobalJSObject())
   251   , mStack(ScriptSettingsStack::Ref())
   252 {
   253   MOZ_ASSERT(aGlobalObject);
   254   MOZ_ASSERT_IF(!aCx, aIsMainThread); // cx is mandatory off-main-thread.
   255   MOZ_ASSERT_IF(aCx && aIsMainThread, aCx == FindJSContext(aGlobalObject));
   256   mStack.Push(this);
   257 }
   259 AutoEntryScript::~AutoEntryScript()
   260 {
   261   MOZ_ASSERT(mStack.Incumbent() == this);
   262   mStack.Pop();
   263 }
   265 AutoIncumbentScript::AutoIncumbentScript(nsIGlobalObject* aGlobalObject)
   266   : ScriptSettingsStackEntry(aGlobalObject, /* aCandidate = */ false)
   267   , mStack(ScriptSettingsStack::Ref())
   268   , mCallerOverride(nsContentUtils::GetCurrentJSContextForThread())
   269 {
   270   mStack.Push(this);
   271 }
   273 AutoIncumbentScript::~AutoIncumbentScript()
   274 {
   275   MOZ_ASSERT(mStack.Incumbent() == this);
   276   mStack.Pop();
   277 }
   279 AutoNoJSAPI::AutoNoJSAPI(bool aIsMainThread)
   280   : mStack(ScriptSettingsStack::Ref())
   281 {
   282   MOZ_ASSERT_IF(nsContentUtils::GetCurrentJSContextForThread(),
   283                 !JS_IsExceptionPending(nsContentUtils::GetCurrentJSContextForThread()));
   284   if (aIsMainThread) {
   285     mCxPusher.construct(static_cast<JSContext*>(nullptr),
   286                         /* aAllowNull = */ true);
   287   }
   288   mStack.PushNoJSAPI();
   289 }
   291 AutoNoJSAPI::~AutoNoJSAPI()
   292 {
   293   mStack.Pop();
   294 }
   296 } // namespace dom
   297 } // namespace mozilla

mercurial