Bug 840488 - Directly mark compartments whose docshells disable script execution. r=bz
authorBobby Holley <bobbyholley@gmail.com>
Tue, 12 Nov 2013 16:43:33 -0800
changeset 155011 a0a49b75b8aaffd096673f2bee153b4f6c473f0b
parent 155010 52a8082a281f16affbbdf79f081df098efe580cc
child 155012 92ab5fa1942923179d97a7660a34fce7784c602d
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersbz
bugs840488
milestone28.0a1
Bug 840488 - Directly mark compartments whose docshells disable script execution. r=bz
caps/src/nsScriptSecurityManager.cpp
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
docshell/base/nsIDocShell.idl
dom/base/nsGlobalWindow.cpp
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/src/xpcpublic.h
--- a/caps/src/nsScriptSecurityManager.cpp
+++ b/caps/src/nsScriptSecurityManager.cpp
@@ -1632,34 +1632,18 @@ nsScriptSecurityManager::CanExecuteScrip
             *result = false;
             return NS_OK;
         }
 
         nsIScriptGlobalObject *sgo = scriptContext->GetGlobalObject();
         if (!sgo) {
             return NS_ERROR_FAILURE;
         }
-
-        // window can be null here if we're running with a non-DOM window
-        // as the script global (i.e. a XUL prototype document).
-        nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(sgo);
-        nsCOMPtr<nsIDocShell> docshell;
-        nsresult rv;
-        if (window) {
-            docshell = window->GetDocShell();
-        }
-
-        if (docshell) {
-          rv = docshell->GetCanExecuteScripts(result);
-          if (NS_FAILED(rv)) return rv;
-          if (!*result) return NS_OK;
-        }
     }
 
