DOMFullscreenParent.sys.mjs (11446B)
1 /* vim: set ts=2 sw=2 sts=2 et tw=80: */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 export class DOMFullscreenParent extends JSWindowActorParent { 7 // These properties get set by browser-fullScreenAndPointerLock.js. 8 // TODO: Bug 1743703 - Consider moving the messaging component of 9 // browser-fullScreenAndPointerLock.js into the actor 10 waitingForChildEnterFullscreen = false; 11 waitingForChildExitFullscreen = false; 12 // Cache the next message recipient actor and in-process browsing context that 13 // is computed by _getNextMsgRecipientActor() of 14 // browser-fullScreenAndPointerLock.js, this is used to ensure the fullscreen 15 // cleanup messages goes the same route as fullscreen request, especially for 16 // the cleanup that happens after actor is destroyed. 17 // TODO: Bug 1743703 - Consider moving the messaging component of 18 // browser-fullScreenAndPointerLock.js into the actor 19 nextMsgRecipient = null; 20 21 updateFullscreenWindowReference(aWindow) { 22 if (aWindow.document.documentElement.hasAttribute("inDOMFullscreen")) { 23 this._fullscreenWindow = aWindow; 24 } else { 25 delete this._fullscreenWindow; 26 } 27 } 28 29 cleanupDomFullscreen(aWindow) { 30 if (!aWindow.FullScreen) { 31 return; 32 } 33 34 // If we don't need to wait for child reply, i.e. cleanupDomFullscreen 35 // doesn't message to child, and we've exit the fullscreen, there won't be 36 // DOMFullscreen:Painted message from child and it is possible that no more 37 // paint would be triggered, so just notify fullscreen-painted observer. 38 if ( 39 !aWindow.FullScreen.cleanupDomFullscreen(this) && 40 !aWindow.document.fullscreen 41 ) { 42 Services.obs.notifyObservers(aWindow, "fullscreen-painted"); 43 } 44 } 45 46 /** 47 * Clean up fullscreen state and resume chrome UI if window is in fullscreen 48 * and this actor is the one where the original fullscreen enter or 49 * exit request comes. 50 */ 51 _cleanupFullscreenStateAndResumeChromeUI(aWindow) { 52 this.cleanupDomFullscreen(aWindow); 53 if (this.requestOrigin == this && aWindow.document.fullscreen) { 54 aWindow.windowUtils.remoteFrameFullscreenReverted(); 55 } 56 } 57 58 didDestroy() { 59 this._didDestroy = true; 60 61 let window = this._fullscreenWindow; 62 if (!window) { 63 let topBrowsingContext = this.browsingContext.top; 64 let browser = topBrowsingContext.embedderElement; 65 if (!browser) { 66 return; 67 } 68 69 if ( 70 this.waitingForChildExitFullscreen || 71 this.waitingForChildEnterFullscreen 72 ) { 73 this.waitingForChildExitFullscreen = false; 74 this.waitingForChildEnterFullscreen = false; 75 // We were destroyed while waiting for our DOMFullscreenChild to exit 76 // or enter fullscreen, run cleanup steps anyway. 77 this._cleanupFullscreenStateAndResumeChromeUI(browser.ownerGlobal); 78 } 79 80 if (this != this.requestOrigin) { 81 // The current fullscreen requester should handle the fullsceen event 82 // if any. 83 this.removeListeners(browser.ownerGlobal); 84 } 85 return; 86 } 87 88 if (this.waitingForChildEnterFullscreen) { 89 this.waitingForChildEnterFullscreen = false; 90 if (window.document.fullscreen) { 91 // We were destroyed while waiting for our DOMFullscreenChild 92 // to transition to fullscreen so we abort the entire 93 // fullscreen transition to prevent getting stuck in a 94 // partial fullscreen state. We need to go through the 95 // document since window.Fullscreen could be undefined 96 // at this point. 97 // 98 // This could reject if we're not currently in fullscreen 99 // so just ignore rejection. 100 window.document.exitFullscreen().catch(() => {}); 101 return; 102 } 103 this.cleanupDomFullscreen(window); 104 } 105 106 // Need to resume Chrome UI if the window is still in fullscreen UI 107 // to avoid the window stays in fullscreen problem. (See Bug 1620341) 108 if (window.document.documentElement.hasAttribute("inDOMFullscreen")) { 109 this.cleanupDomFullscreen(window); 110 if (window.windowUtils) { 111 window.windowUtils.remoteFrameFullscreenReverted(); 112 } 113 } else if (this.waitingForChildExitFullscreen) { 114 this.waitingForChildExitFullscreen = false; 115 // We were destroyed while waiting for our DOMFullscreenChild to exit 116 // run cleanup steps anyway. 117 this._cleanupFullscreenStateAndResumeChromeUI(window); 118 } 119 this.updateFullscreenWindowReference(window); 120 } 121 122 receiveMessage(aMessage) { 123 let topBrowsingContext = this.browsingContext.top; 124 let browser = topBrowsingContext.embedderElement; 125 126 if (!browser) { 127 // No need to go further when the browser is not accessible anymore 128 // (which can happen when the tab is closed for instance), 129 return; 130 } 131 132 let window = browser.ownerGlobal; 133 switch (aMessage.name) { 134 case "DOMFullscreen:Request": { 135 this.manager.fullscreen = true; 136 this.waitingForChildExitFullscreen = false; 137 this.requestOrigin = this; 138 this.addListeners(window); 139 window.windowUtils.remoteFrameFullscreenChanged(browser); 140 break; 141 } 142 case "DOMFullscreen:NewOrigin": { 143 // Don't show the warning if we've already exited fullscreen. 144 if (window.document.fullscreen) { 145 window.PointerlockFsWarning.showFullScreen( 146 aMessage.data.originNoSuffix 147 ); 148 } 149 this.updateFullscreenWindowReference(window); 150 break; 151 } 152 case "DOMFullscreen:Entered": { 153 this.manager.fullscreen = true; 154 this.nextMsgRecipient = null; 155 this.waitingForChildEnterFullscreen = false; 156 window.FullScreen.enterDomFullscreen(browser, this); 157 this.updateFullscreenWindowReference(window); 158 break; 159 } 160 case "DOMFullscreen:Exit": { 161 this.manager.fullscreen = false; 162 this.waitingForChildEnterFullscreen = false; 163 window.windowUtils.remoteFrameFullscreenReverted(); 164 break; 165 } 166 case "DOMFullscreen:Exited": { 167 this.manager.fullscreen = false; 168 this.waitingForChildExitFullscreen = false; 169 this.cleanupDomFullscreen(window); 170 this.updateFullscreenWindowReference(window); 171 break; 172 } 173 case "DOMFullscreen:Painted": { 174 this.waitingForChildExitFullscreen = false; 175 Services.obs.notifyObservers(window, "fullscreen-painted"); 176 this.sendAsyncMessage("DOMFullscreen:Painted", {}); 177 Glean.fullscreen.change.stopAndAccumulate(this.timerId); 178 this.timerId = null; 179 break; 180 } 181 } 182 } 183 184 handleEvent(aEvent) { 185 let window = aEvent.currentTarget.ownerGlobal; 186 // We can not get the corresponding browsing context from actor if the actor 187 // has already destroyed, so use event target to get browsing context 188 // instead. 189 let requestOrigin = window.browsingContext.fullscreenRequestOrigin?.get(); 190 if (this != requestOrigin) { 191 // The current fullscreen requester should handle the fullsceen event, 192 // ignore them if we are not the current requester. 193 this.removeListeners(window); 194 return; 195 } 196 197 switch (aEvent.type) { 198 case "MozDOMFullscreen:Entered": { 199 // The event target is the element which requested the DOM 200 // fullscreen. If we were entering DOM fullscreen for a remote 201 // browser, the target would be the browser which was the parameter of 202 // `remoteFrameFullscreenChanged` call. If the fullscreen 203 // request was initiated from an in-process browser, we need 204 // to get its corresponding browser here. 205 let browser; 206 if (aEvent.target.ownerGlobal == window) { 207 browser = aEvent.target; 208 } else { 209 browser = aEvent.target.ownerGlobal.docShell.chromeEventHandler; 210 } 211 212 // Addon installation should be cancelled when entering fullscreen for security and usability reasons. 213 // Installation prompts in fullscreen can trick the user into installing unwanted addons. 214 // In fullscreen the notification box does not have a clear visual association with its parent anymore. 215 if (window.gXPInstallObserver) { 216 window.gXPInstallObserver.removeAllNotifications(browser); 217 } 218 219 this.timerId = Glean.fullscreen.change.start(); 220 window.FullScreen.enterDomFullscreen(browser, this); 221 this.updateFullscreenWindowReference(window); 222 223 if (!this.hasBeenDestroyed() && this.requestOrigin) { 224 window.PointerlockFsWarning.showFullScreen( 225 this.requestOrigin.manager.documentPrincipal.originNoSuffix 226 ); 227 } 228 break; 229 } 230 case "MozDOMFullscreen:Exited": { 231 this.timerId = Glean.fullscreen.change.start(); 232 233 // Make sure that the actor has not been destroyed before 234 // accessing its browsing context. Otherwise, a error may 235 // occur and hence cleanupDomFullscreen not executed, resulting 236 // in the browser window being in an unstable state. 237 // (Bug 1590138). 238 if (!this.hasBeenDestroyed() && !this.requestOrigin) { 239 this.requestOrigin = this; 240 } 241 this.cleanupDomFullscreen(window); 242 this.updateFullscreenWindowReference(window); 243 244 // If the document is supposed to be in fullscreen, keep the listener to wait for 245 // further events. 246 if (!this.manager.fullscreen) { 247 this.removeListeners(window); 248 } 249 break; 250 } 251 } 252 } 253 254 addListeners(aWindow) { 255 aWindow.addEventListener( 256 "MozDOMFullscreen:Entered", 257 this, 258 /* useCapture */ true, 259 /* wantsUntrusted */ 260 false 261 ); 262 aWindow.addEventListener( 263 "MozDOMFullscreen:Exited", 264 this, 265 /* useCapture */ true, 266 /* wantsUntrusted */ false 267 ); 268 } 269 270 removeListeners(aWindow) { 271 aWindow.removeEventListener("MozDOMFullscreen:Entered", this, true); 272 aWindow.removeEventListener("MozDOMFullscreen:Exited", this, true); 273 } 274 275 /** 276 * Get the actor where the original fullscreen 277 * enter or exit request comes from. 278 */ 279 get requestOrigin() { 280 let chromeBC = this.browsingContext.topChromeWindow?.browsingContext; 281 let requestOrigin = chromeBC?.fullscreenRequestOrigin; 282 return requestOrigin && requestOrigin.get(); 283 } 284 285 /** 286 * Store the actor where the original fullscreen 287 * enter or exit request comes from in the top level 288 * browsing context. 289 */ 290 set requestOrigin(aActor) { 291 let chromeBC = this.browsingContext.topChromeWindow?.browsingContext; 292 if (!chromeBC) { 293 console.error("not able to get browsingContext for chrome window."); 294 return; 295 } 296 297 if (aActor) { 298 chromeBC.fullscreenRequestOrigin = Cu.getWeakReference(aActor); 299 } else { 300 delete chromeBC.fullscreenRequestOrigin; 301 } 302 } 303 304 hasBeenDestroyed() { 305 if (this._didDestroy) { 306 return true; 307 } 308 309 // The 'didDestroy' callback is not always getting called. 310 // So we can't rely on it here. Instead, we will try to access 311 // the browsing context to judge wether the actor has 312 // been destroyed or not. 313 try { 314 return !this.browsingContext; 315 } catch { 316 return true; 317 } 318 } 319 }