mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-15 22:35:43 +00:00
Bug 1739220 - Handle fullscreen state in a more reliable way; r=smaug,Gijs
Differential Revision: https://phabricator.services.mozilla.com/D131185
This commit is contained in:
parent
73230b8c3e
commit
f9a4a6bd64
@ -11,26 +11,24 @@ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
class DOMFullscreenChild extends JSWindowActorChild {
|
||||
receiveMessage(aMessage) {
|
||||
let window = this.contentWindow;
|
||||
if (!window) {
|
||||
if (!aMessage.data.remoteFrameBC) {
|
||||
this.sendAsyncMessage("DOMFullscreen:Exit", {});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let windowUtils = window.windowUtils;
|
||||
if (!windowUtils) {
|
||||
return;
|
||||
}
|
||||
let windowUtils = window?.windowUtils;
|
||||
|
||||
switch (aMessage.name) {
|
||||
case "DOMFullscreen:Entered": {
|
||||
if (!windowUtils) {
|
||||
// If we are not able to enter fullscreen, tell the parent to just
|
||||
// exit.
|
||||
this.sendAsyncMessage("DOMFullscreen:Exit", {});
|
||||
break;
|
||||
}
|
||||
|
||||
let remoteFrameBC = aMessage.data.remoteFrameBC;
|
||||
if (remoteFrameBC) {
|
||||
let remoteFrame = remoteFrameBC.embedderElement;
|
||||
this._isNotTheRequestSource = true;
|
||||
windowUtils.remoteFrameFullscreenChanged(remoteFrame);
|
||||
} else {
|
||||
this._waitForMozAfterPaint = true;
|
||||
this._lastTransactionId = windowUtils.lastTransactionId;
|
||||
if (
|
||||
!windowUtils.handleFullscreenRequests() &&
|
||||
@ -45,23 +43,39 @@ class DOMFullscreenChild extends JSWindowActorChild {
|
||||
break;
|
||||
}
|
||||
case "DOMFullscreen:CleanUp": {
|
||||
let remoteFrameBC = aMessage.data.remoteFrameBC;
|
||||
if (remoteFrameBC) {
|
||||
this._isNotTheRequestSource = true;
|
||||
}
|
||||
|
||||
let isNotTheRequestSource = !!aMessage.data.remoteFrameBC;
|
||||
// If we've exited fullscreen at this point, no need to record
|
||||
// transaction id or call exit fullscreen. This is especially
|
||||
// important for pre-e10s, since in that case, it is possible
|
||||
// that no more paint would be triggered after this point.
|
||||
if (this.document.fullscreenElement) {
|
||||
this._lastTransactionId = windowUtils.lastTransactionId;
|
||||
windowUtils.exitFullscreen();
|
||||
this._isNotTheRequestSource = isNotTheRequestSource;
|
||||
// Need to wait for the MozAfterPaint after exiting fullscreen if
|
||||
// this is the request source.
|
||||
this._waitForMozAfterPaint = !this._isNotTheRequestSource;
|
||||
// windowUtils could be null if the associated window is not current
|
||||
// active window. In this case, document must be in the process of
|
||||
// exiting fullscreen, it is okay to not ask it to exit fullscreen.
|
||||
if (windowUtils) {
|
||||
this._lastTransactionId = windowUtils.lastTransactionId;
|
||||
windowUtils.exitFullscreen();
|
||||
}
|
||||
} else if (isNotTheRequestSource) {
|
||||
// If we are not the request source and have exited fullscreen, reply
|
||||
// Exited to parent as parent is waiting for our reply.
|
||||
this.sendAsyncMessage("DOMFullscreen:Exited", {});
|
||||
} else {
|
||||
// If we've already exited fullscreen, it is possible that no more
|
||||
// paint would be triggered, so don't wait for MozAfterPaint.
|
||||
// TODO: There might be some way to move this code around a bit to
|
||||
// make it easier to follow. Somehow handle the "local" case in
|
||||
// one place and the isNotTheRequestSource case after that.
|
||||
this.sendAsyncMessage("DOMFullscreen:Painted", {});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "DOMFullscreen:Painted": {
|
||||
Services.obs.notifyObservers(this.contentWindow, "fullscreen-painted");
|
||||
Services.obs.notifyObservers(window, "fullscreen-painted");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -99,15 +113,20 @@ class DOMFullscreenChild extends JSWindowActorChild {
|
||||
|
||||
delete this._isNotTheRequestSource;
|
||||
this.sendAsyncMessage(aEvent.type.replace("Moz", ""), {});
|
||||
} else {
|
||||
let rootWindow = this.contentWindow.windowRoot;
|
||||
rootWindow.addEventListener("MozAfterPaint", this);
|
||||
if (!this.document || !this.document.fullscreenElement) {
|
||||
// If we receive any fullscreen change event, and find we are
|
||||
// actually not in fullscreen, also ask the parent to exit to
|
||||
// ensure that the parent always exits fullscreen when we do.
|
||||
this.sendAsyncMessage("DOMFullscreen:Exit", {});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (this._waitForMozAfterPaint) {
|
||||
delete this._waitForMozAfterPaint;
|
||||
this._listeningWindow = this.contentWindow.windowRoot;
|
||||
this._listeningWindow.addEventListener("MozAfterPaint", this);
|
||||
}
|
||||
|
||||
if (!this.document || !this.document.fullscreenElement) {
|
||||
// If we receive any fullscreen change event, and find we are
|
||||
// actually not in fullscreen, also ask the parent to exit to
|
||||
// ensure that the parent always exits fullscreen when we do.
|
||||
this.sendAsyncMessage("DOMFullscreen:Exit", {});
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -120,8 +139,8 @@ class DOMFullscreenChild extends JSWindowActorChild {
|
||||
!this._lastTransactionId ||
|
||||
aEvent.transactionId > this._lastTransactionId
|
||||
) {
|
||||
let rootWindow = this.contentWindow.windowRoot;
|
||||
rootWindow.removeEventListener("MozAfterPaint", this);
|
||||
this._listeningWindow.removeEventListener("MozAfterPaint", this);
|
||||
delete this._listeningWindow;
|
||||
this.sendAsyncMessage("DOMFullscreen:Painted", {});
|
||||
}
|
||||
break;
|
||||
|
@ -9,7 +9,19 @@ var EXPORTED_SYMBOLS = ["DOMFullscreenParent"];
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
class DOMFullscreenParent extends JSWindowActorParent {
|
||||
waitingForChildFullscreen = false;
|
||||
// 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")) {
|
||||
@ -19,35 +31,71 @@ class DOMFullscreenParent extends JSWindowActorParent {
|
||||
}
|
||||
}
|
||||
|
||||
didDestroy() {
|
||||
let window = this._fullscreenWindow;
|
||||
if (!window) {
|
||||
cleanupDomFullscreen(aWindow) {
|
||||
if (!aWindow.FullScreen) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.waitingForChildFullscreen) {
|
||||
// We were killed 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(() => {});
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
|
||||
didDestroy() {
|
||||
this._didDestroy = true;
|
||||
|
||||
let window = this._fullscreenWindow;
|
||||
if (!window) {
|
||||
if (this.waitingForChildExitFullscreen) {
|
||||
this.waitingForChildExitFullscreen = false;
|
||||
// We were destroyed while waiting for our DOMFullscreenChild to exit
|
||||
// and have exited fullscreen, run cleanup steps anyway.
|
||||
let topBrowsingContext = this.browsingContext.top;
|
||||
let browser = topBrowsingContext.embedderElement;
|
||||
if (browser) {
|
||||
this.cleanupDomFullscreen(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")) {
|
||||
if (window.FullScreen) {
|
||||
window.FullScreen.cleanupDomFullscreen(this);
|
||||
}
|
||||
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 and
|
||||
// have exited fullscreen, run cleanup steps anyway.
|
||||
this.cleanupDomFullscreen(window);
|
||||
}
|
||||
this.updateFullscreenWindowReference(window);
|
||||
}
|
||||
@ -65,6 +113,8 @@ class DOMFullscreenParent extends JSWindowActorParent {
|
||||
let window = browser.ownerGlobal;
|
||||
switch (aMessage.name) {
|
||||
case "DOMFullscreen:Request": {
|
||||
this.waitingForChildExitFullscreen = false;
|
||||
this.nextMsgRecipient = null;
|
||||
this.requestOrigin = this;
|
||||
this.addListeners(window);
|
||||
window.windowUtils.remoteFrameFullscreenChanged(browser);
|
||||
@ -77,24 +127,29 @@ class DOMFullscreenParent extends JSWindowActorParent {
|
||||
aMessage.data.originNoSuffix
|
||||
);
|
||||
}
|
||||
this.updateFullscreenWindowReference(window);
|
||||
break;
|
||||
}
|
||||
case "DOMFullscreen:Entered": {
|
||||
this.waitingForChildFullscreen = false;
|
||||
this.nextMsgRecipient = null;
|
||||
this.waitingForChildEnterFullscreen = false;
|
||||
window.FullScreen.enterDomFullscreen(browser, this);
|
||||
this.updateFullscreenWindowReference(window);
|
||||
break;
|
||||
}
|
||||
case "DOMFullscreen:Exit": {
|
||||
this.waitingForChildEnterFullscreen = false;
|
||||
window.windowUtils.remoteFrameFullscreenReverted();
|
||||
break;
|
||||
}
|
||||
case "DOMFullscreen:Exited": {
|
||||
window.FullScreen.cleanupDomFullscreen(this);
|
||||
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");
|
||||
@ -143,7 +198,7 @@ class DOMFullscreenParent extends JSWindowActorParent {
|
||||
if (!this.hasBeenDestroyed() && !this.requestOrigin) {
|
||||
this.requestOrigin = this;
|
||||
}
|
||||
window.FullScreen.cleanupDomFullscreen(this);
|
||||
this.cleanupDomFullscreen(window);
|
||||
this.updateFullscreenWindowReference(window);
|
||||
this.removeListeners(window);
|
||||
break;
|
||||
@ -197,6 +252,10 @@ class DOMFullscreenParent extends JSWindowActorParent {
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -478,10 +478,10 @@ var FullScreen = {
|
||||
remoteFrameBC: inProcessBC,
|
||||
});
|
||||
|
||||
// Record that the actor is waiting for its child to enter
|
||||
// fullscreen so that if it dies we can abort.
|
||||
targetActor.waitingForChildFullscreen = true;
|
||||
if (inProcessBC) {
|
||||
// Record that the actor is waiting for its child to enter
|
||||
// fullscreen so that if it dies we can abort.
|
||||
targetActor.waitingForChildEnterFullscreen = true;
|
||||
// We aren't messaging the request origin yet, skip this time.
|
||||
return;
|
||||
}
|
||||
@ -557,13 +557,20 @@ var FullScreen = {
|
||||
* the cleanup.
|
||||
*/
|
||||
cleanupDomFullscreen(aActor) {
|
||||
let needToWaitForChildExit = false;
|
||||
let [target, inProcessBC] = this._getNextMsgRecipientActor(aActor);
|
||||
if (target) {
|
||||
target.sendAsyncMessage("DOMFullscreen:CleanUp", {
|
||||
remoteFrameBC: inProcessBC,
|
||||
});
|
||||
needToWaitForChildExit = true;
|
||||
if (!target.waitingForChildExitFullscreen) {
|
||||
// Record that the actor is waiting for its child to exit fullscreen so
|
||||
// that if it dies we can continue cleanup.
|
||||
target.waitingForChildExitFullscreen = true;
|
||||
target.sendAsyncMessage("DOMFullscreen:CleanUp", {
|
||||
remoteFrameBC: inProcessBC,
|
||||
});
|
||||
}
|
||||
if (inProcessBC) {
|
||||
return;
|
||||
return needToWaitForChildExit;
|
||||
}
|
||||
}
|
||||
|
||||
@ -580,12 +587,17 @@ var FullScreen = {
|
||||
);
|
||||
|
||||
document.documentElement.removeAttribute("inDOMFullscreen");
|
||||
|
||||
return needToWaitForChildExit;
|
||||
},
|
||||
|
||||
_abortEnterFullscreen() {
|
||||
// This function is called synchronously in fullscreen change, so
|
||||
// we have to avoid calling exitFullscreen synchronously here.
|
||||
setTimeout(() => document.exitFullscreen(), 0);
|
||||
//
|
||||
// This could reject if we're not currently in fullscreen
|
||||
// so just ignore rejection.
|
||||
setTimeout(() => document.exitFullscreen().catch(() => {}), 0);
|
||||
if (TelemetryStopwatch.running("FULLSCREEN_CHANGE_MS")) {
|
||||
// Cancel the stopwatch for any fullscreen change to avoid
|
||||
// errors if it is started again.
|
||||
@ -607,9 +619,27 @@ var FullScreen = {
|
||||
* in process browsing context which is its child. Will be
|
||||
* [null, null] if there is no OOP parent actor and request origin
|
||||
* is unset. [null, null] is also returned if the intended actor or
|
||||
* the calling actor has been destroyed.
|
||||
* the calling actor has been destroyed or its associated
|
||||
* WindowContext is in BFCache.
|
||||
*/
|
||||
_getNextMsgRecipientActor(aActor) {
|
||||
// Walk up the cached nextMsgRecipient to find the next available actor if
|
||||
// any.
|
||||
if (aActor.nextMsgRecipient) {
|
||||
let nextMsgRecipient = aActor.nextMsgRecipient;
|
||||
while (nextMsgRecipient) {
|
||||
let [actor] = nextMsgRecipient;
|
||||
if (
|
||||
!actor.hasBeenDestroyed() &&
|
||||
actor.windowContext &&
|
||||
!actor.windowContext.isInBFCache
|
||||
) {
|
||||
return nextMsgRecipient;
|
||||
}
|
||||
nextMsgRecipient = actor.nextMsgRecipient;
|
||||
}
|
||||
}
|
||||
|
||||
if (aActor.hasBeenDestroyed()) {
|
||||
return [null, null];
|
||||
}
|
||||
@ -640,11 +670,16 @@ var FullScreen = {
|
||||
if (parentBC && parentBC.currentWindowGlobal) {
|
||||
target = parentBC.currentWindowGlobal.getActor("DOMFullscreen");
|
||||
inProcessBC = childBC;
|
||||
aActor.nextMsgRecipient = [target, inProcessBC];
|
||||
} else {
|
||||
target = aActor.requestOrigin;
|
||||
}
|
||||
|
||||
if (!target || target.hasBeenDestroyed()) {
|
||||
if (
|
||||
!target ||
|
||||
target.hasBeenDestroyed() ||
|
||||
target.windowContext?.isInBFCache
|
||||
) {
|
||||
return [null, null];
|
||||
}
|
||||
return [target, inProcessBC];
|
||||
|
Loading…
Reference in New Issue
Block a user