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;
25 delete this._fullscreenWindow;
29 cleanupDomFullscreen(aWindow) {
30 if (!aWindow.FullScreen) {
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.
39 !aWindow.FullScreen.cleanupDomFullscreen(this) &&
40 !aWindow.document.fullscreen
42 Services.obs.notifyObservers(aWindow, "fullscreen-painted");
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
51 _cleanupFullscreenStateAndResumeChromeUI(aWindow) {
52 this.cleanupDomFullscreen(aWindow);
53 if (this.requestOrigin == this && aWindow.document.fullscreen) {
54 aWindow.windowUtils.remoteFrameFullscreenReverted();
59 this._didDestroy = true;
61 let window = this._fullscreenWindow;
63 let topBrowsingContext = this.browsingContext.top;
64 let browser = topBrowsingContext.embedderElement;
70 this.waitingForChildExitFullscreen ||
71 this.waitingForChildEnterFullscreen
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);
80 if (this != this.requestOrigin) {
81 // The current fullscreen requester should handle the fullsceen event
83 this.removeListeners(browser.ownerGlobal);
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
98 // This could reject if we're not currently in fullscreen
99 // so just ignore rejection.
100 window.document.exitFullscreen().catch(() => {});
103 this.cleanupDomFullscreen(window);
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();
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);
119 this.updateFullscreenWindowReference(window);
122 receiveMessage(aMessage) {
123 let topBrowsingContext = this.browsingContext.top;
124 let browser = topBrowsingContext.embedderElement;
127 // No need to go further when the browser is not accessible anymore
128 // (which can happen when the tab is closed for instance),
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);
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
149 this.updateFullscreenWindowReference(window);
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);
160 case "DOMFullscreen:Exit": {
161 this.manager.fullscreen = false;
162 this.waitingForChildEnterFullscreen = false;
163 window.windowUtils.remoteFrameFullscreenReverted();
166 case "DOMFullscreen:Exited": {
167 this.manager.fullscreen = false;
168 this.waitingForChildExitFullscreen = false;
169 this.cleanupDomFullscreen(window);
170 this.updateFullscreenWindowReference(window);
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");
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
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);
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.
205 if (aEvent.target.ownerGlobal == window) {
206 browser = aEvent.target;
208 browser = aEvent.target.ownerGlobal.docShell.chromeEventHandler;
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);
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
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.
237 if (!this.hasBeenDestroyed() && !this.requestOrigin) {
238 this.requestOrigin = this;
240 this.cleanupDomFullscreen(window);
241 this.updateFullscreenWindowReference(window);
243 // If the document is supposed to be in fullscreen, keep the listener to wait for
245 if (!this.manager.fullscreen) {
246 this.removeListeners(window);
253 addListeners(aWindow) {
254 aWindow.addEventListener(
255 "MozDOMFullscreen:Entered",
257 /* useCapture */ true,
261 aWindow.addEventListener(
262 "MozDOMFullscreen:Exited",
264 /* useCapture */ true,
265 /* wantsUntrusted */ false
269 removeListeners(aWindow) {
270 aWindow.removeEventListener("MozDOMFullscreen:Entered", this, true);
271 aWindow.removeEventListener("MozDOMFullscreen:Exited", this, true);
275 * Get the actor where the original fullscreen
276 * enter or exit request comes from.
278 get requestOrigin() {
279 let chromeBC = this.browsingContext.topChromeWindow?.browsingContext;
280 let requestOrigin = chromeBC?.fullscreenRequestOrigin;
281 return requestOrigin && requestOrigin.get();
285 * Store the actor where the original fullscreen
286 * enter or exit request comes from in the top level
289 set requestOrigin(aActor) {
290 let chromeBC = this.browsingContext.topChromeWindow?.browsingContext;
292 console.error("not able to get browsingContext for chrome window.");
297 chromeBC.fullscreenRequestOrigin = Cu.getWeakReference(aActor);
299 delete chromeBC.fullscreenRequestOrigin;
304 if (this._didDestroy) {
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.
313 return !this.browsingContext;