Bug 795184 - Make iframe.mozallowfullscreen percolate across process boundaries. r=jlebar
authorChris Pearce <cpearce@mozilla.com>
Mon, 08 Oct 2012 10:12:50 +1300
changeset 109578 454595726fd913c274f3a5125d8c32b5730bf523
parent 109577 db8552af8d73696e8cd058c1721018a169a5e979
child 109579 d407f51ca61e0e64cb5984a0ffa9bc83de9d77c2
push id23634
push userphilringnalda@gmail.com
push dateMon, 08 Oct 2012 01:32:21 +0000
treeherdermozilla-central@e6df45a28902 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjlebar
bugs795184
milestone18.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 795184 - Make iframe.mozallowfullscreen percolate across process boundaries. r=jlebar
content/base/src/nsDocument.cpp
content/html/content/test/file_fullscreen-denied.html
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
docshell/base/nsIDocShell.idl
dom/browser-element/BrowserElementChild.js
dom/browser-element/BrowserElementParent.js
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -9450,31 +9450,26 @@ nsDocument::IsFullScreenEnabled(bool aCa
   }
   if (HasFullScreenSubDocument(this)) {
     LogFullScreenDenied(aLogFailure, "FullScreenDeniedSubDocFullScreen", this);
     return false;
   }
 
   // Ensure that all ancestor <iframe> elements have the mozallowfullscreen
   // boolean attribute set.
