Bug 937317 - Implement and expose GetIncumbentGlobal. r=bz,luke
authorBobby Holley <bobbyholley@gmail.com>
Fri, 06 Dec 2013 12:01:42 -0800
changeset 174966 a6b672cbd54db73e2f0355ae822f30e16c5ffb1a
parent 174965 82df7174496c860848ce4c5b8aaccae00c80c9de
child 174967 c5940c217bd9dd7c65eb116d2b49fb69fdd486d0
push id445
push userffxbld
push dateMon, 10 Mar 2014 22:05:19 +0000
treeherdermozilla-release@dc38b741b04e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz, luke
bugs937317
milestone28.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 937317 - Implement and expose GetIncumbentGlobal. r=bz,luke
content/base/public/nsContentUtils.h
content/base/src/nsContentUtils.cpp
dom/base/ScriptSettings.cpp
dom/base/ScriptSettings.h
js/src/jsapi.cpp
js/src/jsapi.h
js/src/vm/Stack-inl.h
js/src/vm/Stack.h
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -1540,16 +1540,17 @@ public:
   static nsresult ProcessViewportInfo(nsIDocument *aDocument,
                                       const nsAString &viewportInfo);
 
   static nsIScriptContext* GetContextForEventHandlers(nsINode* aNode,
                                                       nsresult* aRv);
 
   static JSContext *GetCurrentJSContext();
   static JSContext *GetSafeJSContext();
+  static JSContext *GetCurrentJSContextForThread();
   static JSContext *GetDefaultJSContextForThread();
 
   /**
    * Case insensitive comparison between two strings. However it only ignores
    * case for ASCII characters a-z.
    */
   static bool EqualsIgnoreASCIICase(const nsAString& aStr1,
                                     const nsAString& aStr2);
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -5258,16 +5258,27 @@ nsContentUtils::GetDefaultJSContextForTh
   if (MOZ_LIKELY(NS_IsMainThread())) {
     return GetSafeJSContext();
   } else {
     return workers::GetCurrentThreadJSContext();
   }
 }
 
 /* static */
