test_syncedtabs.js (9305B)
1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- 2 * vim:set ts=2 sw=2 sts=2 et: 3 */ 4 "use strict"; 5 6 const { Weave } = ChromeUtils.importESModule( 7 "resource://services-sync/main.sys.mjs" 8 ); 9 const { SyncedTabs } = ChromeUtils.importESModule( 10 "resource://services-sync/SyncedTabs.sys.mjs" 11 ); 12 13 Log.repository.getLogger("Sync.RemoteTabs").addAppender(new Log.DumpAppender()); 14 15 // A mock "Tabs" engine which the SyncedTabs module will use instead of the real 16 // engine. We pass a constructor that Sync creates. 17 function MockTabsEngine() { 18 this.clients = {}; // We'll set this dynamically 19 // Mock fxAccounts + recentDeviceList as if we hit the FxA server 20 this.fxAccounts = { 21 device: { 22 recentDeviceList: [ 23 { 24 id: 1, 25 name: "updated desktop name", 26 availableCommands: { 27 "https://identity.mozilla.com/cmd/open-uri": "baz", 28 }, 29 }, 30 { 31 id: 2, 32 name: "updated mobile name", 33 availableCommands: { 34 "https://identity.mozilla.com/cmd/open-uri": "boo", 35 }, 36 }, 37 ], 38 }, 39 }; 40 } 41 42 MockTabsEngine.prototype = { 43 name: "tabs", 44 enabled: true, 45 46 getAllClients() { 47 return Object.values(this.clients); 48 }, 49 50 getOpenURLs() { 51 return new Set(); 52 }, 53 }; 54 55 let tabsEngine; 56 57 // A clients engine that doesn't need to be a constructor. 58 let MockClientsEngine = { 59 clientSettings: null, // Set in `configureClients`. 60 61 isMobile(guid) { 62 if (!guid.endsWith("desktop") && !guid.endsWith("mobile")) { 63 throw new Error( 64 "this module expected guids to end with 'desktop' or 'mobile'" 65 ); 66 } 67 return guid.endsWith("mobile"); 68 }, 69 remoteClientExists(id) { 70 return this.clientSettings[id] !== false; 71 }, 72 getClientName(id) { 73 if (this.clientSettings[id]) { 74 return this.clientSettings[id]; 75 } 76 let client = tabsEngine.clients[id]; 77 let fxaDevice = tabsEngine.fxAccounts.device.recentDeviceList.find( 78 device => device.id === client.fxaDeviceId 79 ); 80 return fxaDevice ? fxaDevice.name : client.clientName; 81 }, 82 83 getClientFxaDeviceId(id) { 84 if (this.clientSettings[id]) { 85 return this.clientSettings[id]; 86 } 87 return tabsEngine.clients[id].fxaDeviceId; 88 }, 89 90 getClientType() { 91 return "desktop"; 92 }, 93 }; 94 95 function configureClients(clients, clientSettings = {}) { 96 // each client record is expected to have an id. 97 for (let [guid, client] of Object.entries(clients)) { 98 client.id = guid; 99 } 100 tabsEngine.clients = clients; 101 // Apply clients collection overrides. 102 MockClientsEngine.clientSettings = clientSettings; 103 // Send an observer that pretends the engine just finished a sync. 104 Services.obs.notifyObservers(null, "weave:engine:sync:finish", "tabs"); 105 } 106 107 add_task(async function setup() { 108 await Weave.Service.promiseInitialized; 109 // Configure Sync with our mock tabs engine and force it to become initialized. 110 await Weave.Service.engineManager.unregister("tabs"); 111 await Weave.Service.engineManager.register(MockTabsEngine); 112 Weave.Service.clientsEngine = MockClientsEngine; 113 tabsEngine = Weave.Service.engineManager.get("tabs"); 114 115 // Tell the Sync XPCOM service it is initialized. 116 let weaveXPCService = Cc["@mozilla.org/weave/service;1"].getService( 117 Ci.nsISupports 118 ).wrappedJSObject; 119 weaveXPCService.ready = true; 120 }); 121 122 // The tests. 123 add_task(async function test_noClients() { 124 // no clients, can't be tabs. 125 await configureClients({}); 126 127 let tabs = await SyncedTabs.getTabClients(); 128 equal(Object.keys(tabs).length, 0); 129 }); 130 131 add_task(async function test_clientWithTabs() { 132 await configureClients({ 133 guid_desktop: { 134 clientName: "My Desktop", 135 tabs: [ 136 { 137 urlHistory: ["http://foo.com/"], 138 icon: "http://foo.com/favicon", 139 lastUsed: 1655745700, // Mon, 20 Jun 2022 17:21:40 GMT 140 }, 141 ], 142 }, 143 guid_mobile: { 144 clientName: "My Phone", 145 tabs: [], 146 }, 147 }); 148 149 let clients = await SyncedTabs.getTabClients(); 150 equal(clients.length, 2); 151 clients.sort((a, b) => { 152 return a.name.localeCompare(b.name); 153 }); 154 equal(clients[0].tabs.length, 1); 155 equal(clients[0].tabs[0].url, "http://foo.com/"); 156 equal(clients[0].tabs[0].icon, "http://foo.com/favicon"); 157 equal(clients[0].tabs[0].lastUsed, 1655745700); 158 // second client has no tabs. 159 equal(clients[1].tabs.length, 0); 160 }); 161 162 add_task(async function test_staleClientWithTabs() { 163 await configureClients( 164 { 165 guid_desktop: { 166 clientName: "My Desktop", 167 tabs: [ 168 { 169 urlHistory: ["http://foo.com/"], 170 icon: "http://foo.com/favicon", 171 lastUsed: 1655745750, 172 }, 173 ], 174 }, 175 guid_mobile: { 176 clientName: "My Phone", 177 tabs: [], 178 }, 179 guid_stale_mobile: { 180 clientName: "My Deleted Phone", 181 tabs: [], 182 }, 183 guid_stale_desktop: { 184 clientName: "My Deleted Laptop", 185 tabs: [ 186 { 187 urlHistory: ["https://bar.com/"], 188 icon: "https://bar.com/favicon", 189 lastUsed: 1655745700, 190 }, 191 ], 192 }, 193 guid_stale_name_desktop: { 194 clientName: "My Generic Device", 195 tabs: [ 196 { 197 urlHistory: ["https://example.edu/"], 198 icon: "https://example.edu/favicon", 199 lastUsed: 1655745800, 200 }, 201 ], 202 }, 203 }, 204 { 205 guid_stale_mobile: false, 206 guid_stale_desktop: false, 207 // We should always use the device name from the clients collection, instead 208 // of the possibly stale tabs collection. 209 guid_stale_name_desktop: "My Laptop", 210 } 211 ); 212 let clients = await SyncedTabs.getTabClients(); 213 clients.sort((a, b) => { 214 return a.name.localeCompare(b.name); 215 }); 216 equal(clients.length, 3); 217 equal(clients[0].name, "My Desktop"); 218 equal(clients[0].tabs.length, 1); 219 equal(clients[0].tabs[0].url, "http://foo.com/"); 220 equal(clients[0].tabs[0].lastUsed, 1655745750); 221 equal(clients[1].name, "My Laptop"); 222 equal(clients[1].tabs.length, 1); 223 equal(clients[1].tabs[0].url, "https://example.edu/"); 224 equal(clients[1].tabs[0].lastUsed, 1655745800); 225 equal(clients[2].name, "My Phone"); 226 equal(clients[2].tabs.length, 0); 227 }); 228 229 add_task(async function test_clientWithTabsIconsDisabled() { 230 Services.prefs.setBoolPref("services.sync.syncedTabs.showRemoteIcons", false); 231 await configureClients({ 232 guid_desktop: { 233 clientName: "My Desktop", 234 tabs: [ 235 { 236 urlHistory: ["http://foo.com/"], 237 icon: "http://foo.com/favicon", 238 }, 239 ], 240 }, 241 }); 242 243 let clients = await SyncedTabs.getTabClients(); 244 equal(clients.length, 1); 245 clients.sort((a, b) => { 246 return a.name.localeCompare(b.name); 247 }); 248 equal(clients[0].tabs.length, 1); 249 equal(clients[0].tabs[0].url, "http://foo.com/"); 250 // Expect the default favicon due to the pref being false. 251 equal(clients[0].tabs[0].icon, "page-icon:http://foo.com/"); 252 Services.prefs.clearUserPref("services.sync.syncedTabs.showRemoteIcons"); 253 }); 254 255 add_task(async function test_filter() { 256 // Nothing matches. 257 await configureClients({ 258 guid_desktop: { 259 clientName: "My Desktop", 260 tabs: [ 261 { 262 urlHistory: ["http://foo.com/"], 263 title: "A test page.", 264 }, 265 { 266 urlHistory: ["http://bar.com/"], 267 title: "Another page.", 268 }, 269 ], 270 }, 271 }); 272 273 let clients = await SyncedTabs.getTabClients("foo"); 274 equal(clients.length, 1); 275 equal(clients[0].tabs.length, 1); 276 equal(clients[0].tabs[0].url, "http://foo.com/"); 277 // check it matches the title. 278 clients = await SyncedTabs.getTabClients("test"); 279 equal(clients.length, 1); 280 equal(clients[0].tabs.length, 1); 281 equal(clients[0].tabs[0].url, "http://foo.com/"); 282 }); 283 284 add_task(async function test_duplicatesTabsAcrossClients() { 285 await configureClients({ 286 guid_desktop: { 287 clientName: "My Desktop", 288 tabs: [ 289 { 290 urlHistory: ["http://foo.com/"], 291 title: "A test page.", 292 }, 293 ], 294 }, 295 guid_mobile: { 296 clientName: "My Phone", 297 tabs: [ 298 { 299 urlHistory: ["http://foo.com/"], 300 title: "A test page.", 301 }, 302 ], 303 }, 304 }); 305 306 let clients = await SyncedTabs.getTabClients(); 307 equal(clients.length, 2); 308 equal(clients[0].tabs.length, 1); 309 equal(clients[1].tabs.length, 1); 310 equal(clients[0].tabs[0].url, "http://foo.com/"); 311 equal(clients[1].tabs[0].url, "http://foo.com/"); 312 }); 313 314 add_task(async function test_clientsTabUpdatedName() { 315 // See the "fxAccounts" object in the MockEngine above for the device list 316 await configureClients({ 317 guid_desktop: { 318 clientName: "My Desktop", 319 tabs: [ 320 { 321 urlHistory: ["http://foo.com/"], 322 icon: "http://foo.com/favicon", 323 }, 324 ], 325 fxaDeviceId: 1, 326 }, 327 guid_mobile: { 328 clientName: "My Phone", 329 tabs: [ 330 { 331 urlHistory: ["http://bar.com/"], 332 icon: "http://bar.com/favicon", 333 }, 334 ], 335 fxaDeviceId: 2, 336 }, 337 }); 338 let clients = await SyncedTabs.getTabClients(); 339 equal(clients.length, 2); 340 equal(clients[0].name, "updated desktop name"); 341 equal(clients[1].name, "updated mobile name"); 342 });