SyncedTabsDeckComponent.sys.mjs (5878B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import { SyncedTabsDeckStore } from "resource:///modules/syncedtabs/SyncedTabsDeckStore.sys.mjs"; 6 import { SyncedTabsDeckView } from "resource:///modules/syncedtabs/SyncedTabsDeckView.sys.mjs"; 7 import { SyncedTabsListStore } from "resource:///modules/syncedtabs/SyncedTabsListStore.sys.mjs"; 8 import { TabListComponent } from "resource:///modules/syncedtabs/TabListComponent.sys.mjs"; 9 import { TabListView } from "resource:///modules/syncedtabs/TabListView.sys.mjs"; 10 import { getChromeWindow } from "resource:///modules/syncedtabs/util.sys.mjs"; 11 import { UIState } from "resource://services-sync/UIState.sys.mjs"; 12 13 /* SyncedTabsDeckComponent 14 * This component instantiates views and storage objects as well as defines 15 * behaviors that will be passed down to the views. This helps keep the views 16 * isolated and easier to test. 17 */ 18 19 export function SyncedTabsDeckComponent({ 20 window, 21 SyncedTabs, 22 deckStore, 23 listStore, 24 listComponent, 25 DeckView, 26 getChromeWindowMock, 27 }) { 28 this._window = window; 29 this._SyncedTabs = SyncedTabs; 30 this._DeckView = DeckView || SyncedTabsDeckView; 31 // used to stub during tests 32 this._getChromeWindow = getChromeWindowMock || getChromeWindow; 33 34 this._deckStore = deckStore || new SyncedTabsDeckStore(); 35 this._syncedTabsListStore = listStore || new SyncedTabsListStore(SyncedTabs); 36 this.tabListComponent = 37 listComponent || 38 new TabListComponent({ 39 window: this._window, 40 store: this._syncedTabsListStore, 41 View: TabListView, 42 SyncedTabs, 43 clipboardHelper: Cc["@mozilla.org/widget/clipboardhelper;1"].getService( 44 Ci.nsIClipboardHelper 45 ), 46 getChromeWindow: this._getChromeWindow, 47 }); 48 } 49 50 SyncedTabsDeckComponent.prototype = { 51 PANELS: { 52 TABS_CONTAINER: "tabs-container", 53 TABS_FETCHING: "tabs-fetching", 54 LOGIN_FAILED: "reauth", 55 NOT_AUTHED_INFO: "notAuthedInfo", 56 SYNC_DISABLED: "syncDisabled", 57 SINGLE_DEVICE_INFO: "singleDeviceInfo", 58 TABS_DISABLED: "tabs-disabled", 59 UNVERIFIED: "unverified", 60 }, 61 62 get container() { 63 return this._deckView ? this._deckView.container : null; 64 }, 65 66 init() { 67 Services.obs.addObserver(this, this._SyncedTabs.TOPIC_TABS_CHANGED); 68 Services.obs.addObserver(this, UIState.ON_UPDATE); 69 70 // Add app locale change support for HTML sidebar 71 Services.obs.addObserver(this, "intl:app-locales-changed"); 72 this.updateDir(); 73 74 // Go ahead and trigger sync 75 this._SyncedTabs.syncTabs().catch(console.error); 76 77 this._deckView = new this._DeckView(this._window, this.tabListComponent, { 78 onConnectDeviceClick: event => this.openConnectDevice(event), 79 onSyncPrefClick: event => this.openSyncPrefs(event), 80 }); 81 82 this._deckStore.on("change", state => this._deckView.render(state)); 83 // Trigger the initial rendering of the deck view 84 // Object.values only in nightly 85 this._deckStore.setPanels( 86 Object.keys(this.PANELS).map(k => this.PANELS[k]) 87 ); 88 // Set the initial panel to display 89 this.updatePanel(); 90 this._recordPanelToggle(true); 91 }, 92 93 uninit() { 94 Services.obs.removeObserver(this, this._SyncedTabs.TOPIC_TABS_CHANGED); 95 Services.obs.removeObserver(this, UIState.ON_UPDATE); 96 Services.obs.removeObserver(this, "intl:app-locales-changed"); 97 this._deckView.destroy(); 98 this._recordPanelToggle(false); 99 }, 100 101 async _recordPanelToggle(opened) { 102 const state = UIState.get(); 103 const { status } = state; 104 Glean.syncedTabs.sidebarToggle.record({ 105 opened, 106 synced_tabs_loaded: status === UIState.STATUS_SIGNED_IN, 107 version: "old", 108 }); 109 }, 110 111 observe(subject, topic) { 112 switch (topic) { 113 case this._SyncedTabs.TOPIC_TABS_CHANGED: 114 this._syncedTabsListStore.getData(); 115 this.updatePanel(); 116 break; 117 case UIState.ON_UPDATE: 118 this.updatePanel(); 119 break; 120 case "intl:app-locales-changed": 121 this.updateDir(); 122 break; 123 default: 124 break; 125 } 126 }, 127 128 async getPanelStatus() { 129 try { 130 const state = UIState.get(); 131 const { status } = state; 132 if (status == UIState.STATUS_NOT_CONFIGURED) { 133 return this.PANELS.NOT_AUTHED_INFO; 134 } else if (status == UIState.STATUS_LOGIN_FAILED) { 135 return this.PANELS.LOGIN_FAILED; 136 } else if (status == UIState.STATUS_NOT_VERIFIED) { 137 return this.PANELS.UNVERIFIED; 138 } else if (!state.syncEnabled) { 139 return this.PANELS.SYNC_DISABLED; 140 } else if (!this._SyncedTabs.isConfiguredToSyncTabs) { 141 return this.PANELS.TABS_DISABLED; 142 } else if (!this._SyncedTabs.hasSyncedThisSession) { 143 return this.PANELS.TABS_FETCHING; 144 } 145 const clients = await this._SyncedTabs.getTabClients(); 146 if (clients.length) { 147 return this.PANELS.TABS_CONTAINER; 148 } 149 return this.PANELS.SINGLE_DEVICE_INFO; 150 } catch (err) { 151 console.error(err); 152 return this.PANELS.NOT_AUTHED_INFO; 153 } 154 }, 155 156 updateDir() { 157 // If the HTML document doesn't exist, we can't update the window 158 if (!this._window.document) { 159 return; 160 } 161 162 if (Services.locale.isAppLocaleRTL) { 163 this._window.document.body.dir = "rtl"; 164 } else { 165 this._window.document.body.dir = "ltr"; 166 } 167 }, 168 169 updatePanel() { 170 // return promise for tests 171 return this.getPanelStatus() 172 .then(panelId => this._deckStore.selectPanel(panelId)) 173 .catch(console.error); 174 }, 175 176 openSyncPrefs() { 177 this._getChromeWindow(this._window).gSync.openPrefs("tabs-sidebar"); 178 }, 179 180 openConnectDevice() { 181 this._getChromeWindow(this._window).gSync.openConnectAnotherDevice( 182 "tabs-sidebar" 183 ); 184 }, 185 };