forked from mirrors/gecko-dev
		
	 80e8eb7818
			
		
	
	
		80e8eb7818
		
	
	
	
	
		
			
			This change makes the parent process delay a fullscreen request if there is a pending fullscreen exit. It also changes the DOMFullscreenParent actor listener lifecycle. Once it has started handling a fullscreen request, it will remain a listener to the Document until it receives an exit event when the manager is out of fullscreen. Differential Revision: https://phabricator.services.mozilla.com/D175186
		
			
				
	
	
		
			318 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			318 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* vim: set ts=2 sw=2 sts=2 et tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| export class DOMFullscreenParent extends JSWindowActorParent {
 | |
|   // These properties get set by browser-fullScreenAndPointerLock.js.
 | |
|   // TODO: Bug 1743703 - Consider moving the messaging component of
 | |
|   //       browser-fullScreenAndPointerLock.js into the actor
 | |
|   waitingForChildEnterFullscreen = false;
 | |
|   waitingForChildExitFullscreen = false;
 | |
|   // Cache the next message recipient actor and in-process browsing context that
 | |
|   // is computed by _getNextMsgRecipientActor() of
 | |
|   // browser-fullScreenAndPointerLock.js, this is used to ensure the fullscreen
 | |
|   // cleanup messages goes the same route as fullscreen request, especially for
 | |
|   // the cleanup that happens after actor is destroyed.
 | |
|   // TODO: Bug 1743703 - Consider moving the messaging component of
 | |
|   //       browser-fullScreenAndPointerLock.js into the actor
 | |
|   nextMsgRecipient = null;
 | |
| 
 | |
|   updateFullscreenWindowReference(aWindow) {
 | |
|     if (aWindow.document.documentElement.hasAttribute("inDOMFullscreen")) {
 | |
|       this._fullscreenWindow = aWindow;
 | |
|     } else {
 | |
|       delete this._fullscreenWindow;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   cleanupDomFullscreen(aWindow) {
 | |
|     if (!aWindow.FullScreen) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // If we don't need to wait for child reply, i.e. cleanupDomFullscreen
 | |
|     // doesn't message to child, and we've exit the fullscreen, there won't be
 | |
|     // DOMFullscreen:Painted message from child and it is possible that no more
 | |
|     // paint would be triggered, so just notify fullscreen-painted observer.
 | |
|     if (
 | |
|       !aWindow.FullScreen.cleanupDomFullscreen(this) &&
 | |
|       !aWindow.document.fullscreen
 | |
|     ) {
 | |
|       Services.obs.notifyObservers(aWindow, "fullscreen-painted");
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Clean up fullscreen state and resume chrome UI if window is in fullscreen
 | |
|    * and this actor is the one where the original fullscreen enter or
 | |
|    * exit request comes.
 | |
|    */
 | |
|   _cleanupFullscreenStateAndResumeChromeUI(aWindow) {
 | |
|     this.cleanupDomFullscreen(aWindow);
 | |
|     if (this.requestOrigin == this && aWindow.document.fullscreen) {
 | |
|       aWindow.windowUtils.remoteFrameFullscreenReverted();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   didDestroy() {
 | |
|     this._didDestroy = true;
 | |
| 
 | |
|     let window = this._fullscreenWindow;
 | |
|     if (!window) {
 | |
|       let topBrowsingContext = this.browsingContext.top;
 | |
|       let browser = topBrowsingContext.embedderElement;
 | |
|       if (!browser) {
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       if (
 | |
|         this.waitingForChildExitFullscreen ||
 | |
|         this.waitingForChildEnterFullscreen
 | |
|       ) {
 | |
|         this.waitingForChildExitFullscreen = false;
 | |
|         this.waitingForChildEnterFullscreen = false;
 | |
|         // We were destroyed while waiting for our DOMFullscreenChild to exit
 | |
|         // or enter fullscreen, run cleanup steps anyway.
 | |
|         this._cleanupFullscreenStateAndResumeChromeUI(browser.ownerGlobal);
 | |
|       }
 | |
| 
 | |
|       if (this != this.requestOrigin) {
 | |
|         // The current fullscreen requester should handle the fullsceen event
 | |
|         // if any.
 | |
|         this.removeListeners(browser.ownerGlobal);
 | |
|       }
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (this.waitingForChildEnterFullscreen) {
 | |
|       this.waitingForChildEnterFullscreen = false;
 | |
|       if (window.document.fullscreen) {
 | |
|         // We were destroyed while waiting for our DOMFullscreenChild
 | |
|         // to transition to fullscreen so we abort the entire
 | |
|         // fullscreen transition to prevent getting stuck in a
 | |
|         // partial fullscreen state. We need to go through the
 | |
|         // document since window.Fullscreen could be undefined
 | |
|         // at this point.
 | |
|         //
 | |
|         // This could reject if we're not currently in fullscreen
 | |
|         // so just ignore rejection.
 | |
|         window.document.exitFullscreen().catch(() => {});
 | |
|         return;
 | |
|       }
 | |
|       this.cleanupDomFullscreen(window);
 | |
|     }
 | |
| 
 | |
|     // Need to resume Chrome UI if the window is still in fullscreen UI
 | |
|     // to avoid the window stays in fullscreen problem. (See Bug 1620341)
 | |
|     if (window.document.documentElement.hasAttribute("inDOMFullscreen")) {
 | |
|       this.cleanupDomFullscreen(window);
 | |
|       if (window.windowUtils) {
 | |
|         window.windowUtils.remoteFrameFullscreenReverted();
 | |
|       }
 | |
|     } else if (this.waitingForChildExitFullscreen) {
 | |
|       this.waitingForChildExitFullscreen = false;
 | |
|       // We were destroyed while waiting for our DOMFullscreenChild to exit
 | |
|       // run cleanup steps anyway.
 | |
|       this._cleanupFullscreenStateAndResumeChromeUI(window);
 | |
|     }
 | |
|     this.updateFullscreenWindowReference(window);
 | |
|   }
 | |
| 
 | |
|   receiveMessage(aMessage) {
 | |
|     let topBrowsingContext = this.browsingContext.top;
 | |
|     let browser = topBrowsingContext.embedderElement;
 | |
| 
 | |
|     if (!browser) {
 | |
|       // No need to go further when the browser is not accessible anymore
 | |
|       // (which can happen when the tab is closed for instance),
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     let window = browser.ownerGlobal;
 | |
|     switch (aMessage.name) {
 | |
|       case "DOMFullscreen:Request": {
 | |
|         this.manager.fullscreen = true;
 | |
|         this.waitingForChildExitFullscreen = false;
 | |
|         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.document.fullscreen) {
 | |
|           window.PointerlockFsWarning.showFullScreen(
 | |
|             aMessage.data.originNoSuffix
 | |
|           );
 | |
|         }
 | |
|         this.updateFullscreenWindowReference(window);
 | |
|         break;
 | |
|       }
 | |
|       case "DOMFullscreen:Entered": {
 | |
|         this.manager.fullscreen = true;
 | |
|         this.nextMsgRecipient = null;
 | |
|         this.waitingForChildEnterFullscreen = false;
 | |
|         window.FullScreen.enterDomFullscreen(browser, this);
 | |
|         this.updateFullscreenWindowReference(window);
 | |
|         break;
 | |
|       }
 | |
|       case "DOMFullscreen:Exit": {
 | |
|         this.manager.fullscreen = false;
 | |
|         this.waitingForChildEnterFullscreen = false;
 | |
|         window.windowUtils.remoteFrameFullscreenReverted();
 | |
|         break;
 | |
|       }
 | |
|       case "DOMFullscreen:Exited": {
 | |
|         this.manager.fullscreen = false;
 | |
|         this.waitingForChildExitFullscreen = false;
 | |
|         this.cleanupDomFullscreen(window);
 | |
|         this.updateFullscreenWindowReference(window);
 | |
|         break;
 | |
|       }
 | |
|       case "DOMFullscreen:Painted": {
 | |
|         this.waitingForChildExitFullscreen = false;
 | |
|         Services.obs.notifyObservers(window, "fullscreen-painted");
 | |
|         this.sendAsyncMessage("DOMFullscreen:Painted", {});
 | |
|         TelemetryStopwatch.finish("FULLSCREEN_CHANGE_MS");
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   handleEvent(aEvent) {
 | |
|     let window = aEvent.currentTarget.ownerGlobal;
 | |
|     // We can not get the corresponding browsing context from actor if the actor
 | |
|     // has already destroyed, so use event target to get browsing context
 | |
|     // instead.
 | |
|     let requestOrigin = window.browsingContext.fullscreenRequestOrigin?.get();
 | |
|     if (this != requestOrigin) {
 | |
|       // The current fullscreen requester should handle the fullsceen event,
 | |
|       // ignore them if we are not the current requester.
 | |
|       this.removeListeners(window);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     switch (aEvent.type) {
 | |
|       case "MozDOMFullscreen:Entered": {
 | |
|         // The event target is the element which requested the DOM
 | |
|         // fullscreen. If we were entering DOM fullscreen for a remote
 | |
|         // browser, the target would be the browser which was the parameter of
 | |
|         // `remoteFrameFullscreenChanged` call. If the fullscreen
 | |
|         // request was initiated from an in-process browser, we need
 | |
|         // to get its corresponding browser here.
 | |
|         let browser;
 | |
|         if (aEvent.target.ownerGlobal == window) {
 | |
|           browser = aEvent.target;
 | |
|         } else {
 | |
|           browser = aEvent.target.ownerGlobal.docShell.chromeEventHandler;
 | |
|         }
 | |
| 
 | |
|         // Addon installation should be cancelled when entering fullscreen for security and usability reasons.
 | |
|         // Installation prompts in fullscreen can trick the user into installing unwanted addons.
 | |
|         // In fullscreen the notification box does not have a clear visual association with its parent anymore.
 | |
|         if (window.gXPInstallObserver) {
 | |
|           window.gXPInstallObserver.removeAllNotifications(browser);
 | |
|         }
 | |
| 
 | |
|         TelemetryStopwatch.start("FULLSCREEN_CHANGE_MS");
 | |
|         window.FullScreen.enterDomFullscreen(browser, this);
 | |
|         this.updateFullscreenWindowReference(window);
 | |
| 
 | |
|         if (!this.hasBeenDestroyed() && this.requestOrigin) {
 | |
|           window.PointerlockFsWarning.showFullScreen(
 | |
|             this.requestOrigin.manager.documentPrincipal.originNoSuffix
 | |
|           );
 | |
|         }
 | |
|         break;
 | |
|       }
 | |
|       case "MozDOMFullscreen:Exited": {
 | |
|         TelemetryStopwatch.start("FULLSCREEN_CHANGE_MS");
 | |
| 
 | |
|         // Make sure that the actor has not been destroyed before
 | |
|         // accessing its browsing context. Otherwise, a error may
 | |
|         // occur and hence cleanupDomFullscreen not executed, resulting
 | |
|         // in the browser window being in an unstable state.
 | |
|         // (Bug 1590138).
 | |
|         if (!this.hasBeenDestroyed() && !this.requestOrigin) {
 | |
|           this.requestOrigin = this;
 | |
|         }
 | |
|         this.cleanupDomFullscreen(window);
 | |
|         this.updateFullscreenWindowReference(window);
 | |
| 
 | |
|         // If the document is supposed to be in fullscreen, keep the listener to wait for
 | |
|         // further events.
 | |
|         if (!this.manager.fullscreen) {
 | |
|           this.removeListeners(window);
 | |
|         }
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   addListeners(aWindow) {
 | |
|     aWindow.addEventListener(
 | |
|       "MozDOMFullscreen:Entered",
 | |
|       this,
 | |
|       /* useCapture */ true,
 | |
|       /* wantsUntrusted */
 | |
|       false
 | |
|     );
 | |
|     aWindow.addEventListener(
 | |
|       "MozDOMFullscreen:Exited",
 | |
|       this,
 | |
|       /* useCapture */ true,
 | |
|       /* 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 chromeBC = this.browsingContext.topChromeWindow?.browsingContext;
 | |
|     let requestOrigin = chromeBC?.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) {
 | |
|     let chromeBC = this.browsingContext.topChromeWindow?.browsingContext;
 | |
|     if (!chromeBC) {
 | |
|       console.error("not able to get browsingContext for chrome window.");
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (aActor) {
 | |
|       chromeBC.fullscreenRequestOrigin = Cu.getWeakReference(aActor);
 | |
|     } else {
 | |
|       delete chromeBC.fullscreenRequestOrigin;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   hasBeenDestroyed() {
 | |
|     if (this._didDestroy) {
 | |
|       return true;
 | |
|     }
 | |
| 
 | |
|     // The 'didDestroy' callback is not always getting called.
 | |
|     // So we can't rely on it here. Instead, we will try to access
 | |
|     // the browsing context to judge wether the actor has
 | |
|     // been destroyed or not.
 | |
|     try {
 | |
|       return !this.browsingContext;
 | |
|     } catch {
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
| }
 |