summaryrefslogtreecommitdiffstats
path: root/browser/components/firefoxview/syncedtabs.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/firefoxview/syncedtabs.mjs')
-rw-r--r--browser/components/firefoxview/syncedtabs.mjs387
1 files changed, 69 insertions, 318 deletions
diff --git a/browser/components/firefoxview/syncedtabs.mjs b/browser/components/firefoxview/syncedtabs.mjs
index d64da45a30..1c65650c10 100644
--- a/browser/components/firefoxview/syncedtabs.mjs
+++ b/browser/components/firefoxview/syncedtabs.mjs
@@ -4,13 +4,9 @@
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
- BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
- 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"
);
@@ -24,43 +20,52 @@ import { ViewPage } from "./viewpage.mjs";
import {
escapeHtmlEntities,
isSearchEnabled,
- searchTabList,
MAX_TABS_FOR_RECENT_BROWSING,
+ navigateToLink,
} from "./helpers.mjs";
-const SYNCED_TABS_CHANGED = "services.sync.tabs.changed";
-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, {
+ contextMenu: true,
+ pairDeviceCallback: () =>
+ Services.telemetry.recordEvent(
+ "firefoxview_next",
+ "fxa_mobile",
+ "sync",
+ null,
+ {
+ has_devices: TabsSetupFlowManager.secondaryDeviceConnected.toString(),
+ }
+ ),
+ signupCallback: () =>
+ Services.telemetry.recordEvent(
+ "firefoxview_next",
+ "fxa_continue",
+ "sync",
+ null
+ ),
+ });
+
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 +77,19 @@ 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);
-
- this.updateStates();
+ this.controller.addSyncObservers();
+ this.controller.updateStates();
this.onVisibilityChange();
if (this.recentBrowsing) {
this.recentBrowsingElement.addEventListener(
"fxview-search-textbox-query",
- this
+ this.onSearchQuery
);
}
}
@@ -103,75 +101,21 @@ class SyncedTabsInView extends ViewPage {
this._started = false;
TabsSetupFlowManager.updateViewVisibility(this._id, "unloaded");
this.onVisibilityChange();
-
- Services.obs.removeObserver(this.boundObserve, TOPIC_SETUPSTATE_CHANGED);
- Services.obs.removeObserver(this.boundObserve, SYNCED_TABS_CHANGED);
+ this.controller.removeSyncObservers();
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 +140,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 +169,7 @@ class SyncedTabsInView extends ViewPage {
?hidden=${!buttonLabel}
data-l10n-id="${ifDefined(buttonLabel)}"
data-action="${action}"
- @click=${this.handleEvent}
+ @click=${e => this.controller.handleEvent(e)}
aria-details="empty-container"
></button>
</fxview-empty-state>
@@ -307,28 +177,19 @@ class SyncedTabsInView extends ViewPage {
}
onOpenLink(event) {
- let currentWindow = this.getWindow();
- if (currentWindow.openTrustedLinkIn) {
- let where = lazy.BrowserUtils.whereToOpenLink(
- event.detail.originalEvent,
- false,
- true
- );
- if (where == "current") {
- where = "tab";
+ navigateToLink(event);
+
+ Services.telemetry.recordEvent(
+ "firefoxview_next",
+ "synced_tabs",
+ "tabs",
+ null,
+ {
+ page: this.recentBrowsing ? "recentbrowsing" : "syncedtabs",
}
- currentWindow.openTrustedLinkIn(event.originalTarget.url, where);
- Services.telemetry.recordEvent(
- "firefoxview_next",
- "synced_tabs",
- "tabs",
- null,
- {
- page: this.recentBrowsing ? "recentbrowsing" : "syncedtabs",
- }
- );
- }
- if (this.searchQuery) {
+ );
+
+ if (this.controller.searchQuery) {
const searchesHistogram = Services.telemetry.getKeyedHistogramById(
"FIREFOX_VIEW_CUMULATIVE_SEARCHES"
);
@@ -384,7 +245,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>
`,
@@ -405,7 +266,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;
}
@@ -422,7 +284,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}
@@ -434,33 +296,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(
@@ -509,7 +347,7 @@ class SyncedTabsInView extends ViewPage {
isShowAllLinkVisible(tabItems) {
return (
this.recentBrowsing &&
- this.searchQuery &&
+ this.controller.searchQuery &&
tabItems.length > this.maxTabsLength &&
!this.showAll
);
@@ -536,35 +374,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() {
@@ -589,7 +402,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(),
@@ -606,12 +419,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=${e => this.controller.handleEvent(e)}
>
<img
class="icon"
@@ -635,9 +448,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
@@ -656,71 +469,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(numTabs) {
- /*
- Services.telemetry.recordEvent(
- "firefoxview_next",
- "synced_tabs",
- "tabs",
- null,
- {
- count: numTabs.toString(),
- }
- );
-*/
- }
}
customElements.define("view-syncedtabs", SyncedTabsInView);