forked from mirrors/gecko-dev
Bug 1852622 - Track a lastSetActive property on each tab and use that when sorting open tabs for recency in firefox view. r=jsudiaman,Gijs,fxview-reviewers,tabbrowser-reviewers,mak,sclements
Differential Revision: https://phabricator.services.mozilla.com/D189444
This commit is contained in:
parent
e3a5f85385
commit
6e6ac6197e
7 changed files with 403 additions and 2 deletions
|
|
@ -259,6 +259,21 @@
|
|||
return this._lastAccessed == Infinity ? Date.now() : this._lastAccessed;
|
||||
}
|
||||
|
||||
get lastSeenActive() {
|
||||
const isForegroundWindow =
|
||||
this.ownerGlobal ==
|
||||
BrowserWindowTracker.getTopWindow({ allowPopups: true });
|
||||
// the timestamp for the selected tab in the active window is always now
|
||||
if (isForegroundWindow && this.selected) {
|
||||
return Date.now();
|
||||
}
|
||||
if (this._lastSeenActive) {
|
||||
return this._lastSeenActive;
|
||||
}
|
||||
// Use the application start time as the fallback value
|
||||
return Services.startup.getStartupInfo().start.getTime();
|
||||
}
|
||||
|
||||
get _overPlayingIcon() {
|
||||
return this.overlayIcon?.matches(":hover");
|
||||
}
|
||||
|
|
@ -291,6 +306,10 @@
|
|||
this._lastAccessed = this.selected ? Infinity : aDate || Date.now();
|
||||
}
|
||||
|
||||
updateLastSeenActive() {
|
||||
this._lastSeenActive = Date.now();
|
||||
}
|
||||
|
||||
updateLastUnloadedByTabUnloader() {
|
||||
this._lastUnloaded = Date.now();
|
||||
Services.telemetry.scalarAdd("browser.engagement.tab_unload_count", 1);
|
||||
|
|
|
|||
|
|
@ -111,6 +111,8 @@
|
|||
Services.els.addSystemEventListener(document, "keypress", this, false);
|
||||
document.addEventListener("visibilitychange", this);
|
||||
window.addEventListener("framefocusrequested", this);
|
||||
window.addEventListener("activate", this);
|
||||
window.addEventListener("deactivate", this);
|
||||
|
||||
this.tabContainer.init();
|
||||
this._setupInitialBrowserAndTab();
|
||||
|
|
@ -1201,6 +1203,11 @@
|
|||
newTab.recordTimeFromUnloadToReload();
|
||||
newTab.updateLastAccessed();
|
||||
oldTab.updateLastAccessed();
|
||||
// if this is the foreground window, update the last-seen timestamps.
|
||||
if (this.ownerGlobal == BrowserWindowTracker.getTopWindow()) {
|
||||
newTab.updateLastSeenActive();
|
||||
oldTab.updateLastSeenActive();
|
||||
}
|
||||
|
||||
let oldFindBar = oldTab._findBar;
|
||||
if (
|
||||
|
|
@ -5762,6 +5769,11 @@
|
|||
this.selectedBrowser.docShellIsActive = !inactive;
|
||||
}
|
||||
break;
|
||||
case "activate":
|
||||
// Intentional fallthrough
|
||||
case "deactivate":
|
||||
this.selectedTab.updateLastSeenActive();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -5875,6 +5887,8 @@
|
|||
}
|
||||
document.removeEventListener("visibilitychange", this);
|
||||
window.removeEventListener("framefocusrequested", this);
|
||||
window.removeEventListener("activate", this);
|
||||
window.removeEventListener("deactivate", this);
|
||||
|
||||
if (gMultiProcessBrowser) {
|
||||
if (this._switcher) {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ ChromeUtils.defineLazyGetter(lazy, "fxAccounts", () => {
|
|||
});
|
||||
|
||||
const TOPIC_DEVICELIST_UPDATED = "fxaccounts:devicelist_updated";
|
||||
const TOPIC_CURRENT_BROWSER_CHANGED = "net:current-browser-id";
|
||||
|
||||
/**
|
||||
* A collection of open tabs grouped by window.
|
||||
|
|
@ -65,6 +66,9 @@ class OpenTabsInView extends ViewPage {
|
|||
tabContainer.addEventListener("TabOpen", this);
|
||||
tabContainer.addEventListener("TabPinned", this);
|
||||
tabContainer.addEventListener("TabUnpinned", this);
|
||||
// BrowserWindowWatcher doesnt always notify "net:current-browser-id" when
|
||||
// restoring a window, so we need to listen for "activate" events here as well.
|
||||
win.addEventListener("activate", this);
|
||||
this._updateOpenTabsList();
|
||||
}
|
||||
},
|
||||
|
|
@ -77,6 +81,7 @@ class OpenTabsInView extends ViewPage {
|
|||
tabContainer.removeEventListener("TabOpen", this);
|
||||
tabContainer.removeEventListener("TabPinned", this);
|
||||
tabContainer.removeEventListener("TabUnpinned", this);
|
||||
win.removeEventListener("activate", this);
|
||||
this._updateOpenTabsList();
|
||||
}
|
||||
}
|
||||
|
|
@ -98,6 +103,10 @@ class OpenTabsInView extends ViewPage {
|
|||
if (!this.observerAdded) {
|
||||
Services.obs.addObserver(this.boundObserve, lazy.UIState.ON_UPDATE);
|
||||
Services.obs.addObserver(this.boundObserve, TOPIC_DEVICELIST_UPDATED);
|
||||
Services.obs.addObserver(
|
||||
this.boundObserve,
|
||||
TOPIC_CURRENT_BROWSER_CHANGED
|
||||
);
|
||||
this.observerAdded = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -106,6 +115,10 @@ class OpenTabsInView extends ViewPage {
|
|||
if (this.observerAdded) {
|
||||
Services.obs.removeObserver(this.boundObserve, lazy.UIState.ON_UPDATE);
|
||||
Services.obs.removeObserver(this.boundObserve, TOPIC_DEVICELIST_UPDATED);
|
||||
Services.obs.removeObserver(
|
||||
this.boundObserve,
|
||||
TOPIC_CURRENT_BROWSER_CHANGED
|
||||
);
|
||||
this.observerAdded = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -123,6 +136,10 @@ class OpenTabsInView extends ViewPage {
|
|||
if (deviceListUpdated) {
|
||||
this.devices = this.currentWindow.gSync.getSendTabTargets();
|
||||
}
|
||||
break;
|
||||
case TOPIC_CURRENT_BROWSER_CHANGED:
|
||||
this.requestUpdate();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -220,7 +237,17 @@ class OpenTabsInView extends ViewPage {
|
|||
getRecentBrowsingTemplate() {
|
||||
const tabs = Array.from(this.windows.values())
|
||||
.flat()
|
||||
.sort((a, b) => b.lastAccessed - a.lastAccessed);
|
||||
.sort((a, b) => {
|
||||
let dt = b.lastSeenActive - a.lastSeenActive;
|
||||
if (dt) {
|
||||
return dt;
|
||||
}
|
||||
// try to break a deadlock by sorting the selected tab higher
|
||||
if (!(a.selected || b.selected)) {
|
||||
return 0;
|
||||
}
|
||||
return a.selected ? -1 : 1;
|
||||
});
|
||||
return html`<view-opentabs-card
|
||||
.tabs=${tabs}
|
||||
.recentBrowsing=${true}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,11 @@ async function openFirefoxViewTab(win) {
|
|||
const alreadyLoaded =
|
||||
fxViewTab?.linkedBrowser?.currentURI.spec.split("#")[0] ==
|
||||
getFirefoxViewURL();
|
||||
const enteredPromise = TestUtils.topicObserved("firefoxview-entered");
|
||||
const enteredPromise =
|
||||
alreadyLoaded &&
|
||||
fxViewTab.linkedBrowser.contentDocument.visibilityState == "visible"
|
||||
? Promise.resolve()
|
||||
: TestUtils.topicObserved("firefoxview-entered");
|
||||
await BrowserTestUtils.synthesizeMouseAtCenter(
|
||||
"#firefox-view-button",
|
||||
{ type: "mousedown" },
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ support-files = [ "../head.js"]
|
|||
|
||||
["browser_opentabs_firefoxview_next.js"]
|
||||
|
||||
["browser_opentabs_recency_next.js"]
|
||||
skip-if = ["os == 'mac' && verify"] # macos times out, see bug 1857293
|
||||
|
||||
["browser_recentlyclosed_firefoxview_next.js"]
|
||||
|
||||
["browser_syncedtabs_errors_firefoxview_next.js"]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,333 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/* import-globals-from ../head.js */
|
||||
|
||||
const tabURL1 = "data:,Tab1";
|
||||
const tabURL2 = "data:,Tab2";
|
||||
const tabURL3 = "data:,Tab3";
|
||||
const tabURL4 = "data:,Tab4";
|
||||
|
||||
let gInitialTab;
|
||||
let gInitialTabURL;
|
||||
|
||||
add_setup(function () {
|
||||
gInitialTab = gBrowser.selectedTab;
|
||||
gInitialTabURL = tabUrl(gInitialTab);
|
||||
});
|
||||
|
||||
function tabUrl(tab) {
|
||||
return tab.linkedBrowser.currentURI?.spec;
|
||||
}
|
||||
|
||||
async function minimizeWindow(win) {
|
||||
let promiseSizeModeChange = BrowserTestUtils.waitForEvent(
|
||||
win,
|
||||
"sizemodechange"
|
||||
);
|
||||
win.minimize();
|
||||
await promiseSizeModeChange;
|
||||
ok(
|
||||
!win.gBrowser.selectedTab.linkedBrowser.docShellIsActive,
|
||||
"Docshell should be Inactive"
|
||||
);
|
||||
ok(win.document.hidden, "Top level window should be hidden");
|
||||
}
|
||||
|
||||
async function restoreWindow(win) {
|
||||
ok(win.document.hidden, "Top level window should be hidden");
|
||||
let promiseSizeModeChange = BrowserTestUtils.waitForEvent(
|
||||
win,
|
||||
"sizemodechange"
|
||||
);
|
||||
info("Calling window.restore");
|
||||
win.restore();
|
||||
// From browser/base/content/test/general/browser_minimize.js:
|
||||
// On Ubuntu `window.restore` doesn't seem to work, use a timer to make the
|
||||
// test fail faster and more cleanly than with a test timeout.
|
||||
info("Waiting for sizemodechange event");
|
||||
let timer;
|
||||
await Promise.race([
|
||||
promiseSizeModeChange,
|
||||
new Promise((resolve, reject) => {
|
||||
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
|
||||
timer = setTimeout(() => {
|
||||
reject("timed out waiting for sizemodechange event");
|
||||
}, 5000);
|
||||
}),
|
||||
]);
|
||||
clearTimeout(timer);
|
||||
info(
|
||||
"Waiting occlusionstatechange if win.isFullyOccluded: " +
|
||||
win.isFullyOccluded
|
||||
);
|
||||
// From browser/base/content/test/general/browser_minimize.js:
|
||||
// The sizemodechange event can sometimes be fired before the
|
||||
// occlusionstatechange event, especially in chaos mode.
|
||||
if (win.isFullyOccluded) {
|
||||
await BrowserTestUtils.waitForEvent(win, "occlusionstatechange");
|
||||
}
|
||||
ok(
|
||||
win.gBrowser.selectedTab.linkedBrowser.docShellIsActive,
|
||||
"Docshell should be active again"
|
||||
);
|
||||
ok(!win.document.hidden, "Top level window should be visible");
|
||||
}
|
||||
|
||||
async function prepareOpenTabs(urls, win = window) {
|
||||
const reusableTabURLs = ["about:newtab", "about:blank"];
|
||||
const gBrowser = win.gBrowser;
|
||||
|
||||
for (let url of urls) {
|
||||
if (
|
||||
gBrowser.visibleTabs.length == 1 &&
|
||||
reusableTabURLs.includes(gBrowser.selectedBrowser.currentURI.spec)
|
||||
) {
|
||||
// we'll load into this tab rather than opening a new one
|
||||
info(
|
||||
`Loading ${url} into blank tab: ${gBrowser.selectedBrowser.currentURI.spec}`
|
||||
);
|
||||
BrowserTestUtils.startLoadingURIString(gBrowser.selectedBrowser, url);
|
||||
await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, null, url);
|
||||
} else {
|
||||
info(`Loading ${url} into new tab`);
|
||||
await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
|
||||
}
|
||||
await new Promise(res => win.requestAnimationFrame(res));
|
||||
}
|
||||
Assert.equal(
|
||||
gBrowser.visibleTabs.length,
|
||||
urls.length,
|
||||
`Prepared ${urls.length} tabs as expected`
|
||||
);
|
||||
Assert.equal(
|
||||
tabUrl(gBrowser.selectedTab),
|
||||
urls[urls.length - 1],
|
||||
"The selectedTab is the last of the URLs given as expected"
|
||||
);
|
||||
}
|
||||
|
||||
async function cleanup(...windowsToClose) {
|
||||
await Promise.all(
|
||||
windowsToClose.map(win => BrowserTestUtils.closeWindow(win))
|
||||
);
|
||||
|
||||
while (gBrowser.visibleTabs.length > 1) {
|
||||
await SessionStoreTestUtils.closeTab(gBrowser.tabs.at(-1));
|
||||
}
|
||||
if (gBrowser.selectedBrowser.currentURI.spec !== gInitialTabURL) {
|
||||
BrowserTestUtils.startLoadingURIString(
|
||||
gBrowser.selectedBrowser,
|
||||
gInitialTabURL
|
||||
);
|
||||
await BrowserTestUtils.browserLoaded(
|
||||
gBrowser.selectedBrowser,
|
||||
null,
|
||||
gInitialTabURL
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkTabList(document, expected) {
|
||||
const tabsView = document.querySelector("view-opentabs");
|
||||
const openTabsCard = tabsView.shadowRoot.querySelector("view-opentabs-card");
|
||||
await openTabsCard.updateCompleted;
|
||||
const tabList = openTabsCard.shadowRoot.querySelector("fxview-tab-list");
|
||||
Assert.ok(tabList, "Found the tab list element");
|
||||
|
||||
let actual = Array.from(tabList.rowEls).map(row => row.url);
|
||||
Assert.deepEqual(
|
||||
actual,
|
||||
expected,
|
||||
"Tab list has items with URLs in the expected order"
|
||||
);
|
||||
}
|
||||
|
||||
add_task(async function test_single_window_tabs() {
|
||||
await prepareOpenTabs([tabURL1, tabURL2]);
|
||||
await openFirefoxViewTab(window).then(async viewTab => {
|
||||
const browser = viewTab.linkedBrowser;
|
||||
await checkTabList(browser.contentDocument, [tabURL2, tabURL1]);
|
||||
});
|
||||
|
||||
// switch to the first tab
|
||||
await BrowserTestUtils.switchTab(gBrowser, gBrowser.visibleTabs[0]);
|
||||
// and check the results in the open tabs section of Recent Browsing
|
||||
await openFirefoxViewTab(window).then(async viewTab => {
|
||||
const browser = viewTab.linkedBrowser;
|
||||
await checkTabList(browser.contentDocument, [tabURL1, tabURL2]);
|
||||
});
|
||||
await cleanup();
|
||||
});
|
||||
|
||||
add_task(async function test_multiple_window_tabs() {
|
||||
const fxViewURL = getFirefoxViewURL();
|
||||
const win1 = window;
|
||||
await prepareOpenTabs([tabURL1, tabURL2]);
|
||||
const win2 = await BrowserTestUtils.openNewBrowserWindow();
|
||||
await prepareOpenTabs([tabURL3, tabURL4], win2);
|
||||
|
||||
// to avoid confusing the results by activating different windows,
|
||||
// check fxview in the current window - which is win2
|
||||
info("Switching to fxview tab in win2");
|
||||
await openFirefoxViewTab(win2).then(async viewTab => {
|
||||
const browser = viewTab.linkedBrowser;
|
||||
await checkTabList(browser.contentDocument, [
|
||||
tabURL4,
|
||||
tabURL3,
|
||||
tabURL2,
|
||||
tabURL1,
|
||||
]);
|
||||
});
|
||||
Assert.equal(
|
||||
tabUrl(win2.gBrowser.selectedTab),
|
||||
fxViewURL,
|
||||
`The selected tab in window 2 is ${fxViewURL}`
|
||||
);
|
||||
|
||||
info("Switching to first tab (tab3) in win2");
|
||||
await BrowserTestUtils.switchTab(win2.gBrowser, win2.gBrowser.visibleTabs[0]);
|
||||
Assert.equal(
|
||||
tabUrl(win2.gBrowser.selectedTab),
|
||||
tabURL3,
|
||||
`The selected tab in window 2 is ${tabURL3}`
|
||||
);
|
||||
|
||||
info("Opening fxview in win2 to confirm tab3 is most recent");
|
||||
await openFirefoxViewTab(win2).then(async viewTab => {
|
||||
const browser = viewTab.linkedBrowser;
|
||||
info("Check result of selecting 1ist tab in window 2");
|
||||
await checkTabList(browser.contentDocument, [
|
||||
tabURL3,
|
||||
tabURL4,
|
||||
tabURL2,
|
||||
tabURL1,
|
||||
]);
|
||||
});
|
||||
|
||||
info("Focusing win1, where tab2 should be selected");
|
||||
await SimpleTest.promiseFocus(win1);
|
||||
Assert.equal(
|
||||
tabUrl(win1.gBrowser.selectedTab),
|
||||
tabURL2,
|
||||
`The selected tab in window 1 is ${tabURL2}`
|
||||
);
|
||||
|
||||
info("Opening fxview in win1 to confirm tab2 is most recent");
|
||||
await openFirefoxViewTab(win1).then(async viewTab => {
|
||||
const browser = viewTab.linkedBrowser;
|
||||
info(
|
||||
"In fxview, check result of activating window 1, where tab 2 is selected"
|
||||
);
|
||||
await checkTabList(browser.contentDocument, [
|
||||
tabURL2,
|
||||
tabURL3,
|
||||
tabURL4,
|
||||
tabURL1,
|
||||
]);
|
||||
});
|
||||
|
||||
info("Switching to first visible tab (tab1) in win1");
|
||||
await BrowserTestUtils.switchTab(win1.gBrowser, win1.gBrowser.visibleTabs[0]);
|
||||
|
||||
// check result in the fxview in the 1st window
|
||||
info("Opening fxview in win1 to confirm tab1 is most recent");
|
||||
await openFirefoxViewTab(win1).then(async viewTab => {
|
||||
const browser = viewTab.linkedBrowser;
|
||||
info("Check result of selecting 1st tab in win1");
|
||||
await checkTabList(browser.contentDocument, [
|
||||
tabURL1,
|
||||
tabURL2,
|
||||
tabURL3,
|
||||
tabURL4,
|
||||
]);
|
||||
});
|
||||
|
||||
await cleanup(win2);
|
||||
});
|
||||
|
||||
add_task(async function test_windows_activation() {
|
||||
const win1 = window;
|
||||
await prepareOpenTabs([tabURL1], win1);
|
||||
let fxViewTab;
|
||||
info("switch to firefox-view and leave it selected");
|
||||
await openFirefoxViewTab(win1).then(tab => (fxViewTab = tab));
|
||||
|
||||
const win2 = await BrowserTestUtils.openNewBrowserWindow();
|
||||
await prepareOpenTabs([tabURL2], win2);
|
||||
const win3 = await BrowserTestUtils.openNewBrowserWindow();
|
||||
await prepareOpenTabs([tabURL3], win3);
|
||||
|
||||
await SimpleTest.promiseFocus(win1);
|
||||
|
||||
const browser = fxViewTab.linkedBrowser;
|
||||
await checkTabList(browser.contentDocument, [tabURL3, tabURL2, tabURL1]);
|
||||
|
||||
info("switch to win2 and confirm its selected tab becomes most recent");
|
||||
await SimpleTest.promiseFocus(win2);
|
||||
await checkTabList(browser.contentDocument, [tabURL2, tabURL3, tabURL1]);
|
||||
await cleanup(win2, win3);
|
||||
});
|
||||
|
||||
add_task(async function test_minimize_restore_windows() {
|
||||
const win1 = window;
|
||||
await prepareOpenTabs([tabURL1, tabURL2]);
|
||||
const win2 = await BrowserTestUtils.openNewBrowserWindow();
|
||||
await prepareOpenTabs([tabURL3, tabURL4], win2);
|
||||
|
||||
// to avoid confusing the results by activating different windows,
|
||||
// check fxview in the current window - which is win2
|
||||
info("Opening fxview in win2 to confirm tab4 is most recent");
|
||||
await openFirefoxViewTab(win2).then(async viewTab => {
|
||||
const browser = viewTab.linkedBrowser;
|
||||
await checkTabList(browser.contentDocument, [
|
||||
tabURL4,
|
||||
tabURL3,
|
||||
tabURL2,
|
||||
tabURL1,
|
||||
]);
|
||||
});
|
||||
//
|
||||
info("Switching to the first tab (tab3) in 2nd window");
|
||||
await BrowserTestUtils.switchTab(win2.gBrowser, win2.gBrowser.visibleTabs[0]);
|
||||
|
||||
// then minimize the window, focusing the 1st window
|
||||
info("Minimizing win2, leaving tab 3 selected");
|
||||
await minimizeWindow(win2);
|
||||
info("Focusing win1, where tab2 is selected - making it most recent");
|
||||
await SimpleTest.promiseFocus(win1);
|
||||
|
||||
Assert.equal(
|
||||
tabUrl(win1.gBrowser.selectedTab),
|
||||
tabURL2,
|
||||
`The selected tab in window 1 is ${tabURL2}`
|
||||
);
|
||||
|
||||
info("Opening fxview in win1 to confirm tab2 is most recent");
|
||||
await openFirefoxViewTab(win1).then(async viewTab => {
|
||||
const browser = viewTab.linkedBrowser;
|
||||
await checkTabList(browser.contentDocument, [
|
||||
tabURL2,
|
||||
tabURL3,
|
||||
tabURL4,
|
||||
tabURL1,
|
||||
]);
|
||||
info(
|
||||
"Restoring win2 and focusing it - which should make its selected tab most recent"
|
||||
);
|
||||
await restoreWindow(win2);
|
||||
await SimpleTest.promiseFocus(win2);
|
||||
|
||||
info(
|
||||
"Checking tab order in fxview in win1, to confirm tab3 is most recent"
|
||||
);
|
||||
await checkTabList(browser.contentDocument, [
|
||||
tabURL3,
|
||||
tabURL2,
|
||||
tabURL4,
|
||||
tabURL1,
|
||||
]);
|
||||
});
|
||||
|
||||
await cleanup(win2);
|
||||
});
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const {
|
||||
getFirefoxViewURL,
|
||||
withFirefoxView,
|
||||
assertFirefoxViewTab,
|
||||
assertFirefoxViewTabSelected,
|
||||
|
|
|
|||
Loading…
Reference in a new issue