Bug 989528 - Implement AutoJSAPI. r=bz
authorBobby Holley <bobbyholley@gmail.com>
Mon, 14 Apr 2014 20:26:59 -0700
changeset 196938 3c079cc52af9b789e3ef1bf71dbea670d1c3a2cf
parent 196937 b79772297b2e4465643ffc7bc8d9f06c9ed9d45b
child 196939 b59ad321f19eac4377bf859925b25b74d64f61b4
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs989528
milestone31.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 989528 - Implement AutoJSAPI. r=bz
dom/base/ScriptSettings.cpp
dom/base/ScriptSettings.h
--- a/dom/base/ScriptSettings.cpp
+++ b/dom/base/ScriptSettings.cpp
@@ -179,17 +179,17 @@ GetWebIDLCallerPrincipal()
   //
   // Because of (1), all of the cases where we might return a non-null
   // WebIDL Caller are guaranteed to have put an entry on the Script Settings
   // Stack, so we can restrict our search to that. Moreover, (2) gives us a
   // criterion to determine whether an entry in the Script Setting Stack means
   // that we should return a non-null WebIDL Caller.
   //
   // Once we fix bug 951991, this can all be simplified.
-  if (!aes->mCxPusher.ref().IsStackTop()) {
+  if (!aes->CxPusherIsStackTop()) {
     return nullptr;
   }
 
   return aes->mWebIDLCallerPrincipal;
 }
 
 static JSContext*
 FindJSContext(nsIGlobalObject* aGlobalObject)
@@ -201,38 +201,53 @@ FindJSContext(nsIGlobalObject* aGlobalOb
     cx = sgo->GetScriptContext()->GetNativeContext();
   }
   if (!cx) {
     cx = nsContentUtils::GetSafeJSContext();
   }
   return cx;
 }
 
+AutoJSAPI::AutoJSAPI()
+  : mCx(nsContentUtils::GetDefaultJSContextForThread())
+{
+  if (NS_IsMainThread()) {
+    mCxPusher.construct(mCx);
+  }
+
+  // Leave the cx in a null compartment.
+  mNullAc.construct(mCx);
+}
+
+AutoJSAPI::AutoJSAPI(JSContext *aCx, bool aIsMainThread, bool aSkipNullAc)
+  : mCx(aCx)
+{
+  MOZ_ASSERT_IF(aIsMainThread, NS_IsMainThread());
+  if (aIsMainThread) {
+    mCxPusher.construct(mCx);
+  }
+
+  // In general we want to leave the cx in a null compartment, but we let
+  // subclasses skip this if they plan to immediately enter a compartment.
+  if (!aSkipNullAc) {
+    mNullAc.construct(mCx);
+  }
+}
+
 AutoEntryScript::AutoEntryScript(nsIGlobalObject* aGlobalObject,
                                  bool aIsMainThread,
                                  JSContext* aCx)
-  : ScriptSettingsStackEntry(aGlobalObject, /* aCandidate = */ true)
+  : AutoJSAPI(aCx ? aCx : FindJSContext(aGlobalObject), aIsMainThread, /* aSkipNullAc = */ true)
+  , ScriptSettingsStackEntry(aGlobalObject, /* aCandidate = */ true)
+  , mAc(cx(), aGlobalObject->GetGlobalJSObject())
   , mStack(ScriptSettingsStack::Ref())
-  , mCx(aCx)
 {
   MOZ_ASSERT(aGlobalObject);
-  MOZ_ASSERT_IF(!mCx, aIsMainThread); // cx is mandatory off-main-thread.
-  MOZ_ASSERT_IF(mCx && aIsMainThread, mCx == FindJSContext(aGlobalObject));
-  if (!mCx) {
-    // If the caller didn't provide a cx, hunt one down. This isn't exactly
-    // fast, but the callers that care about performance can pass an explicit
-    // cx for now. Eventually, the whole cx pushing thing will go away
-    // entirely.
-    mCx = FindJSContext(aGlobalObject);
-    MOZ_ASSERT(mCx);
-  }
-  if (aIsMainThread) {
-    mCxPusher.construct(mCx);
-  }
-  mAc.construct(mCx, aGlobalObject->GetGlobalJSObject());
+  MOZ_ASSERT_IF(!aCx, aIsMainThread); // cx is mandatory off-main-thread.
+  MOZ_ASSERT_IF(aCx && aIsMainThread, aCx == FindJSContext(aGlobalObject));
   mStack.Push(this);
 }
 
 AutoEntryScript::~AutoEntryScript()
 {
   MOZ_ASSERT(mStack.Incumbent() == this);
   mStack.Pop();
 }
