fune/browser/components/myfirefox/tabs-pickup.js
Sam Foster bd470bc9de Bug 1763138 - Hook up the tabs-setup flow to the sync status and fxa device list to enable the first steps. r=dao,fluent-reviewers,flod
* 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
2022-04-28 06:14:05 +00:00

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 };