Bug 1880539 - Refactor firefoxview/syncedtabs.mjs to make business logic more reusable r=fxview-reviewers,nsharpley,sclements

Decoupling complex business logic from UI components.

https://treeherder.mozilla.org/jobs?repo=try&revision=a65ebee15c1ef5f47411326b0c9ba2394e17d80f

Differential Revision: https://phabricator.services.mozilla.com/D204081
This commit is contained in:
Jonathan Sudiaman 2024-03-19 23:21:13 +00:00
parent 805b20a4fa
commit 86b13540d5
3 changed files with 342 additions and 294 deletions

View file

@ -0,0 +1,297 @@
/* 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/. */
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
SyncedTabs: "resource://services-sync/SyncedTabs.sys.mjs",
});
import { SyncedTabsErrorHandler } from "resource:///modules/firefox-view-synced-tabs-error-handler.sys.mjs";
import { TabsSetupFlowManager } from "resource:///modules/firefox-view-tabs-setup-manager.sys.mjs";
import { searchTabList } from "chrome://browser/content/firefoxview/helpers.mjs";
const SYNCED_TABS_CHANGED = "services.sync.tabs.changed";
const TOPIC_SETUPSTATE_CHANGED = "firefox-view.setupstate.changed";
/**
* The controller for synced tabs components.
*
* @implements {ReactiveController}
*/
export class SyncedTabsController {
currentSetupStateIndex = -1;
currentSyncedTabs = [];
devices = [];
/**
* The current error state as determined by `SyncedTabsErrorHandler`.
*
* @type {number}
*/
errorState = null;
/**
* Component associated with this controller.
*
* @type {ReactiveControllerHost}
*/
host;
searchQuery = "";
/**
* Construct a new SyncedTabsController.
*
* @param {ReactiveControllerHost} host
*/
constructor(host) {
this.observe = this.observe.bind(this);
this.host = host;
this.host.addController(this);
}
hostConnected() {
this.host.addEventListener("click", this);
}
hostDisconnected() {
this.host.removeEventListener("click", this);
}
handleEvent(event) {
if (event.type == "click" && event.target.dataset.action) {
const { ErrorType } = SyncedTabsErrorHandler;
switch (event.target.dataset.action) {
case `${ErrorType.SYNC_ERROR}`:
case `${ErrorType.NETWORK_OFFLINE}`:
case `${ErrorType.PASSWORD_LOCKED}`: {
TabsSetupFlowManager.tryToClearError();
break;
}
case `${ErrorType.SIGNED_OUT}`:
case "sign-in": {
TabsSetupFlowManager.openFxASignup(event.target.ownerGlobal);
break;
}
case "add-device": {
TabsSetupFlowManager.openFxAPairDevice(event.target.ownerGlobal);
break;
}
case "sync-tabs-disabled": {
TabsSetupFlowManager.syncOpenTabs(event.target);
break;
}
case `${ErrorType.SYNC_DISCONNECTED}`: {
const win = event.target.ownerGlobal;
const { switchToTabHavingURI } =
win.docShell.chromeEventHandler.ownerGlobal;
switchToTabHavingURI(
"about:preferences?action=choose-what-to-sync#sync",
true,
{}
);
break;
}
}
}
}
async observe(_, topic, errorState) {
if (topic == TOPIC_SETUPSTATE_CHANGED) {
await this.updateStates(errorState);
}
if (topic == SYNCED_TABS_CHANGED) {
await this.getSyncedTabData();
}
this.host.requestUpdate();
}
async updateStates(errorState) {
let stateIndex = TabsSetupFlowManager.uiStateIndex;
errorState = errorState || SyncedTabsErrorHandler.getErrorType();
if (stateIndex == 4 && this.currentSetupStateIndex !== stateIndex) {
// trigger an initial request for the synced tabs list
await this.getSyncedTabData();
}
this.currentSetupStateIndex = stateIndex;
this.errorState = errorState;
}
actionMappings = {
"sign-in": {
header: "firefoxview-syncedtabs-signin-header",
description: "firefoxview-syncedtabs-signin-description",
buttonLabel: "firefoxview-syncedtabs-signin-primarybutton",
},
"add-device": {
header: "firefoxview-syncedtabs-adddevice-header",
description: "firefoxview-syncedtabs-adddevice-description",
buttonLabel: "firefoxview-syncedtabs-adddevice-primarybutton",
descriptionLink: {
name: "url",
url: "https://support.mozilla.org/kb/how-do-i-set-sync-my-computer#w_connect-additional-devices-to-sync",
},
},
"sync-tabs-disabled": {
header: "firefoxview-syncedtabs-synctabs-header",
description: "firefoxview-syncedtabs-synctabs-description",
buttonLabel: "firefoxview-tabpickup-synctabs-primarybutton",
},
loading: {
header: "firefoxview-syncedtabs-loading-header",
description: "firefoxview-syncedtabs-loading-description",
},
};
#getMessageCardForState({ error = false, action, errorState }) {
errorState = errorState || this.errorState;
let header,
description,
descriptionLink,
buttonLabel,
headerIconUrl,
mainImageUrl;
let descriptionArray;
if (error) {
let link;
({ header, description, link, buttonLabel } =
SyncedTabsErrorHandler.getFluentStringsForErrorType(errorState));
action = `${errorState}`;
headerIconUrl = "chrome://global/skin/icons/info-filled.svg";
mainImageUrl =
"chrome://browser/content/firefoxview/synced-tabs-error.svg";
descriptionArray = [description];
if (errorState == "password-locked") {
descriptionLink = {};
// This is ugly, but we need to special case this link so we can
// coexist with the old view.
descriptionArray.push("firefoxview-syncedtab-password-locked-link");
descriptionLink.name = "syncedtab-password-locked-link";
descriptionLink.url = link.href;
}
} else {
header = this.actionMappings[action].header;
description = this.actionMappings[action].description;
buttonLabel = this.actionMappings[action].buttonLabel;
descriptionLink = this.actionMappings[action].descriptionLink;
mainImageUrl =
"chrome://browser/content/firefoxview/synced-tabs-error.svg";
descriptionArray = [description];
}
return {
action,
buttonLabel,
descriptionArray,
descriptionLink,
error,
header,
headerIconUrl,
mainImageUrl,
};
}
getRenderInfo() {
let renderInfo = {};
for (let tab of this.currentSyncedTabs) {
if (!(tab.client in renderInfo)) {
renderInfo[tab.client] = {
name: tab.device,
deviceType: tab.deviceType,
tabs: [],
};
}
renderInfo[tab.client].tabs.push(tab);
}
// Add devices without tabs
for (let device of this.devices) {
if (!(device.id in renderInfo)) {
renderInfo[device.id] = {
name: device.name,
deviceType: device.clientType,
tabs: [],
};
}
}
for (let id in renderInfo) {
renderInfo[id].tabItems = this.searchQuery
? searchTabList(this.searchQuery, this.getTabItems(renderInfo[id].tabs))
: this.getTabItems(renderInfo[id].tabs);
}
return renderInfo;
}
getMessageCard() {
switch (this.currentSetupStateIndex) {
case 0 /* error-state */:
if (this.errorState) {
return this.#getMessageCardForState({ error: true });
}
return this.#getMessageCardForState({ action: "loading" });
case 1 /* not-signed-in */:
if (Services.prefs.prefHasUserValue("services.sync.lastversion")) {
// If this pref is set, the user has signed out of sync.
// This path is also taken if we are disconnected from sync. See bug 1784055
return this.#getMessageCardForState({
error: true,
errorState: "signed-out",
});
}
return this.#getMessageCardForState({ action: "sign-in" });
case 2 /* connect-secondary-device*/:
return this.#getMessageCardForState({ action: "add-device" });
case 3 /* disabled-tab-sync */:
return this.#getMessageCardForState({ action: "sync-tabs-disabled" });
case 4 /* synced-tabs-loaded*/:
// There seems to be an edge case where sync says everything worked
// fine but we have no devices.
if (!this.devices.length) {
return this.#getMessageCardForState({ action: "add-device" });
}
}
return null;
}
getTabItems(tabs) {
return tabs?.map(tab => ({
icon: tab.icon,
title: tab.title,
time: tab.lastUsed * 1000,
url: tab.url,
primaryL10nId: "firefoxview-tabs-list-tab-button",
primaryL10nArgs: JSON.stringify({ targetURI: tab.url }),
secondaryL10nId: "fxviewtabrow-options-menu-button",
secondaryL10nArgs: JSON.stringify({ tabTitle: tab.title }),
}));
}
updateTabsList(syncedTabs) {
if (!syncedTabs.length) {
this.currentSyncedTabs = syncedTabs;
}
const tabsToRender = syncedTabs;
// Return early if new tabs are the same as previous ones
if (lazy.ObjectUtils.deepEqual(tabsToRender, this.currentSyncedTabs)) {
return;
}
this.currentSyncedTabs = tabsToRender;
}
async getSyncedTabData() {
this.devices = await lazy.SyncedTabs.getTabClients();
let tabs = await lazy.SyncedTabs.createRecentTabsList(this.devices, 50, {
removeAllDupes: false,
removeDeviceDupes: true,
});
this.updateTabsList(tabs);
}
}

