forked from mirrors/gecko-dev
		
	 bd470bc9de
			
		
	
	
		bd470bc9de
		
	
	
	
	
		
			
			* The FxA signin/signup step is wired up * The signin with another device step to open the about:preferences deep-link is mostly wired up Not implemented here: * Detecting and displaying recent remote tabs is not implemented yet * Offline and other error conditions not handled * We may need to wait for FxA to be ready * Only the bare bones of markup/CSS is implemented * String for all but the Step 1 of 3 in scope here are still placeholders Differential Revision: https://phabricator.services.mozilla.com/D144354
		
			
				
	
	
		
			256 lines
		
	
	
	
		
			7.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			256 lines
		
	
	
	
		
			7.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* 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/frame-script */
 | |
| 
 | |
| const { switchToTabHavingURI } = window.docShell.chromeEventHandler.ownerGlobal;
 | |
| 
 | |
| const { XPCOMUtils } = ChromeUtils.import(
 | |
|   "resource://gre/modules/XPCOMUtils.jsm"
 | |
| );
 | |
| 
 | |
| const tabsSetupFlowManager = new (class {
 | |
|   constructor() {
 | |
|     this.QueryInterface = ChromeUtils.generateQI(["nsIObserver"]);
 | |
| 
 | |
|     this.setupState = new Map();
 | |
|     this._currentSetupStateName = "";
 | |
|     this.sync = {};
 | |
| 
 | |
|     XPCOMUtils.defineLazyModuleGetters(this, {
 | |
|       Services: "resource://gre/modules/Services.jsm",
 | |
|       fxAccounts: "resource://gre/modules/FxAccounts.jsm",
 | |
|     });
 | |
|     ChromeUtils.defineModuleGetter(
 | |
|       this.sync,
 | |
|       "UIState",
 | |
|       "resource://services-sync/UIState.jsm"
 | |
|     );
 | |
| 
 | |
|     this.registerSetupState({
 | |
|       uiStateIndex: 0,
 | |
|       name: "not-signed-in",
 | |
|       exitConditions: () => {
 | |
|         return this.fxaSignedIn;
 | |
|       },
 | |
|     });
 | |
|     // TODO: handle offline, sync service not ready or available
 | |
|     this.registerSetupState({
 | |
|       uiStateIndex: 1,
 | |
|       name: "no-mobile-device",
 | |
|       exitConditions: () => {
 | |
|         return this.mobileDeviceConnected;
 | |
|       },
 | |
|     });
 | |
|     this.registerSetupState({
 | |
|       uiStateIndex: 2,
 | |
|       name: "disabled-tab-sync",
 | |
|       exitConditions: () => {
 | |
|         // Bug 1763139 - Implement the actual logic to advance to next step
 | |
|         // This will basically be a check for the pref to enable tab-syncing
 | |
|         return false;
 | |
|       },
 | |
|     });
 | |
|     this.registerSetupState({
 | |
|       uiStateIndex: 3,
 | |
|       name: "synced-tabs-not-ready",
 | |
|       exitConditions: () => {
 | |
|         // Bug 1763139 - Implement the actual logic to advance to next step
 | |
|         return false;
 | |
|       },
 | |
|     });
 | |
|     this.registerSetupState({
 | |
|       uiStateIndex: 4,
 | |
|       name: "show-synced-tabs-agreement",
 | |
|       exitConditions: () => {
 | |
|         // Bug 1763139 - Implement the actual logic to advance to next step
 | |
|         return false;
 | |
|       },
 | |
|     });
 | |
|   }
 | |
|   async initialize(elem) {
 | |
|     this.elem = elem;
 | |
|     this.elem.addEventListener("click", this);
 | |
|     this.Services.obs.addObserver(this, this.sync.UIState.ON_UPDATE);
 | |
|     this.Services.obs.addObserver(this, "fxaccounts:device_connected");
 | |
|     this.Services.obs.addObserver(this, "fxaccounts:device_disconnected");
 | |
| 
 | |
|     await this.fxAccounts.getSignedInUser();
 | |
|     this.maybeUpdateUI();
 | |
|   }
 | |
|   uninit() {
 | |
|     this.Services.obs.removeObserver(this, this.sync.UIState.ON_UPDATE);
 | |
|     this.Services.obs.removeObserver(this, "fxaccounts:device_connected");
 | |
|     this.Services.obs.removeObserver(this, "fxaccounts:device_disconnected");
 | |
|   }
 | |
|   get fxaSignedIn() {
 | |
|     return (
 | |
|       this.sync.UIState.get().status === this.sync.UIState.STATUS_SIGNED_IN
 | |
|     );
 | |
|   }
 | |
|   get mobileDeviceConnected() {
 | |
|     let mobileDevice = this.fxAccounts.device?.recentDeviceList?.find(
 | |
|       device => device.type == "mobile"
 | |
|     );
 | |
|     return !!mobileDevice;
 | |
|   }
 | |
|   registerSetupState(state) {
 | |
|     this.setupState.set(state.name, state);
 | |
|   }
 | |
| 
 | |
|   async observe(subject, topic, data) {
 | |
|     switch (topic) {
 | |
|       case this.sync.UIState.ON_UPDATE:
 | |
|         this.maybeUpdateUI();
 | |
|         break;
 | |
|       case "fxaccounts:device_connected":
 | |
|       case "fxaccounts:device_disconnected":
 | |
|         await this.fxAccounts.device.refreshDeviceList();
 | |
|         this.maybeUpdateUI();
 | |
|         break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   handleEvent(event) {
 | |
|     if (event.type == "click" && event.target.dataset.action) {
 | |
|       switch (event.target.dataset.action) {
 | |
|         case "view0-primary-action": {
 | |
|           this.openFxASignup(event.target);
 | |
|           break;
 | |
|         }
 | |
|         case "view1-primary-action": {
 | |
|           this.openSyncPreferences(event.target);
 | |
|           break;
 | |
|         }
 | |
|         case "view2-primary-action": {
 | |
|           this.syncOpenTabs(event.target);
 | |
|           break;
 | |
|         }
 | |
|         case "view3-primary-action": {
 | |
|           this.confirmSetupComplete(event.target);
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   maybeUpdateUI() {
 | |
|     let nextSetupStateName = this._currentSetupStateName;
 | |
| 
 | |
|     // state transition conditions
 | |
|     for (let state of this.setupState.values()) {
 | |
|       nextSetupStateName = state.name;
 | |
|       if (!state.exitConditions()) {
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (nextSetupStateName !== this._currentSetupStateName) {
 | |
|       this.elem.updateSetupState(
 | |
|         this.setupState.get(nextSetupStateName).uiStateIndex
 | |
|       );
 | |
|       this._currentSetupStateName = nextSetupStateName;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async openFxASignup() {
 | |
|     const url = await this.fxAccounts.constructor.config.promiseConnectAccountURI(
 | |
|       "myfirefox"
 | |
|     );
 | |
|     switchToTabHavingURI(url, true);
 | |
|   }
 | |
|   openSyncPreferences(containerElem) {
 | |
|     const url = "about:preferences?action=pair#sync";
 | |
|     switchToTabHavingURI(url, true);
 | |
|   }
 | |
|   syncOpenTabs(containerElem) {
 | |
|     // Bug 1763139 - Implement the actual logic to advance to next step
 | |
|     this.elem.updateSetupState(
 | |
|       this.setupState.get("synced-tabs-not-ready").uiStateIndex
 | |
|     );
 | |
|   }
 | |
|   confirmSetupComplete(containerElem) {
 | |
|     // Bug 1763139 - Implement the actual logic to advance to next step
 | |
|     this.elem.updateSetupState(
 | |
|       this.setupState.get("show-synced-tabs-agreement").uiStateIndex
 | |
|     );
 | |
|   }
 | |
| })();
 | |
| 
 | |
| class TabsPickupContainer extends HTMLElement {
 | |
|   constructor() {
 | |
|     super();
 | |
|     this.manager = null;
 | |
|     this._currentSetupStateIndex = -1;
 | |
|   }
 | |
|   get setupContainerElem() {
 | |
|     return this.querySelector(".sync-setup-container");
 | |
|   }
 | |
|   get tabsContainerElem() {
 | |
|     return this.querySelector(".synced-tabs-container");
 | |
|   }
 | |
|   appendTemplatedElement(templateId, elementId) {
 | |
|     const template = document.getElementById(templateId);
 | |
|     const templateContent = template.content;
 | |
|     const cloned = templateContent.cloneNode(true);
 | |
|     if (elementId) {
 | |
|       // populate id-prefixed attributes on elements that need them
 | |
|       for (let elem of cloned.querySelectorAll("[data-prefix]")) {
 | |
|         let [name, value] = elem.dataset.prefix
 | |
|           .split(":")
 | |
|           .map(str => str.trim());
 | |
|         elem.setAttribute(name, elementId + value);
 | |
|         delete elem.dataset.prefix;
 | |
|       }
 | |
|     }
 | |
|     this.appendChild(cloned);
 | |
|   }
 | |
|   updateSetupState(stateIndex) {
 | |
|     const currStateIndex = this._currentSetupStateIndex;
 | |
|     if (stateIndex === undefined) {
 | |
|       stateIndex = currStateIndex;
 | |
|     }
 | |
|     if (stateIndex === this._currentSetupStateIndex) {
 | |
|       return;
 | |
|     }
 | |
|     this._currentSetupStateIndex = stateIndex;
 | |
|     this.render();
 | |
|   }
 | |
|   render() {
 | |
|     if (!this.isConnected) {
 | |
|       return;
 | |
|     }
 | |
|     let setupElem = this.setupContainerElem;
 | |
|     let tabsElem = this.tabsContainerElem;
 | |
|     const stateIndex = this._currentSetupStateIndex;
 | |
| 
 | |
|     // show/hide either the setup or tab list containers, creating each as necessary
 | |
|     if (stateIndex < 4) {
 | |
|       if (!setupElem) {
 | |
|         this.appendTemplatedElement("sync-setup-template", "tabpickup-steps");
 | |
|         setupElem = this.setupContainerElem;
 | |
|       }
 | |
|       if (tabsElem) {
 | |
|         tabsElem.hidden = true;
 | |
|       }
 | |
|       setupElem.hidden = false;
 | |
|       setupElem.selectedViewName = `sync-setup-view${stateIndex}`;
 | |
|     } else {
 | |
|       if (!tabsElem) {
 | |
|         this.appendTemplatedElement(
 | |
|           "synced-tabs-template",
 | |
|           "tabpickup-tabs-container"
 | |
|         );
 | |
|         tabsElem = this.tabsContainerElem;
 | |
|       }
 | |
|       if (setupElem) {
 | |
|         setupElem.hidden = true;
 | |
|       }
 | |
|       tabsElem.hidden = false;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| customElements.define("tabs-pickup-container", TabsPickupContainer);
 | |
| 
 | |
| export { tabsSetupFlowManager };
 |