Backed out 7 changesets (bug 1839993) for causing build bustages on DecoderTemplate...
[gecko.git] / browser / actors / DOMFullscreenParent.sys.mjs
blob7c481c10515a391f95ea95d5c205feedea352788
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/. */
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;
21   updateFullscreenWindowReference(aWindow) {
22     if (aWindow.document.documentElement.hasAttribute("inDOMFullscreen")) {
23       this._fullscreenWindow = aWindow;
24     } else {
25       delete this._fullscreenWindow;
26     }
27   }
29   cleanupDomFullscreen(aWindow) {
30     if (!aWindow.FullScreen) {
31       return;
32     }
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   }
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   }
58   didDestroy() {
59     this._didDestroy = true;
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       }
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       }
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     }
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     }
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   }
122   receiveMessage(aMessage) {
123     let topBrowsingContext = this.browsingContext.top;
124     let browser = topBrowsingContext.embedderElement;
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     }
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         TelemetryStopwatch.finish("FULLSCREEN_CHANGE_MS");
178         break;
179       }
180     }
181   }
183   handleEvent(aEvent) {
184     let window = aEvent.currentTarget.ownerGlobal;
185     // We can not get the corresponding browsing context from actor if the actor
186     // has already destroyed, so use event target to get browsing context
187     // instead.
188     let requestOrigin = window.browsingContext.fullscreenRequestOrigin?.get();
189     if (this != requestOrigin) {
190       // The current fullscreen requester should handle the fullsceen event,
191       // ignore them if we are not the current requester.
192       this.removeListeners(window);
193       return;
194     }
196     switch (aEvent.type) {
197       case "MozDOMFullscreen:Entered": {
198         // The event target is the element which requested the DOM
199         // fullscreen. If we were entering DOM fullscreen for a remote
200         // browser, the target would be the browser which was the parameter of
201         // `remoteFrameFullscreenChanged` call. If the fullscreen
202         // request was initiated from an in-process browser, we need
203         // to get its corresponding browser here.
204         let browser;
205         if (aEvent.target.ownerGlobal == window) {
206           browser = aEvent.target;
207         } else {
208           browser = aEvent.target.ownerGlobal.docShell.chromeEventHandler;
209         }
211         // Addon installation should be cancelled when entering fullscreen for security and usability reasons.
212         // Installation prompts in fullscreen can trick the user into installing unwanted addons.
213         // In fullscreen the notification box does not have a clear visual association with its parent anymore.
214         if (window.gXPInstallObserver) {
215           window.gXPInstallObserver.removeAllNotifications(browser);
216         }
218         TelemetryStopwatch.start("FULLSCREEN_CHANGE_MS");
219         window.FullScreen.enterDomFullscreen(browser, this);
220         this.updateFullscreenWindowReference(window);
222         if (!this.hasBeenDestroyed() && this.requestOrigin) {
223           window.PointerlockFsWarning.showFullScreen(
224             this.requestOrigin.manager.documentPrincipal.originNoSuffix
225           );
226         }
227         break;
228       }
229       case "MozDOMFullscreen:Exited": {
230         TelemetryStopwatch.start("FULLSCREEN_CHANGE_MS");
232         // Make sure that the actor has not been destroyed before
233         // accessing its browsing context. Otherwise, a error may
234         // occur and hence cleanupDomFullscreen not executed, resulting
235         // in the browser window being in an unstable state.
236         // (Bug 1590138).
237         if (!this.hasBeenDestroyed() && !this.requestOrigin) {
238           this.requestOrigin = this;
239         }
240         this.cleanupDomFullscreen(window);
241         this.updateFullscreenWindowReference(window);
243         // If the document is supposed to be in fullscreen, keep the listener to wait for
244         // further events.
245         if (!this.manager.fullscreen) {
246           this.removeListeners(window);
247         }
248         break;
249       }
250     }
251   }
253   addListeners(aWindow) {
254     aWindow.addEventListener(
255       "MozDOMFullscreen:Entered",
256       this,
257       /* useCapture */ true,
258       /* wantsUntrusted */
259       false
260     );
261     aWindow.addEventListener(
262       "MozDOMFullscreen:Exited",
263       this,
264       /* useCapture */ true,
265       /* wantsUntrusted */ false
266     );
267   }
269   removeListeners(aWindow) {
270     aWindow.removeEventListener("MozDOMFullscreen:Entered", this, true);
271     aWindow.removeEventListener("MozDOMFullscreen:Exited", this, true);
272   }
274   /**
275    * Get the actor where the original fullscreen
276    * enter or exit request comes from.
277    */
278   get requestOrigin() {
279     let chromeBC = this.browsingContext.topChromeWindow?.browsingContext;
280     let requestOrigin = chromeBC?.fullscreenRequestOrigin;
281     return requestOrigin && requestOrigin.get();
282   }
284   /**
285    * Store the actor where the original fullscreen
286    * enter or exit request comes from in the top level
287    * browsing context.
288    */
289   set requestOrigin(aActor) {
290     let chromeBC = this.browsingContext.topChromeWindow?.browsingContext;
291     if (!chromeBC) {
292       console.error("not able to get browsingContext for chrome window.");
293       return;
294     }
296     if (aActor) {
297       chromeBC.fullscreenRequestOrigin = Cu.getWeakReference(aActor);
298     } else {
299       delete chromeBC.fullscreenRequestOrigin;
300     }
301   }
303   hasBeenDestroyed() {
304     if (this._didDestroy) {
305       return true;
306     }
308     // The 'didDestroy' callback is not always getting called.
309     // So we can't rely on it here. Instead, we will try to access
310     // the browsing context to judge wether the actor has
311     // been destroyed or not.
312     try {
313       return !this.browsingContext;
314     } catch {
315       return true;
316     }
317   }