fune/browser/components/customizableui/test/browser_synced_tabs_menu.js
Cristian Tuns b3bf09cc0d Backed out 6 changesets (bug 1816934, bug 1817182, bug 1817179, bug 1817183) for causing dt failures in browser_jsterm_autocomplete_null.js CLOSED TREE
Backed out changeset 17d4c013ed92 (bug 1817183)
Backed out changeset cfed8d9c23f3 (bug 1817183)
Backed out changeset 62fe2f589efe (bug 1817182)
Backed out changeset 557bd773fb85 (bug 1817179)
Backed out changeset 7f8a7865868b (bug 1816934)
Backed out changeset d6c1d4c0d2a0 (bug 1816934)
2023-02-17 10:51:33 -05:00

525 lines
16 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
requestLongerTimeout(2);
const { FxAccounts } = ChromeUtils.import(
"resource://gre/modules/FxAccounts.jsm"
);
let { SyncedTabs } = ChromeUtils.importESModule(
"resource://services-sync/SyncedTabs.sys.mjs"
);
let { UIState } = ChromeUtils.importESModule(
"resource://services-sync/UIState.sys.mjs"
);
ChromeUtils.defineModuleGetter(
this,
"UITour",
"resource:///modules/UITour.jsm"
);
const DECKINDEX_TABS = 0;
const DECKINDEX_FETCHING = 1;
const DECKINDEX_TABSDISABLED = 2;
const DECKINDEX_NOCLIENTS = 3;
const SAMPLE_TAB_URL = "https://example.com/";
var initialLocation = gBrowser.currentURI.spec;
var newTab = null;
// A helper to notify there are new tabs. Returns a promise that is resolved
// once the UI has been updated.
function updateTabsPanel() {
let promiseTabsUpdated = promiseObserverNotified(
"synced-tabs-menu:test:tabs-updated"
);
Services.obs.notifyObservers(null, SyncedTabs.TOPIC_TABS_CHANGED);
return promiseTabsUpdated;
}
// This is the mock we use for SyncedTabs.jsm - tests may override various
// functions.
let mockedInternal = {
get isConfiguredToSyncTabs() {
return true;
},
getTabClients() {
return Promise.resolve([]);
},
syncTabs() {
return Promise.resolve();
},
hasSyncedThisSession: false,
};
add_setup(async function() {
const getSignedInUser = FxAccounts.config.getSignedInUser;
FxAccounts.config.getSignedInUser = async () =>
Promise.resolve({ uid: "uid", email: "foo@bar.com" });
Services.prefs.setCharPref(
"identity.fxaccounts.remote.root",
"https://example.com/"
);
let oldInternal = SyncedTabs._internal;
SyncedTabs._internal = mockedInternal;
let origNotifyStateUpdated = UIState._internal.notifyStateUpdated;
// Sync start-up will interfere with our tests, don't let UIState send UI updates.
UIState._internal.notifyStateUpdated = () => {};
// Force gSync initialization
gSync.init();
registerCleanupFunction(() => {
FxAccounts.config.getSignedInUser = getSignedInUser;
Services.prefs.clearUserPref("identity.fxaccounts.remote.root");
UIState._internal.notifyStateUpdated = origNotifyStateUpdated;
SyncedTabs._internal = oldInternal;
});
});
// The test expects the about:preferences#sync page to open in the current tab
async function openPrefsFromMenuPanel(expectedPanelId, entryPoint) {
info("Check Sync button functionality");
CustomizableUI.addWidgetToArea(
"sync-button",
CustomizableUI.AREA_FIXED_OVERFLOW_PANEL
);
await waitForOverflowButtonShown();
// check the button's functionality
await document.getElementById("nav-bar").overflowable.show();
if (entryPoint == "uitour") {
UITour.tourBrowsersByWindow.set(window, new Set());
UITour.tourBrowsersByWindow.get(window).add(gBrowser.selectedBrowser);
}
let syncButton = document.getElementById("sync-button");
ok(syncButton, "The Sync button was added to the Panel Menu");
let tabsUpdatedPromise = promiseObserverNotified(
"synced-tabs-menu:test:tabs-updated"
);
syncButton.click();
let syncPanel = document.getElementById("PanelUI-remotetabs");
let viewShownPromise = BrowserTestUtils.waitForEvent(syncPanel, "ViewShown");
await Promise.all([tabsUpdatedPromise, viewShownPromise]);
ok(syncPanel.getAttribute("visible"), "Sync Panel is in view");
// Sync is not configured - verify that state is reflected.
let subpanel = document.getElementById(expectedPanelId);
ok(!subpanel.hidden, "sync setup element is visible");
// Find and click the "setup" button.
let setupButton = subpanel.querySelector(".PanelUI-remotetabs-button");
setupButton.click();
await new Promise(resolve => {
let handler = async e => {
if (
e.originalTarget != gBrowser.selectedBrowser.contentDocument ||
e.target.location.href == "about:blank"
) {
info("Skipping spurious 'load' event for " + e.target.location.href);
return;
}
gBrowser.selectedBrowser.removeEventListener("load", handler, true);
resolve();
};
gBrowser.selectedBrowser.addEventListener("load", handler, true);
});
newTab = gBrowser.selectedTab;
is(
gBrowser.currentURI.spec,
"about:preferences?entrypoint=" + entryPoint + "#sync",
"Firefox Sync preference page opened with `menupanel` entrypoint"
);
ok(!isOverflowOpen(), "The panel closed");
if (isOverflowOpen()) {
await hideOverflow();
}
}
function hideOverflow() {
let panelHidePromise = promiseOverflowHidden(window);
PanelUI.overflowPanel.hidePopup();
return panelHidePromise;
}
async function asyncCleanup() {
// reset the panel UI to the default state
await resetCustomization();
ok(CustomizableUI.inDefaultState, "The panel UI is in default state again.");
// restore the tabs
BrowserTestUtils.addTab(gBrowser, initialLocation);
gBrowser.removeTab(newTab);
UITour.tourBrowsersByWindow.delete(window);
}
// When Sync is not setup.
add_task(async function() {
gSync.updateAllUI({ status: UIState.STATUS_NOT_CONFIGURED });
await openPrefsFromMenuPanel("PanelUI-remotetabs-setupsync", "synced-tabs");
});
add_task(asyncCleanup);
// When an account is connected by Sync is not enabled.
add_task(async function() {
gSync.updateAllUI({ status: UIState.STATUS_SIGNED_IN, syncEnabled: false });
await openPrefsFromMenuPanel(
"PanelUI-remotetabs-syncdisabled",
"synced-tabs"
);
});
add_task(asyncCleanup);
// When Sync is configured in an unverified state.
add_task(async function() {
gSync.updateAllUI({
status: UIState.STATUS_NOT_VERIFIED,
email: "foo@bar.com",
});
await openPrefsFromMenuPanel("PanelUI-remotetabs-unverified", "synced-tabs");
});
add_task(asyncCleanup);
// When Sync is configured in a "needs reauthentication" state.
add_task(async function() {
gSync.updateAllUI({
status: UIState.STATUS_LOGIN_FAILED,
email: "foo@bar.com",
});
await openPrefsFromMenuPanel("PanelUI-remotetabs-reauthsync", "synced-tabs");
});
// Test the Connect Another Device button
add_task(async function() {
gSync.updateAllUI({
status: UIState.STATUS_SIGNED_IN,
syncEnabled: true,
email: "foo@bar.com",
lastSync: new Date(),
});
let button = document.getElementById(
"PanelUI-remotetabs-connect-device-button"
);
ok(button, "found the button");
await document.getElementById("nav-bar").overflowable.show();
let expectedUrl =
"https://example.com/connect_another_device?context=" +
"fx_desktop_v3&entrypoint=synced-tabs&service=sync&uid=uid&email=foo%40bar.com";
let promiseTabOpened = BrowserTestUtils.waitForNewTab(gBrowser, expectedUrl);
button.click();
// the panel should have been closed.
ok(!isOverflowOpen(), "click closed the panel");
await promiseTabOpened;
gBrowser.removeTab(gBrowser.selectedTab);
});
// Test the "Sync Now" button
add_task(async function() {
gSync.updateAllUI({
status: UIState.STATUS_SIGNED_IN,
syncEnabled: true,
email: "foo@bar.com",
lastSync: new Date(),
});
await document.getElementById("nav-bar").overflowable.show();
let tabsUpdatedPromise = promiseObserverNotified(
"synced-tabs-menu:test:tabs-updated"
);
let syncPanel = document.getElementById("PanelUI-remotetabs");
let viewShownPromise = BrowserTestUtils.waitForEvent(syncPanel, "ViewShown");
let syncButton = document.getElementById("sync-button");
syncButton.click();
await Promise.all([tabsUpdatedPromise, viewShownPromise]);
ok(syncPanel.getAttribute("visible"), "Sync Panel is in view");
let subpanel = document.getElementById("PanelUI-remotetabs-main");
ok(!subpanel.hidden, "main pane is visible");
let deck = document.getElementById("PanelUI-remotetabs-deck");
// The widget is still fetching tabs, as we've neutered everything that
// provides them
is(deck.selectedIndex, DECKINDEX_FETCHING, "first deck entry is visible");
// Tell the widget there are tabs available, but with zero clients.
mockedInternal.getTabClients = () => {
return Promise.resolve([]);
};
mockedInternal.hasSyncedThisSession = true;
await updateTabsPanel();
// The UI should be showing the "no clients" pane.
is(
deck.selectedIndex,
DECKINDEX_NOCLIENTS,
"no-clients deck entry is visible"
);
// Tell the widget there are tabs available - we have 3 clients, one with no
// tabs.
mockedInternal.getTabClients = () => {
return Promise.resolve([
{
id: "guid_mobile",
type: "client",
name: "My Phone",
lastModified: 1492201200,
tabs: [],
},
{
id: "guid_desktop",
type: "client",
name: "My Desktop",
lastModified: 1492201200,
tabs: [
{
title: "http://example.com/10",
lastUsed: 10, // the most recent
},
{
title: "http://example.com/1",
lastUsed: 1, // the least recent.
},
{
title: "http://example.com/5",
lastUsed: 5,
},
],
},
{
id: "guid_second_desktop",
name: "My Other Desktop",
lastModified: 1492201200,
tabs: [
{
title: "http://example.com/6",
lastUsed: 6,
},
],
},
]);
};
await updateTabsPanel();
// The UI should be showing tabs!
is(deck.selectedIndex, DECKINDEX_TABS, "no-clients deck entry is visible");
let tabList = document.getElementById("PanelUI-remotetabs-tabslist");
let node = tabList.firstElementChild;
// First entry should be the client with the most-recent tab.
is(node.nodeName, "vbox");
let currentClient = node;
node = node.firstElementChild;
is(node.getAttribute("itemtype"), "client", "node is a client entry");
is(node.textContent, "My Desktop", "correct client");
// Next entry is the most-recent tab
node = node.nextElementSibling;
is(node.getAttribute("itemtype"), "tab", "node is a tab");
is(node.getAttribute("label"), "http://example.com/10");
// Next entry is the next-most-recent tab
node = node.nextElementSibling;
is(node.getAttribute("itemtype"), "tab", "node is a tab");
is(node.getAttribute("label"), "http://example.com/5");
// Next entry is the least-recent tab from the first client.
node = node.nextElementSibling;
is(node.getAttribute("itemtype"), "tab", "node is a tab");
is(node.getAttribute("label"), "http://example.com/1");
node = node.nextElementSibling;
is(node, null, "no more siblings");
// Next is a toolbarseparator between the clients.
node = currentClient.nextElementSibling;
is(node.nodeName, "toolbarseparator");
// Next is the container for client 2.
node = node.nextElementSibling;
is(node.nodeName, "vbox");
currentClient = node;
// Next is the client with 1 tab.
node = node.firstElementChild;
is(node.getAttribute("itemtype"), "client", "node is a client entry");
is(node.textContent, "My Other Desktop", "correct client");
// Its single tab
node = node.nextElementSibling;
is(node.getAttribute("itemtype"), "tab", "node is a tab");
is(node.getAttribute("label"), "http://example.com/6");
node = node.nextElementSibling;
is(node, null, "no more siblings");
// Next is a toolbarseparator between the clients.
node = currentClient.nextElementSibling;
is(node.nodeName, "toolbarseparator");
// Next is the container for client 3.
node = node.nextElementSibling;
is(node.nodeName, "vbox");
currentClient = node;
// Next is the client with no tab.
node = node.firstElementChild;
is(node.getAttribute("itemtype"), "client", "node is a client entry");
is(node.textContent, "My Phone", "correct client");
// There is a single node saying there's no tabs for the client.
node = node.nextElementSibling;
is(node.nodeName, "label", "node is a label");
is(node.getAttribute("itemtype"), "", "node is neither a tab nor a client");
node = node.nextElementSibling;
is(node, null, "no more siblings");
is(currentClient.nextElementSibling, null, "no more clients");
// Check accessibility. There should be containers for each client, with an
// aria attribute that identifies the client name.
let clientContainers = [
...tabList.querySelectorAll("[aria-labelledby]").values(),
];
let labelIds = clientContainers.map(container =>
container.getAttribute("aria-labelledby")
);
let labels = labelIds.map(id => document.getElementById(id).textContent);
Assert.deepEqual(labels.sort(), [
"My Desktop",
"My Other Desktop",
"My Phone",
]);
let didSync = false;
let oldDoSync = gSync.doSync;
gSync.doSync = function() {
didSync = true;
gSync.doSync = oldDoSync;
};
let syncNowButton = document.getElementById("PanelUI-remotetabs-syncnow");
is(syncNowButton.disabled, false);
syncNowButton.click();
ok(didSync, "clicking the button called the correct function");
await hideOverflow();
});
// Test the pagination capabilities (Show More/All tabs)
add_task(async function() {
mockedInternal.getTabClients = () => {
return Promise.resolve([
{
id: "guid_desktop",
type: "client",
name: "My Desktop",
lastModified: 1492201200,
tabs: (function() {
let allTabsDesktop = [];
// We choose 77 tabs, because TABS_PER_PAGE is 25, which means
// on the second to last page we should have 22 items shown
// (because we have to show at least NEXT_PAGE_MIN_TABS=5 tabs on the last page)
for (let i = 1; i <= 77; i++) {
allTabsDesktop.push({ title: "Tab #" + i, url: SAMPLE_TAB_URL });
}
return allTabsDesktop;
})(),
},
]);
};
gSync.updateAllUI({
status: UIState.STATUS_SIGNED_IN,
syncEnabled: true,
lastSync: new Date(),
email: "foo@bar.com",
});
await document.getElementById("nav-bar").overflowable.show();
let tabsUpdatedPromise = promiseObserverNotified(
"synced-tabs-menu:test:tabs-updated"
);
let syncPanel = document.getElementById("PanelUI-remotetabs");
let viewShownPromise = BrowserTestUtils.waitForEvent(syncPanel, "ViewShown");
let syncButton = document.getElementById("sync-button");
syncButton.click();
await Promise.all([tabsUpdatedPromise, viewShownPromise]);
// Check pre-conditions
ok(syncPanel.getAttribute("visible"), "Sync Panel is in view");
let subpanel = document.getElementById("PanelUI-remotetabs-main");
ok(!subpanel.hidden, "main pane is visible");
let deck = document.getElementById("PanelUI-remotetabs-deck");
is(deck.selectedIndex, DECKINDEX_TABS, "we should be showing tabs");
function checkTabsPage(tabsShownCount, showMoreLabel) {
let tabList = document.getElementById("PanelUI-remotetabs-tabslist");
let node = tabList.firstElementChild.firstElementChild;
is(node.getAttribute("itemtype"), "client", "node is a client entry");
is(node.textContent, "My Desktop", "correct client");
for (let i = 0; i < tabsShownCount; i++) {
node = node.nextElementSibling;
is(node.getAttribute("itemtype"), "tab", "node is a tab");
is(
node.getAttribute("label"),
"Tab #" + (i + 1),
"the tab is the correct one"
);
is(
node.getAttribute("targetURI"),
SAMPLE_TAB_URL,
"url is the correct one"
);
}
let showMoreButton;
if (showMoreLabel) {
node = showMoreButton = node.nextElementSibling;
is(
node.getAttribute("itemtype"),
"showmorebutton",
"node is a show more button"
);
is(node.getAttribute("label"), showMoreLabel);
}
node = node.nextElementSibling;
is(node, null, "no more entries");
return showMoreButton;
}
async function checkCanOpenURL() {
let tabList = document.getElementById("PanelUI-remotetabs-tabslist");
let node = tabList.firstElementChild.firstElementChild.nextElementSibling;
let promiseTabOpened = BrowserTestUtils.waitForLocationChange(
gBrowser,
SAMPLE_TAB_URL
);
node.click();
await promiseTabOpened;
}
let showMoreButton;
function clickShowMoreButton() {
let promise = promiseObserverNotified("synced-tabs-menu:test:tabs-updated");
showMoreButton.click();
return promise;
}
showMoreButton = checkTabsPage(25, "Show More Tabs");
await clickShowMoreButton();
checkTabsPage(77, null);
/* calling this will close the overflow menu */
await checkCanOpenURL();
});