-  nsINode* node = static_cast<nsINode*>(this);
-  do {
-    nsIContent* content = static_cast<nsIContent*>(node);
-    if (content->IsHTML(nsGkAtoms::iframe) &&
-        !content->HasAttr(kNameSpaceID_None, nsGkAtoms::mozallowfullscreen)) {
-      // The node requesting fullscreen, or one of its crossdoc ancestors,
-      // is an iframe which doesn't have the "mozalllowfullscreen" attribute.
-      // This request is not authorized by the parent document.
-      LogFullScreenDenied(aLogFailure, "FullScreenDeniedIframeDisallowed", this);
-      return false;
-    }
-    node = nsContentUtils::GetCrossDocParentNode(node);
-  } while (node);
-
-  return true;
+  nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocumentContainer);
+  bool allowed = false;
+  if (docShell) {
+    docShell->GetFullscreenAllowed(&allowed);
+  }
+  if (!allowed) {
+    LogFullScreenDenied(aLogFailure, "FullScreenDeniedIframeDisallowed", this);
+  }
+
+  return allowed;
 }
 
 static void
 DispatchPointerLockChange(nsIDocument* aTarget)
 {
   nsRefPtr<nsAsyncDOMEvent> e =
     new nsAsyncDOMEvent(aTarget,
                         NS_LITERAL_STRING("mozpointerlockchange"),
--- a/content/html/content/test/file_fullscreen-denied.html
+++ b/content/html/content/test/file_fullscreen-denied.html
@@ -45,17 +45,17 @@ function begin() {
 
   // Request full-screen from a non trusted context (this script isn't a user
   // generated event!).
   SpecialPowers.setBoolPref("full-screen-api.allow-trusted-requests-only", true);
   fullscreendenied = false;
   document.body.mozRequestFullScreen();
   setTimeout(
     function() {
-      ok(!document.mozFullScreen, "Should not grant request in non-truested context");
+      ok(!document.mozFullScreen, "Should not grant request in non-trusted context");
       ok(fullscreendenied, "Request in non-trusted event handler should be denied");
       // Test requesting full-screen mode in a long-running user-generated event handler.
       // The request in the key handler should not be granted.
       window.addEventListener("keypress", keyHandler, false);
       synthesizeKey("VK_A", {});
     }, 0);
 }
 
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -732,16 +732,17 @@ nsDocShell::nsDocShell():
     mAppType(nsIDocShell::APP_TYPE_UNKNOWN),
     mLoadType(0),
     mMarginWidth(-1),
     mMarginHeight(-1),
     mItemType(typeContent),
     mPreviousTransIndex(-1),
     mLoadedTransIndex(-1),
     mSandboxFlags(0),
+    mFullscreenAllowed(CHECK_ATTRIBUTES),
     mCreated(false),
     mAllowSubframes(true),
     mAllowPlugins(true),
     mAllowJavascript(true),
     mAllowMetaRedirects(true),
     mAllowImages(true),
     mAllowDNSPrefetch(true),
     mAllowWindowControl(true),
@@ -2155,16 +2156,82 @@ NS_IMETHODIMP nsDocShell::GetAllowWindow
 
 NS_IMETHODIMP nsDocShell::SetAllowWindowControl(bool aAllowWindowControl)
 {
     mAllowWindowControl = aAllowWindowControl;
     return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDocShell::GetFullscreenAllowed(bool* aFullscreenAllowed)
+{
+    NS_ENSURE_ARG_POINTER(aFullscreenAllowed);
+
+    // Content boundaries have their mFullscreenAllowed retrieved from their
+    // corresponding iframe in their parent upon creation.
+    if (mFullscreenAllowed != CHECK_ATTRIBUTES) {
+        *aFullscreenAllowed = (mFullscreenAllowed == PARENT_ALLOWS);
+        return NS_OK;
+    }
+
+    // Assume false until we determine otherwise...
+    *aFullscreenAllowed = false;
+
+    // For non-content boundaries, check that the enclosing iframe element
+    // has the mozallowfullscreen attribute set to true. If any ancestor
+    // iframe does not have mozallowfullscreen=true, then fullscreen is
+    // prohibited.
+    nsCOMPtr<nsPIDOMWindow> win = do_GetInterface(GetAsSupports(this));
+    if (!win) {
+        return NS_OK;
+    }
+    nsCOMPtr<nsIContent> frameElement = do_QueryInterface(win->GetFrameElementInternal());
+    if (frameElement &&
+        frameElement->IsHTML(nsGkAtoms::iframe) &&
+        !frameElement->HasAttr(kNameSpaceID_None, nsGkAtoms::mozallowfullscreen)) {
+        return NS_OK;
+    }
+
+    // If we have no parent then we're the root docshell; no ancestor of the
+    // original docshell doesn't have a mozallowfullscreen attribute, so
+    // report fullscreen as allowed.
+    nsCOMPtr<nsIDocShellTreeItem> dsti = do_GetInterface(GetAsSupports(this));
+    NS_ENSURE_TRUE(dsti, NS_OK);
+
+    nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
+    dsti->GetParent(getter_AddRefs(parentTreeItem));
+    if (!parentTreeItem) {
+        *aFullscreenAllowed = true;
+        return NS_OK;
+    }
+    // Otherwise, we have a parent, continue the checking for
+    // mozFullscreenAllowed in the parent docshell's ancestors.
+    nsCOMPtr<nsIDocShell> parent = do_QueryInterface(parentTreeItem);
+    NS_ENSURE_TRUE(parent, NS_OK);
+    
+    return parent->GetFullscreenAllowed(aFullscreenAllowed);
+}
+
+NS_IMETHODIMP
+nsDocShell::SetFullscreenAllowed(bool aFullscreenAllowed)
+{
+    if (!nsIDocShell::GetIsContentBoundary()) {
+        // Only allow setting of fullscreenAllowed on content/process boundaries.
+        // At non-boundaries the fullscreenAllowed attribute is calculated based on
+        // whether all enclosing frames have the "mozFullscreenAllowed" attribute
+        // set to "true". fullscreenAllowed is set at the process boundaries to
+        // propagate the value of the parent's "mozFullscreenAllowed" attribute
+        // across process boundaries.
+        return NS_ERROR_UNEXPECTED;
+    }
+    mFullscreenAllowed = (aFullscreenAllowed ? PARENT_ALLOWS : PARENT_PROHIBITS);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDocShell::GetDocShellEnumerator(int32_t aItemType, int32_t aDirection, nsISimpleEnumerator **outEnum)
 {
     NS_ENSURE_ARG_POINTER(outEnum);
     *outEnum = nullptr;
     
     nsRefPtr<nsDocShellEnumerator> docShellEnum;
     if (aDirection == ENUMERATE_FORWARDS)
         docShellEnum = new nsDocShellForwardsEnumerator;
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -773,16 +773,35 @@ protected:
 
     // Index into the SHTransaction list, indicating the previous and current
     // transaction at the time that this DocShell begins to load
     int32_t                    mPreviousTransIndex;
     int32_t                    mLoadedTransIndex;
 
     uint32_t                   mSandboxFlags;
 
+    // mFullscreenAllowed stores how we determine whether fullscreen is allowed
+    // when GetFullscreenAllowed() is called. Fullscreen is allowed in a
+    // docshell when all containing iframes have the mozallowfullscreen
+    // attribute set to true. When mFullscreenAllowed is CHECK_ATTRIBUTES
+    // we check this docshell's containing frame for the mozallowfullscreen
+    // attribute, and recurse onto the parent docshell to ensure all containing
+    // frames also have the mozallowfullscreen attribute. If we find an ancestor
+    // docshell with mFullscreenAllowed not equal to CHECK_ATTRIBUTES, we've
+    // reached a content boundary, and mFullscreenAllowed denotes whether the
+    // parent across the content boundary has mozallowfullscreen=true in all its
+    // containing iframes. mFullscreenAllowed defaults to CHECK_ATTRIBUTES and
+    // is set otherwise when docshells which are content boundaries are created.
+    enum FullscreenAllowedState {
+        CHECK_ATTRIBUTES,
+        PARENT_ALLOWS,
+        PARENT_PROHIBITS
+    };
+    FullscreenAllowedState     mFullscreenAllowed;
+
     bool                       mCreated;
     bool                       mAllowSubframes;
     bool                       mAllowPlugins;
     bool                       mAllowJavascript;
     bool                       mAllowMetaRedirects;
     bool                       mAllowImages;
     bool                       mAllowDNSPrefetch;
     bool                       mAllowWindowControl;
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -34,17 +34,17 @@ interface nsISHEntry;
 interface nsILayoutHistoryState;
 interface nsISecureBrowserUI;
 interface nsIDOMStorage;
 interface nsIPrincipal;
 interface nsIWebBrowserPrint;
 interface nsIVariant;
 interface nsIPrivacyTransitionObserver;
 
-[scriptable, builtinclass, uuid(9b283337-097d-4fa8-a2da-916318eaf828)]
+[scriptable, builtinclass, uuid(e93b2f6a-c543-448b-9239-55c96e31672e)]
 interface nsIDocShell : nsISupports
 {
   /**
    * Loads a given URI.  This will give priority to loading the requested URI
    * in the object implementing	this interface.  If it can't be loaded here
    * however, the URL dispatcher will go through its normal process of content
    * loading.
    *
@@ -680,9 +680,24 @@ interface nsIDocShell : nsISupports
 
   /**
    * Are plugins allowed in the current document loaded in this docshell ?
    * (if there is one). This depends on whether plugins are allowed by this
    * docshell itself or if the document is sandboxed and hence plugins should
    * not be allowed.
    */
   [noscript, notxpcom] bool pluginsAllowedInCurrentDoc();
+  
+
+  /**
+   * Attribute that determines whether fullscreen is allowed to be entered for
+   * this subtree of the docshell tree. This is true when all iframes containing
+   * this docshell have their "mozallowfullscreen" attribute set to "true".
+   * fullscreenAllowed is only writable at content boundaries, where it is used
+   * to propagate the value of the cross process parent's iframe's
+   * "mozallowfullscreen" attribute to the child process. Setting
+   * fullscreenAllowed on docshells which aren't content boundaries throws an
+   * exception.
+   */
+  [infallible] readonly attribute boolean fullscreenAllowed;
+  
+  void setFullscreenAllowed(in boolean allowed);
 };
--- a/dom/browser-element/BrowserElementChild.js
+++ b/dom/browser-element/BrowserElementChild.js
@@ -69,16 +69,18 @@ BrowserElementChild.prototype = {
   _init: function() {
     debug("Starting up.");
     sendAsyncMsg("hello");
 
     // Set the docshell's name according to our <iframe>'s name attribute.
     docShell.QueryInterface(Ci.nsIDocShellTreeItem).name =
       sendSyncMsg('get-name')[0];
 
+    docShell.setFullscreenAllowed(sendSyncMsg('get-fullscreen-allowed')[0]);
+
     BrowserElementPromptService.mapWindowToBrowserElementChild(content, this);
 
     docShell.QueryInterface(Ci.nsIWebProgress)
             .addProgressListener(this._progressListener,
                                  Ci.nsIWebProgress.NOTIFY_LOCATION |
                                  Ci.nsIWebProgress.NOTIFY_SECURITY |
                                  Ci.nsIWebProgress.NOTIFY_STATE_WINDOW);
 
--- a/dom/browser-element/BrowserElementParent.js
+++ b/dom/browser-element/BrowserElementParent.js
@@ -187,16 +187,17 @@ function BrowserElementParent(frameLoade
         return handler.apply(self, arguments);
       }
     }
     self._mm.addMessageListener('browser-element-api:' + msg, checkedHandler);
   }
 
   addMessageListener("hello", this._recvHello);
   addMessageListener("get-name", this._recvGetName);
+  addMessageListener("get-fullscreen-allowed", this._recvGetFullscreenAllowed);
   addMessageListener("contextmenu", this._fireCtxMenuEvent);
   addMessageListener("locationchange", this._fireEventFromMsg);
   addMessageListener("loadstart", this._fireEventFromMsg);
   addMessageListener("loadend", this._fireEventFromMsg);
   addMessageListener("titlechange", this._fireEventFromMsg);
   addMessageListener("iconchange", this._fireEventFromMsg);
   addMessageListener("close", this._fireEventFromMsg);
   addMessageListener("securitychange", this._fireEventFromMsg);
@@ -354,16 +355,20 @@ BrowserElementParent.prototype = {
     if (this._window.document.mozHidden) {
       this._ownerVisibilityChange();
     }
   },
 
   _recvGetName: function(data) {
     return this._frameElement.getAttribute('name');
   },
+  
+  _recvGetFullscreenAllowed: function(data) {
+    return this._frameElement.hasAttribute('mozallowfullscreen');
+  },
 
   _fireCtxMenuEvent: function(data) {
     let evtName = data.name.substring('browser-element-api:'.length);
     let detail = data.json;
 
     debug('fireCtxMenuEventFromMsg: ' + evtName + ' ' + detail);
     let evt = this._createEvent(evtName, detail);