/* 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, {
BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
SyncedTabs: "resource://services-sync/SyncedTabs.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"
);
import {
html,
ifDefined,
when,
} from "chrome://global/content/vendor/lit.all.mjs";
import { ViewPage } from "./viewpage.mjs";
import {
escapeHtmlEntities,
isSearchEnabled,
searchTabList,
MAX_TABS_FOR_RECENT_BROWSING,
} 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 {
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;
}
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 },
};
static queries = {
cardEls: { all: "card-container" },
emptyState: "fxview-empty-state",
searchTextbox: "fxview-search-textbox",
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.onVisibilityChange();
if (this.recentBrowsing) {
this.recentBrowsingElement.addEventListener(
"fxview-search-textbox-query",
this
);
}
}
stop() {
if (!this._started) {
return;
}
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);
if (this.recentBrowsing) {
this.recentBrowsingElement.removeEventListener(
"fxview-search-textbox-query",
this
);
}
}
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();
}
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();
}
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];
}
return html`