Bug 1873025 - Fix the displayed relative time for open tabs listing to match the recency sorting.r=jsudiaman,fxview-reviewers,tabbrowser-reviewers,dao
* Carry over lastAccessed times from tabs that haven't been seen/active this session. * Use the lastSeenActive rather than lastAccessed timestamp for labeling open tabs in firefox view. Differential Revision: https://phabricator.services.mozilla.com/D198892
This commit is contained in:
@@ -259,6 +259,14 @@
|
|||||||
return this._lastAccessed == Infinity ? Date.now() : this._lastAccessed;
|
return this._lastAccessed == Infinity ? Date.now() : this._lastAccessed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a timestamp which attempts to represent the last time the user saw this tab.
|
||||||
|
* If the tab has not been active in this session, any lastAccessed is used. We
|
||||||
|
* differentiate between selected and explicitly visible; a selected tab in a hidden
|
||||||
|
* window is last seen when that window and tab were last visible.
|
||||||
|
* We use the application start time as a fallback value when no other suitable value
|
||||||
|
* is available.
|
||||||
|
*/
|
||||||
get lastSeenActive() {
|
get lastSeenActive() {
|
||||||
const isForegroundWindow =
|
const isForegroundWindow =
|
||||||
this.ownerGlobal ==
|
this.ownerGlobal ==
|
||||||
@@ -270,8 +278,16 @@
|
|||||||
if (this._lastSeenActive) {
|
if (this._lastSeenActive) {
|
||||||
return this._lastSeenActive;
|
return this._lastSeenActive;
|
||||||
}
|
}
|
||||||
// Use the application start time as the fallback value
|
|
||||||
return Services.startup.getStartupInfo().start.getTime();
|
const appStartTime = Services.startup.getStartupInfo().start.getTime();
|
||||||
|
if (!this._lastAccessed || this._lastAccessed >= appStartTime) {
|
||||||
|
// When the tab was created this session but hasn't been seen by the user,
|
||||||
|
// default to the application start time.
|
||||||
|
return appStartTime;
|
||||||
|
}
|
||||||
|
// The tab was restored from a previous session but never seen.
|
||||||
|
// Use the lastAccessed as the best proxy for when the user might have seen it.
|
||||||
|
return this._lastAccessed;
|
||||||
}
|
}
|
||||||
|
|
||||||
get _overPlayingIcon() {
|
get _overPlayingIcon() {
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ support-files = ["tab_that_closes.html"]
|
|||||||
|
|
||||||
["browser_hiddentab_contextmenu.js"]
|
["browser_hiddentab_contextmenu.js"]
|
||||||
|
|
||||||
|
["browser_lastSeenActive.js"]
|
||||||
|
|
||||||
["browser_lazy_tab_browser_events.js"]
|
["browser_lazy_tab_browser_events.js"]
|
||||||
|
|
||||||
["browser_link_in_tab_title_and_url_prefilled_blank_page.js"]
|
["browser_link_in_tab_title_and_url_prefilled_blank_page.js"]
|
||||||
|
|||||||
260
browser/base/content/test/tabs/browser_lastSeenActive.js
Normal file
260
browser/base/content/test/tabs/browser_lastSeenActive.js
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
const { SessionStoreTestUtils } = ChromeUtils.importESModule(
|
||||||
|
"resource://testing-common/SessionStoreTestUtils.sys.mjs"
|
||||||
|
);
|
||||||
|
|
||||||
|
const triggeringPrincipal_base64 = E10SUtils.SERIALIZED_SYSTEMPRINCIPAL;
|
||||||
|
|
||||||
|
SessionStoreTestUtils.init(this, window);
|
||||||
|
// take a state snapshot we can restore to after each test
|
||||||
|
const ORIG_STATE = SessionStore.getBrowserState();
|
||||||
|
|
||||||
|
const SECOND_MS = 1000;
|
||||||
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
||||||
|
const today = new Date().getTime();
|
||||||
|
const yesterday = new Date(Date.now() - DAY_MS).getTime();
|
||||||
|
|
||||||
|
function tabEntry(url, lastAccessed) {
|
||||||
|
return {
|
||||||
|
entries: [{ url, triggeringPrincipal_base64 }],
|
||||||
|
lastAccessed,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make the given window focused and active
|
||||||
|
*/
|
||||||
|
async function switchToWindow(win) {
|
||||||
|
info("switchToWindow, waiting for promiseFocus");
|
||||||
|
await SimpleTest.promiseFocus(win);
|
||||||
|
info("switchToWindow, waiting for correct Services.focus.activeWindow");
|
||||||
|
await BrowserTestUtils.waitForCondition(
|
||||||
|
() => Services.focus.activeWindow == win
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function changeSizeMode(win, mode) {
|
||||||
|
let promise = BrowserTestUtils.waitForEvent(win, "sizemodechange");
|
||||||
|
win[mode]();
|
||||||
|
await promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cleanup() {
|
||||||
|
await switchToWindow(window);
|
||||||
|
await SessionStoreTestUtils.promiseBrowserState(ORIG_STATE);
|
||||||
|
is(
|
||||||
|
BrowserWindowTracker.orderedWindows.length,
|
||||||
|
1,
|
||||||
|
"One window at the end of test cleanup"
|
||||||
|
);
|
||||||
|
info("cleanup, browser state restored");
|
||||||
|
}
|
||||||
|
|
||||||
|
function deltaTime(time, expectedTime) {
|
||||||
|
return Math.abs(expectedTime - time);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWindowUrl(win) {
|
||||||
|
return win.gBrowser.selectedBrowser?.currentURI?.spec;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWindowByTabUrl(url) {
|
||||||
|
return BrowserWindowTracker.orderedWindows.find(
|
||||||
|
win => getWindowUrl(win) == url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
add_task(async function restoredTabs() {
|
||||||
|
const now = Date.now();
|
||||||
|
await SessionStoreTestUtils.promiseBrowserState({
|
||||||
|
windows: [
|
||||||
|
{
|
||||||
|
tabs: [
|
||||||
|
tabEntry("data:,Window0-Tab0", yesterday),
|
||||||
|
tabEntry("data:,Window0-Tab1", yesterday),
|
||||||
|
],
|
||||||
|
selected: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
is(
|
||||||
|
gBrowser.visibleTabs[1],
|
||||||
|
gBrowser.selectedTab,
|
||||||
|
"The selected tab is the 2nd visible tab"
|
||||||
|
);
|
||||||
|
is(
|
||||||
|
getWindowUrl(window),
|
||||||
|
"data:,Window0-Tab1",
|
||||||
|
"The expected tab is selected"
|
||||||
|
);
|
||||||
|
Assert.greaterOrEqual(
|
||||||
|
gBrowser.selectedTab.lastSeenActive,
|
||||||
|
now,
|
||||||
|
"The selected tab's lastSeenActive is now"
|
||||||
|
);
|
||||||
|
Assert.greaterOrEqual(
|
||||||
|
gBrowser.selectedTab.lastAccessed,
|
||||||
|
now,
|
||||||
|
"The selected tab's lastAccessed is now"
|
||||||
|
);
|
||||||
|
|
||||||
|
// tab restored from session but never seen or active
|
||||||
|
is(
|
||||||
|
gBrowser.visibleTabs[0].lastSeenActive,
|
||||||
|
yesterday,
|
||||||
|
"The restored tab's lastSeenActive is yesterday"
|
||||||
|
);
|
||||||
|
await cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function switchingTabs() {
|
||||||
|
let now = Date.now();
|
||||||
|
let initialTab = gBrowser.selectedTab;
|
||||||
|
let applicationStart = Services.startup.getStartupInfo().start.getTime();
|
||||||
|
let openedTab = BrowserTestUtils.addTab(gBrowser, "data:,Tab1");
|
||||||
|
await BrowserTestUtils.browserLoaded(openedTab.linkedBrowser);
|
||||||
|
|
||||||
|
ok(!openedTab.selected, "The background tab we opened isn't selected");
|
||||||
|
Assert.greaterOrEqual(
|
||||||
|
initialTab.selected && initialTab.lastSeenActive,
|
||||||
|
now,
|
||||||
|
"The initial tab is selected and last seen now"
|
||||||
|
);
|
||||||
|
|
||||||
|
is(
|
||||||
|
openedTab.lastSeenActive,
|
||||||
|
applicationStart,
|
||||||
|
`Background tab got default lastSeenActive value, delta: ${deltaTime(
|
||||||
|
openedTab.lastSeenActive,
|
||||||
|
applicationStart
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
now = Date.now();
|
||||||
|
await BrowserTestUtils.switchTab(gBrowser, openedTab);
|
||||||
|
Assert.greaterOrEqual(
|
||||||
|
openedTab.lastSeenActive,
|
||||||
|
now,
|
||||||
|
"The tab we switched to is last seen now"
|
||||||
|
);
|
||||||
|
|
||||||
|
await cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function switchingWindows() {
|
||||||
|
info("Restoring to the test browser state");
|
||||||
|
await SessionStoreTestUtils.promiseBrowserState({
|
||||||
|
windows: [
|
||||||
|
{
|
||||||
|
tabs: [tabEntry("data:,Window1-Tab0", yesterday)],
|
||||||
|
selected: 1,
|
||||||
|
sizemodeBeforeMinimized: "normal",
|
||||||
|
sizemode: "maximized",
|
||||||
|
zIndex: 1, // this will be the selected window
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tabs: [tabEntry("data:,Window2-Tab0", yesterday)],
|
||||||
|
selected: 1,
|
||||||
|
sizemodeBeforeMinimized: "normal",
|
||||||
|
sizemode: "maximized",
|
||||||
|
zIndex: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
info("promiseBrowserState resolved");
|
||||||
|
info(
|
||||||
|
`BrowserWindowTracker.pendingWindows: ${BrowserWindowTracker.pendingWindows.size}`
|
||||||
|
);
|
||||||
|
await Promise.all(
|
||||||
|
Array.from(BrowserWindowTracker.pendingWindows.values()).map(
|
||||||
|
win => win.deferred.promise
|
||||||
|
)
|
||||||
|
);
|
||||||
|
info("All the pending windows are resolved");
|
||||||
|
info("Waiting for the firstBrowserLoaded in each of the windows");
|
||||||
|
await Promise.all(
|
||||||
|
BrowserWindowTracker.orderedWindows.map(win => {
|
||||||
|
const selectedUrl = getWindowUrl(win);
|
||||||
|
if (selectedUrl && selectedUrl !== "about:blank") {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return BrowserTestUtils.firstBrowserLoaded(win, false);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
let expectedTabURLs = ["data:,Window1-Tab0", "data:,Window2-Tab0"];
|
||||||
|
let [win1, win2] = expectedTabURLs.map(url => getWindowByTabUrl(url));
|
||||||
|
if (BrowserWindowTracker.getTopWindow() !== win1) {
|
||||||
|
info("Switch to win1 which isn't active/top after restoring session");
|
||||||
|
// In theory the zIndex values in the session state should make win1 active
|
||||||
|
// But in practice that isn't always true. To ensure we're testing from a known state,
|
||||||
|
// ensure the first window is active before proceeding with the test
|
||||||
|
await switchToWindow(win1);
|
||||||
|
[win1, win2] = expectedTabURLs.map(url => getWindowByTabUrl(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
let actualTabURLs = Array.from(BrowserWindowTracker.orderedWindows).map(win =>
|
||||||
|
getWindowUrl(win)
|
||||||
|
);
|
||||||
|
Assert.deepEqual(
|
||||||
|
actualTabURLs,
|
||||||
|
expectedTabURLs,
|
||||||
|
"Both windows are open with selected tab URLs in the expected order"
|
||||||
|
);
|
||||||
|
|
||||||
|
let lastSeenTimes = [win1, win2].map(
|
||||||
|
win => win.gBrowser.selectedTab.lastSeenActive
|
||||||
|
);
|
||||||
|
|
||||||
|
info("Focusing the other window");
|
||||||
|
await switchToWindow(win2);
|
||||||
|
// wait a little so the timestamps will differ and then check again
|
||||||
|
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
|
||||||
|
await new Promise(res => setTimeout(res, 100));
|
||||||
|
Assert.greater(
|
||||||
|
win2.gBrowser.selectedTab.lastSeenActive,
|
||||||
|
lastSeenTimes[1],
|
||||||
|
"The foreground window selected tab is last seen more recently than it was before being focused"
|
||||||
|
);
|
||||||
|
Assert.greater(
|
||||||
|
win2.gBrowser.selectedTab.lastSeenActive,
|
||||||
|
win1.gBrowser.selectedTab.lastSeenActive,
|
||||||
|
"The foreground window selected tab is last seen more recently than the backgrounded one"
|
||||||
|
);
|
||||||
|
|
||||||
|
lastSeenTimes = [win1, win2].map(
|
||||||
|
win => win.gBrowser.selectedTab.lastSeenActive
|
||||||
|
);
|
||||||
|
// minimize the foreground window and focus the other
|
||||||
|
let promiseSizeModeChange = BrowserTestUtils.waitForEvent(
|
||||||
|
win2,
|
||||||
|
"sizemodechange"
|
||||||
|
);
|
||||||
|
win2.minimize();
|
||||||
|
info("Waiting for the sizemodechange on minimized window");
|
||||||
|
await promiseSizeModeChange;
|
||||||
|
await switchToWindow(win1);
|
||||||
|
|
||||||
|
ok(
|
||||||
|
!win2.gBrowser.selectedTab.linkedBrowser.docShellIsActive,
|
||||||
|
"Docshell should be Inactive"
|
||||||
|
);
|
||||||
|
ok(win2.document.hidden, "Minimized windows's document should be hidden");
|
||||||
|
|
||||||
|
// wait a little so the timestamps will differ and then check again
|
||||||
|
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
|
||||||
|
await new Promise(res => setTimeout(res, 100));
|
||||||
|
Assert.greater(
|
||||||
|
win1.gBrowser.selectedTab.lastSeenActive,
|
||||||
|
win2.gBrowser.selectedTab.lastSeenActive,
|
||||||
|
"The foreground window selected tab is last seen more recently than the minimized one"
|
||||||
|
);
|
||||||
|
Assert.greater(
|
||||||
|
win1.gBrowser.selectedTab.lastSeenActive,
|
||||||
|
lastSeenTimes[0],
|
||||||
|
"The foreground window selected tab is last seen more recently than it was before being focused"
|
||||||
|
);
|
||||||
|
|
||||||
|
await cleanup();
|
||||||
|
});
|
||||||
@@ -1024,7 +1024,7 @@ function getTabListItems(tabs, isRecentBrowsing) {
|
|||||||
? JSON.stringify({ tabTitle: tab.label })
|
? JSON.stringify({ tabTitle: tab.label })
|
||||||
: null,
|
: null,
|
||||||
tabElement: tab,
|
tabElement: tab,
|
||||||
time: tab.lastAccessed,
|
time: tab.lastSeenActive,
|
||||||
title: tab.label,
|
title: tab.label,
|
||||||
url,
|
url,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user