diff options
Diffstat (limited to 'browser/components/firefoxview/tab-pickup-container.mjs')
-rw-r--r-- | browser/components/firefoxview/tab-pickup-container.mjs | 323 |
1 files changed, 323 insertions, 0 deletions
diff --git a/browser/components/firefoxview/tab-pickup-container.mjs b/browser/components/firefoxview/tab-pickup-container.mjs new file mode 100644 index 0000000000..f554298a2a --- /dev/null +++ b/browser/components/firefoxview/tab-pickup-container.mjs @@ -0,0 +1,323 @@ +/* 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/. */ + +/* eslint-env mozilla/remote-page */ + +import { onToggleContainer } from "./helpers.mjs"; + +const { TabsSetupFlowManager } = ChromeUtils.importESModule( + "resource:///modules/firefox-view-tabs-setup-manager.sys.mjs" +); + +const TOPIC_SETUPSTATE_CHANGED = "firefox-view.setupstate.changed"; +const UI_OPEN_STATE = "browser.tabs.firefox-view.ui-state.tab-pickup.open"; + +class TabPickupContainer extends HTMLDetailsElement { + constructor() { + super(); + this.boundObserve = (...args) => this.observe(...args); + this._currentSetupStateIndex = -1; + this.errorState = null; + this.tabListAdded = null; + } + get setupContainerElem() { + return this.querySelector(".sync-setup-container"); + } + + get tabsContainerElem() { + return this.querySelector(".synced-tabs-container"); + } + + get tabPickupListElem() { + return this.querySelector(".synced-tabs-container tab-pickup-list"); + } + + getWindow() { + return this.ownerGlobal.browsingContext.embedderWindowGlobal.browsingContext + .window; + } + + connectedCallback() { + this.addEventListener("click", this); + this.addEventListener("toggle", this); + this.addEventListener("visibilitychange", this); + Services.obs.addObserver(this.boundObserve, TOPIC_SETUPSTATE_CHANGED); + + for (let elem of this.querySelectorAll("a[data-support-url]")) { + elem.href = + Services.urlFormatter.formatURLPref("app.support.baseURL") + + elem.dataset.supportUrl; + } + + // we wait until the list shows up before trying to populate it, + // when its safe to assume the custom-element's methods will be available + this.tabListAdded = this.promiseChildAdded(); + this.update(); + } + + promiseChildAdded() { + return new Promise(resolve => { + if (typeof this.tabPickupListElem?.getSyncedTabData == "function") { + resolve(); + return; + } + this.addEventListener( + "list-ready", + event => { + resolve(); + }, + { once: true } + ); + }); + } + + cleanup() { + Services.obs.removeObserver(this.boundObserve, TOPIC_SETUPSTATE_CHANGED); + } + + disconnectedCallback() { + this.cleanup(); + } + + handleEvent(event) { + if (event.type == "toggle") { + onToggleContainer(this); + return; + } + if (event.type == "click" && event.target.dataset.action) { + switch (event.target.dataset.action) { + case "view0-sync-error-action": + case "view0-network-offline-action": + case "view0-password-locked-action": { + TabsSetupFlowManager.tryToClearError(); + break; + } + case "view0-signed-out-action": + case "view1-primary-action": { + TabsSetupFlowManager.openFxASignup(event.target.ownerGlobal); + break; + } + case "view2-primary-action": + case "mobile-promo-primary-action": { + TabsSetupFlowManager.openFxAPairDevice(event.target.ownerGlobal); + break; + } + case "view3-primary-action": { + TabsSetupFlowManager.syncOpenTabs(event.target); + break; + } + case "mobile-promo-dismiss": { + TabsSetupFlowManager.dismissMobilePromo(event.target); + break; + } + case "mobile-confirmation-dismiss": { + TabsSetupFlowManager.dismissMobileConfirmation(event.target); + break; + } + case "view0-sync-disconnected-action": { + const window = event.target.ownerGlobal; + const { + switchToTabHavingURI, + } = window.docShell.chromeEventHandler.ownerGlobal; + switchToTabHavingURI( + "about:preferences?action=choose-what-to-sync#sync", + true, + {} + ); + break; + } + } + } + // Returning to fxview seems like a likely time for a device check + if ( + event.type == "visibilitychange" && + document.visibilityState === "visible" + ) { + this.update(); + } + } + + async observe(subject, topic, errorState) { + if (topic == TOPIC_SETUPSTATE_CHANGED) { + this.update({ errorState }); + } + } + + get mobilePromoElem() { + return this.querySelector(".promo-box"); + } + get mobileSuccessElem() { + return this.querySelector(".confirmation-message-box"); + } + + update({ + stateIndex = TabsSetupFlowManager.uiStateIndex, + showMobilePromo = TabsSetupFlowManager.shouldShowMobilePromo, + showMobilePairSuccess = TabsSetupFlowManager.shouldShowMobileConnectedSuccess, + errorState = TabsSetupFlowManager.getErrorType(), + waitingForTabs = TabsSetupFlowManager.waitingForTabs, + } = {}) { + let needsRender = false; + if (waitingForTabs !== this._waitingForTabs) { + this._waitingForTabs = waitingForTabs; + needsRender = true; + } + + if (showMobilePromo !== this._showMobilePromo) { + this._showMobilePromo = showMobilePromo; + needsRender = true; + } + if (showMobilePairSuccess !== this._showMobilePairSuccess) { + this._showMobilePairSuccess = showMobilePairSuccess; + needsRender = true; + } + if (stateIndex == 4 && this._currentSetupStateIndex !== stateIndex) { + // trigger an initial request for the synced tabs list + this.tabListAdded.then(() => { + this.tabPickupListElem.getSyncedTabData(); + }); + } + if (stateIndex !== this._currentSetupStateIndex || stateIndex == 0) { + this._currentSetupStateIndex = stateIndex; + needsRender = true; + this.errorState = errorState; + } + needsRender && this.render(); + } + + generateErrorMessage() { + // We map the error state strings to Fluent string IDs so that it's easier + // to change strings in the future without having to update all of the + // error state strings. + const errorStateStringMappings = { + "sync-error": { + header: "firefoxview-tabpickup-sync-error-header", + description: "firefoxview-tabpickup-generic-sync-error-description", + buttonLabel: "firefoxview-tabpickup-sync-error-primarybutton", + }, + + "fxa-admin-disabled": { + header: "firefoxview-tabpickup-fxa-admin-disabled-header", + description: "firefoxview-tabpickup-fxa-admin-disabled-description", + // The button is hidden for this errorState, so we don't include the + // buttonLabel property. + }, + + "network-offline": { + header: "firefoxview-tabpickup-network-offline-header", + description: "firefoxview-tabpickup-network-offline-description", + buttonLabel: "firefoxview-tabpickup-network-offline-primarybutton", + }, + + "sync-disconnected": { + header: "firefoxview-tabpickup-sync-disconnected-header", + description: "firefoxview-tabpickup-sync-disconnected-description", + buttonLabel: "firefoxview-tabpickup-sync-disconnected-primarybutton", + }, + + "password-locked": { + header: "firefoxview-tabpickup-password-locked-header", + description: "firefoxview-tabpickup-password-locked-description", + buttonLabel: "firefoxview-tabpickup-password-locked-primarybutton", + link: { + label: "firefoxview-tabpickup-password-locked-link", + href: + Services.urlFormatter.formatURLPref("app.support.baseURL") + + "primary-password-stored-logins", + }, + }, + "signed-out": { + header: "firefoxview-tabpickup-signed-out-header", + description: "firefoxview-tabpickup-signed-out-description", + buttonLabel: "firefoxview-tabpickup-signed-out-primarybutton", + }, + }; + + const errorStateHeader = this.querySelector( + "#tabpickup-steps-view0-header" + ); + const errorStateDescription = this.querySelector( + "#error-state-description" + ); + const errorStateButton = this.querySelector("#error-state-button"); + const errorStateLink = this.querySelector("#error-state-link"); + const errorStateProperties = errorStateStringMappings[this.errorState]; + + document.l10n.setAttributes(errorStateHeader, errorStateProperties.header); + document.l10n.setAttributes( + errorStateDescription, + errorStateProperties.description + ); + + errorStateButton.hidden = this.errorState == "fxa-admin-disabled"; + + if (this.errorState != "fxa-admin-disabled") { + document.l10n.setAttributes( + errorStateButton, + errorStateProperties.buttonLabel + ); + errorStateButton.setAttribute( + "data-action", + `view0-${this.errorState}-action` + ); + } + + if (errorStateProperties.link) { + document.l10n.setAttributes( + errorStateLink, + errorStateProperties.link.label + ); + errorStateLink.href = errorStateProperties.link.href; + errorStateLink.hidden = false; + } else { + errorStateLink.hidden = true; + } + } + + render() { + if (!this.isConnected) { + return; + } + + let setupElem = this.setupContainerElem; + let tabsElem = this.tabsContainerElem; + let mobilePromoElem = this.mobilePromoElem; + let mobileSuccessElem = this.mobileSuccessElem; + + const stateIndex = this._currentSetupStateIndex; + const isLoading = this._waitingForTabs; + + mobilePromoElem.hidden = !this._showMobilePromo; + mobileSuccessElem.hidden = !this._showMobilePairSuccess; + + this.open = + !TabsSetupFlowManager.isTabSyncSetupComplete || + Services.prefs.getBoolPref(UI_OPEN_STATE, true); + + // show/hide either the setup or tab list containers, creating each as necessary + if (stateIndex < 4) { + tabsElem.hidden = true; + setupElem.hidden = false; + setupElem.selectedViewName = `sync-setup-view${stateIndex}`; + + if (stateIndex == 0 && this.errorState) { + this.generateErrorMessage(); + } + return; + } + + setupElem.hidden = true; + tabsElem.hidden = false; + tabsElem.classList.toggle("loading", isLoading); + } + + async onReload() { + await TabsSetupFlowManager.syncOnPageReload(); + } +} +customElements.define("tab-pickup-container", TabPickupContainer, { + extends: "details", +}); + +export { TabPickupContainer }; |