# HG changeset patch # User Steven MacLeod # Date 1601937483 0 # Mon Oct 05 22:38:03 2020 +0000 # Node ID 3e884c48633fb4017402ef2c7053f8d947676dd5 # Parent b4b7e943b93cdc77a479bd5484f7661985bdb7d4 Bug 1656741, r=Gijs Differential Revision: https://phabricator.services.mozilla.com/D91760 diff -r b4b7e943b93c -r 3e884c48633f browser/actors/DOMFullscreenParent.jsm --- a/browser/actors/DOMFullscreenParent.jsm Mon Oct 05 22:20:41 2020 +0000 +++ b/browser/actors/DOMFullscreenParent.jsm Mon Oct 05 22:38:03 2020 +0000 @@ -9,6 +9,8 @@ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); class DOMFullscreenParent extends JSWindowActorParent { + waitingForChildFullscreen = false; + updateFullscreenWindowReference(aWindow) { if (aWindow.document.documentElement.hasAttribute("inDOMFullscreen")) { this._fullscreenWindow = aWindow; @@ -23,6 +25,20 @@ return; } + if (this.waitingForChildFullscreen) { + // We were killed 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; + } + // 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")) { @@ -64,6 +80,7 @@ break; } case "DOMFullscreen:Entered": { + this.waitingForChildFullscreen = false; window.FullScreen.enterDomFullscreen(browser, this); this.updateFullscreenWindowReference(window); break; diff -r b4b7e943b93c -r 3e884c48633f browser/base/content/browser-fullScreenAndPointerLock.js --- a/browser/base/content/browser-fullScreenAndPointerLock.js Mon Oct 05 22:20:41 2020 +0000 +++ b/browser/base/content/browser-fullScreenAndPointerLock.js Mon Oct 05 22:38:03 2020 +0000 @@ -420,12 +420,27 @@ // before the check is fine since we also check the activeness of // the requesting document in content-side handling code. if (this._isRemoteBrowser(aBrowser)) { - if ( - !this._sendMessageToTheRightContent(aActor, "DOMFullscreen:Entered") - ) { + let [targetActor, inProcessBC] = this._getNextMsgRecipientActor(aActor); + if (!targetActor) { + // If there is no appropriate actor to send the message we have + // no way to complete the transition and should abort by exiting + // fullscreen. + this._abortEnterFullscreen(); + return; + } + targetActor.sendAsyncMessage("DOMFullscreen:Entered", { + remoteFrameBC: inProcessBC, + }); + + // Record that the actor is waiting for its child to enter + // fullscreen so that if it dies we can abort. + targetActor.waitingForChildFullscreen = true; + if (inProcessBC) { + // We aren't messaging the request origin yet, skip this time. 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 @@ -437,9 +452,7 @@ // full-screen was made. Cancel full-screen. Services.focus.activeWindow != window ) { - // This function is called synchronously in fullscreen change, so - // we have to avoid calling exitFullscreen synchronously here. - setTimeout(() => document.exitFullscreen(), 0); + this._abortEnterFullscreen(); return; } @@ -453,7 +466,6 @@ this._logWarningPermissionPromptFS("promptCanceled"); } } - document.documentElement.setAttribute("inDOMFullscreen", true); if (gFindBarInitialized) { @@ -488,9 +500,25 @@ } }, + /** + * Clean up full screen, starting from the request origin's first ancestor + * frame that is OOP. + * + * If there are OOP ancestor frames, we notify the first of those and then bail to + * be called again in that process when it has dealt with the change. This is + * repeated until all ancestor processes have been updated. Once that has happened + * we remove our handlers and attributes and notify the request origin to complete + * the cleanup. + */ cleanupDomFullscreen(aActor) { - if (!this._sendMessageToTheRightContent(aActor, "DOMFullscreen:CleanUp")) { - return; + let [target, inProcessBC] = this._getNextMsgRecipientActor(aActor); + if (target) { + target.sendAsyncMessage("DOMFullscreen:CleanUp", { + remoteFrameBC: inProcessBC, + }); + if (inProcessBC) { + return; + } } PopupNotifications.panel.removeEventListener( @@ -508,40 +536,43 @@ document.documentElement.removeAttribute("inDOMFullscreen"); }, + _abortEnterFullscreen() { + // This function is called synchronously in fullscreen change, so + // we have to avoid calling exitFullscreen synchronously here. + setTimeout(() => document.exitFullscreen(), 0); + if (TelemetryStopwatch.running("FULLSCREEN_CHANGE_MS")) { + // Cancel the stopwatch for any fullscreen change to avoid + // errors if it is started again. + TelemetryStopwatch.cancel("FULLSCREEN_CHANGE_MS"); + } + }, + /** * Search for the first ancestor of aActor that lives in a different process. - * If found, that ancestor is sent the message and return false. - * Otherwise, the recipient should be the actor of the request origin and return true - * from this function. - * - * The method will be called again as a result of targeted child process doing - * "FullScreen.enterDomFullscreen()" or "FullScreen.cleanupDomFullscreen()". - * The return value is used to postpone entering or exiting Full Screen in the parent - * until there is no ancestor anymore. + * If found, that ancestor actor and the browsing context for its child which + * was in process are returned. Otherwise [request origin, null]. * * * @param {JSWindowActorParent} aActor * The actor that called this function. - * @param {String} message - * Message to be sent. * - * @return {boolean} - * The return value is used to postpone entering or exiting Full Screen in the - * parent until there is no ancestor anymore. - * Return false if the message is send to the first ancestor of aActor that - * lives in a different process - * Return true if the message is sent to the request source - * or false otherwise. + * @return {[JSWindowActorParent, BrowsingContext]} + * The parent actor which should be sent the next msg and the + * in process browsing context which is its child. Will be + * [null, null] if there is no OOP parent actor and request origin + * is unset. [null, null] is also returned if the intended actor or + * the calling actor has been destroyed. */ - _sendMessageToTheRightContent(aActor, aMessage) { + _getNextMsgRecipientActor(aActor) { if (aActor.hasBeenDestroyed()) { - // Just restore the chrome UI when the actor is dead. - return true; + return [null, null]; } let childBC = aActor.browsingContext; let parentBC = childBC.parent; + // Walk up the browsing context tree from aActor's browsing context + // to find the first ancestor browsing context that's in a different process. while (parentBC) { if (!childBC.currentWindowGlobal || !parentBC.currentWindowGlobal) { break; @@ -557,24 +588,20 @@ } } + let target = null; + let inProcessBC = null; + if (parentBC && parentBC.currentWindowGlobal) { - let parentActor = parentBC.currentWindowGlobal.getActor("DOMFullscreen"); - parentActor.sendAsyncMessage(aMessage, { - remoteFrameBC: childBC, - }); - return false; + target = parentBC.currentWindowGlobal.getActor("DOMFullscreen"); + inProcessBC = childBC; + } else { + target = aActor.requestOrigin; } - // 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. - if (!aActor.requestOrigin.hasBeenDestroyed()) { - aActor.requestOrigin.sendAsyncMessage(aMessage, {}); - aActor.requestOrigin = null; + if (!target || target.hasBeenDestroyed()) { + return [null, null]; } - return true; + return [target, inProcessBC]; }, _isRemoteBrowser(aBrowser) {