/* 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, { SyncedTabsController: "resource:///modules/SyncedTabsController.sys.mjs", }); const { TabsSetupFlowManager } = ChromeUtils.importESModule( "resource:///modules/firefox-view-tabs-setup-manager.sys.mjs" ); import { html, ifDefined, when, } from "chrome://global/content/vendor/lit.all.mjs"; import { ViewPage } from "./viewpage.mjs"; import { escapeHtmlEntities, MAX_TABS_FOR_RECENT_BROWSING, navigateToLink, } from "./helpers.mjs"; // eslint-disable-next-line import/no-unassigned-import import "chrome://browser/content/firefoxview/syncedtabs-tab-list.mjs"; const UI_OPEN_STATE = "browser.tabs.firefox-view.ui-state.tab-pickup.open"; class SyncedTabsInView extends ViewPage { controller = new lazy.SyncedTabsController(this, { contextMenu: true, pairDeviceCallback: () => Glean.firefoxviewNext.fxaMobileSync.record({ has_devices: TabsSetupFlowManager.secondaryDeviceConnected, }), signupCallback: () => Glean.firefoxviewNext.fxaContinueSync.record(), }); constructor() { super(); this._started = false; this._id = Math.floor(Math.random() * 10e6); if (this.recentBrowsing) { this.maxTabsLength = MAX_TABS_FOR_RECENT_BROWSING; } else { // Setting maxTabsLength to -1 for no max this.maxTabsLength = -1; } this.fullyUpdated = false; this.showAll = false; this.cumulativeSearches = 0; this.onSearchQuery = this.onSearchQuery.bind(this); } static properties = { ...ViewPage.properties, showAll: { type: Boolean }, cumulativeSearches: { type: Number }, }; static queries = { cardEls: { all: "card-container" }, emptyState: "fxview-empty-state", searchTextbox: "fxview-search-textbox", tabLists: { all: "syncedtabs-tab-list" }, }; start() { if (this._started) { return; } this._started = true; this.controller.addSyncObservers(); this.controller.updateStates(); this.onVisibilityChange(); if (this.recentBrowsing) { this.recentBrowsingElement.addEventListener( "fxview-search-textbox-query", this.onSearchQuery ); } } stop() { if (!this._started) { return; } this._started = false; TabsSetupFlowManager.updateViewVisibility(this._id, "unloaded"); this.onVisibilityChange(); this.controller.removeSyncObservers(); if (this.recentBrowsing) { this.recentBrowsingElement.removeEventListener( "fxview-search-textbox-query", this.onSearchQuery ); } } disconnectedCallback() { super.disconnectedCallback(); this.stop(); } viewVisibleCallback() { this.start(); } viewHiddenCallback() { this.stop(); } onVisibilityChange() { const isOpen = this.open; const isVisible = this.isVisible; if (isVisible && isOpen) { this.update(); TabsSetupFlowManager.updateViewVisibility(this._id, "visible"); } else { TabsSetupFlowManager.updateViewVisibility( this._id, isVisible ? "closed" : "hidden" ); } this.toggleVisibilityInCardContainer(); } generateMessageCard({ action, buttonLabel, descriptionArray, descriptionLink, header, mainImageUrl, }) { return html` `; } onOpenLink(event) { navigateToLink(event); Glean.firefoxviewNext.syncedTabsTabs.record({ page: this.recentBrowsing ? "recentbrowsing" : "syncedtabs", }); if (this.controller.searchQuery) { Glean.firefoxview.cumulativeSearches[ this.recentBrowsing ? "recentbrowsing" : "syncedtabs" ].accumulateSingleSample(this.cumulativeSearches); this.cumulativeSearches = 0; } } onContextMenu(e) { this.triggerNode = e.originalTarget; e.target.querySelector("panel-list").toggle(e.detail.originalEvent); } onCloseTab(e) { const { url, fxaDeviceId, tertiaryActionClass } = e.originalTarget; if (tertiaryActionClass === "dismiss-button") { // Set new pending close tab this.controller.requestCloseRemoteTab(fxaDeviceId, url); } else if (tertiaryActionClass === "undo-button") { // User wants to undo this.controller.removePendingTabToClose(fxaDeviceId, url); } this.requestUpdate(); } panelListTemplate() { return html`
`; } noDeviceTabsTemplate(deviceName, deviceType, isSearchResultsEmpty = false) { const template = html`

${deviceName}

${when( isSearchResultsEmpty, () => html`
`, () => html`
` )}`; return this.recentBrowsing ? template : html`${template}`; } onSearchQuery(e) { this.controller.searchQuery = e.detail.query; this.cumulativeSearches = e.detail.query ? this.cumulativeSearches + 1 : 0; this.showAll = false; } deviceTemplate(deviceName, deviceType, tabItems) { return html`

${deviceName}

${this.panelListTemplate()} `; } generateTabList() { let renderArray = []; let renderInfo = this.controller.getRenderInfo(); for (let id in renderInfo) { let tabItems = renderInfo[id].tabItems; if (tabItems.length) { const template = this.recentBrowsing ? this.deviceTemplate( renderInfo[id].name, renderInfo[id].deviceType, tabItems ) : html`${this.deviceTemplate( renderInfo[id].name, renderInfo[id].deviceType, tabItems )} `; renderArray.push(template); if (this.isShowAllLinkVisible(tabItems)) { renderArray.push( html` ` ); } } else { // Check renderInfo[id].tabs.length to determine whether to display an // empty tab list message or empty search results message. // If there are no synced tabs, we always display the empty tab list // message, even if there is an active search query. renderArray.push( this.noDeviceTabsTemplate( renderInfo[id].name, renderInfo[id].deviceType, Boolean(renderInfo[id].tabs.length) ) ); } } return renderArray; } isShowAllLinkVisible(tabItems) { return ( this.recentBrowsing && this.controller.searchQuery && tabItems.length > this.maxTabsLength && !this.showAll ); } enableShowAll(event) { if ( event.type == "click" || (event.type == "keydown" && event.code == "Enter") || (event.type == "keydown" && event.code == "Space") ) { event.preventDefault(); this.showAll = true; Glean.firefoxviewNext.searchShowAllShowallbutton.record({ section: "syncedtabs", }); } } generateCardContent() { const cardProperties = this.controller.getMessageCard(); return cardProperties ? this.generateMessageCard(cardProperties) : this.generateTabList(); } render() { this.open = !TabsSetupFlowManager.isTabSyncSetupComplete || Services.prefs.getBoolPref(UI_OPEN_STATE, true); let renderArray = []; renderArray.push( html` ` ); renderArray.push( html` ` ); if (!this.recentBrowsing) { renderArray.push( html`
${when( this.controller.currentSetupStateIndex === 4, () => html` ` )}
` ); } if (this.recentBrowsing) { renderArray.push( html` >

${this.generateCardContent()}
` ); } else { renderArray.push( html`
${this.generateCardContent()}
` ); } return renderArray; } updated() { this.fullyUpdated = true; this.toggleVisibilityInCardContainer(); } } customElements.define("view-syncedtabs", SyncedTabsInView);