summaryrefslogtreecommitdiffstats
path: root/browser/components/firefoxview/tab-pickup-container.mjs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--browser/components/firefoxview/tab-pickup-container.mjs323
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 };