Bug 1505916 - [Fission] Part 2: Make the fullscreen code work with oop iframes. r=NeilDeakin,smaug
authorAbdoulaye Oumar Ly <ablayelyfondou@gmail.com>
Fri, 11 Oct 2019 14:30:28 +0000
changeset 497279 fcaa0242c20458b719aa356a4d988a97e4ad3a3b
parent 497278 8b47aa47a640e1ac2bb3250d8d1ecbcce7b24a4b
child 497280 bd04c04797d0af75173fe7f8f63630ef5b7ec285
push id36681
push usercbrindusan@mozilla.com
push dateFri, 11 Oct 2019 21:50:12 +0000
treeherdermozilla-central@c5e6477c3a24 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersNeilDeakin, smaug
bugs1505916
milestone71.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 1505916 - [Fission] Part 2: Make the fullscreen code work with oop iframes. r=NeilDeakin,smaug e10s scenario: 1. An DOM element request fulscreen mode. 2. The request is redirected to the parent. 3. Parent enters fullscreen. 4. Parent notifies child that it has finished entering fullscreen. 5. Child goes fullscreen. 6. Then, child notifies parent that it has finished transitioning to fullscreen. 4. Finally, parent notify observers that fullscreen paint has finished. Let's go into the details of how step 5 works in the above scenario. 5.a The element that made the request is set to fullscreen. 5.b Then, the document where that element lives is set to fullscreen as well as all of its ancestors until we reach the top level document. (see Document::ApplyFulscreen method) Now in Fission world, we may have a request comming from an oop iframe. And it that case since we won't have to ancestor documents living in different content process(es), we will first notiy those content processes (one after another from bottom to top) to go fullscreen. Once they all do, the content process where the request originated will be told to enter fullscreen. Differential Revision: https://phabricator.services.mozilla.com/D45972
browser/actors/DOMFullscreenChild.jsm
browser/actors/DOMFullscreenParent.jsm
browser/base/content/browser-fullScreenAndPointerLock.js
dom/base/Document.cpp
dom/base/Document.h
--- a/browser/actors/DOMFullscreenChild.jsm
+++ b/browser/actors/DOMFullscreenChild.jsm
@@ -14,32 +14,44 @@ class DOMFullscreenChild extends JSWindo
     let windowUtils = window && window.windowUtils;
 
     if (!windowUtils) {
       return;
     }
 
     switch (aMessage.name) {
       case "DOMFullscreen:Entered": {
-        this._lastTransactionId = windowUtils.lastTransactionId;
-        if (
-          !windowUtils.handleFullscreenRequests() &&
-          !this.document.fullscreenElement
-        ) {
-          // If we don't actually have any pending fullscreen request
-          // to handle, neither we have been in fullscreen, tell the
-          // parent to just exit.
-          this.sendAsyncMessage("DOMFullscreen:Exit", {});
+        let remoteFrameBC = aMessage.data.remoteFrameBC;
+        if (remoteFrameBC) {
+          let remoteFrame = remoteFrameBC.embedderElement;
+          this._isNotTheRequestSource = true;
+          windowUtils.remoteFrameFullscreenChanged(remoteFrame);
+        } else {
+          this._lastTransactionId = windowUtils.lastTransactionId;
+          if (
+            !windowUtils.handleFullscreenRequests() &&
+            !this.document.fullscreenElement
+          ) {
+            // If we don't actually have any pending fullscreen request
+            // to handle, neither we have been in fullscreen, tell the
+            // parent to just exit.
+            this.sendAsyncMessage("DOMFullscreen:Exit", {});
+          }
         }
         break;
       }
       case "DOMFullscreen:CleanUp": {
+        let remoteFrameBC = aMessage.data.remoteFrameBC;
+        if (remoteFrameBC) {
+          this._isNotTheRequestSource = true;
+        }
+
         // If we've exited fullscreen at this point, no need to record
         // transaction id or call exit fullscreen. This is especially
-        // important for non-e10s, since in that case, it is possible
+        // important for pre-e10s, since in that case, it is possible
         // that no more paint would be triggered after this point.
         if (this.document.fullscreenElement) {
           this._lastTransactionId = windowUtils.lastTransactionId;
           windowUtils.exitFullscreen();
         }
         break;
       }
       case "DOMFullscreen:Painted": {
@@ -62,30 +74,39 @@ class DOMFullscreenChild extends JSWindo
         break;
       }
       case "MozDOMFullscreen:Exit": {
         this.sendAsyncMessage("DOMFullscreen:Exit", {});
         break;
       }
       case "MozDOMFullscreen:Entered":
       case "MozDOMFullscreen:Exited": {
-        let rootWindow = this.contentWindow.windowRoot;
-        rootWindow.addEventListener("MozAfterPaint", this);
-        if (!this.document || !this.document.fullscreenElement) {
-          // If we receive any fullscreen change event, and find we are
-          // actually not in fullscreen, also ask the parent to exit to
-          // ensure that the parent always exits fullscreen when we do.
-          this.sendAsyncMessage("DOMFullscreen:Exit", {});
+        if (this._isNotTheRequestSource) {
+          // Fullscreen change event for a frame in the
+          // middle (content frame embedding the oop frame where the
+          // request comes from)
+
+          delete this._isNotTheRequestSource;
+          this.sendAsyncMessage(aEvent.type.replace("Moz", ""), {});
+        } else {
+          let rootWindow = this.contentWindow.windowRoot;
+          rootWindow.addEventListener("MozAfterPaint", this);
+          if (!this.document || !this.document.fullscreenElement) {
+            // If we receive any fullscreen change event, and find we are
+            // actually not in fullscreen, also ask the parent to exit to
+            // ensure that the parent always exits fullscreen when we do.
+            this.sendAsyncMessage("DOMFullscreen:Exit", {});
+          }
         }
         break;
       }
       case "MozAfterPaint": {
         // Only send Painted signal after we actually finish painting
         // the transition for the fullscreen change.
-        // Note that this._lastTransactionId is not set when in non-e10s
+        // Note that this._lastTransactionId is not set when in pre-e10s
         // mode, so we need to check that explicitly.
         if (
           !this._lastTransactionId ||
           aEvent.transactionId > this._lastTransactionId
         ) {
           let rootWindow = this.contentWindow.windowRoot;
           rootWindow.removeEventListener("MozAfterPaint", this);
           this.sendAsyncMessage("DOMFullscreen:Painted", {});
--- a/browser/actors/DOMFullscreenParent.jsm
+++ b/browser/actors/DOMFullscreenParent.jsm
@@ -10,16 +10,17 @@ const { Services } = ChromeUtils.import(
 
 class DOMFullscreenParent extends JSWindowActorParent {
   receiveMessage(aMessage) {
     let topBrowsingContext = this.browsingContext.top;
     let browser = topBrowsingContext.embedderElement;
     let window = browser.ownerGlobal;
     switch (aMessage.name) {
       case "DOMFullscreen:Request": {
+        this.requestOrigin = this;
         this.addListeners(window);
         window.windowUtils.remoteFrameFullscreenChanged(browser);
         break;
       }
       case "DOMFullscreen:NewOrigin": {
         // Don't show the warning if we've already exited fullscreen.
         if (window.fullscreen) {
           window.PointerlockFsWarning.showFullScreen(
@@ -73,16 +74,19 @@ class DOMFullscreenParent extends JSWind
         window.gXPInstallObserver.removeAllNotifications(browser);
 
         TelemetryStopwatch.start("FULLSCREEN_CHANGE_MS");
         window.FullScreen.enterDomFullscreen(browser, this);
         break;
       }
       case "MozDOMFullscreen:Exited":
         TelemetryStopwatch.start("FULLSCREEN_CHANGE_MS");
+        if (!this.requestOrigin) {
+          this.requestOrigin = this;
+        }
         window.FullScreen.cleanupDomFullscreen(this);
         this.removeListeners(window);
         break;
     }
   }
 
   addListeners(aWindow) {
     aWindow.addEventListener(
@@ -99,9 +103,33 @@ class DOMFullscreenParent extends JSWind
       /* wantsUntrusted */ false
     );
   }
 
   removeListeners(aWindow) {
     aWindow.removeEventListener("MozDOMFullscreen:Entered", this, true);
     aWindow.removeEventListener("MozDOMFullscreen:Exited", this, true);
   }
+
+  /**
+   * Get the actor where the original fullscreen
+   * enter or exit request comes from.
+   */
+  get requestOrigin() {
+    let requestOrigin = this.browsingContext.top.fullscreenRequestOrigin;
+    return requestOrigin && requestOrigin.get();
+  }
+
+  /**
+   * Store the actor where the original fullscreen
+   * enter or exit request comes from in the top level
+   * browsing context.
+   */
+  set requestOrigin(aActor) {
+    if (aActor) {
+      this.browsingContext.top.fullscreenRequestOrigin = Cu.getWeakReference(
+        aActor
+      );
+    } else {
+      delete this.browsingContext.top.fullscreenRequestOrigin;
+    }
+  }
 }
--- a/browser/base/content/browser-fullScreenAndPointerLock.js
+++ b/browser/base/content/browser-fullScreenAndPointerLock.js
@@ -409,35 +409,43 @@ var FullScreen = {
     ) {
       this.exitDomFullScreen();
       this._logWarningPermissionPromptFS("fullScreenCanceled");
     }
   },
 
   enterDomFullscreen(aBrowser, aActor) {
     if (!document.fullscreenElement) {
+      aActor.requestOrigin = null;
       return;
     }
 
     // If we have a current pointerlock warning shown then hide it
     // before transition.
     PointerlockFsWarning.close();
 
     // If it is a remote browser, send a message to ask the content
     // to enter fullscreen state. We don't need to do so if it is an
     // in-process browser, since all related document should have
     // entered fullscreen state at this point.
+    // Additionally, in Fission world, we may need to notify the
+    // frames in the middle (content frames that embbed the oop iframe where
+    // the element requesting fullscreen lives) to enter fullscreen
+    // first.
     // This should be done before the active tab check below to ensure
     // that the content document handles the pending request. Doing so
     // before the check is fine since we also check the activeness of
     // the requesting document in content-side handling code.
     if (this._isRemoteBrowser(aBrowser)) {
-      aActor.sendAsyncMessage("DOMFullscreen:Entered", {});
+      if (
+        !this._sendMessageToTheRightContent(aActor, "DOMFullscreen:Entered")
+      ) {
+        return;
+      }
     }
-
     // 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 (
       !aBrowser ||
       gBrowser.selectedBrowser != aBrowser ||
       // The top-level window has lost focus since the request to enter
@@ -490,32 +498,83 @@ var FullScreen = {
       MousePosTracker.removeListener(this);
       document.removeEventListener("keypress", this._keyToggleCallback);
       document.removeEventListener("popupshown", this._setPopupOpen);
       document.removeEventListener("popuphidden", this._setPopupOpen);
     }
   },
 
   cleanupDomFullscreen(aActor) {
+    if (!this._sendMessageToTheRightContent(aActor, "DOMFullscreen:CleanUp")) {
+      return;
+    }
+
     PopupNotifications.panel.removeEventListener(
       "popupshowing",
       () => this._handlePermPromptShow(),
       true
     );
-    aActor.sendAsyncMessage("DOMFullscreen:CleanUp", {});
 
     PointerlockFsWarning.close();
     gBrowser.tabContainer.removeEventListener(
       "TabSelect",
       this.exitDomFullScreen
     );
 
     document.documentElement.removeAttribute("inDOMFullscreen");
   },
 
+  /**
+   * Search for the first ancestor of aActor that lives in a different process.
+   * If found, that ancestor is sent the message. Otherwise, the recipient should
+   * be the actor of the request origin.
+   *
+   * @param {JSWindowActorParent} aActor
+   *        The actor that called this function.
+   * @param {String} message
+   *        Message to be sent.
+   *
+   * @return {boolean}
+   *         Return true if the message is sent to the request source
+   *         or false otherwise.
+   */
+  _sendMessageToTheRightContent(aActor, aMessage) {
+    let childBC = aActor.browsingContext;
+    let parentBC = childBC.parent;
+
+    while (parentBC) {
+      let childPid = childBC.currentWindowGlobal.osPid;
+      let parentPid = parentBC.currentWindowGlobal.osPid;
+
+      if (childPid == parentPid) {
+        childBC = parentBC;
+        parentBC = childBC.parent;
+      } else {
+        break;
+      }
+    }
+
+    if (parentBC) {
+      let parentActor = parentBC.currentWindowGlobal.getActor("DOMFullscreen");
+      parentActor.sendAsyncMessage(aMessage, {
+        remoteFrameBC: childBC,
+      });
+      return false;
+    }
+
+    // All content frames living outside the process where
+    // the element requesting fullscreen lives should
+    // have entered or exited fullscreen at this point.
+    // So let's notify the process where the original request
+    // comes from.
+    aActor.requestOrigin.sendAsyncMessage(aMessage, {});
+    aActor.requestOrigin = null;
+    return true;
+  },
+
   _isRemoteBrowser(aBrowser) {
     return gMultiProcessBrowser && aBrowser.getAttribute("remote") == "true";
   },
 
   getMouseTargetRect() {
     return this._mouseTargetRect;
   },
 
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -13458,17 +13458,17 @@ static bool IsInActiveTab(Document* aDoc
   return activeWindow == rootWin;
 }
 
 nsresult Document::RemoteFrameFullscreenChanged(Element* aFrameElement) {
   // Ensure the frame element is the fullscreen element in this document.
   // If the frame element is already the fullscreen element in this document,
   // this has no effect.
   auto request = FullscreenRequest::CreateForRemote(aFrameElement);
-  RequestFullscreen(std::move(request));
+  RequestFullscreen(std::move(request), XRE_IsContentProcess());
   return NS_OK;
 }
 
 nsresult Document::RemoteFrameFullscreenReverted() {
   UniquePtr<FullscreenExit> exit = FullscreenExit::CreateForRemote(this);
   RestorePreviousFullscreenState(std::move(exit));
   return NS_OK;
 }
@@ -13612,24 +13612,25 @@ static bool ShouldApplyFullscreenDirectl
     // because nsGlobalWindow::SetFullscreenInternal() will do nothing
     // if it is already in fullscreen. If we do not apply the state but
     // instead add it to the queue and wait for the window as normal,
     // we would get stuck.
     return true;
   }
 }
 
-void Document::RequestFullscreen(UniquePtr<FullscreenRequest> aRequest) {
+void Document::RequestFullscreen(UniquePtr<FullscreenRequest> aRequest,
+                                 bool applyFullScreenDirectly) {
   nsCOMPtr<nsPIDOMWindowOuter> rootWin = GetRootWindow(this);
   if (!rootWin) {
     aRequest->MayRejectPromise();
     return;
   }
 
-  if (ShouldApplyFullscreenDirectly(this, rootWin)) {
+  if (applyFullScreenDirectly || ShouldApplyFullscreenDirectly(this, rootWin)) {
     ApplyFullscreen(std::move(aRequest));
     return;
   }
 
   // Per spec only HTML, <svg>, and <math> should be allowed, but
   // we also need to allow XUL elements right now.
   Element* elem = aRequest->Element();
   if (!elem->IsHTMLElement() && !elem->IsXULElement() &&
--- a/dom/base/Document.h
+++ b/dom/base/Document.h
@@ -2145,17 +2145,18 @@ class Document : public nsINode,
 
   // Do the "fullscreen element ready check" from the fullscreen spec.
   // It returns true if the given element is allowed to go into fullscreen.
   // It is responsive to dispatch "fullscreenerror" event when necessary.
   bool FullscreenElementReadyCheck(const FullscreenRequest&);
 
   // This is called asynchronously by Document::AsyncRequestFullscreen()
   // to move this document into fullscreen mode if allowed.
-  void RequestFullscreen(UniquePtr<FullscreenRequest> aRequest);
+  void RequestFullscreen(UniquePtr<FullscreenRequest> aRequest,
+                         bool applyFullScreenDirectly = false);
 
   // Removes all elements from the fullscreen stack, removing full-scren
   // styles from the top element in the stack.
   void CleanupFullscreenState();
 
   // Pushes aElement onto the fullscreen stack, and removes fullscreen styles
   // from the former fullscreen stack top, and its ancestors, and applies the
   // styles to aElement. aElement becomes the new "fullscreen element".