Bug 961362 - DOM Fullscreen API support for e10s. r=smaug,billm.
authorMike Conley <mconley@mozilla.com>
Tue, 19 Aug 2014 16:58:00 -0400
changeset 200593 4359b6c8800d25389be5c049ce21804e47f8892f
parent 200592 1be6f1487e215155314207918e349361d04e064a
child 200594 34da96e649b570a084744e0d75e9706a0be10a3d
push id27349
push userryanvm@gmail.com
push dateWed, 20 Aug 2014 20:07:38 +0000
treeherdermozilla-central@e47e353550a8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, billm
bugs961362
milestone34.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 961362 - DOM Fullscreen API support for e10s. r=smaug,billm.
browser/base/content/browser-fullScreen.js
browser/base/content/browser.js
browser/base/content/content.js
content/base/src/nsDocument.cpp
toolkit/content/browser-child.js
toolkit/content/widgets/remote-browser.xml
--- a/browser/base/content/browser-fullScreen.js
+++ b/browser/base/content/browser-fullScreen.js
@@ -4,16 +4,31 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 var FullScreen = {
   _XULNS: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
   get _fullScrToggler() {
     delete this._fullScrToggler;
     return this._fullScrToggler = document.getElementById("fullscr-toggler");
   },
+
+  init: function() {
+    // called when we go into full screen, even if initiated by a web page script
+    window.addEventListener("fullscreen", this, true);
+    window.messageManager.addMessageListener("MozEnteredDomFullscreen", this);
+
+    if (window.fullScreen)
+      this.toggle();
+  },
+
+  uninit: function() {
+    window.messageManager.removeMessageListener("MozEnteredDomFullscreen", this);
+    this.cleanup();
+  },
+
   toggle: function (event) {
     var enterFS = window.fullScreen;
 
     // We get the fullscreen event _before_ the window transitions into or out of FS mode.
     if (event && event.type == "fullscreen")
       enterFS = !enterFS;
 
     // Toggle the View:FullScreen command, which controls elements like the
@@ -90,38 +105,56 @@ var FullScreen = {
   exitDomFullScreen : function() {
     document.mozCancelFullScreen();
   },
 
   handleEvent: function (event) {
     switch (event.type) {
       case "activate":
         if (document.mozFullScreen) {
-          this.showWarning(this.fullscreenDoc);
+          this.showWarning(this.fullscreenOrigin);
         }
         break;
+      case "fullscreen":
+        this.toggle(event);
+        break;
       case "transitionend":
         if (event.propertyName == "opacity")
           this.cancelWarning();
         break;
     }
   },
 
-  enterDomFullscreen : function(event) {
+  receiveMessage: function(aMessage) {
+    if (aMessage.name == "MozEnteredDomFullscreen") {
+      // If we're a multiprocess browser, then the request to enter fullscreen
+      // did not bubble up to the root browser document - it stopped at the root
+      // of the content document. That means we have to kick off the switch to
+      // fullscreen here at the operating system level in the parent process
+      // ourselves.
+      let data = aMessage.data;
+      let browser = aMessage.target;
+      if (gMultiProcessBrowser && browser.getAttribute("remote") == "true") {
+        let windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                                .getInterface(Ci.nsIDOMWindowUtils);
+        windowUtils.remoteFrameFullscreenChanged(browser, data.origin);
+      }
+      this.enterDomFullscreen(browser, data.origin);
+    }
+  },
+
+  enterDomFullscreen : function(aBrowser, aOrigin) {
     if (!document.mozFullScreen)
       return;
 
-    // However, if we receive a "MozEnteredDomFullScreen" event for a document
-    // which is not a subdocument of a currently active (ie. visible) browser
-    // or iframe, we know that we've switched to a different frame since the
-    // request to enter full-screen was made, so we should exit full-screen
-    // since the "full-screen document" isn't acutally visible.
-    if (!event.target.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
-                                 .getInterface(Ci.nsIWebNavigation)
-                                 .QueryInterface(Ci.nsIDocShell).isActive) {
+    // If we've received a fullscreen notification, we have to ensure that the
+    // element that's requesting fullscreen belongs to the browser that's currently
+    // active. If not, we exit fullscreen since the "full-screen document" isn't
+    // actually visible now.
+    if (gBrowser.selectedBrowser != aBrowser) {
       document.mozCancelFullScreen();
       return;
     }
 
     let focusManager = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
     if (focusManager.activeWindow != window) {
       // The top-level window has lost focus since the request to enter
       // full-screen was made. Cancel full-screen.
@@ -131,17 +164,17 @@ var FullScreen = {
 
     // Ensure the sidebar is hidden.
     if (!document.getElementById("sidebar-box").hidden)
       toggleSidebar();
 
     if (gFindBarInitialized)
       gFindBar.close();
 
-    this.showWarning(event.target);
+    this.showWarning(aOrigin);
 
     // Exit DOM full-screen mode upon open, close, or change tab.
     gBrowser.tabContainer.addEventListener("TabOpen", this.exitDomFullScreen);
     gBrowser.tabContainer.addEventListener("TabClose", this.exitDomFullScreen);
     gBrowser.tabContainer.addEventListener("TabSelect", this.exitDomFullScreen);
 
     // Add listener to detect when the fullscreen window is re-focused.
     // If a fullscreen window loses focus, we show a warning when the
@@ -173,17 +206,19 @@ var FullScreen = {
       this._fullScrToggler.removeEventListener("mouseover", this._expandCallback, false);
       this._fullScrToggler.removeEventListener("dragenter", this._expandCallback, false);
       this.cancelWarning();
       gBrowser.tabContainer.removeEventListener("TabOpen", this.exitDomFullScreen);
       gBrowser.tabContainer.removeEventListener("TabClose", this.exitDomFullScreen);
       gBrowser.tabContainer.removeEventListener("TabSelect", this.exitDomFullScreen);
       if (!this.useLionFullScreen)
         window.removeEventListener("activate", this);
-      this.fullscreenDoc = null;
+
+      window.messageManager
+            .broadcastAsyncMessage("DOMFullscreen:Cleanup");
     }
   },
 
   observe: function(aSubject, aTopic, aData)
   {
     if (aData == "browser.fullscreen.autohide") {
       if (gPrefService.getBoolPref("browser.fullscreen.autohide")) {
         gBrowser.mPanelContainer.addEventListener("mousemove",
@@ -332,17 +367,17 @@ var FullScreen = {
     this.warningBox = null;
   },
 
   setFullscreenAllowed: function(isApproved) {
     // The "remember decision" checkbox is hidden when showing for documents that
     // the permission manager can't handle (documents with URIs without a host).
     // We simply require those to be approved every time instead.
     let rememberCheckbox = document.getElementById("full-screen-remember-decision");
-    let uri = this.fullscreenDoc.nodePrincipal.URI;
+    let uri = BrowserUtils.makeURI(this.fullscreenOrigin);
     if (!rememberCheckbox.hidden) {
       if (rememberCheckbox.checked)
         Services.perms.add(uri,
                            "fullscreen",
                            isApproved ? Services.perms.ALLOW_ACTION : Services.perms.DENY_ACTION,
                            Services.perms.EXPIRE_NEVER);
       else if (isApproved) {
         // The user has only temporarily approved fullscren for this fullscreen
@@ -365,37 +400,39 @@ var FullScreen = {
         document.addEventListener("mozfullscreenchange", onFullscreenchange);
       }
     }
     if (this.warningBox)
       this.warningBox.setAttribute("fade-warning-out", "true");
     // If the document has been granted fullscreen, notify Gecko so it can resume
     // any pending pointer lock requests, otherwise exit fullscreen; the user denied
     // the fullscreen request.
-    if (isApproved)
-      Services.obs.notifyObservers(this.fullscreenDoc, "fullscreen-approved", "");
-    else
+    if (isApproved) {
+      gBrowser.selectedBrowser
+              .messageManager
+              .sendAsyncMessage("DOMFullscreen:Approved");
+    } else {
       document.mozCancelFullScreen();
+    }
   },
 
   warningBox: null,
   warningFadeOutTimeout: null,
-  fullscreenDoc: null,
 
   // Shows the fullscreen approval UI, or if the domain has already been approved
   // for fullscreen, shows a warning that the site has entered fullscreen for a short
   // duration.
-  showWarning: function(targetDoc) {
+  showWarning: function(aOrigin) {
     if (!document.mozFullScreen ||
         !gPrefService.getBoolPref("full-screen-api.approval-required"))
       return;
 
     // Set the strings on the fullscreen approval UI.
-    this.fullscreenDoc = targetDoc;
-    let uri = this.fullscreenDoc.nodePrincipal.URI;
+    this.fullscreenOrigin = aOrigin;
+    let uri = BrowserUtils.makeURI(aOrigin);
     let host = null;
     try {
       host = uri.host;
     } catch (e) { }
     let hostLabel = document.getElementById("full-screen-domain-text");
     let rememberCheckbox = document.getElementById("full-screen-remember-decision");
     let isApproved = false;
     if (host) {
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1282,27 +1282,17 @@ var gBrowserInit = {
 
     gBrowser.mPanelContainer.addEventListener("InstallBrowserTheme", LightWeightThemeWebInstaller, false, true);
     gBrowser.mPanelContainer.addEventListener("PreviewBrowserTheme", LightWeightThemeWebInstaller, false, true);
     gBrowser.mPanelContainer.addEventListener("ResetBrowserThemePreview", LightWeightThemeWebInstaller, false, true);
 
     if (Win7Features)
       Win7Features.onOpenWindow();
 
-   // called when we go into full screen, even if initiated by a web page script
-    window.addEventListener("fullscreen", onFullScreen, true);
-
-    // Called when we enter DOM full-screen mode. Note we can already be in browser
-    // full-screen mode when we enter DOM full-screen mode.
-    window.addEventListener("MozEnteredDomFullscreen", onMozEnteredDomFullscreen, true);
-
-    if (window.fullScreen)
-      onFullScreen();
-    if (document.mozFullScreen)
-      onMozEnteredDomFullscreen();
+    FullScreen.init();
 
 #ifdef MOZ_SERVICES_SYNC
     // initialize the sync UI
     gSyncUI.init();
     gFxAccounts.init();
 #endif
 
 #ifdef MOZ_DATA_REPORTING
@@ -1423,17 +1413,17 @@ var gBrowserInit = {
     // uninit methods don't depend on the services having been initialized).
 
     CombinedStopReload.uninit();
 
     gGestureSupport.init(false);
 
     gHistorySwipeAnimation.uninit();
 
-    FullScreen.cleanup();
+    FullScreen.uninit();
 
 #ifdef MOZ_SERVICES_SYNC
     gFxAccounts.uninit();
 #endif
 
     Services.obs.removeObserver(gPluginHandler.pluginCrashed, "plugin-crashed");
 
     try {
@@ -2750,24 +2740,16 @@ function SwitchToMetro() {
 
   let intervalID = window.setInterval(this._checkDefaultAndSwitchToMetro, 1000);
   window.setTimeout(function() { window.clearInterval(intervalID); }, 10000);
 #endif
 #endif
 #endif
 }
 
-function onFullScreen(event) {
-  FullScreen.toggle(event);
-}
-
-function onMozEnteredDomFullscreen(event) {
-  FullScreen.enterDomFullscreen(event);
-}
-
 function getWebNavigation()
 {
   return gBrowser.webNavigation;
 }
 
 function BrowserReloadWithFlags(reloadFlags) {
   let url = gBrowser.currentURI.spec;
   if (gBrowser.updateBrowserRemotenessByURL(gBrowser.selectedBrowser, url)) {
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -573,8 +573,45 @@ let PageStyleHandler = {
 PageStyleHandler.init();
 
 // Keep a reference to the translation content handler to avoid it it being GC'ed.
 let trHandler = null;
 if (Services.prefs.getBoolPref("browser.translation.detectLanguage")) {
   Cu.import("resource:///modules/translation/TranslationContentHandler.jsm");
   trHandler = new TranslationContentHandler(global, docShell);
 }
+
+let DOMFullscreenHandler = {
+  _fullscreenDoc: null,
+
+  init: function() {
+    addMessageListener("DOMFullscreen:Approved", this);
+    addMessageListener("DOMFullscreen:CleanUp", this);
+    addEventListener("MozEnteredDomFullscreen", this);
+  },
+
+  receiveMessage: function(aMessage) {
+    switch(aMessage.name) {
+      case "DOMFullscreen:Approved": {
+        if (this._fullscreenDoc) {
+          Services.obs.notifyObservers(this._fullscreenDoc,
+                                       "fullscreen-approved",
+                                       "");
+        }
+        break;
+      }
+      case "DOMFullscreen:CleanUp": {
+        this._fullscreenDoc = null;
+        break;
+      }
+    }
+  },
+
+  handleEvent: function(aEvent) {
+    if (aEvent.type == "MozEnteredDomFullscreen") {
+      this._fullscreenDoc = aEvent.target;
+      sendAsyncMessage("MozEnteredDomFullscreen", {
+        origin: this._fullscreenDoc.nodePrincipal.origin,
+      });
+    }
+  }
+};
+DOMFullscreenHandler.init();
\ No newline at end of file
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -193,16 +193,17 @@
 #include "mozilla/dom/AnimationTimeline.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/DocumentFragment.h"
 #include "mozilla/dom/Event.h"
 #include "mozilla/dom/HTMLBodyElement.h"
 #include "mozilla/dom/HTMLInputElement.h"
 #include "mozilla/dom/NodeFilterBinding.h"
 #include "mozilla/dom/OwningNonNull.h"
+#include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/UndoManager.h"
 #include "mozilla/dom/WebComponentsBinding.h"
 #include "nsFrame.h"
 #include "nsDOMCaretPosition.h"
 #include "nsIDOMHTMLTextAreaElement.h"
 #include "nsViewportInfo.h"
 #include "nsIContentPermissionPrompt.h"
 #include "mozilla/StaticPtr.h"
@@ -10538,18 +10539,19 @@ nsIDocument::ExitFullscreen(nsIDocument*
   if (aRunAsync) {
     NS_DispatchToCurrentThread(new nsCallExitFullscreen(aDoc));
     return;
   }
   nsDocument::ExitFullscreen(aDoc);
 }
 
 // Returns true if the document is a direct child of a cross process parent
-// mozbrowser iframe. This is the case when the document has a null parent,
-// and its DocShell reports that it is a browser frame.
+// mozbrowser iframe or TabParent. This is the case when the document has
+// a null parent and its DocShell reports that it is a browser frame, or
+// we can get a TabChild from it.
 static bool
 HasCrossProcessParent(nsIDocument* aDocument)
 {
   if (XRE_GetProcessType() != GeckoProcessType_Content) {
     return false;
   }
   if (aDocument->GetParentDocument() != nullptr) {
     return false;
@@ -10557,17 +10559,22 @@ HasCrossProcessParent(nsIDocument* aDocu
   nsPIDOMWindow* win = aDocument->GetWindow();
   if (!win) {
     return false;
   }
   nsCOMPtr<nsIDocShell> docShell = win->GetDocShell();
   if (!docShell) {
     return false;
   }
-  return docShell->GetIsBrowserOrApp();
+  TabChild* tabChild(TabChild::GetFrom(docShell));
+  if (!tabChild) {
+    return false;
+  }
+
+  return true;
 }
 
 static bool
 CountFullscreenSubDocuments(nsIDocument* aDoc, void* aData)
 {
   if (aDoc->IsFullScreenDoc()) {
     uint32_t* count = static_cast<uint32_t*>(aData);
     (*count)++;
--- a/toolkit/content/browser-child.js
+++ b/toolkit/content/browser-child.js
@@ -370,16 +370,58 @@ if (Services.prefs.getBoolPref("browser.
 
 addMessageListener("NetworkPrioritizer:AdjustPriority", (msg) => {
   let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
   let loadGroup = webNav.QueryInterface(Ci.nsIDocumentLoader)
                         .loadGroup.QueryInterface(Ci.nsISupportsPriority);
   loadGroup.adjustPriority(msg.data.adjustment);
 });
 
+let DOMFullscreenManager = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+                                         Ci.nsISupportsWeakReference]),
+
+  init: function() {
+    Services.obs.addObserver(this, "ask-parent-to-exit-fullscreen", false);
+    Services.obs.addObserver(this, "ask-parent-to-rollback-fullscreen", false);
+    addMessageListener("DOMFullscreen:ChildrenMustExit", () => {
+      let utils = content.QueryInterface(Ci.nsIInterfaceRequestor)
+                         .getInterface(Ci.nsIDOMWindowUtils);
+      utils.exitFullscreen();
+    });
+    addEventListener("unload", () => {
+      Services.obs.removeObserver(this, "ask-parent-to-exit-fullscreen");
+      Services.obs.removeObserver(this, "ask-parent-to-rollback-fullscreen");
+    });
+  },
+
+  observe: function(aSubject, aTopic, aData) {
+    // Observer notifications are global, which means that these notifications
+    // might be coming from elements that are not actually children within this
+    // windows' content. We should ignore those. This will not be necessary once
+    // we fix bug 1053413 and stop using observer notifications for this stuff.
+    if (aSubject.defaultView.top !== content) {
+      return;
+    }
+
+    switch (aTopic) {
+      case "ask-parent-to-exit-fullscreen": {
+        sendAsyncMessage("DOMFullscreen:RequestExit");
+        break;
+      }
+      case "ask-parent-to-rollback-fullscreen": {
+        sendAsyncMessage("DOMFullscreen:RequestRollback");
+        break;
+      }
+    }
+  },
+};
+
+DOMFullscreenManager.init();
+
 let AutoCompletePopup = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompletePopup]),
 
   init: function() {
     // Hook up the form fill autocomplete controller.
     let controller = Cc["@mozilla.org/satchel/form-fill-controller;1"]
                        .getService(Ci.nsIFormFillController);
 
--- a/toolkit/content/widgets/remote-browser.xml
+++ b/toolkit/content/widgets/remote-browser.xml
@@ -204,31 +204,41 @@
 
           this.messageManager.addMessageListener("Browser:Init", this);
           this.messageManager.addMessageListener("DOMTitleChanged", this);
           this.messageManager.addMessageListener("ImageDocumentLoaded", this);
           this.messageManager.addMessageListener("SetSyncHandler", this);
           this.messageManager.addMessageListener("DocumentInserted", this);
           this.messageManager.addMessageListener("FullZoomChange", this);
           this.messageManager.addMessageListener("TextZoomChange", this);
+          this.messageManager.addMessageListener("DOMFullscreen:RequestExit", this);
+          this.messageManager.addMessageListener("DOMFullscreen:RequestRollback", this);
           this.messageManager.loadFrameScript("chrome://global/content/browser-child.js", true);
 
           if (this.hasAttribute("selectpopup")) {
             this.messageManager.addMessageListener("Forms:ShowDropDown", this);
             this.messageManager.addMessageListener("Forms:HideDropDown", this);
             this.messageManager.loadFrameScript("chrome://global/content/select-child.js", true);
           }
 
           jsm = "resource://gre/modules/RemoteController.jsm";
           let RemoteController = Components.utils.import(jsm, {}).RemoteController;
           this._controller = new RemoteController(this);
           this.controllers.appendController(this._controller);
+
+          Services.obs.addObserver(this, "ask-children-to-exit-fullscreen", false);
         ]]>
       </constructor>
 
+      <destructor>
+        <![CDATA[
+          Services.obs.removeObserver(this, "ask-children-to-exit-fullscreen");
+        ]]>
+      </destructor>
+
       <method name="receiveMessage">
         <parameter name="aMessage"/>
         <body><![CDATA[
           let data = aMessage.data;
           switch (aMessage.name) {
             case "Browser:Init":
               let result = {};
               result.useGlobalHistory = !this.hasAttribute("disableglobalhistory");
@@ -271,24 +281,51 @@
 
             case "Forms:HideDropDown": {
               Cu.import("resource://gre/modules/SelectParentHelper.jsm");
               let dropdown = document.getElementById(this.getAttribute("selectpopup"));
               SelectParentHelper.hide(dropdown);
               break;
             }
 
+            case "DOMFullscreen:RequestExit": {
+              let windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                                      .getInterface(Ci.nsIDOMWindowUtils);
+              windowUtils.exitFullscreen();
+              break;
+            }
+
+            case "DOMFullscreen:RequestRollback": {
+              let windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                                      .getInterface(Ci.nsIDOMWindowUtils);
+              windowUtils.remoteFrameFullscreenReverted();
+              break;
+            }
+
             default:
               // Delegate to browser.xml.
               return this._receiveMessage(aMessage);
               break;
           }
         ]]></body>
       </method>
 
+      <method name="observe">
+        <parameter name="aSubject"/>
+        <parameter name="aTopic"/>
+        <parameter name="aData"/>
+        <body><![CDATA[
+          if (aTopic == "ask-children-to-exit-fullscreen") {
+            if (aSubject == window.document) {
+              this.messageManager.sendAsyncMessage("DOMFullscreen:ChildrenMustExit");
+            }
+          }
+        ]]></body>
+      </method>
+
       <!--
         For out-of-process code, event.screen[XY] is relative to the
         left/top of the content view. For in-process code,
         event.screen[XY] is relative to the left/top of the screen. We
         use this method to map screen coordinates received from a
         (possibly out-of-process) <browser> element to coordinates
         that are relative to the screen. This code handles the
         out-of-process case, where we need to translate by the screen