View file

@ -4,12 +4,9 @@
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
SyncedTabs: "resource://services-sync/SyncedTabs.sys.mjs",
SyncedTabsController: "resource:///modules/SyncedTabsController.sys.mjs",
});
const { SyncedTabsErrorHandler } = ChromeUtils.importESModule(
"resource:///modules/firefox-view-synced-tabs-error-handler.sys.mjs"
);
const { TabsSetupFlowManager } = ChromeUtils.importESModule(
"resource:///modules/firefox-view-tabs-setup-manager.sys.mjs"
);
@ -23,7 +20,6 @@ import { ViewPage } from "./viewpage.mjs";
import {
escapeHtmlEntities,
isSearchEnabled,
searchTabList,
MAX_TABS_FOR_RECENT_BROWSING,
navigateToLink,
} from "./helpers.mjs";
@ -33,34 +29,26 @@ const TOPIC_SETUPSTATE_CHANGED = "firefox-view.setupstate.changed";
const UI_OPEN_STATE = "browser.tabs.firefox-view.ui-state.tab-pickup.open";
class SyncedTabsInView extends ViewPage {
controller = new lazy.SyncedTabsController(this);
constructor() {
super();
this._started = false;
this.boundObserve = (...args) => this.observe(...args);
this._currentSetupStateIndex = -1;
this.errorState = null;
this._id = Math.floor(Math.random() * 10e6);
this.currentSyncedTabs = [];
if (this.recentBrowsing) {
this.maxTabsLength = MAX_TABS_FOR_RECENT_BROWSING;
} else {
// Setting maxTabsLength to -1 for no max
this.maxTabsLength = -1;
}
this.devices = [];
this.fullyUpdated = false;
this.searchQuery = "";
this.showAll = false;
this.cumulativeSearches = 0;
this.onSearchQuery = this.onSearchQuery.bind(this);
}
static properties = {
...ViewPage.properties,
errorState: { type: Number },
currentSyncedTabs: { type: Array },
_currentSetupStateIndex: { type: Number },
devices: { type: Array },
searchQuery: { type: String },
showAll: { type: Boolean },
cumulativeSearches: { type: Number },
};
@ -72,26 +60,21 @@ class SyncedTabsInView extends ViewPage {
tabLists: { all: "fxview-tab-list" },
};
connectedCallback() {
super.connectedCallback();
this.addEventListener("click", this);
}
start() {
if (this._started) {
return;
}
this._started = true;
Services.obs.addObserver(this.boundObserve, TOPIC_SETUPSTATE_CHANGED);
Services.obs.addObserver(this.boundObserve, SYNCED_TABS_CHANGED);
Services.obs.addObserver(this.controller.observe, TOPIC_SETUPSTATE_CHANGED);
Services.obs.addObserver(this.controller.observe, SYNCED_TABS_CHANGED);
this.updateStates();
this.controller.updateStates();
this.onVisibilityChange();
if (this.recentBrowsing) {
this.recentBrowsingElement.addEventListener(
"fxview-search-textbox-query",
this
this.onSearchQuery
);
}
}
@ -104,74 +87,25 @@ class SyncedTabsInView extends ViewPage {
TabsSetupFlowManager.updateViewVisibility(this._id, "unloaded");
this.onVisibilityChange();
Services.obs.removeObserver(this.boundObserve, TOPIC_SETUPSTATE_CHANGED);
Services.obs.removeObserver(this.boundObserve, SYNCED_TABS_CHANGED);
Services.obs.removeObserver(
this.controller.observe,
TOPIC_SETUPSTATE_CHANGED
);
Services.obs.removeObserver(this.controller.observe, SYNCED_TABS_CHANGED);
if (this.recentBrowsing) {
this.recentBrowsingElement.removeEventListener(
"fxview-search-textbox-query",
this
this.onSearchQuery
);
}
}
willUpdate(changedProperties) {
if (changedProperties.has("searchQuery")) {
this.cumulativeSearches = this.searchQuery
? this.cumulativeSearches + 1
: 0;
}
}
disconnectedCallback() {
super.disconnectedCallback();
this.stop();
}
handleEvent(event) {
if (event.type == "click" && event.target.dataset.action) {
const { ErrorType } = SyncedTabsErrorHandler;
switch (event.target.dataset.action) {
case `${ErrorType.SYNC_ERROR}`:
case `${ErrorType.NETWORK_OFFLINE}`:
case `${ErrorType.PASSWORD_LOCKED}`: {
TabsSetupFlowManager.tryToClearError();
break;
}
case `${ErrorType.SIGNED_OUT}`:
case "sign-in": {
TabsSetupFlowManager.openFxASignup(event.target.ownerGlobal);
break;
}
case "add-device": {
TabsSetupFlowManager.openFxAPairDevice(event.target.ownerGlobal);
break;
}
case "sync-tabs-disabled": {
TabsSetupFlowManager.syncOpenTabs(event.target);
break;
}
case `${ErrorType.SYNC_DISCONNECTED}`: {
const win = event.target.ownerGlobal;
const { switchToTabHavingURI } =
win.docShell.chromeEventHandler.ownerGlobal;
switchToTabHavingURI(
"about:preferences?action=choose-what-to-sync#sync",
true,
{}
);
break;
}
}
}
if (event.type == "change") {
TabsSetupFlowManager.syncOpenTabs(event.target);
}
if (this.recentBrowsing && event.type === "fxview-search-textbox-query") {
this.onSearchQuery(event);
}
}
viewVisibleCallback() {
this.start();
}
@ -196,90 +130,16 @@ class SyncedTabsInView extends ViewPage {
this.toggleVisibilityInCardContainer();
}
async observe(subject, topic, errorState) {
if (topic == TOPIC_SETUPSTATE_CHANGED) {
this.updateStates(errorState);
}
if (topic == SYNCED_TABS_CHANGED) {
this.getSyncedTabData();
}
}
updateStates(errorState) {
let stateIndex = TabsSetupFlowManager.uiStateIndex;
errorState = errorState || SyncedTabsErrorHandler.getErrorType();
if (stateIndex == 4 && this._currentSetupStateIndex !== stateIndex) {
// trigger an initial request for the synced tabs list
this.getSyncedTabData();
}
this._currentSetupStateIndex = stateIndex;
this.errorState = errorState;
}
actionMappings = {
"sign-in": {
header: "firefoxview-syncedtabs-signin-header",
description: "firefoxview-syncedtabs-signin-description",
buttonLabel: "firefoxview-syncedtabs-signin-primarybutton",
},
"add-device": {
header: "firefoxview-syncedtabs-adddevice-header",
description: "firefoxview-syncedtabs-adddevice-description",
buttonLabel: "firefoxview-syncedtabs-adddevice-primarybutton",
descriptionLink: {
name: "url",
url: "https://support.mozilla.org/kb/how-do-i-set-sync-my-computer#w_connect-additional-devices-to-sync",
},
},
"sync-tabs-disabled": {
header: "firefoxview-syncedtabs-synctabs-header",
description: "firefoxview-syncedtabs-synctabs-description",
buttonLabel: "firefoxview-tabpickup-synctabs-primarybutton",
},
loading: {
header: "firefoxview-syncedtabs-loading-header",
description: "firefoxview-syncedtabs-loading-description",
},
};
generateMessageCard({ error = false, action, errorState }) {
errorState = errorState || this.errorState;
let header,
description,
descriptionLink,
buttonLabel,
headerIconUrl,
mainImageUrl;
let descriptionArray;
if (error) {
let link;
({ header, description, link, buttonLabel } =
SyncedTabsErrorHandler.getFluentStringsForErrorType(errorState));
action = `${errorState}`;
headerIconUrl = "chrome://global/skin/icons/info-filled.svg";
mainImageUrl =
"chrome://browser/content/firefoxview/synced-tabs-error.svg";
descriptionArray = [description];
if (errorState == "password-locked") {
descriptionLink = {};
// This is ugly, but we need to special case this link so we can
// coexist with the old view.
descriptionArray.push("firefoxview-syncedtab-password-locked-link");
descriptionLink.name = "syncedtab-password-locked-link";
descriptionLink.url = link.href;
}
} else {
header = this.actionMappings[action].header;
description = this.actionMappings[action].description;
buttonLabel = this.actionMappings[action].buttonLabel;
descriptionLink = this.actionMappings[action].descriptionLink;
mainImageUrl =
"chrome://browser/content/firefoxview/synced-tabs-error.svg";
descriptionArray = [description];
}
generateMessageCard({
action,
buttonLabel,
descriptionArray,
descriptionLink,
error,
header,
headerIconUrl,
mainImageUrl,
}) {
return html`
<fxview-empty-state
headerLabel=${header}
@ -299,7 +159,7 @@ class SyncedTabsInView extends ViewPage {
?hidden=${!buttonLabel}
data-l10n-id="${ifDefined(buttonLabel)}"
data-action="${action}"
@click=${this.handleEvent}
@click=${this.controller.handleEvent}
aria-details="empty-container"
></button>
</fxview-empty-state>
@ -319,7 +179,7 @@ class SyncedTabsInView extends ViewPage {
}
);
if (this.searchQuery) {
if (this.controller.searchQuery) {
const searchesHistogram = Services.telemetry.getKeyedHistogramById(
"FIREFOX_VIEW_CUMULATIVE_SEARCHES"
);
@ -375,7 +235,7 @@ class SyncedTabsInView extends ViewPage {
class="blackbox notabs search-results-empty"
data-l10n-id="firefoxview-search-results-empty"
data-l10n-args=${JSON.stringify({
query: escapeHtmlEntities(this.searchQuery),
query: escapeHtmlEntities(this.controller.searchQuery),
})}
></div>
`,
@ -396,7 +256,8 @@ class SyncedTabsInView extends ViewPage {
}
onSearchQuery(e) {
this.searchQuery = e.detail.query;
this.controller.searchQuery = e.detail.query;
this.cumulativeSearches = e.detail.query ? this.cumulativeSearches + 1 : 0;
this.showAll = false;
}
@ -413,7 +274,7 @@ class SyncedTabsInView extends ViewPage {
secondaryActionClass="options-button"
hasPopup="menu"
.tabItems=${ifDefined(tabItems)}
.searchQuery=${this.searchQuery}
.searchQuery=${this.controller.searchQuery}
maxTabsLength=${this.showAll ? -1 : this.maxTabsLength}
@fxview-tab-list-primary-action=${this.onOpenLink}
@fxview-tab-list-secondary-action=${this.onContextMenu}
@ -425,33 +286,9 @@ class SyncedTabsInView extends ViewPage {
generateTabList() {
let renderArray = [];
let renderInfo = {};
for (let tab of this.currentSyncedTabs) {
if (!(tab.client in renderInfo)) {
renderInfo[tab.client] = {
name: tab.device,
deviceType: tab.deviceType,
tabs: [],
};
}
renderInfo[tab.client].tabs.push(tab);
}
// Add devices without tabs
for (let device of this.devices) {
if (!(device.id in renderInfo)) {
renderInfo[device.id] = {
name: device.name,
deviceType: device.clientType,
tabs: [],
};
}
}
let renderInfo = this.controller.getRenderInfo();
for (let id in renderInfo) {
let tabItems = this.searchQuery
? searchTabList(this.searchQuery, this.getTabItems(renderInfo[id].tabs))
: this.getTabItems(renderInfo[id].tabs);
let tabItems = renderInfo[id].tabItems;
if (tabItems.length) {
const template = this.recentBrowsing
? this.deviceTemplate(
@ -500,7 +337,7 @@ class SyncedTabsInView extends ViewPage {
isShowAllLinkVisible(tabItems) {
return (
this.recentBrowsing &&
this.searchQuery &&
this.controller.searchQuery &&
tabItems.length > this.maxTabsLength &&
!this.showAll
);
@ -527,35 +364,10 @@ class SyncedTabsInView extends ViewPage {
}
generateCardContent() {
switch (this._currentSetupStateIndex) {
case 0 /* error-state */:
if (this.errorState) {
return this.generateMessageCard({ error: true });
}
return this.generateMessageCard({ action: "loading" });
case 1 /* not-signed-in */:
if (Services.prefs.prefHasUserValue("services.sync.lastversion")) {
// If this pref is set, the user has signed out of sync.
// This path is also taken if we are disconnected from sync. See bug 1784055
return this.generateMessageCard({
error: true,
errorState: "signed-out",
});
}
return this.generateMessageCard({ action: "sign-in" });
case 2 /* connect-secondary-device*/:
return this.generateMessageCard({ action: "add-device" });
case 3 /* disabled-tab-sync */:
return this.generateMessageCard({ action: "sync-tabs-disabled" });
case 4 /* synced-tabs-loaded*/:
// There seems to be an edge case where sync says everything worked
// fine but we have no devices.
if (!this.devices.length) {
return this.generateMessageCard({ action: "add-device" });
}
return this.generateTabList();
}
return html``;
const cardProperties = this.controller.getMessageCard();
return cardProperties
? this.generateMessageCard(cardProperties)
: this.generateTabList();
}
render() {
@ -580,7 +392,7 @@ class SyncedTabsInView extends ViewPage {
data-l10n-id="firefoxview-synced-tabs-header"
></h2>
${when(
isSearchEnabled() || this._currentSetupStateIndex === 4,
isSearchEnabled() || this.controller.currentSetupStateIndex === 4,
() => html`<div class="syncedtabs-header">
${when(
isSearchEnabled(),
@ -597,12 +409,12 @@ class SyncedTabsInView extends ViewPage {
</div>`
)}
${when(
this._currentSetupStateIndex === 4,
this.controller.currentSetupStateIndex === 4,
() => html`
<button
class="small-button"
data-action="add-device"
@click=${this.handleEvent}
@click=${this.controller.handleEvent}
>
<img
class="icon"
@ -626,9 +438,9 @@ class SyncedTabsInView extends ViewPage {
html`<card-container
preserveCollapseState
shortPageName="syncedtabs"
?showViewAll=${this._currentSetupStateIndex == 4 &&
this.currentSyncedTabs.length}
?isEmptyState=${!this.currentSyncedTabs.length}
?showViewAll=${this.controller.currentSetupStateIndex == 4 &&
this.controller.currentSyncedTabs.length}
?isEmptyState=${!this.controller.currentSyncedTabs.length}
>
>
<h3
@ -647,71 +459,9 @@ class SyncedTabsInView extends ViewPage {
return renderArray;
}
async onReload() {
await TabsSetupFlowManager.syncOnPageReload();
}
getTabItems(tabs) {
tabs = tabs || this.tabs;
return tabs?.map(tab => ({
icon: tab.icon,
title: tab.title,
time: tab.lastUsed * 1000,
url: tab.url,
primaryL10nId: "firefoxview-tabs-list-tab-button",
primaryL10nArgs: JSON.stringify({ targetURI: tab.url }),
secondaryL10nId: "fxviewtabrow-options-menu-button",
secondaryL10nArgs: JSON.stringify({ tabTitle: tab.title }),
}));
}
updateTabsList(syncedTabs) {
if (!syncedTabs.length) {
this.currentSyncedTabs = syncedTabs;
this.sendTabTelemetry(0);
}
const tabsToRender = syncedTabs;
// Return early if new tabs are the same as previous ones
if (
JSON.stringify(tabsToRender) == JSON.stringify(this.currentSyncedTabs)
) {
return;
}
this.currentSyncedTabs = tabsToRender;
// Record the full tab count
this.sendTabTelemetry(syncedTabs.length);
}
async getSyncedTabData() {
this.devices = await lazy.SyncedTabs.getTabClients();
let tabs = await lazy.SyncedTabs.createRecentTabsList(this.devices, 50, {
removeAllDupes: false,
removeDeviceDupes: true,
});
this.updateTabsList(tabs);
}
updated() {
this.fullyUpdated = true;
this.toggleVisibilityInCardContainer();
}
sendTabTelemetry() {
/*
Services.telemetry.recordEvent(
"firefoxview_next",
"synced_tabs",
"tabs",
null,
{
count: numTabs.toString(),
}
);
*/
}
}
customElements.define("view-syncedtabs", SyncedTabsInView);

View file

@ -605,7 +605,8 @@ add_task(async function test_cumulative_searches_syncedtabs_telemetry() {
);
await TestUtils.waitForCondition(
() =>
syncedTabs.tabLists[0].rowEls.length === 1 && syncedTabs?.searchQuery,
syncedTabs.tabLists[0].rowEls.length === 1 &&
syncedTabs.controller.searchQuery,
"Expected search results are not shown yet."
);