Bug 1236512 - Part 3: Set docShellIsActive to false when browser window is fully covered by another application; r=mconley

MozReview-Commit-ID: DLsmWp1h8pa
This commit is contained in:
Edgar Chen 2017-06-05 14:56:00 +08:00
Родитель 7a94f225de
Коммит 2ae9d877a0
6 изменённых файлов: 161 добавлений и 22 удалений

Просмотреть файл

@ -1111,7 +1111,8 @@
oldBrowser.docShellIsActive = false;
newBrowser.setAttribute("primary", "true");
newBrowser.docShellIsActive =
(window.windowState != window.STATE_MINIMIZED);
(window.windowState != window.STATE_MINIMIZED &&
!window.isFullyOccluded);
}
var updateBlockedPopups = false;
@ -3885,7 +3886,8 @@
return this._switcher.shouldActivateDocShell(aBrowser);
}
return (aBrowser == this.selectedBrowser &&
window.windowState != window.STATE_MINIMIZED) ||
window.windowState != window.STATE_MINIMIZED &&
!window.isFullyOccluded) ||
this._printPreviewBrowsers.has(aBrowser);
]]>
</body>
@ -4035,7 +4037,7 @@
let browser = tab.linkedBrowser;
let {tabParent} = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
if (state == this.STATE_LOADING) {
this.assert(!this.minimized);
this.assert(!this.minimizedOrFullyOccluded);
browser.docShellIsActive = true;
if (!tabParent) {
this.onLayersReady(browser);
@ -4061,8 +4063,9 @@
}
},
get minimized() {
return window.windowState == window.STATE_MINIMIZED;
get minimizedOrFullyOccluded() {
return window.windowState == window.STATE_MINIMIZED ||
window.isFullyOccluded;
},
init() {
@ -4078,6 +4081,7 @@
window.addEventListener("MozLayerTreeCleared", this);
window.addEventListener("TabRemotenessChange", this);
window.addEventListener("sizemodechange", this);
window.addEventListener("occlusionstatechange", this);
window.addEventListener("SwapDocShells", this, true);
window.addEventListener("EndSwapDocShells", this, true);
@ -4086,7 +4090,7 @@
let tabIsLoaded = !browser.isRemoteBrowser ||
browser.frameLoader.tabParent.hasPresented;
if (!this.minimized) {
if (!this.minimizedOrFullyOccluded) {
this.log("Initial tab is loaded?: " + tabIsLoaded);
this.setTabState(tab, tabIsLoaded ? this.STATE_LOADED
: this.STATE_LOADING);
@ -4108,6 +4112,7 @@
window.removeEventListener("MozLayerTreeCleared", this);
window.removeEventListener("TabRemotenessChange", this);
window.removeEventListener("sizemodechange", this);
window.removeEventListener("occlusionstatechange", this);
window.removeEventListener("SwapDocShells", this, true);
window.removeEventListener("EndSwapDocShells", this, true);
@ -4129,7 +4134,8 @@
this.assert(!this.loadTimer);
this.assert(!this.loadingTab);
this.assert(this.lastVisibleTab === this.requestedTab);
this.assert(this.minimized || this.getTabState(this.requestedTab) == this.STATE_LOADED);
this.assert(this.minimizedOrFullyOccluded ||
this.getTabState(this.requestedTab) == this.STATE_LOADED);
this.destroy();
@ -4182,7 +4188,7 @@
requestedBrowser.currentURI.spec != "about:blank";
let fl = requestedBrowser.frameLoader;
shouldBeBlank = !this.minimized &&
shouldBeBlank = !this.minimizedOrFullyOccluded &&
(!fl.tabParent ||
(!hasSufficientlyLoaded && !fl.tabParent.hasPresented));
}
@ -4218,7 +4224,7 @@
// Show or hide the spinner as needed.
let needSpinner = this.getTabState(showTab) != this.STATE_LOADED &&
!this.minimized &&
!this.minimizedOrFullyOccluded &&
!shouldBeBlank;
if (!needSpinner && this.spinnerTab) {
@ -4280,7 +4286,7 @@
// We've decided to try to load requestedTab.
loadRequestedTab() {
this.assert(!this.loadTimer);
this.assert(!this.minimized);
this.assert(!this.minimizedOrFullyOccluded);
// 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.
@ -4349,7 +4355,7 @@
// If we're not loading anything, try loading the requested tab.
let requestedState = this.getTabState(this.requestedTab);
if (!this.loadTimer && !this.minimized &&
if (!this.loadTimer && !this.minimizedOrFullyOccluded &&
(requestedState == this.STATE_UNLOADED ||
requestedState == this.STATE_UNLOADING)) {
this.loadRequestedTab();
@ -4504,8 +4510,8 @@
}
},
onSizeModeChange() {
if (this.minimized) {
onSizeModeOrOcclusionStateChange() {
if (this.minimizedOrFullyOccluded) {
for (let [tab, state] of this.tabState) {
// Skip print preview browsers since they shouldn't affect tab switching.
if (this.tabbrowser._printPreviewBrowsers.has(tab.linkedBrowser)) {
@ -4639,8 +4645,9 @@
this.onLayersCleared(event.originalTarget);
} else if (event.type == "TabRemotenessChange") {
this.onRemotenessChange(event.target);
} else if (event.type == "sizemodechange") {
this.onSizeModeChange();
} else if (event.type == "sizemodechange" ||
event.type == "occlusionstatechange") {
this.onSizeModeOrOcclusionStateChange();
} else if (event.type == "SwapDocShells") {
this.onSwapDocShells(event.originalTarget, event.detail);
} else if (event.type == "EndSwapDocShells") {
@ -5115,8 +5122,10 @@
this._handleKeyPressEventMac(aEvent);
break;
case "sizemodechange":
case "occlusionstatechange":
if (aEvent.target == window && !this._switcher) {
this.mCurrentBrowser.preserveLayers(window.windowState == window.STATE_MINIMIZED);
this.mCurrentBrowser.preserveLayers(
window.windowState == window.STATE_MINIMIZED || window.isFullyOccluded);
this.mCurrentBrowser.docShellIsActive = this.shouldActivateDocShell(this.mCurrentBrowser);
}
break;
@ -5343,6 +5352,7 @@
els.addSystemEventListener(document, "keypress", this, false);
}
window.addEventListener("sizemodechange", this);
window.addEventListener("occlusionstatechange", this);
var uniqueId = this._generateUniquePanelID();
this.mPanelContainer.childNodes[0].id = uniqueId;
@ -5463,6 +5473,7 @@
els.removeSystemEventListener(document, "keypress", this, false);
}
window.removeEventListener("sizemodechange", this);
window.removeEventListener("occlusionstatechange", this);
if (gMultiProcessBrowser) {
let messageManager = window.getGroupMessageManager("browsers");

Просмотреть файл

@ -17,6 +17,9 @@ function frameScript() {
inFullscreen: content.fullScreen
});
});
addMessageListener("Test:WaitActivated", () => {
waitUntilActive();
});
content.document.addEventListener("fullscreenchange", () => {
sendAsyncMessage("Test:FullscreenChanged", {
inDOMFullscreen: !!content.document.fullscreenElement,
@ -65,6 +68,13 @@ const FS_CHANGE_DOM = 1 << 0;
const FS_CHANGE_SIZE = 1 << 1;
const FS_CHANGE_BOTH = FS_CHANGE_DOM | FS_CHANGE_SIZE;
function waitForDocActivated() {
return new Promise(resolve => {
listenOneMessage("Test:Activated", resolve);
gMessageManager.sendAsyncMessage("Test:WaitActivated");
});
}
function waitForFullscreenChanges(aFlags) {
return new Promise(resolve => {
let fullscreenData = null;
@ -72,11 +82,18 @@ function waitForFullscreenChanges(aFlags) {
function tryResolve() {
if ((!(aFlags & FS_CHANGE_DOM) || fullscreenData) &&
(!(aFlags & FS_CHANGE_SIZE) || sizemodeChanged)) {
if (!fullscreenData) {
queryFullscreenState().then(resolve);
} else {
resolve(fullscreenData);
}
// In the platforms that support reporting occlusion state (e.g. Mac),
// enter/exit fullscreen mode will trigger docshell being set to
// non-activate and then set to activate back again.
// For those platform, we should wait until the docshell has been
// activated again, otherwise, the fullscreen request might be denied.
waitForDocActivated().then(() => {
if (!fullscreenData) {
queryFullscreenState().then(resolve);
} else {
resolve(fullscreenData);
}
});
}
}
if (aFlags & FS_CHANGE_SIZE) {

Просмотреть файл

@ -80,6 +80,16 @@ function shouldSkipTest(test) {
return false;
}
function waitForEvent(eventTarget, eventName, checkFn, callback) {
eventTarget.addEventListener(eventName, function listener(event) {
if (checkFn && !checkFn(event)) {
return;
}
eventTarget.removeEventListener(eventName, listener);
callback();
});
}
function runNextTest() {
if (gTestIndex < gTestWindows.length) {
let test = gTestWindows[gTestIndex];
@ -95,7 +105,18 @@ function runNextTest() {
// OS X Lion before we run the test.
testWindow.addEventListener("load", function() {
SimpleTest.waitForFocus(function() {
SimpleTest.waitForFocus(testWindow.begin, testWindow);
SimpleTest.waitForFocus(function() {
// For the platforms that support reporting occlusion state (e.g. Mac),
// we should wait until the docshell has been activated again,
// otherwise, the fullscreen request might be denied.
if (testWindow.document.hidden) {
waitForEvent(testWindow.document, "visibilitychange", (event) => {
return !testWindow.document.hidden;
}, testWindow.begin);
return;
}
testWindow.begin();
}, testWindow);
});
}, {once: true});
}

Просмотреть файл

@ -28,6 +28,8 @@ skip-if = !e10s
[browser_bug1004814.js]
[browser_bug1008941_dismissGeolocationHanger.js]
tags = geolocation
[browser_bug1236512.js]
skip-if = os != "mac"
[browser_bug1238427.js]
[browser_bug1316330.js]
skip-if = !e10s

Просмотреть файл

@ -0,0 +1,80 @@
/**
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
"use strict";
const testPageURL = "http://mochi.test:8888/browser/dom/tests/browser/dummy.html";
async function testContentVisibilityState(aIsHidden, aBrowser) {
await ContentTask.spawn(aBrowser.selectedBrowser, aIsHidden, (aExpectedResult) => {
is(content.document.hidden, aExpectedResult, "document.hidden");
is(content.document.visibilityState, aExpectedResult ? "hidden" : "visible",
"document.visibilityState");
});
}
async function waitContentVisibilityChange(aIsHidden, aBrowser) {
await ContentTask.spawn(aBrowser.selectedBrowser, aIsHidden,
async function(aExpectedResult) {
let visibilityState = aExpectedResult ? "hidden" : "visible";
if (content.document.hidden === aExpectedResult &&
content.document.visibilityState === visibilityState) {
ok(true, "already changed to expected visibility state");
return;
}
info("wait visibilitychange event");
await ContentTaskUtils.waitForEvent(content.document, "visibilitychange",
true /* capture */, (aEvent) => {
info(`visibilitychange: ${content.document.hidden} ${content.document.visibilityState}`);
return content.document.hidden === aExpectedResult &&
content.document.visibilityState === visibilityState;
});
});
}
/**
* This test is to test the visibility state will change to "hidden" when browser
* window is fully covered by another non-translucent application. Note that we
* only support this on Mac for now, other platforms don't support reporting
* occlusion state.
*/
add_task(async function() {
info("creating test window");
// Specify the width, height, left and top, so that the new window can be
// fully covered by "window".
let winTest = await BrowserTestUtils.openNewBrowserWindow({ width: 500,
height: 500,
left: 200,
top: 200 });
let browserTest = winTest.gBrowser;
info(`loading test page: ${testPageURL}`);
browserTest.selectedBrowser.loadURI(testPageURL);
await BrowserTestUtils.browserLoaded(browserTest.selectedBrowser);
info("test init visibility state");
await testContentVisibilityState(false /* isHidden */, browserTest);
info("test window should report 'hidden' if it is fully covered by another " +
"window");
await new Promise(resolve => waitForFocus(resolve, window));
await waitContentVisibilityChange(true /* isHidden */, browserTest);
info("test window should still report 'hidden' since it is still fully covered " +
"by another window");
let tab = BrowserTestUtils.addTab(browserTest);
await BrowserTestUtils.switchTab(browserTest, tab);
await BrowserTestUtils.removeTab(browserTest.selectedTab);
await testContentVisibilityState(true /* isHidden */, browserTest);
info("test window should report 'visible' if it is not fully covered by " +
"another window");
await new Promise(resolve => waitForFocus(resolve, winTest));
await waitContentVisibilityChange(false /* isHidden */, browserTest);
info("closing test window");
await BrowserTestUtils.closeWindow(winTest);
});

Просмотреть файл

@ -605,6 +605,14 @@ this.BrowserTestUtils = {
features += ",height=" + options.height;
}
if (options.left) {
features += ",left=" + options.left;
}
if (options.top) {
features += ",top=" + options.top;
}
if (options.hasOwnProperty("remote")) {
let remoteState = options.remote ? "remote" : "non-remote";
features += `,${remoteState}`;