Bug 1793691 - adjust test-info-all to include manifests. r=gbrown
[gecko.git] / browser / actors / DOMFullscreenParent.jsm
blob64a4f987a77ac745182ec67e0befd1ab2abc683d
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 "use strict";
7 var EXPORTED_SYMBOLS = ["DOMFullscreenParent"];
9 class DOMFullscreenParent extends JSWindowActorParent {
10   // These properties get set by browser-fullScreenAndPointerLock.js.
11   // TODO: Bug 1743703 - Consider moving the messaging component of
12   //       browser-fullScreenAndPointerLock.js into the actor
13   waitingForChildEnterFullscreen = false;
14   waitingForChildExitFullscreen = false;
15   // Cache the next message recipient actor and in-process browsing context that
16   // is computed by _getNextMsgRecipientActor() of
17   // browser-fullScreenAndPointerLock.js, this is used to ensure the fullscreen
18   // cleanup messages goes the same route as fullscreen request, especially for
19   // the cleanup that happens after actor is destroyed.
20   // TODO: Bug 1743703 - Consider moving the messaging component of
21   //       browser-fullScreenAndPointerLock.js into the actor
22   nextMsgRecipient = null;
24   updateFullscreenWindowReference(aWindow) {
25     if (aWindow.document.documentElement.hasAttribute("inDOMFullscreen")) {
26       this._fullscreenWindow = aWindow;
27     } else {
28       delete this._fullscreenWindow;
29     }
30   }
32   cleanupDomFullscreen(aWindow) {
33     if (!aWindow.FullScreen) {
34       return;
35     }
37     // If we don't need to wait for child reply, i.e. cleanupDomFullscreen
38     // doesn't message to child, and we've exit the fullscreen, there won't be
39     // DOMFullscreen:Painted message from child and it is possible that no more
40     // paint would be triggered, so just notify fullscreen-painted observer.
41     if (
42       !aWindow.FullScreen.cleanupDomFullscreen(this) &&
43       !aWindow.document.fullscreen
44     ) {
45       Services.obs.notifyObservers(aWindow, "fullscreen-painted");
46     }
47   }
49   /**
50    * Clean up fullscreen state and resume chrome UI if window is in fullscreen
51    * and this actor is the one where the original fullscreen enter or
52    * exit request comes.
53    */
54   _cleanupFullscreenStateAndResumeChromeUI(aWindow) {
55     this.cleanupDomFullscreen(aWindow);
56     if (this.requestOrigin == this && aWindow.document.fullscreen) {
57       aWindow.windowUtils.remoteFrameFullscreenReverted();
58     }
59   }
61   didDestroy() {
62     this._didDestroy = true;
64     let window = this._fullscreenWindow;
65     if (!window) {
66       let topBrowsingContext = this.browsingContext.top;
67       let browser = topBrowsingContext.embedderElement;
68       if (!browser) {
69         return;
70       }
72       if (
73         this.waitingForChildExitFullscreen ||
74         this.waitingForChildEnterFullscreen
75       ) {
76         this.waitingForChildExitFullscreen = false;
77         this.waitingForChildEnterFullscreen = false;
78         // We were destroyed while waiting for our DOMFullscreenChild to exit
79         // or enter fullscreen, run cleanup steps anyway.
80         this._cleanupFullscreenStateAndResumeChromeUI(browser.ownerGlobal);
81       }
82       return;
83     }
85     if (this.waitingForChildEnterFullscreen) {
86       this.waitingForChildEnterFullscreen = false;
87       if (window.document.fullscreen) {
88         // We were destroyed while waiting for our DOMFullscreenChild
89         // to transition to fullscreen so we abort the entire
90         // fullscreen transition to prevent getting stuck in a
91         // partial fullscreen state. We need to go through the
92         // document since window.Fullscreen could be undefined
93         // at this point.
94         //
95         // This could reject if we're not currently in fullscreen
96         // so just ignore rejection.
97         window.document.exitFullscreen().catch(() => {});
98         return;
99       }
100       this.cleanupDomFullscreen(window);
101     }
103     // Need to resume Chrome UI if the window is still in fullscreen UI
104     // to avoid the window stays in fullscreen problem. (See Bug 1620341)
105     if (window.document.documentElement.hasAttribute("inDOMFullscreen")) {
106       this.cleanupDomFullscreen(window);
107       if (window.windowUtils) {
108         window.windowUtils.remoteFrameFullscreenReverted();
109       }
110     } else if (this.waitingForChildExitFullscreen) {
111       this.waitingForChildExitFullscreen = false;
112       // We were destroyed while waiting for our DOMFullscreenChild to exit
113       // run cleanup steps anyway.
114       this._cleanupFullscreenStateAndResumeChromeUI(window);
115     }
116     this.updateFullscreenWindowReference(window);
117   }
119   receiveMessage(aMessage) {
120     let topBrowsingContext = this.browsingContext.top;
121     let browser = topBrowsingContext.embedderElement;
123     if (!browser) {
124       // No need to go further when the browser is not accessible anymore
125       // (which can happen when the tab is closed for instance),
126       return;
127     }
129     let window = browser.ownerGlobal;
130     switch (aMessage.name) {
131       case "DOMFullscreen:Request": {
132         this.waitingForChildExitFullscreen = false;
133         this.requestOrigin = this;
134         this.addListeners(window);
135         window.windowUtils.remoteFrameFullscreenChanged(browser);
136         break;
137       }
138       case "DOMFullscreen:NewOrigin": {
139         // Don't show the warning if we've already exited fullscreen.
140         if (window.document.fullscreen) {
141           window.PointerlockFsWarning.showFullScreen(
142             aMessage.data.originNoSuffix
143           );
144         }
145         this.updateFullscreenWindowReference(window);
146         break;
147       }
148       case "DOMFullscreen:Entered": {
149         this.nextMsgRecipient = null;
150         this.waitingForChildEnterFullscreen = false;
151         window.FullScreen.enterDomFullscreen(browser, this);
152         this.updateFullscreenWindowReference(window);
153         break;
154       }
155       case "DOMFullscreen:Exit": {
156         this.waitingForChildEnterFullscreen = false;
157         window.windowUtils.remoteFrameFullscreenReverted();
158         break;
159       }
160       case "DOMFullscreen:Exited": {
161         this.waitingForChildExitFullscreen = false;
162         this.cleanupDomFullscreen(window);
163         this.updateFullscreenWindowReference(window);
164         break;
165       }
166       case "DOMFullscreen:Painted": {
167         this.waitingForChildExitFullscreen = false;
168         Services.obs.notifyObservers(window, "fullscreen-painted");
169         this.sendAsyncMessage("DOMFullscreen:Painted", {});
170         TelemetryStopwatch.finish("FULLSCREEN_CHANGE_MS");
171         break;
172       }
173     }
174   }
176   handleEvent(aEvent) {
177     let window = aEvent.currentTarget.ownerGlobal;
178     switch (aEvent.type) {
179       case "MozDOMFullscreen:Entered": {
180         // The event target is the element which requested the DOM
181         // fullscreen. If we were entering DOM fullscreen for a remote
182         // browser, the target would be the browser which was the parameter of
183         // `remoteFrameFullscreenChanged` call. If the fullscreen
184         // request was initiated from an in-process browser, we need
185         // to get its corresponding browser here.
186         let browser;
187         if (aEvent.target.ownerGlobal == window) {
188           browser = aEvent.target;
189         } else {
190           browser = aEvent.target.ownerGlobal.docShell.chromeEventHandler;
191         }
193         // Addon installation should be cancelled when entering fullscreen for security and usability reasons.
194         // Installation prompts in fullscreen can trick the user into installing unwanted addons.
195         // In fullscreen the notification box does not have a clear visual association with its parent anymore.
196         if (window.gXPInstallObserver) {
197           window.gXPInstallObserver.removeAllNotifications(browser);
198         }
200         TelemetryStopwatch.start("FULLSCREEN_CHANGE_MS");
201         window.FullScreen.enterDomFullscreen(browser, this);
202         this.updateFullscreenWindowReference(window);
203         break;
204       }
205       case "MozDOMFullscreen:Exited": {
206         TelemetryStopwatch.start("FULLSCREEN_CHANGE_MS");
208         // Make sure that the actor has not been destroyed before
209         // accessing its browsing context. Otherwise, a error may
210         // occur and hence cleanupDomFullscreen not executed, resulting
211         // in the browser window being in an unstable state.
212         // (Bug 1590138).
213         if (!this.hasBeenDestroyed() && !this.requestOrigin) {
214           this.requestOrigin = this;
215         }
216         this.cleanupDomFullscreen(window);
217         this.updateFullscreenWindowReference(window);
218         this.removeListeners(window);
219         break;
220       }
221     }
222   }
224   addListeners(aWindow) {
225     aWindow.addEventListener(
226       "MozDOMFullscreen:Entered",
227       this,
228       /* useCapture */ true,
229       /* wantsUntrusted */
230       false
231     );
232     aWindow.addEventListener(
233       "MozDOMFullscreen:Exited",
234       this,
235       /* useCapture */ true,
236       /* wantsUntrusted */ false
237     );
238   }
240   removeListeners(aWindow) {
241     aWindow.removeEventListener("MozDOMFullscreen:Entered", this, true);
242     aWindow.removeEventListener("MozDOMFullscreen:Exited", this, true);
243   }
245   /**
246    * Get the actor where the original fullscreen
247    * enter or exit request comes from.
248    */
249   get requestOrigin() {
250     let requestOrigin = this.browsingContext.top.fullscreenRequestOrigin;
251     return requestOrigin && requestOrigin.get();
252   }
254   /**
255    * Store the actor where the original fullscreen
256    * enter or exit request comes from in the top level
257    * browsing context.
258    */
259   set requestOrigin(aActor) {
260     if (aActor) {
261       this.browsingContext.top.fullscreenRequestOrigin = Cu.getWeakReference(
262         aActor
263       );
264     } else {
265       delete this.browsingContext.top.fullscreenRequestOrigin;
266     }
267   }
269   hasBeenDestroyed() {
270     if (this._didDestroy) {
271       return true;
272     }
274     // The 'didDestroy' callback is not always getting called.
275     // So we can't rely on it here. Instead, we will try to access
276     // the browsing context to judge wether the actor has
277     // been destroyed or not.
278     try {
279       return !this.browsingContext;
280     } catch {
281       return true;
282     }
283   }