Bug 1755748 - Keep PiP tabs in active state. r=mconley,dthayer

Differential Revision: https://phabricator.services.mozilla.com/D140394
This commit is contained in:
Niklas Baumgardner 2022-03-10 23:33:26 +00:00
parent 3f93068a72
commit 8df33a3b36
5 changed files with 142 additions and 6 deletions

View File

@ -63,6 +63,7 @@
XPCOMUtils.defineLazyModuleGetters(this, {
E10SUtils: "resource://gre/modules/E10SUtils.jsm",
PictureInPicture: "resource://gre/modules/PictureInPicture.jsm",
});
XPCOMUtils.defineLazyServiceGetters(this, {
MacSharingService: [
@ -5290,7 +5291,8 @@
(aBrowser == this.selectedBrowser &&
window.windowState != window.STATE_MINIMIZED &&
!window.isFullyOccluded) ||
this._printPreviewBrowsers.has(aBrowser)
this._printPreviewBrowsers.has(aBrowser) ||
this.PictureInPicture.isOriginatingBrowser(aBrowser)
);
},

View File

@ -12,6 +12,7 @@ const { XPCOMUtils } = ChromeUtils.import(
);
XPCOMUtils.defineLazyModuleGetters(this, {
AppConstants: "resource://gre/modules/AppConstants.jsm",
PictureInPicture: "resource://gre/modules/PictureInPicture.jsm",
Services: "resource://gre/modules/Services.jsm",
});
@ -649,8 +650,7 @@ class AsyncTabSwitcher {
let numPending = 0;
let numWarming = 0;
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)) {
if (!this.shouldDeactivateDocShell(tab.linkedBrowser)) {
continue;
}
@ -726,7 +726,7 @@ class AsyncTabSwitcher {
// Unload any tabs that can be unloaded.
for (let [tab, state] of this.tabState) {
if (this.tabbrowser._printPreviewBrowsers.has(tab.linkedBrowser)) {
if (!this.shouldDeactivateDocShell(tab.linkedBrowser)) {
continue;
}
@ -852,8 +852,7 @@ class AsyncTabSwitcher {
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)) {
if (!this.shouldDeactivateDocShell(tab.linkedBrowser)) {
continue;
}
@ -915,6 +914,19 @@ class AsyncTabSwitcher {
}
}
/**
* Check if the browser should be deactivated. If the browser is a print preivew or
* PiP browser then we won't deactive it.
* @param browser The browser to check if it should be deactivated
* @returns false if a print preview or PiP browser else true
*/
shouldDeactivateDocShell(browser) {
return !(
this.tabbrowser._printPreviewBrowsers.has(browser) ||
PictureInPicture.isOriginatingBrowser(browser)
);
}
shouldActivateDocShell(browser) {
let tab = this.tabbrowser.getTabForBrowser(browser);
let state = this.getTabState(tab);
@ -1220,6 +1232,8 @@ class AsyncTabSwitcher {
let linkedBrowser = tab.linkedBrowser;
let isActive = linkedBrowser && linkedBrowser.docShellIsActive;
let isRendered = linkedBrowser && linkedBrowser.renderLayers;
let isPiP =
linkedBrowser && PictureInPicture.isOriginatingBrowser(linkedBrowser);
if (tab === this.lastVisibleTab) {
tabString += "V";
@ -1253,6 +1267,9 @@ class AsyncTabSwitcher {
if (isRendered) {
extraStates += "R";
}
if (isPiP) {
extraStates += "P";
}
if (extraStates != "") {
tabString += `(${extraStates})`;
}

View File

@ -148,6 +148,9 @@ var PictureInPicture = {
// Maps PiP player windows to their originating content's browser
weakWinToBrowser: new WeakMap(),
// Maps a browser to the number of PiP windows it has
browserWeakMap: new WeakMap(),
/**
* Returns the player window if one exists and if it hasn't yet been closed.
*
@ -173,12 +176,39 @@ var PictureInPicture = {
}
},
/**
* Increase the count of PiP windows for a given browser
* @param browser The browser to increase PiP count in browserWeakMap
*/
addPiPBrowserToWeakMap(browser) {
let count = this.browserWeakMap.has(browser)
? this.browserWeakMap.get(browser)
: 0;
this.browserWeakMap.set(browser, count + 1);
},
/**
* Decrease the count of PiP windows for a given browser.
* If the count becomes 0, we will remove the browser from the WeakMap
* @param browser The browser to decrease PiP count in browserWeakMap
*/
removePiPBrowserFromWeakMap(browser) {
let count = this.browserWeakMap.get(browser);
if (count <= 1) {
this.browserWeakMap.delete(browser);
} else {
this.browserWeakMap.set(browser, count - 1);
}
},
onPipSwappedBrowsers(event) {
let otherTab = event.detail;
if (otherTab) {
for (let win of Services.wm.getEnumerator(WINDOW_TYPE)) {
if (this.weakWinToBrowser.get(win) === event.target.linkedBrowser) {
this.weakWinToBrowser.set(win, otherTab.linkedBrowser);
this.removePiPBrowserFromWeakMap(event.target.linkedBrowser);
this.addPiPBrowserToWeakMap(otherTab.linkedBrowser);
}
}
otherTab.addEventListener("TabSwapPictureInPicture", this);
@ -285,6 +315,8 @@ var PictureInPicture = {
if (!win) {
return;
}
this.removePiPBrowserFromWeakMap(this.weakWinToBrowser.get(win));
await this.closePipWindow(win);
gCloseReasons.set(win, reason);
},
@ -335,6 +367,7 @@ var PictureInPicture = {
gNextWindowID++;
this.weakWinToBrowser.set(win, browser);
this.addPiPBrowserToWeakMap(browser);
Services.prefs.setBoolPref(
"media.videocontrols.picture-in-picture.video-toggle.has-used",
@ -719,6 +752,18 @@ var PictureInPicture = {
Services.prefs.setBoolPref(TOGGLE_ENABLED_PREF, false);
},
/**
* This is used in AsyncTabSwitcher.jsm and tabbrowser.js to check if the browser
* currently has a PiP window.
* If the browser has a PiP window we want to keep the browser in an active state because
* the browser is still partially visible.
* @param browser The browser to check if it has a PiP window
* @returns true if browser has PiP window else false
*/
isOriginatingBrowser(browser) {
return this.browserWeakMap.has(browser);
},
moveToggle() {
// Get the current position
let position = Services.prefs.getStringPref(

View File

@ -37,6 +37,7 @@ prefs =
media.videocontrols.picture-in-picture.video-toggle.position="right"
[browser_aaa_run_first_firstTimePiPToggleEvents.js]
[browser_backgroundTab.js]
[browser_cannotTriggerFromContent.js]
[browser_close_unpip_focus.js]
[browser_closePipPause.js]

View File

@ -0,0 +1,71 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* This test creates a PiP window, then switches to another tab and confirms
* that the PiP tab is still active.
*/
add_task(async () => {
let videoID = "no-controls";
let firstTab = gBrowser.selectedTab;
await BrowserTestUtils.withNewTab(
{
url: TEST_PAGE,
gBrowser,
},
async browser => {
let originatingTab = gBrowser.getTabForBrowser(browser);
let pipWin = await triggerPictureInPicture(browser, videoID);
ok(pipWin, "Got Picture-in-Picture window.");
await BrowserTestUtils.switchTab(gBrowser, firstTab);
let switcher = gBrowser._getSwitcher();
Assert.equal(
switcher.getTabState(originatingTab),
switcher.STATE_LOADED,
"The originating browser tab should be in STATE_LOADED."
);
await BrowserTestUtils.closeWindow(pipWin);
}
);
});
/**
* This test creates a PiP window, then minimizes the browser and confirms
* that the PiP tab is still active.
*/
add_task(async () => {
let videoID = "no-controls";
await BrowserTestUtils.withNewTab(
{
url: TEST_PAGE,
gBrowser,
},
async browser => {
let originatingTab = gBrowser.getTabForBrowser(browser);
let pipWin = await triggerPictureInPicture(browser, videoID);
ok(pipWin, "Got Picture-in-Picture window.");
let promiseSizeModeChange = BrowserTestUtils.waitForEvent(
window,
"sizemodechange"
);
window.minimize();
await promiseSizeModeChange;
let switcher = gBrowser._getSwitcher();
Assert.equal(
switcher.getTabState(originatingTab),
switcher.STATE_LOADED,
"The originating browser tab should be in STATE_LOADED."
);
await BrowserTestUtils.closeWindow(pipWin);
}
);
});