Bug 1779559 - Do this in toolkit instead. r=Gijs

This simplifies a bit the tabbrowser/tab switcher code, and makes it
work in all windows.

The WPT failures are due to bug 1780212.

Differential Revision: https://phabricator.services.mozilla.com/D151822
This commit is contained in:
Emilio Cobos Álvarez 2022-07-19 19:25:48 +00:00
parent 1f6f403400
commit 0170e759f8
11 changed files with 134 additions and 66 deletions

View File

@ -42,8 +42,7 @@
Services.els.addSystemEventListener(document, "keydown", this, false);
Services.els.addSystemEventListener(document, "keypress", this, false);
window.addEventListener("sizemodechange", this);
window.addEventListener("occlusionstatechange", this);
document.addEventListener("visibilitychange", this);
window.addEventListener("framefocusrequested", this);
this.tabContainer.init();
@ -1101,9 +1100,7 @@
oldBrowser.removeAttribute("primary");
oldBrowser.docShellIsActive = false;
newBrowser.setAttribute("primary", "true");
newBrowser.docShellIsActive =
window.windowState != window.STATE_MINIMIZED &&
!window.isFullyOccluded;
newBrowser.docShellIsActive = !document.hidden;
}
this._selectedBrowser = newBrowser;
@ -5316,9 +5313,7 @@
return this._switcher.shouldActivateDocShell(aBrowser);
}
return (
(aBrowser == this.selectedBrowser &&
window.windowState != window.STATE_MINIMIZED &&
!window.isFullyOccluded) ||
(aBrowser == this.selectedBrowser && !document.hidden) ||
this._printPreviewBrowsers.has(aBrowser) ||
this.PictureInPicture.isOriginatingBrowser(aBrowser)
);
@ -5633,17 +5628,11 @@
aEvent.preventDefault();
break;
}
case "sizemodechange":
case "occlusionstatechange":
if (aEvent.target == window) {
const inactive =
window.windowState == window.STATE_MINIMIZED ||
window.isFullyOccluded;
window.browsingContext.isActive = !inactive;
if (!this._switcher) {
this.selectedBrowser.preserveLayers(inactive);
this.selectedBrowser.docShellIsActive = !inactive;
}
case "visibilitychange":
const inactive = document.hidden;
if (!this._switcher) {
this.selectedBrowser.preserveLayers(inactive);
this.selectedBrowser.docShellIsActive = !inactive;
}
break;
}
@ -5770,8 +5759,7 @@
false
);
}
window.removeEventListener("sizemodechange", this);
window.removeEventListener("occlusionstatechange", this);
document.removeEventListener("visibilitychange", this);
window.removeEventListener("framefocusrequested", this);
if (gMultiProcessBrowser) {

View File

@ -17,6 +17,15 @@ registerCleanupFunction(async function() {
}
});
add_setup(async () => {
// Disable window occlusion. See bug 1733955 / bug 1779559.
if (navigator.platform.indexOf("Win") == 0) {
await SpecialPowers.pushPrefEnv({
set: [["widget.windows.window_occlusion_tracking.enabled", false]],
});
}
});
add_task(async function checkStateDuringPrefFlips() {
ok(
Services.prefs.getBoolPref(kDownloadAutoHidePref),

View File

@ -158,10 +158,9 @@ class AsyncTabSwitcher {
this.window.addEventListener("MozLayerTreeReady", this);
this.window.addEventListener("MozLayerTreeCleared", this);
this.window.addEventListener("TabRemotenessChange", this);
this.window.addEventListener("sizemodechange", this);
this.window.addEventListener("occlusionstatechange", this);
this.window.addEventListener("SwapDocShells", this, true);
this.window.addEventListener("EndSwapDocShells", this, true);
this.window.document.addEventListener("visibilitychange", this);
let initialTab = this.requestedTab;
let initialBrowser = initialTab.linkedBrowser;
@ -175,7 +174,7 @@ class AsyncTabSwitcher {
// browser. Let's clear it.
initialBrowser.preserveLayers(false);
if (!this.minimizedOrFullyOccluded) {
if (!this.windowHidden) {
this.log("Initial tab is loaded?: " + tabIsLoaded);
this.setTabState(
initialTab,
@ -204,10 +203,9 @@ class AsyncTabSwitcher {
this.window.removeEventListener("MozLayerTreeReady", this);
this.window.removeEventListener("MozLayerTreeCleared", this);
this.window.removeEventListener("TabRemotenessChange", this);
this.window.removeEventListener("sizemodechange", this);
this.window.removeEventListener("occlusionstatechange", this);
this.window.removeEventListener("SwapDocShells", this, true);
this.window.removeEventListener("EndSwapDocShells", this, true);
this.window.document.removeEventListener("visibilitychange", this);
this.tabbrowser._switcher = null;
}
@ -273,7 +271,7 @@ class AsyncTabSwitcher {
let browser = tab.linkedBrowser;
let { remoteTab } = browser.frameLoader;
if (state == this.STATE_LOADING) {
this.assert(!this.minimizedOrFullyOccluded);
this.assert(!this.windowHidden);
// If we're not in the process of warming this tab, we
// don't need to delay activating its DocShell.
@ -317,11 +315,8 @@ class AsyncTabSwitcher {
}
}
get minimizedOrFullyOccluded() {
return (
this.window.windowState == this.window.STATE_MINIMIZED ||
this.window.isFullyOccluded
);
get windowHidden() {
return this.window.document.hidden;
}
get tabLayerCache() {
@ -339,7 +334,7 @@ class AsyncTabSwitcher {
this.assert(!this.loadingTab);
this.assert(this.lastVisibleTab === this.requestedTab);
this.assert(
this.minimizedOrFullyOccluded ||
this.windowHidden ||
this.getTabState(this.requestedTab) == this.STATE_LOADED
);
@ -385,7 +380,7 @@ class AsyncTabSwitcher {
let fl = requestedBrowser.frameLoader;
shouldBeBlank =
!this.minimizedOrFullyOccluded &&
!this.windowHidden &&
(!fl.remoteTab ||
(!hasSufficientlyLoaded && !fl.remoteTab.hasPresented));
@ -393,7 +388,7 @@ class AsyncTabSwitcher {
let flag = shouldBeBlank ? "blank" : "nonblank";
this.addLogFlag(
flag,
this.minimizedOrFullyOccluded,
this.windowHidden,
fl.remoteTab,
isBusy,
isLocalAbout,
@ -439,7 +434,7 @@ class AsyncTabSwitcher {
// Show or hide the spinner as needed.
let needSpinner =
this.getTabState(showTab) != this.STATE_LOADED &&
!this.minimizedOrFullyOccluded &&
!this.windowHidden &&
!shouldBeBlank &&
!this.loadTimer;
@ -525,7 +520,7 @@ class AsyncTabSwitcher {
// We've decided to try to load requestedTab.
loadRequestedTab() {
this.assert(!this.loadTimer);
this.assert(!this.minimizedOrFullyOccluded);
this.assert(!this.windowHidden);
// loadingTab can be non-null here if we timed out loading the current tab.
// In that case we just overwrite it with a different tab; it's had its chance.
@ -553,7 +548,7 @@ class AsyncTabSwitcher {
canCheckDocShellState &&
state == this.STATE_LOADED &&
!browser.docShellIsActive &&
!this.minimizedOrFullyOccluded
!this.windowHidden
) {
browser.docShellIsActive = true;
this.logState(
@ -635,7 +630,7 @@ class AsyncTabSwitcher {
let stateOfRequestedTab = this.getTabState(this.requestedTab);
if (
!this.loadTimer &&
!this.minimizedOrFullyOccluded &&
!this.windowHidden &&
(stateOfRequestedTab == this.STATE_UNLOADED ||
stateOfRequestedTab == this.STATE_UNLOADING ||
this.warmingTabs.has(this.requestedTab))
@ -854,8 +849,8 @@ class AsyncTabSwitcher {
this.lastVisibleTab = null;
}
onSizeModeOrOcclusionStateChange() {
if (this.minimizedOrFullyOccluded) {
onVisibilityChange() {
if (this.windowHidden) {
for (let [tab, state] of this.tabState) {
if (!this.shouldDeactivateDocShell(tab.linkedBrowser)) {
continue;
@ -962,7 +957,7 @@ class AsyncTabSwitcher {
// crashed, already visible, or already requested, warming
// up the tab makes no sense.
if (
this.minimizedOrFullyOccluded ||
this.windowHidden ||
!tab.linkedPanel ||
tab.closing ||
!tab.linkedBrowser.isRemoteBrowser ||
@ -1118,9 +1113,8 @@ class AsyncTabSwitcher {
case "TabRemotenessChange":
this.onRemotenessChange(event.target);
break;
case "sizemodechange":
case "occlusionstatechange":
this.onSizeModeOrOcclusionStateChange();
case "visibilitychange":
this.onVisibilityChange();
break;
case "SwapDocShells":
this.onSwapDocShells(event.originalTarget, event.detail);

View File

@ -116,8 +116,7 @@ let NewTabPagePreloading = {
!this.enabled ||
window.gBrowser.preloadedBrowser ||
!window.toolbar.visible ||
window.windowState == window.STATE_MINIMIZED ||
window.isFullyOccluded
window.document.hidden
) {
return;
}

View File

@ -0,0 +1,7 @@
[onvisibilitychange.html]
bug: Previously-running minimize test leaves window hidden
expected:
if os == "linux": TIMEOUT
[onvisibilitychange attribute is a proper event handler]
expected:
if os == "linux": NOTRUN

View File

@ -0,0 +1,12 @@
[test_child_document.html]
[document.visibilityState for frame with no style attribute == visible]
expected:
if os == "linux": FAIL
[document.visibilityState for frame with 'display:none' style == visible]
expected:
if os == "linux": FAIL
[document.visibilityState for frame with 'visibility:hidden' style == visible]
expected:
if os == "linux": FAIL

View File

@ -0,0 +1,4 @@
[unload-bubbles.html]
expected: TIMEOUT
[visibilitychange event bubbles when fired on unload]
expected: TIMEOUT

View File

@ -0,0 +1,4 @@
[unload.html]
expected: TIMEOUT
[visibilitychange fires on unload]
expected: TIMEOUT

View File

@ -16,13 +16,17 @@
</pre>
</body>
<script class="testbody" type="application/javascript">
<script class="testbody">
<![CDATA[
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
let gWindow = null;
const kIsLinux = navigator.platform.includes("Lin");
// On Linux sizemode changes might be async.
const kAsyncChanges = kIsLinux;
let gSizeModeDidChange = false;
let gSizeModeDidChangeTo = 0;
@ -31,11 +35,28 @@ function sizemodeChanged(e) {
gSizeModeDidChangeTo = gWindow.windowState;
}
function expectSizeModeChange(newMode, duringActionCallback) {
async function expectSizeModeChange(newMode, duringActionCallback) {
gSizeModeDidChange = false;
let promise = null;
if (kAsyncChanges) {
if (newMode) {
promise = new Promise(resolve => {
gWindow.addEventListener("sizemodechange", function() {
SimpleTest.executeSoon(resolve);
}, { once: true })
});
} else {
promise = new Promise(SimpleTest.executeSoon);
}
}
duringActionCallback();
if (promise) {
await promise;
}
if (newMode == 0) {
// No change should have taken place, no event should have fired.
ok(!gSizeModeDidChange, "No sizemodechange event should have fired.");
@ -43,15 +64,17 @@ function expectSizeModeChange(newMode, duringActionCallback) {
// Size mode change event was expected to fire.
ok(gSizeModeDidChange, "A sizemodechanged event should have fired.");
is(gSizeModeDidChangeTo, newMode, "The new sizemode should have the expected value.");
const expectedHidden = newMode == gWindow.STATE_MINIMIZED || gWindow.isFullyOccluded;
if (gWindow.document.hidden != expectedHidden) {
await new Promise(resolve => {
gWindow.addEventListener("visibilitychange", resolve, { once: true });
});
}
is(gWindow.document.hidden, expectedHidden, "Should be inactive if minimized or occluded.");
}
}
function startTest() {
if (navigator.platform.includes("Lin")) {
ok(true, "This test is disabled on Linux because it expects window sizemode changes to be synchronous (which is not the case on Linux).");
SimpleTest.finish();
return;
};
openWindow();
}
@ -61,33 +84,38 @@ function openWindow() {
SimpleTest.waitForFocus(runTest, gWindow);
}
function runTest() {
async function runTest() {
if (kIsLinux) {
todo(false, "Test times out on automation because bug 1780212");
SimpleTest.finish();
return;
}
// Install event handler.
gWindow.addEventListener("sizemodechange", sizemodeChanged);
// Run tests.
expectSizeModeChange(gWindow.STATE_MINIMIZED, function () {
await expectSizeModeChange(gWindow.STATE_MINIMIZED, function () {
gWindow.minimize();
});
expectSizeModeChange(gWindow.STATE_NORMAL, function () {
await expectSizeModeChange(gWindow.STATE_NORMAL, function () {
gWindow.restore();
});
expectSizeModeChange(gWindow.STATE_MAXIMIZED, function () {
await expectSizeModeChange(gWindow.STATE_MAXIMIZED, function () {
gWindow.maximize();
});
expectSizeModeChange(gWindow.STATE_NORMAL, function () {
await expectSizeModeChange(gWindow.STATE_NORMAL, function () {
gWindow.restore();
});
// Normal window resizing shouldn't fire a sizemodechanged event, bug 715867.
expectSizeModeChange(0, function () {
await expectSizeModeChange(0, function () {
gWindow.resizeTo(gWindow.outerWidth + 10, gWindow.outerHeight);
});
expectSizeModeChange(0, function () {
await expectSizeModeChange(0, function () {
gWindow.resizeTo(gWindow.outerWidth, gWindow.outerHeight + 10);
});

View File

@ -2822,6 +2822,8 @@ void AppWindow::SizeModeChanged(nsSizeMode sizeMode) {
}
mWindow->SetSizeMode(sizeMode);
RecomputeBrowsingContextVisibility();
// Persist mode, but not immediately, because in many (all?)
// cases this will merge with the similar call in NS_SIZE and
// write the attribute values only once.
@ -2927,13 +2929,33 @@ void AppWindow::MacFullscreenMenubarOverlapChanged(
}
}
void AppWindow::RecomputeBrowsingContextVisibility() {
if (!mDocShell) {
return;
}
if (RefPtr bc = mDocShell->GetBrowsingContext()) {
nsCOMPtr<nsIWidget> widget;
mDocShell->GetMainWidget(getter_AddRefs(widget));
const bool isActive = [&] {
if (!widget) {
return false;
}
return widget->SizeMode() != nsSizeMode_Minimized &&
!widget->IsFullyOccluded();
}();
bc->SetIsActive(isActive, IgnoreErrors());
}
}
void AppWindow::OcclusionStateChanged(bool aIsFullyOccluded) {
nsCOMPtr<nsPIDOMWindowOuter> ourWindow =
mDocShell ? mDocShell->GetWindow() : nullptr;
if (ourWindow) {
if (!mDocShell) {
return;
}
RecomputeBrowsingContextVisibility();
if (RefPtr win = mDocShell->GetWindow()) {
// And always fire a user-defined occlusionstatechange event on the window
ourWindow->DispatchCustomEvent(u"occlusionstatechange"_ns,
ChromeOnlyDispatch::eYes);
win->DispatchCustomEvent(u"occlusionstatechange"_ns,
ChromeOnlyDispatch::eYes);
}
}

View File

@ -170,6 +170,7 @@ class AppWindow final : public nsIBaseWindow,
MOZ_CAN_RUN_SCRIPT void MacFullscreenMenubarOverlapChanged(
mozilla::DesktopCoord aOverlapAmount);
MOZ_CAN_RUN_SCRIPT void OcclusionStateChanged(bool aIsFullyOccluded);
void RecomputeBrowsingContextVisibility();
MOZ_CAN_RUN_SCRIPT void OSToolbarButtonPressed();
MOZ_CAN_RUN_SCRIPT
bool ZLevelChanged(bool aImmediate, nsWindowZ* aPlacement,