mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 21:01:08 +00:00
80e8eb7818
This change makes the parent process delay a fullscreen request if there is a pending fullscreen exit. It also changes the DOMFullscreenParent actor listener lifecycle. Once it has started handling a fullscreen request, it will remain a listener to the Document until it receives an exit event when the manager is out of fullscreen. Differential Revision: https://phabricator.services.mozilla.com/D175186
319 lines
11 KiB
JavaScript
319 lines
11 KiB
JavaScript
/* 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;
|
|
}
|
|
}
|
|
}
|