Bug 1774169 - Add observer, keyboard nav & tests to Tab Pickup list r=Gijs

Differential Revision: https://phabricator.services.mozilla.com/D149991
This commit is contained in:
Sarah Clements 2022-06-23 14:31:35 +00:00
Родитель b83a85e23e
Коммит 9b7f3ccecb
7 изменённых файлов: 267 добавлений и 15 удалений

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

@ -509,7 +509,7 @@ body > main > aside {
background-size: cover;
}
.favicon, .icon {
.favicon, .icon, .synced-tab-li-favicon {
width: 16px;
height: 16px;
}

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

@ -72,5 +72,9 @@ window.addEventListener("load", () => {
window.addEventListener("unload", () => {
tabsSetupFlowManager?.uninit();
const tabPickupList = document.querySelector("tab-pickup-list");
if (tabPickupList) {
tabPickupList.cleanup();
}
document.getElementById("recently-closed-tabs-container").cleanup();
});

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

@ -18,10 +18,13 @@ import {
createFaviconElement,
} from "./helpers.js";
const SYNCED_TABS_CHANGED = "services.sync.tabs.changed";
class TabPickupList extends HTMLElement {
constructor() {
super();
this.maxTabsLength = 3;
this.boundObserve = (...args) => this.getSyncedTabData(...args);
}
get tabsList() {
@ -37,14 +40,24 @@ class TabPickupList extends HTMLElement {
connectedCallback() {
this.addEventListener("click", this);
this.getSyncedTabData();
Services.obs.addObserver(this.boundObserve, SYNCED_TABS_CHANGED);
}
handleEvent(event) {
if (event.type == "click") {
if (
event.type == "click" ||
(event.type == "keydown" && event.keyCode == KeyEvent.DOM_VK_RETURN)
) {
this.openTab(event);
}
}
cleanup() {
Services.obs.removeObserver(this.boundObserve, SYNCED_TABS_CHANGED);
}
openTab(event) {
event.preventDefault();
const item = event.target.closest(".synced-tab-li");
@ -66,19 +79,29 @@ class TabPickupList extends HTMLElement {
.sort((a, b) => b.lastUsed - a.lastUsed)
.slice(0, this.maxTabsLength);
if (tabs.length) {
this.initiateTabsList(tabs);
} else {
// TODO show empty state placeholder
}
this.updateTabsList(tabs);
}
initiateTabsList(syncedTabs) {
updateTabsList(syncedTabs) {
while (this.tabsList.firstChild) {
this.tabsList.firstChild.remove();
}
if (!syncedTabs.length) {
// TODO show empty state placeholder, see bug 1774168
this.tabsList.hidden = true;
return;
}
for (let i = 0; i < syncedTabs.length; i++) {
const li = this.generateListItem(syncedTabs[i], i);
this.tabsList.append(li);
}
this.tabsList.hidden = false;
// TODO implement placeholder for empty li, see bug 1775469
if (this.tabsList.hidden) {
this.tabsList.hidden = false;
}
}
generateListItem(tab, index) {

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

@ -277,12 +277,8 @@ class TabsPickupContainer extends HTMLElement {
}
tabsElem.hidden = false;
const tabPickupList = document.querySelector("tab-pickup-list");
if (stateIndex == 4 && !tabPickupList.tabsList.hasChildNodes()) {
if (tabsElem) {
tabsElem.classList.toggle("loading", false);
}
tabPickupList.getSyncedTabData();
if (stateIndex == 4) {
tabsElem.classList.toggle("loading", false);
}
}
}

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

@ -7,3 +7,4 @@ support-files = head.js
[browser_firefoxview_tab.js]
[browser_recently_closed_tabs.js]
[browser_setup_state.js]
[browser_tab_pickup_list.js]

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

@ -0,0 +1,210 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
const { UIState } = ChromeUtils.import("resource://services-sync/UIState.jsm");
const { sinon } = ChromeUtils.import("resource://testing-common/Sinon.jsm");
XPCOMUtils.defineLazyModuleGetters(globalThis, {
SyncedTabs: "resource://services-sync/SyncedTabs.jsm",
});
const syncedTabsData = [
{
id: 1,
type: "client",
name: "My desktop",
clientType: "desktop",
lastModified: 1655730486760,
tabs: [
{
type: "tab",
title: "Sandboxes - Sinon.JS",
url: "https://sinonjs.org/releases/latest/sandbox/",
icon: "https://sinonjs.org/assets/images/favicon.png",
lastUsed: 1655391592, // Thu Jun 16 2022 14:59:52 GMT+0000
},
{
type: "tab",
title: "Internet for people, not profits - Mozilla",
url: "https://www.mozilla.org/",
icon:
"https://www.mozilla.org/media/img/favicons/mozilla/favicon.d25d81d39065.ico",
lastUsed: 1655730486, // Mon Jun 20 2022 13:08:06 GMT+0000
},
],
},
{
id: 2,
type: "client",
name: "My iphone",
clientType: "mobile",
lastModified: 1655727832930,
tabs: [
{
type: "tab",
title: "The Guardian",
url: "https://www.theguardian.com/",
icon: "page-icon:https://www.theguardian.com/",
lastUsed: 1655291890, // Wed Jun 15 2022 11:18:10 GMT+0000
},
{
type: "tab",
title: "The Times",
url: "https://www.thetimes.co.uk/",
icon: "page-icon:https://www.thetimes.co.uk/",
lastUsed: 1655727485, // Mon Jun 20 2022 12:18:05 GMT+0000
},
],
},
];
const syncedTabsData2 = structuredClone(syncedTabsData);
syncedTabsData2[1].tabs.push(
{
type: "tab",
title: "Phabricator Home",
url: "https://phabricator.services.mozilla.com/",
icon: "https://phabricator.services.mozilla.com/favicon.d25d81d39065.ico",
lastUsed: 1655745700, // Mon, 20 Jun 2022 17:21:40 GMT
},
{
type: "tab",
title: "Firefox Privacy Notice",
url: "https://www.mozilla.org/en-US/privacy/firefox/",
icon:
"https://www.mozilla.org/media/img/favicons/mozilla/favicon.d25d81d39065.ico",
lastUsed: 1655745700, // Mon, 20 Jun 2022 17:21:40 GMT
}
);
function setupMocks() {
const sandbox = sinon.createSandbox();
sandbox.stub(fxAccounts.device, "recentDeviceList").get(() => [
{
id: 1,
name: "My desktop",
isCurrentDevice: true,
type: "desktop",
},
{
id: 2,
name: "My iphone",
type: "mobile",
},
]);
sandbox.stub(UIState, "get").returns({
status: UIState.STATUS_SIGNED_IN,
syncEnabled: true,
});
const syncedTabsMock = sandbox.stub(SyncedTabs, "getTabClients");
syncedTabsMock.onFirstCall().returns(syncedTabsData);
syncedTabsMock.onSecondCall().returns(syncedTabsData2);
return sandbox;
}
async function setupListState(browser) {
// Skip the synced tabs sign up flow to get to a loaded list state
await SpecialPowers.pushPrefEnv({
set: [["services.sync.engine.tabs", true]],
});
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
const recentFetchTime = Math.floor(Date.now() / 1000);
info("updating lastFetch:" + recentFetchTime);
Services.prefs.setIntPref("services.sync.lastTabFetch", recentFetchTime);
await waitForElementVisible(browser, "#tabpickup-steps", false);
await waitForElementVisible(browser, "#tabpickup-tabs-container", true);
const tabsContainer = browser.contentWindow.document.querySelector(
"#tabpickup-tabs-container"
);
await BrowserTestUtils.waitForMutationCondition(
tabsContainer,
{ attributeFilter: ["class"], attributes: true },
() => {
return !tabsContainer.classList.contains("loading");
}
);
}
add_setup(async function() {
const tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
"about:firefoxview"
);
registerCleanupFunction(async function() {
BrowserTestUtils.removeTab(tab);
Services.prefs.clearUserPref("services.sync.engine.tabs");
Services.prefs.clearUserPref("services.sync.lastTabFetch");
});
});
add_task(async function test_tab_list_ordering() {
const browser = gBrowser.selectedBrowser;
const { document } = browser.contentWindow;
const sandbox = setupMocks();
await setupListState(browser);
testVisibility(browser, {
expectedVisible: {
"ol.synced-tabs-list": true,
},
});
ok(
document.querySelector("ol.synced-tabs-list").children.length === 3,
"synced-tabs-list should have three list items"
);
ok(
document
.querySelector("ol.synced-tabs-list")
.firstChild.textContent.includes("Internet for people, not profits"),
"First list item in synced-tabs-list is in the correct order"
);
ok(
document
.querySelector("ol.synced-tabs-list")
.children[2].textContent.includes("Sandboxes - Sinon.JS"),
"Last list item in synced-tabs-list is in the correct order"
);
// Initiate a synced tabs update
Services.obs.notifyObservers(null, "services.sync.tabs.changed");
const syncedTabsList = document.querySelector("ol.synced-tabs-list");
// first list item has been updated
await BrowserTestUtils.waitForMutationCondition(
syncedTabsList,
{ childList: true },
() => syncedTabsList.firstChild.textContent.includes("Firefox")
);
ok(
document.querySelector("ol.synced-tabs-list").children.length === 3,
"Synced-tabs-list should still have three list items"
);
ok(
document
.querySelector("ol.synced-tabs-list")
.children[1].textContent.includes("Phabricator"),
"Second list item in synced-tabs-list has been updated"
);
ok(
document
.querySelector("ol.synced-tabs-list")
.children[2].textContent.includes("Internet for people, not profits"),
"Last list item in synced-tabs-list has been updated"
);
sandbox.restore();
});

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

@ -18,3 +18,21 @@ function testVisibility(browser, expected) {
}
}
}
async function waitForElementVisible(browser, selector, isVisible = true) {
const { document } = browser.contentWindow;
const elem = document.querySelector(selector);
ok(elem, `Got element with selector: ${selector}`);
await BrowserTestUtils.waitForMutationCondition(
elem,
{
attributeFilter: ["hidden"],
},
() => {
return isVisible
? BrowserTestUtils.is_visible(elem)
: BrowserTestUtils.is_hidden(elem);
}
);
}