+JSContext *
+nsContentUtils::GetCurrentJSContextForThread()
+{
+  if (MOZ_LIKELY(NS_IsMainThread())) {
+    return GetCurrentJSContext();
+  } else {
+    return workers::GetCurrentThreadJSContext();
+  }
+}
+
+/* static */
 nsresult
 nsContentUtils::ASCIIToLower(nsAString& aStr)
 {
   PRUnichar* iter = aStr.BeginWriting();
   PRUnichar* end = aStr.EndWriting();
   if (MOZ_UNLIKELY(!iter || !end)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
--- a/dom/base/ScriptSettings.cpp
+++ b/dom/base/ScriptSettings.cpp
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/ThreadLocal.h"
 #include "mozilla/Assertions.h"
 
 #include "jsapi.h"
+#include "xpcpublic.h"
 #include "nsIGlobalObject.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIScriptContext.h"
 #include "nsContentUtils.h"
 #include "nsTArray.h"
 
 namespace mozilla {
 namespace dom {
@@ -86,16 +87,49 @@ InitScriptSettings()
 void DestroyScriptSettings()
 {
   ScriptSettingsStack* ptr = sScriptSettingsTLS.get();
   MOZ_ASSERT(ptr);
   sScriptSettingsTLS.set(nullptr);
   delete ptr;
 }
 
+// Note: When we're ready to expose it, GetEntryGlobal will look similar to
+// GetIncumbentGlobal below.
+
+nsIGlobalObject*
+GetIncumbentGlobal()
+{
+  // We need the current JSContext in order to check the JS for
+  // scripted frames that may have appeared since anyone last
+  // manipulated the stack. If it's null, that means that there
+  // must be no entry point on the stack, and therefore no incumbent
+  // global either.
+  JSContext *cx = nsContentUtils::GetCurrentJSContextForThread();
+  if (!cx) {
+    MOZ_ASSERT(ScriptSettingsStack::Ref().EntryPoint() == nullptr);
+    return nullptr;
+  }
+
+  // See what the JS engine has to say. If we've got a scripted caller
+  // override in place, the JS engine will lie to us and pretend that
+  // there's nothing on the JS stack, which will cause us to check the
+  // incumbent script stack below.
+  JS::RootedScript script(cx);
+  if (JS_DescribeScriptedCaller(cx, &script, nullptr)) {
+    JS::RootedObject global(cx, JS_GetGlobalFromScript(script));
+    MOZ_ASSERT(global);
+    return xpc::GetNativeForGlobal(global);
+  }
+
+  // Ok, nothing from the JS engine. Let's use whatever's on the
+  // explicit stack.
+  return ScriptSettingsStack::Ref().Incumbent();
+}
+
 AutoEntryScript::AutoEntryScript(nsIGlobalObject* aGlobalObject,
                                  bool aIsMainThread,
                                  JSContext* aCx)
   : mStack(ScriptSettingsStack::Ref())
   , mEntry(aGlobalObject, /* aCandidate = */ true)
 {
   MOZ_ASSERT(aGlobalObject);
   if (!aCx) {
@@ -123,16 +157,17 @@ AutoEntryScript::~AutoEntryScript()
 {
   MOZ_ASSERT(mStack.Incumbent() == mEntry.mGlobalObject);
   mStack.Pop();
 }
 
 AutoIncumbentScript::AutoIncumbentScript(nsIGlobalObject* aGlobalObject)
   : mStack(ScriptSettingsStack::Ref())
   , mEntry(aGlobalObject, /* aCandidate = */ false)
+  , mCallerOverride(nsContentUtils::GetCurrentJSContextForThread())
 {
   mStack.Push(&mEntry);
 }
 
 AutoIncumbentScript::~AutoIncumbentScript()
 {
   MOZ_ASSERT(mStack.Incumbent() == mEntry.mGlobalObject);
   mStack.Pop();
--- a/dom/base/ScriptSettings.h
+++ b/dom/base/ScriptSettings.h
@@ -22,16 +22,22 @@ namespace dom {
 
 /*
  * System-wide setup/teardown routines. Init and Destroy should be invoked
  * once each, at startup and shutdown (respectively).
  */
 void InitScriptSettings();
 void DestroyScriptSettings();
 
+// Note: We don't yet expose GetEntryGlobal, because in order for it to be
+// correct, we first need to replace a bunch of explicit cx pushing in the
+// browser with AutoEntryScript. But GetIncumbentGlobal is simpler, because it
+// can mostly be inferred from the JS stack.
+nsIGlobalObject* GetIncumbentGlobal();
+
 class ScriptSettingsStack;
 struct ScriptSettingsStackEntry {
   nsCOMPtr<nsIGlobalObject> mGlobalObject;
   bool mIsCandidateEntryPoint;
 
   ScriptSettingsStackEntry(nsIGlobalObject *aGlobal, bool aCandidate)
     : mGlobalObject(aGlobal)
     , mIsCandidateEntryPoint(aCandidate)
@@ -81,16 +87,17 @@ private:
  */
 class AutoIncumbentScript {
 public:
   AutoIncumbentScript(nsIGlobalObject* aGlobalObject);
   ~AutoIncumbentScript();
 private:
   dom::ScriptSettingsStack& mStack;
   dom::ScriptSettingsStackEntry mEntry;
+  JS::AutoHideScriptedCaller mCallerOverride;
 };
 
 /*
  * A class used for C++ to indicate that existing entry and incumbent scripts
  * should not apply to anything in scope, and that callees should act as if
  * they were invoked "from C++".
  */
 class AutoSystemCaller {
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -6093,22 +6093,54 @@ JS_DescribeScriptedCaller(JSContext *cx,
     script.set(nullptr);
     if (lineno)
         *lineno = 0;
 
     NonBuiltinScriptFrameIter i(cx);
     if (i.done())
         return false;
 
+    // If the caller is hidden, the embedding wants us to return null here so
+    // that it can check its own stack.
+    if (i.activation()->scriptedCallerIsHidden())
+        return false;
+
     script.set(i.script());
     if (lineno)
         *lineno = js::PCToLineNumber(i.script(), i.pc());
     return true;
 }
 
+namespace JS {
+
+JS_PUBLIC_API(void)
+HideScriptedCaller(JSContext *cx)
+{
+    MOZ_ASSERT(cx);
+
+    // If there's no accessible activation on the stack, we'll return null from
+    // JS_DescribeScriptedCaller anyway, so there's no need to annotate
+    // anything.
+    Activation *act = cx->runtime()->mainThread.activation();
+    if (!act)
+        return;
+    act->hideScriptedCaller();
+}
+
+JS_PUBLIC_API(void)
+UnhideScriptedCaller(JSContext *cx)
+{
+    Activation *act = cx->runtime()->mainThread.activation();
+    if (!act)
+        return;
+    act->unhideScriptedCaller();
+}
+
+} /* namespace JS */
+
 #ifdef JS_THREADSAFE
 static PRStatus
 CallOnce(void *func)
 {
     JSInitCallback init = JS_DATA_TO_FUNC_PTR(JSInitCallback, func);
     return init() ? PR_SUCCESS : PR_FAILURE;
 }
 #endif
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -4571,20 +4571,63 @@ JS_CharsToId(JSContext* cx, JS::TwoByteC
  *  Test if the given string is a valid ECMAScript identifier
  */
 extern JS_PUBLIC_API(bool)
 JS_IsIdentifier(JSContext *cx, JS::HandleString str, bool *isIdentifier);
 
 /*
  * Return the current script and line number of the most currently running
  * frame. Returns true if a scripted frame was found, false otherwise.
+ *
+ * If a the embedding has hidden the scripted caller for the topmost activation
+ * record, this will also return false.
  */
 extern JS_PUBLIC_API(bool)
 JS_DescribeScriptedCaller(JSContext *cx, JS::MutableHandleScript script, unsigned *lineno);
 
+namespace JS {
+
+/*
+ * Informs the JS engine that the scripted caller should be hidden. This can be
+ * used by the embedding to maintain an override of the scripted caller in its
+ * calculations, by hiding the scripted caller in the JS engine and pushing data
+ * onto a separate stack, which it inspects when JS_DescribeScriptedCaller
+ * returns null.
+ *
+ * We maintain a counter on each activation record. Add() increments the counter
+ * of the topmost activation, and Remove() decrements it. The count may never
+ * drop below zero, and must always be exactly zero when the activation is
+ * popped from the stack.
+ */
+extern JS_PUBLIC_API(void)
+HideScriptedCaller(JSContext *cx);
+
+extern JS_PUBLIC_API(void)
+UnhideScriptedCaller(JSContext *cx);
+
+class AutoHideScriptedCaller
+{
+  public:
+    AutoHideScriptedCaller(JSContext *cx
+                           MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+      : mContext(cx)
+    {
+        MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+        HideScriptedCaller(mContext);
+    }
+    ~AutoHideScriptedCaller() {
+        UnhideScriptedCaller(mContext);
+    }
+
+  protected:
+    JSContext *mContext;
+    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+};
+
+} /* namepsace JS */
 
 /*
  * Encode/Decode interpreted scripts and functions to/from memory.
  */
 
 extern JS_PUBLIC_API(void *)
 JS_EncodeScript(JSContext *cx, JS::HandleScript script, uint32_t *lengthp);
 
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -807,24 +807,26 @@ AbstractFramePtr::popWith(JSContext *cx)
         MOZ_ASSUME_UNREACHABLE("Invalid frame");
 }
 
 Activation::Activation(JSContext *cx, Kind kind)
   : cx_(cx),
     compartment_(cx->compartment()),
     prev_(cx->mainThread().activation_),
     savedFrameChain_(0),
+    hideScriptedCallerCount_(0),
     kind_(kind)
 {
     cx->mainThread().activation_ = this;
 }
 
 Activation::~Activation()
 {
     JS_ASSERT(cx_->mainThread().activation_ == this);
+    JS_ASSERT(hideScriptedCallerCount_ == 0);
     cx_->mainThread().activation_ = prev_;
 }
 
 InterpreterActivation::InterpreterActivation(RunState &state, JSContext *cx, StackFrame *entryFrame)
   : Activation(cx, Interpreter),
     state_(state),
     entryFrame_(entryFrame),
     opMask_(0)
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -1132,16 +1132,23 @@ class Activation
     Activation *prev_;
 
     // Counter incremented by JS_SaveFrameChain on the top-most activation and
     // decremented by JS_RestoreFrameChain. If > 0, ScriptFrameIter should stop
     // iterating when it reaches this activation (if GO_THROUGH_SAVED is not
     // set).
     size_t savedFrameChain_;
 
+    // Counter incremented by JS::HideScriptedCaller and decremented by
+    // JS::UnhideScriptedCaller. If > 0 for the top activation,
+    // JS_DescribeScriptedCaller will return null instead of querying that
+    // activation, which should prompt the caller to consult embedding-specific
+    // data structures instead.
+    size_t hideScriptedCallerCount_;
+
     enum Kind { Interpreter, Jit, ForkJoin };
     Kind kind_;
 
     inline Activation(JSContext *cx, Kind kind_);
     inline ~Activation();
 
   public:
     JSContext *cx() const {
@@ -1183,16 +1190,27 @@ class Activation
     void restoreFrameChain() {
         JS_ASSERT(savedFrameChain_ > 0);
         savedFrameChain_--;
     }
     bool hasSavedFrameChain() const {
         return savedFrameChain_ > 0;
     }
 
+    void hideScriptedCaller() {
+        hideScriptedCallerCount_++;
+    }
+    void unhideScriptedCaller() {
+        JS_ASSERT(hideScriptedCallerCount_ > 0);
+        hideScriptedCallerCount_--;
+    }
+    bool scriptedCallerIsHidden() const {
+        return hideScriptedCallerCount_ > 0;
+    }
+
   private:
     Activation(const Activation &other) MOZ_DELETE;
     void operator=(const Activation &other) MOZ_DELETE;
 };
 
 // This variable holds a special opcode value which is greater than all normal
 // opcodes, and is chosen such that the bitwise or of this value with any
 // opcode is this value.