summaryrefslogtreecommitdiffstats
path: root/browser/actors/DOMFullscreenParent.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'browser/actors/DOMFullscreenParent.sys.mjs')
-rw-r--r--browser/actors/DOMFullscreenParent.sys.mjs318
1 files changed, 318 insertions, 0 deletions
diff --git a/browser/actors/DOMFullscreenParent.sys.mjs b/browser/actors/DOMFullscreenParent.sys.mjs
new file mode 100644
index 0000000000..7c481c1051
--- /dev/null
+++ b/browser/actors/DOMFullscreenParent.sys.mjs
@@ -0,0 +1,318 @@
+/* vim: set ts=2 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+export class DOMFullscreenParent extends JSWindowActorParent {
+ // These properties get set by browser-fullScreenAndPointerLock.js.
+ // TODO: Bug 1743703 - Consider moving the messaging component of
+ // browser-fullScreenAndPointerLock.js into the actor
+ waitingForChildEnterFullscreen = false;
+ waitingForChildExitFullscreen = false;
+ // Cache the next message recipient actor and in-process browsing context that
+ // is computed by _getNextMsgRecipientActor() of
+ // browser-fullScreenAndPointerLock.js, this is used to ensure the fullscreen
+ // cleanup messages goes the same route as fullscreen request, especially for
+ // the cleanup that happens after actor is destroyed.
+ // TODO: Bug 1743703 - Consider moving the messaging component of
+ // browser-fullScreenAndPointerLock.js into the actor
+ nextMsgRecipient = null;
+
+ updateFullscreenWindowReference(aWindow) {
+ if (aWindow.document.documentElement.hasAttribute("inDOMFullscreen")) {
+ this._fullscreenWindow = aWindow;
+ } else {
+ delete this._fullscreenWindow;
+ }
+ }
+
+ cleanupDomFullscreen(aWindow) {
+ if (!aWindow.FullScreen) {
+ return;
+ }
+
+ // If we don't need to wait for child reply, i.e. cleanupDomFullscreen
+ // doesn't message to child, and we've exit the fullscreen, there won't be
+ // DOMFullscreen:Painted message from child and it is possible that no more
+ // paint would be triggered, so just notify fullscreen-painted observer.
+ if (
+ !aWindow.FullScreen.cleanupDomFullscreen(this) &&
+ !aWindow.document.fullscreen
+ ) {
+ Services.obs.notifyObservers(aWindow, "fullscreen-painted");
+ }
+ }
+
+ /**
+ * Clean up fullscreen state and resume chrome UI if window is in fullscreen
+ * and this actor is the one where the original fullscreen enter or
+ * exit request comes.
+ */
+ _cleanupFullscreenStateAndResumeChromeUI(aWindow) {
+ this.cleanupDomFullscreen(aWindow);
+ if (this.requestOrigin == this && aWindow.document.fullscreen) {
+ aWindow.windowUtils.remoteFrameFullscreenReverted();
+ }
+ }
+
+ didDestroy() {
+ this._didDestroy = true;
+
+ let window = this._fullscreenWindow;
+ if (!window) {
+ let topBrowsingContext = this.browsingContext.top;
+ let browser = topBrowsingContext.embedderElement;
+ if (!browser) {
+ return;
+ }
+
+ if (
+ this.waitingForChildExitFullscreen ||
+ this.waitingForChildEnterFullscreen
+ ) {
+ this.waitingForChildExitFullscreen = false;
+ this.waitingForChildEnterFullscreen = false;
+ // We were destroyed while waiting for our DOMFullscreenChild to exit
+ // or enter fullscreen, run cleanup steps anyway.
+ this._cleanupFullscreenStateAndResumeChromeUI(browser.ownerGlobal);
+ }
+
+ if (this != this.requestOrigin) {
+ // The current fullscreen requester should handle the fullsceen event
+ // if any.
+ this.removeListeners(browser.ownerGlobal);
+ }
+ return;
+ }
+
+ if (this.waitingForChildEnterFullscreen) {
+ this.waitingForChildEnterFullscreen = false;
+ if (window.document.fullscreen) {
+ // We were destroyed 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;
+ }
+ this.cleanupDomFullscreen(window);
+ }
+
+ // 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")) {
+ this.cleanupDomFullscreen(window);
+ if (window.windowUtils) {
+ window.windowUtils.remoteFrameFullscreenReverted();
+ }
+ } else if (this.waitingForChildExitFullscreen) {
+ this.waitingForChildExitFullscreen = false;
+ // We were destroyed while waiting for our DOMFullscreenChild to exit
+ // run cleanup steps anyway.
+ this._cleanupFullscreenStateAndResumeChromeUI(window);
+ }
+ this.updateFullscreenWindowReference(window);
+ }
+
+ receiveMessage(aMessage) {
+ let topBrowsingContext = this.browsingContext.top;
+ let browser = topBrowsingContext.embedderElement;
+
+ if (!browser) {
+ // No need to go further when the browser is not accessible anymore
+ // (which can happen when the tab is closed for instance),
+ return;
+ }
+
+ let window = browser.ownerGlobal;
+ switch (aMessage.name) {
+ case "DOMFullscreen:Request": {
+ this.manager.fullscreen = true;
+ this.waitingForChildExitFullscreen = false;
+ this.requestOrigin = this;
+ this.addListeners(window);
+ window.windowUtils.remoteFrameFullscreenChanged(browser);
+ break;
+ }
+ case "DOMFullscreen:NewOrigin": {
+ // Don't show the warning if we've already exited fullscreen.
+ if (window.document.fullscreen) {
+ window.PointerlockFsWarning.showFullScreen(
+ aMessage.data.originNoSuffix
+ );
+ }
+ this.updateFullscreenWindowReference(window);
+ break;
+ }
+ case "DOMFullscreen:Entered": {
+ this.manager.fullscreen = true;
+ this.nextMsgRecipient = null;
+ this.waitingForChildEnterFullscreen = false;
+ window.FullScreen.enterDomFullscreen(browser, this);
+ this.updateFullscreenWindowReference(window);
+ break;
+ }
+ case "DOMFullscreen:Exit": {
+ this.manager.fullscreen = false;
+ this.waitingForChildEnterFullscreen = false;
+ window.windowUtils.remoteFrameFullscreenReverted();
+ break;
+ }
+ case "DOMFullscreen:Exited": {
+ this.manager.fullscreen = false;
+ this.waitingForChildExitFullscreen = false;
+ this.cleanupDomFullscreen(window);
+ this.updateFullscreenWindowReference(window);
+ break;
+ }
+ case "DOMFullscreen:Painted": {
+ this.waitingForChildExitFullscreen = false;
+ Services.obs.notifyObservers(window, "fullscreen-painted");
+ this.sendAsyncMessage("DOMFullscreen:Painted", {});
+ TelemetryStopwatch.finish("FULLSCREEN_CHANGE_MS");
+ break;
+ }
+ }
+ }
+
+ handleEvent(aEvent) {
+ let window = aEvent.currentTarget.ownerGlobal;
+ // We can not get the corresponding browsing context from actor if the actor
+ // has already destroyed, so use event target to get browsing context
+ // instead.
+ let requestOrigin = window.browsingContext.fullscreenRequestOrigin?.get();
+ if (this != requestOrigin) {
+ // The current fullscreen requester should handle the fullsceen event,
+ // ignore them if we are not the current requester.
+ this.removeListeners(window);
+ return;
+ }
+
+ switch (aEvent.type) {
+ case "MozDOMFullscreen:Entered": {
+ // The event target is the element which requested the DOM
+ // fullscreen. If we were entering DOM fullscreen for a remote
+ // browser, the target would be the browser which was the parameter of
+ // `remoteFrameFullscreenChanged` call. If the fullscreen
+ // request was initiated from an in-process browser, we need
+ // to get its corresponding browser here.
+ let browser;
+ if (aEvent.target.ownerGlobal == window) {
+ browser = aEvent.target;
+ } else {
+ browser = aEvent.target.ownerGlobal.docShell.chromeEventHandler;
+ }
+
+ // Addon installation should be cancelled when entering fullscreen for security and usability reasons.
+ // Installation prompts in fullscreen can trick the user into installing unwanted addons.
+ // In fullscreen the notification box does not have a clear visual association with its parent anymore.
+ if (window.gXPInstallObserver) {
+ window.gXPInstallObserver.removeAllNotifications(browser);
+ }
+
+ TelemetryStopwatch.start("FULLSCREEN_CHANGE_MS");
+ window.FullScreen.enterDomFullscreen(browser, this);
+ this.updateFullscreenWindowReference(window);
+
+ if (!this.hasBeenDestroyed() && this.requestOrigin) {
+ window.PointerlockFsWarning.showFullScreen(
+ this.requestOrigin.manager.documentPrincipal.originNoSuffix
+ );
+ }
+ break;
+ }
+ case "MozDOMFullscreen:Exited": {
+ TelemetryStopwatch.start("FULLSCREEN_CHANGE_MS");
+
+ // Make sure that the actor has not been destroyed before
+ // accessing its browsing context. Otherwise, a error may
+ // occur and hence cleanupDomFullscreen not executed, resulting
+ // in the browser window being in an unstable state.
+ // (Bug 1590138).
+ if (!this.hasBeenDestroyed() && !this.requestOrigin) {
+ this.requestOrigin = this;
+ }
+ this.cleanupDomFullscreen(window);
+ this.updateFullscreenWindowReference(window);
+
+ // If the document is supposed to be in fullscreen, keep the listener to wait for
+ // further events.
+ if (!this.manager.fullscreen) {
+ this.removeListeners(window);
+ }
+ break;
+ }
+ }
+ }
+
+ addListeners(aWindow) {
+ aWindow.addEventListener(
+ "MozDOMFullscreen:Entered",
+ this,
+ /* useCapture */ true,
+ /* wantsUntrusted */
+ false
+ );
+ aWindow.addEventListener(
+ "MozDOMFullscreen:Exited",
+ this,
+ /* useCapture */ true,
+ /* wantsUntrusted */ false
+ );
+ }
+
+ removeListeners(aWindow) {
+ aWindow.removeEventListener("MozDOMFullscreen:Entered", this, true);
+ aWindow.removeEventListener("MozDOMFullscreen:Exited", this, true);
+ }
+
+ /**
+ * Get the actor where the original fullscreen
+ * enter or exit request comes from.
+ */
+ get requestOrigin() {
+ let chromeBC = this.browsingContext.topChromeWindow?.browsingContext;
+ let requestOrigin = chromeBC?.fullscreenRequestOrigin;
+ return requestOrigin && requestOrigin.get();
+ }
+
+ /**
+ * Store the actor where the original fullscreen
+ * enter or exit request comes from in the top level
+ * browsing context.
+ */
+ set requestOrigin(aActor) {
+ let chromeBC = this.browsingContext.topChromeWindow?.browsingContext;
+ if (!chromeBC) {
+ console.error("not able to get browsingContext for chrome window.");
+ return;
+ }
+
+ if (aActor) {
+ chromeBC.fullscreenRequestOrigin = Cu.getWeakReference(aActor);
+ } else {
+ delete chromeBC.fullscreenRequestOrigin;
+ }
+ }
+
+ hasBeenDestroyed() {
+ if (this._didDestroy) {
+ return true;
+ }
+
+ // The 'didDestroy' callback is not always getting called.
+ // So we can't rely on it here. Instead, we will try to access
+ // the browsing context to judge wether the actor has
+ // been destroyed or not.
+ try {
+ return !this.browsingContext;
+ } catch {
+ return true;
+ }
+ }
+}