296 lines
8.4 KiB
JavaScript
296 lines
8.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 SYNC_TABS_PREF = "services.sync.engine.tabs";
|
|
const RECENT_TABS_SYNC = "services.sync.lastTabFetch";
|
|
|
|
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",
|
|
SyncedTabs: "resource://services-sync/SyncedTabs.jsm",
|
|
});
|
|
XPCOMUtils.defineLazyGetter(this, "fxAccounts", () => {
|
|
return ChromeUtils.import(
|
|
"resource://gre/modules/FxAccounts.jsm"
|
|
).getFxAccountsSingleton();
|
|
});
|
|
ChromeUtils.defineModuleGetter(
|
|
this.sync,
|
|
"UIState",
|
|
"resource://services-sync/UIState.jsm"
|
|
);
|
|
|
|
// this.syncTabsPrefEnabled will track the value of the tabs pref
|
|
XPCOMUtils.defineLazyPreferenceGetter(
|
|
this,
|
|
"syncTabsPrefEnabled",
|
|
SYNC_TABS_PREF,
|
|
false,
|
|
() => {
|
|
this.maybeUpdateUI();
|
|
}
|
|
);
|
|
XPCOMUtils.defineLazyPreferenceGetter(
|
|
this,
|
|
"lastTabFetch",
|
|
RECENT_TABS_SYNC,
|
|
false,
|
|
() => {
|
|
this.maybeUpdateUI();
|
|
}
|
|
);
|
|
|
|
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: "connect-mobile-device",
|
|
exitConditions: () => {
|
|
return this.secondaryDeviceConnected;
|
|
},
|
|
});
|
|
this.registerSetupState({
|
|
uiStateIndex: 2,
|
|
name: "disabled-tab-sync",
|
|
exitConditions: () => {
|
|
return this.syncTabsPrefEnabled;
|
|
},
|
|
});
|
|
this.registerSetupState({
|
|
uiStateIndex: 3,
|
|
name: "synced-tabs-not-ready",
|
|
enter: () => {
|
|
if (!this.didRecentTabSync) {
|
|
this.SyncedTabs.syncTabs();
|
|
}
|
|
},
|
|
exitConditions: () => {
|
|
return this.didRecentTabSync;
|
|
},
|
|
});
|
|
this.registerSetupState({
|
|
uiStateIndex: 4,
|
|
name: "synced-tabs-loaded",
|
|
exitConditions: () => {
|
|
// This is the end state
|
|
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 secondaryDeviceConnected() {
|
|
let recentDevices = this.fxAccounts.device?.recentDeviceList?.length;
|
|
return recentDevices > 1;
|
|
}
|
|
get didRecentTabSync() {
|
|
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
return (
|
|
nowSeconds - this.lastTabFetch <
|
|
this.SyncedTabs.TABS_FRESH_ENOUGH_INTERVAL_SECONDS
|
|
);
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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) {
|
|
const state = this.setupState.get(nextSetupStateName);
|
|
this.elem.updateSetupState(state.uiStateIndex);
|
|
this._currentSetupStateName = nextSetupStateName;
|
|
if ("function" == typeof state.enter) {
|
|
state.enter();
|
|
}
|
|
}
|
|
}
|
|
|
|
async openFxASignup() {
|
|
const url = await this.fxAccounts.constructor.config.promiseConnectAccountURI(
|
|
"firefoxview"
|
|
);
|
|
switchToTabHavingURI(url, true);
|
|
}
|
|
openSyncPreferences(containerElem) {
|
|
const url = "about:preferences?action=pair#sync";
|
|
switchToTabHavingURI(url, true);
|
|
}
|
|
syncOpenTabs(containerElem) {
|
|
// Flip the pref on.
|
|
// The observer should trigger re-evaluating state and advance to next step
|
|
this.Services.prefs.setBoolPref(SYNC_TABS_PREF, true);
|
|
}
|
|
})();
|
|
|
|
class TabsPickupContainer extends HTMLElement {
|
|
constructor() {
|
|
super();
|
|
this.manager = null;
|
|
this._currentSetupStateIndex = -1;
|
|
XPCOMUtils.defineLazyModuleGetters(this, {
|
|
Services: "resource://gre/modules/Services.jsm",
|
|
});
|
|
}
|
|
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;
|
|
}
|
|
for (let elem of cloned.querySelectorAll("a[data-support-url]")) {
|
|
elem.href =
|
|
this.Services.urlFormatter.formatURLPref("app.support.baseURL") +
|
|
elem.dataset.supportUrl;
|
|
}
|
|
}
|
|
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 < 3) {
|
|
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}`;
|
|
return;
|
|
}
|
|
|
|
if (!tabsElem) {
|
|
this.appendTemplatedElement(
|
|
"synced-tabs-template",
|
|
"tabpickup-tabs-container"
|
|
);
|
|
tabsElem = this.tabsContainerElem;
|
|
tabsElem.classList.toggle("loading", stateIndex == 3);
|
|
}
|
|
if (setupElem) {
|
|
setupElem.hidden = true;
|
|
}
|
|
tabsElem.hidden = false;
|
|
|
|
if (stateIndex == 4) {
|
|
tabsElem.classList.toggle("loading", false);
|
|
}
|
|
}
|
|
}
|
|
customElements.define("tabs-pickup-container", TabsPickupContainer);
|
|
|
|
export { tabsSetupFlowManager };
|