-    // OK, the docshell doesn't have script execution explicitly disabled.
     // Check whether our URI is an "about:" URI that allows scripts.  If it is,
     // we need to allow JS to run.  In this case, don't apply the JS enabled
     // pref or policies.  On failures, just press on and don't do this special
     // case.
     nsCOMPtr<nsIURI> principalURI;
     aPrincipal->GetURI(getter_AddRefs(principalURI));
     if (!principalURI) {
         // Broken principal of some sort.  Disallow.
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -757,16 +757,17 @@ nsDocShell::nsDocShell():
     mAllowAuth(true),
     mAllowKeywordFixup(false),
     mIsOffScreenBrowser(false),
     mIsActive(true),
     mIsAppTab(false),
     mUseGlobalHistory(false),
     mInPrivateBrowsing(false),
     mDeviceSizeIsPageSize(false),
+    mCanExecuteScripts(false),
     mFiredUnloadEvent(false),
     mEODForCurrentDocument(false),
     mURIResultedInDocument(false),
     mIsBeingDestroyed(false),
     mIsExecutingOnLoadHandler(false),
     mIsPrintingOrPP(false),
     mSavingOldViewer(false),
 #ifdef DEBUG
@@ -2079,16 +2080,17 @@ nsDocShell::GetAllowJavascript(bool * aA
     *aAllowJavascript = mAllowJavascript;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::SetAllowJavascript(bool aAllowJavascript)
 {
     mAllowJavascript = aAllowJavascript;
+    RecomputeCanExecuteScripts();
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::GetUsePrivateBrowsing(bool* aUsePrivateBrowsing)
 {
     NS_ENSURE_ARG_POINTER(aUsePrivateBrowsing);
     
@@ -2812,16 +2814,58 @@ nsDocShell::GetParent(nsIDocShellTreeIte
 
 already_AddRefed<nsDocShell>
 nsDocShell::GetParentDocshell()
 {
     nsCOMPtr<nsIDocShell> docshell = do_QueryInterface(GetAsSupports(mParent));
     return docshell.forget().downcast<nsDocShell>();
 }
 
+void
+nsDocShell::RecomputeCanExecuteScripts()
+{
+    bool old = mCanExecuteScripts;
+    nsRefPtr<nsDocShell> parent = GetParentDocshell();
+
+    // If we have no tree owner, that means that we've been detached from the
+    // docshell tree (this is distinct from having no parent dochshell, which
+    // is the case for root docshells). In that case, don't allow script.
+    if (!mTreeOwner) {
+        mCanExecuteScripts = false;
+    // If scripting has been explicitly disabled on our docshell, we're done.
+    } else if (!mAllowJavascript) {
+        mCanExecuteScripts = false;
+    // If we have a parent, inherit.
+    } else if (parent) {
+        mCanExecuteScripts = parent->mCanExecuteScripts;
+    // Otherwise, we're the root of the tree, and we haven't explicitly disabled
+    // script. Allow.
+    } else {
+        mCanExecuteScripts = true;
+    }
+
+    // Inform our active DOM window.
+    //
+    // This will pass the outer, which will be in the scope of the active inner.
+    if (mScriptGlobal && mScriptGlobal->GetGlobalJSObject()) {
+        xpc::Scriptability& scriptability =
+          xpc::Scriptability::Get(mScriptGlobal->GetGlobalJSObject());
+        scriptability.SetDocShellAllowsScript(mCanExecuteScripts);
+    }
+
+    // If our value has changed, our children might be affected. Recompute their
+    // value as well.
+    if (old != mCanExecuteScripts) {
+        nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList);
+        while (iter.HasMore()) {
+            static_cast<nsDocShell*>(iter.GetNext())->RecomputeCanExecuteScripts();
+        }
+    }
+}
+
 nsresult
 nsDocShell::SetDocLoaderParent(nsDocLoader * aParent)
 {
     nsDocLoader::SetDocLoaderParent(aParent);
 
     // Curse ambiguous nsISupports inheritance!
     nsISupports* parent = GetAsSupports(aParent);
 
@@ -2881,16 +2925,20 @@ nsDocShell::SetDocLoaderParent(nsDocLoad
         NS_SUCCEEDED(parentAsLoadContext->GetUsePrivateBrowsing(&value)))
     {
         SetPrivateBrowsing(value);
     }
     
     nsCOMPtr<nsIURIContentListener> parentURIListener(do_GetInterface(parent));
     if (parentURIListener)
         mContentListener->SetParentContentListener(parentURIListener);
+
+    // Our parent has changed. Recompute scriptability.
+    RecomputeCanExecuteScripts();
+
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::GetSameTypeParent(nsIDocShellTreeItem ** aParent)
 {
     NS_ENSURE_ARG_POINTER(aParent);
     *aParent = nullptr;
@@ -3435,16 +3483,26 @@ nsDocShell::SetTreeOwner(nsIDocShellTree
         nsCOMPtr<nsIDocShellTreeItem> child = do_QueryObject(iter.GetNext());
         NS_ENSURE_TRUE(child, NS_ERROR_FAILURE);
         int32_t childType = ~mItemType; // Set it to not us in case the get fails
         child->GetItemType(&childType); // We don't care if this fails, if it does we won't set the owner
         if (childType == mItemType)
             child->SetTreeOwner(aTreeOwner);
     }
 
+    // Our tree owner has changed. Recompute scriptability.
+    //
+    // Note that this is near-redundant with the recomputation in
+    // SetDocLoaderParent(), but not so for the root DocShell, where the call to
+    // SetTreeOwner() happens after the initial AddDocLoaderAsChildOfRoot(),
+    // and we never set another parent. Given that this is neither expensive nor
+    // performance-critical, let's be safe and unconditionally recompute this
+    // state whenever dependent state changes.
+    RecomputeCanExecuteScripts();
+
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::SetChildOffset(uint32_t aChildOffset)
 {
     mChildOffset = aChildOffset;
     return NS_OK;
@@ -12646,30 +12704,17 @@ nsDocShell::GetPrintPreview(nsIWebBrowse
 
 #ifdef DEBUG
 unsigned long nsDocShell::gNumberOfDocShells = 0;
 #endif
 
 NS_IMETHODIMP
 nsDocShell::GetCanExecuteScripts(bool *aResult)
 {
-  NS_ENSURE_ARG_POINTER(aResult);
-  *aResult = false; // disallow by default
-
-  nsRefPtr<nsDocShell> docshell = this;
-  do {
-      nsresult rv = docshell->GetAllowJavascript(aResult);
-      if (NS_FAILED(rv)) return rv;
-      if (!*aResult) {
-          return NS_OK;
-      }
-
-      docshell = docshell->GetParentDocshell();
-  } while (docshell);
-
+  *aResult = mCanExecuteScripts;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::SetIsApp(uint32_t aOwnAppId)
 {
     mOwnOrContainingAppId = aOwnAppId;
     if (aOwnAppId != nsIScriptSecurityManager::NO_APP_ID &&
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -808,16 +808,22 @@ protected:
     bool                       mAllowKeywordFixup;
     bool                       mIsOffScreenBrowser;
     bool                       mIsActive;
     bool                       mIsAppTab;
     bool                       mUseGlobalHistory;
     bool                       mInPrivateBrowsing;
     bool                       mDeviceSizeIsPageSize;
 
+    // Because scriptability depends on the mAllowJavascript values of our
+    // ancestors, we cache the effective scriptability and recompute it when
+    // it might have changed;
+    bool                       mCanExecuteScripts;
+    void RecomputeCanExecuteScripts();
+
     // This boolean is set to true right before we fire pagehide and generally
     // unset when we embed a new content viewer.  While it's true no navigation
     // is allowed in this docshell.
     bool                       mFiredUnloadEvent;
 
     // this flag is for bug #21358. a docshell may load many urls
     // which don't result in new documents being created (i.e. a new
     // content viewer) we want to make sure we don't call a on load
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -565,17 +565,17 @@ interface nsIDocShell : nsIDocShellTreeI
    */
   readonly attribute nsIWebBrowserPrint printPreview;
 
   /**
    * Whether this docshell can execute scripts based on its hierarchy.
    * The rule of thumb here is that we disable js if this docshell or any
    * of its parents disallow scripting.
    */
-  readonly attribute boolean canExecuteScripts;
+  [infallible] readonly attribute boolean canExecuteScripts;
 
   /**
    * Sets whether a docshell is active. An active docshell is one that is
    * visible, and thus is not a good candidate for certain optimizations
    * like image frame discarding. Docshells are active unless told otherwise.
    */
   attribute boolean isActive;
 
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -2057,16 +2057,19 @@ WindowStateHolder::WindowStateHolder(nsG
   : mInnerWindow(aWindow)
 {
   NS_PRECONDITION(aWindow, "null window");
   NS_PRECONDITION(aWindow->IsInnerWindow(), "Saving an outer window");
 
   mInnerWindowHolder = aHolder;
 
   aWindow->SuspendTimeouts();
+
+  // When a global goes into the bfcache, we disable script.
+  xpc::Scriptability::Get(aWindow->mJSObject).SetDocShellAllowsScript(false);
 }
 
 WindowStateHolder::~WindowStateHolder()
 {
   if (mInnerWindow) {
     // This window was left in the bfcache and is now going away. We need to
     // free it up.
     // Note that FreeInnerObjects may already have been called on the
@@ -2455,16 +2458,20 @@ nsGlobalWindow::SetNewDocument(nsIDocume
           scope->mWaiverWrapperMap->Reparent(cx, newInnerWindow->mJSObject);
         }
       }
     }
 
     // Enter the new global's compartment.
     JSAutoCompartment ac(cx, mJSObject);
 
+    // Set scriptability based on the state of the docshell.
+    bool allow = GetDocShell()->GetCanExecuteScripts();
+    xpc::Scriptability::Get(mJSObject).SetDocShellAllowsScript(allow);
+
     // If we created a new inner window above, we need to do the last little bit
     // of initialization now that the dust has settled.
     if (createdInnerWindow) {
       nsIXPConnect *xpc = nsContentUtils::XPConnect();
       nsCOMPtr<nsIXPConnectWrappedNative> wrapper;
       nsresult rv = xpc->GetWrappedNativeOfJSObject(cx, newInnerWindow->mJSObject,
                                                     getter_AddRefs(wrapper));
       NS_ENSURE_SUCCESS(rv, rv);
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -405,37 +405,45 @@ EnsureCompartmentPrivate(JSCompartment *
     CompartmentPrivate *priv = GetCompartmentPrivate(c);
     if (priv)
         return priv;
     priv = new CompartmentPrivate();
     JS_SetCompartmentPrivate(c, priv);
     return priv;
 }
 
-Scriptability::Scriptability() : mScriptBlocks(0) {}
+Scriptability::Scriptability() : mScriptBlocks(0)
+                               , mDocShellAllowsScript(true)
+{}
 
 bool
 Scriptability::Allowed()
 {
-    return mScriptBlocks == 0;
+    return mDocShellAllowsScript && mScriptBlocks == 0;
 }
 
 void
 Scriptability::Block()
 {
     ++mScriptBlocks;
 }
 
 void
 Scriptability::Unblock()
 {
     MOZ_ASSERT(mScriptBlocks > 0);
     --mScriptBlocks;
 }
 
+void
+Scriptability::SetDocShellAllowsScript(bool aAllowed)
+{
+    mDocShellAllowsScript = aAllowed;
+}
+
 /* static */
 Scriptability&
 Scriptability::Get(JSObject *aScope)
 {
     return EnsureCompartmentPrivate(aScope)->scriptability;
 }
 
 bool
--- a/js/xpconnect/src/xpcpublic.h
+++ b/js/xpconnect/src/xpcpublic.h
@@ -37,25 +37,30 @@ namespace xpc {
 
 class Scriptability {
 public:
     Scriptability();
     bool Allowed();
 
     void Block();
     void Unblock();
+    void SetDocShellAllowsScript(bool aAllowed);
 
     static Scriptability& Get(JSObject *aScope);
 
 private:
     // Whenever a consumer wishes to prevent script from running on a global,
     // it increments this value with a call to Block(). When it wishes to
     // re-enable it (if ever), it decrements this value with a call to Unblock().
     // Script may not run if this value is non-zero.
     uint32_t mScriptBlocks;
+
+    // Whether the docshell allows javascript in this scope. If this scope
+    // doesn't have a docshell, this value is always true.
+    bool mDocShellAllowsScript;
 };
 
 JSObject *
 TransplantObject(JSContext *cx, JS::HandleObject origobj, JS::HandleObject target);
 
 // Return a raw XBL scope object corresponding to contentScope, which must
 // be an object whose global is a DOM window.
 //