--- a/dom/base/ScriptSettings.h
+++ b/dom/base/ScriptSettings.h
@@ -91,39 +91,88 @@ struct ScriptSettingsStackEntry {
 
 private:
   ScriptSettingsStackEntry() : mGlobalObject(nullptr)
                              , mIsCandidateEntryPoint(true)
   {}
 };
 
 /*
+ * For any interaction with JSAPI, an AutoJSAPI (or one of its subclasses)
+ * must be on the stack.
+ *
+ * This base class should be instantiated as-is when the caller wants to use
+ * JSAPI but doesn't expect to run script. Its current duties are as-follows:
+ *
+ * * Grabbing an appropriate JSContext, and, on the main thread, pushing it onto
+ *   the JSContext stack.
+ * * Entering a null compartment, so that the consumer is forced to select a
+ *   compartment to enter before manipulating objects.
+ *
+ * Additionally, the following duties are planned, but not yet implemented:
+ *
+ * * De-poisoning the JSRuntime to allow manipulation of JSAPI. We can't
+ *   actually implement this poisoning until all the JSContext pushing in the
+ *   system goes through AutoJSAPI (see bug 951991). For now, this de-poisoning
+ *   effectively corresponds to having a non-null cx on the stack.
+ * * Reporting any exceptions left on the JSRuntime, unless the caller steals
+ *   or silences them.
+ * * Entering a JSAutoRequest. At present, this is handled by the cx pushing
+ *   on the main thread, and by other code on workers. Depending on the order
+ *   in which various cleanup lands, this may never be necessary, because
+ *   JSAutoRequests may go away.
+ *
+ * In situations where the consumer expects to run script, AutoEntryScript
+ * should be used, which does additional manipulation of the script settings
+ * stack. In bug 991758, we'll add hard invariants to SpiderMonkey, such that
+ * any attempt to run script without an AutoEntryScript on the stack will
+ * fail. This prevents system code from accidentally triggering script
+ * execution at inopportune moments via surreptitious getters and proxies.
+ */
+class AutoJSAPI {
+public:
+  // Public constructor for use when the base class is constructed as-is. It
+  // uses the SafeJSContext (or worker equivalent), and enters a null
+  // compartment.
+  AutoJSAPI();
+  JSContext* cx() const { return mCx; }
+
+  bool CxPusherIsStackTop() { return mCxPusher.ref().IsStackTop(); }
+
+protected:
+  // Protected constructor, allowing subclasses to specify a particular cx to
+  // be used.
+  AutoJSAPI(JSContext *aCx, bool aIsMainThread, bool aSkipNullAC = false);
+
+private:
+  mozilla::Maybe<AutoCxPusher> mCxPusher;
+  mozilla::Maybe<JSAutoNullCompartment> mNullAc;
+  JSContext *mCx;
+};
+
+/*
  * A class that represents a new script entry point.
  */
-class AutoEntryScript : protected ScriptSettingsStackEntry {
+class AutoEntryScript : public AutoJSAPI,
+                        protected ScriptSettingsStackEntry {
 public:
   AutoEntryScript(nsIGlobalObject* aGlobalObject,
                   bool aIsMainThread = NS_IsMainThread(),
                   // Note: aCx is mandatory off-main-thread.
                   JSContext* aCx = nullptr);
   ~AutoEntryScript();
 
   void SetWebIDLCallerPrincipal(nsIPrincipal *aPrincipal) {
     mWebIDLCallerPrincipal = aPrincipal;
   }
 
-  JSContext* cx() const { return mCx; }
-
 private:
+  JSAutoCompartment mAc;
   dom::ScriptSettingsStack& mStack;
   nsCOMPtr<nsIPrincipal> mWebIDLCallerPrincipal;
-  JSContext *mCx;
-  mozilla::Maybe<AutoCxPusher> mCxPusher;
-  mozilla::Maybe<JSAutoCompartment> mAc; // This can de-Maybe-fy when mCxPusher
-                                         // goes away.
   friend nsIPrincipal* GetWebIDLCallerPrincipal();
 };
 
 /*
  * A class that can be used to force a particular incumbent script on the stack.
  */
 class AutoIncumbentScript : protected ScriptSettingsStackEntry {
 public: