test_tab_tracker.js (10709B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 ChromeUtils.importESModule("resource://services-sync/engines/tabs.sys.mjs"); 5 const { Service } = ChromeUtils.importESModule( 6 "resource://services-sync/service.sys.mjs" 7 ); 8 9 const { SyncScheduler } = ChromeUtils.importESModule( 10 "resource://services-sync/policies.sys.mjs" 11 ); 12 13 var scheduler = new SyncScheduler(Service); 14 let clientsEngine; 15 16 add_task(async function setup() { 17 await Service.promiseInitialized; 18 clientsEngine = Service.clientsEngine; 19 20 scheduler.setDefaults(); 21 }); 22 23 function fakeSvcWinMediator() { 24 // actions on windows are captured in logs 25 let logs = []; 26 delete Services.wm; 27 28 function getNext() { 29 let elt = { addTopics: [], remTopics: [], numAPL: 0, numRPL: 0 }; 30 logs.push(elt); 31 return { 32 addEventListener(topic) { 33 elt.addTopics.push(topic); 34 }, 35 removeEventListener(topic) { 36 elt.remTopics.push(topic); 37 }, 38 gBrowser: { 39 addProgressListener() { 40 elt.numAPL++; 41 }, 42 removeProgressListener() { 43 elt.numRPL++; 44 }, 45 }, 46 }; 47 } 48 49 Services.wm = { 50 getEnumerator() { 51 return [getNext(), getNext()]; 52 }, 53 }; 54 return logs; 55 } 56 57 function fakeGetTabState(tab) { 58 return tab; 59 } 60 61 function clearQuickWriteTimer(tracker) { 62 if (tracker.tabsQuickWriteTimer) { 63 tracker.tabsQuickWriteTimer.clear(); 64 } 65 } 66 67 add_task(async function run_test() { 68 let engine = Service.engineManager.get("tabs"); 69 await engine.initialize(); 70 _("We assume that tabs have changed at startup."); 71 let tracker = engine._tracker; 72 tracker.getTabState = fakeGetTabState; 73 74 Assert.ok(tracker.modified); 75 Assert.ok( 76 Utils.deepEquals(Object.keys(await engine.getChangedIDs()), [ 77 clientsEngine.localID, 78 ]) 79 ); 80 81 let logs; 82 83 _("Test listeners are registered on windows"); 84 logs = fakeSvcWinMediator(); 85 tracker.start(); 86 Assert.equal(logs.length, 2); 87 for (let log of logs) { 88 Assert.equal(log.addTopics.length, 3); 89 Assert.ok(log.addTopics.includes("TabOpen")); 90 Assert.ok(log.addTopics.includes("TabClose")); 91 Assert.ok(log.addTopics.includes("unload")); 92 Assert.equal(log.remTopics.length, 0); 93 Assert.equal(log.numAPL, 1, "Added 1 progress listener"); 94 Assert.equal(log.numRPL, 0, "Didn't remove a progress listener"); 95 } 96 97 _("Test listeners are unregistered on windows"); 98 logs = fakeSvcWinMediator(); 99 await tracker.stop(); 100 Assert.equal(logs.length, 2); 101 for (let log of logs) { 102 Assert.equal(log.addTopics.length, 0); 103 Assert.equal(log.remTopics.length, 3); 104 Assert.ok(log.remTopics.includes("TabOpen")); 105 Assert.ok(log.remTopics.includes("TabClose")); 106 Assert.ok(log.remTopics.includes("unload")); 107 Assert.equal(log.numAPL, 0, "Didn't add a progress listener"); 108 Assert.equal(log.numRPL, 1, "Removed 1 progress listener"); 109 } 110 111 _("Test tab listener"); 112 for (let evttype of ["TabOpen", "TabClose"]) { 113 // Pretend we just synced. 114 await tracker.clearChangedIDs(); 115 Assert.ok(!tracker.modified); 116 117 // Send a fake tab event 118 tracker.onTab({ 119 type: evttype, 120 originalTarget: evttype, 121 target: { entries: [], currentURI: "about:config" }, 122 }); 123 Assert.ok(tracker.modified); 124 Assert.ok( 125 Utils.deepEquals(Object.keys(await engine.getChangedIDs()), [ 126 clientsEngine.localID, 127 ]) 128 ); 129 } 130 131 // Pretend we just synced. 132 await tracker.clearChangedIDs(); 133 Assert.ok(!tracker.modified); 134 135 tracker.onTab({ 136 type: "TabOpen", 137 originalTarget: "TabOpen", 138 target: { entries: [], currentURI: "about:config" }, 139 }); 140 Assert.ok( 141 Utils.deepEquals(Object.keys(await engine.getChangedIDs()), [ 142 clientsEngine.localID, 143 ]) 144 ); 145 146 // Pretend we just synced and saw some progress listeners. 147 await tracker.clearChangedIDs(); 148 Assert.ok(!tracker.modified); 149 tracker.onLocationChange({ isTopLevel: false }, undefined, undefined, 0); 150 Assert.ok(!tracker.modified, "non-toplevel request didn't flag as modified"); 151 152 tracker.onLocationChange( 153 { isTopLevel: true }, 154 undefined, 155 Services.io.newURI("https://www.mozilla.org"), 156 Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT 157 ); 158 Assert.ok( 159 tracker.modified, 160 "location change within the same document request did flag as modified" 161 ); 162 163 tracker.onLocationChange( 164 { isTopLevel: true }, 165 undefined, 166 Services.io.newURI("https://www.mozilla.org") 167 ); 168 Assert.ok( 169 tracker.modified, 170 "location change for a new top-level document flagged as modified" 171 ); 172 Assert.ok( 173 Utils.deepEquals(Object.keys(await engine.getChangedIDs()), [ 174 clientsEngine.localID, 175 ]) 176 ); 177 }); 178 179 add_task(async function run_sync_on_tab_change_test() { 180 let testPrefDelay = 20000; 181 182 // This is the pref that determines sync delay after tab change 183 Svc.PrefBranch.setIntPref( 184 "syncedTabs.syncDelayAfterTabChange", 185 testPrefDelay 186 ); 187 // We should only be syncing on tab change if 188 // the user has > 1 client 189 Svc.PrefBranch.setIntPref("clients.devices.desktop", 1); 190 Svc.PrefBranch.setIntPref("clients.devices.mobile", 1); 191 scheduler.updateClientMode(); 192 Assert.equal(scheduler.numClients, 2); 193 194 let engine = Service.engineManager.get("tabs"); 195 196 _("We assume that tabs have changed at startup."); 197 let tracker = engine._tracker; 198 tracker.getTabState = fakeGetTabState; 199 200 Assert.ok(tracker.modified); 201 Assert.ok( 202 Utils.deepEquals(Object.keys(await engine.getChangedIDs()), [ 203 clientsEngine.localID, 204 ]) 205 ); 206 207 _("Test sync is scheduled after a tab change"); 208 for (let evttype of ["TabOpen", "TabClose"]) { 209 // Pretend we just synced 210 await tracker.clearChangedIDs(); 211 clearQuickWriteTimer(tracker); 212 213 // Send a fake tab event 214 tracker.onTab({ 215 type: evttype, 216 originalTarget: evttype, 217 target: { entries: [], currentURI: "about:config" }, 218 }); 219 // Ensure the tracker fired 220 Assert.ok(tracker.modified); 221 // We should be more delayed at or more than what the pref is set at 222 let nextSchedule = tracker.tabsQuickWriteTimer.delay; 223 Assert.greaterOrEqual(nextSchedule, testPrefDelay); 224 } 225 226 _("Test sync is NOT scheduled after an unsupported tab open"); 227 for (let evttype of ["TabOpen"]) { 228 // Send a fake tab event 229 tracker.onTab({ 230 type: evttype, 231 originalTarget: evttype, 232 target: { entries: ["about:newtab"], currentURI: null }, 233 }); 234 // Ensure the tracker fired 235 Assert.ok(tracker.modified); 236 // We should be scheduling <= pref value 237 Assert.lessOrEqual(scheduler.nextSync - Date.now(), testPrefDelay); 238 } 239 240 _("Test navigating within the same tab does NOT trigger a sync"); 241 // Pretend we just synced 242 await tracker.clearChangedIDs(); 243 clearQuickWriteTimer(tracker); 244 245 tracker.onLocationChange( 246 { isTopLevel: true }, 247 undefined, 248 Services.io.newURI("https://www.mozilla.org"), 249 Ci.nsIWebProgressListener.LOCATION_CHANGE_RELOAD 250 ); 251 Assert.ok( 252 !tracker.modified, 253 "location change for reloading doesn't trigger a sync" 254 ); 255 Assert.ok(!tracker.tabsQuickWriteTimer, "reload does not trigger a sync"); 256 257 // Pretend we just synced 258 await tracker.clearChangedIDs(); 259 clearQuickWriteTimer(tracker); 260 261 _("Test navigating to an about page does trigger sync"); 262 tracker.onLocationChange( 263 { isTopLevel: true }, 264 undefined, 265 Services.io.newURI("about:config") 266 ); 267 Assert.ok(tracker.modified, "about page does not trigger a tab modified"); 268 Assert.ok( 269 tracker.tabsQuickWriteTimer, 270 "about schema should trigger a sync happening soon" 271 ); 272 273 _("Test adjusting the filterScheme pref works"); 274 // Pretend we just synced 275 await tracker.clearChangedIDs(); 276 clearQuickWriteTimer(tracker); 277 278 Svc.PrefBranch.setStringPref( 279 "engine.tabs.filteredSchemes", 280 // Removing the about scheme for this test 281 "resource|chrome|file|blob|moz-extension" 282 ); 283 tracker.onLocationChange( 284 { isTopLevel: true }, 285 undefined, 286 Services.io.newURI("about:config") 287 ); 288 Assert.ok( 289 tracker.modified, 290 "about page triggers a modified after we changed the pref" 291 ); 292 Assert.ok( 293 tracker.tabsQuickWriteTimer, 294 "about page should schedule a quickWrite sync soon after we changed the pref" 295 ); 296 297 _("Test no sync after tab change for accounts with <= 1 clients"); 298 // Pretend we just synced 299 await tracker.clearChangedIDs(); 300 clearQuickWriteTimer(tracker); 301 // Setting clients to only 1 so we don't sync after a tab change 302 Svc.PrefBranch.setIntPref("clients.devices.desktop", 1); 303 Svc.PrefBranch.setIntPref("clients.devices.mobile", 0); 304 scheduler.updateClientMode(); 305 Assert.equal(scheduler.numClients, 1); 306 307 tracker.onLocationChange( 308 { isTopLevel: true }, 309 undefined, 310 Services.io.newURI("https://www.mozilla.org") 311 ); 312 Assert.ok( 313 tracker.modified, 314 "location change for a new top-level document flagged as modified" 315 ); 316 Assert.ok( 317 !tracker.tabsQuickWriteTimer, 318 "We should NOT be syncing shortly because there is only one client" 319 ); 320 321 _("Changing the pref adjusts the sync schedule"); 322 Svc.PrefBranch.setIntPref("syncedTabs.syncDelayAfterTabChange", 10000); // 10seconds 323 let delayPref = Svc.PrefBranch.getIntPref( 324 "syncedTabs.syncDelayAfterTabChange" 325 ); 326 let evttype = "TabOpen"; 327 Assert.equal(delayPref, 10000); // ensure our pref is at 10s 328 // Only have task continuity if we have more than 1 device 329 Svc.PrefBranch.setIntPref("clients.devices.desktop", 1); 330 Svc.PrefBranch.setIntPref("clients.devices.mobile", 1); 331 scheduler.updateClientMode(); 332 Assert.equal(scheduler.numClients, 2); 333 clearQuickWriteTimer(tracker); 334 335 // Fire ontab event 336 tracker.onTab({ 337 type: evttype, 338 originalTarget: evttype, 339 target: { entries: [], currentURI: "about:config" }, 340 }); 341 342 // Ensure the tracker fired 343 Assert.ok(tracker.modified); 344 // We should be scheduling <= preference value 345 Assert.equal(tracker.tabsQuickWriteTimer.delay, delayPref); 346 347 _("We should not have a sync scheduled if pref is at 0"); 348 349 Svc.PrefBranch.setIntPref("syncedTabs.syncDelayAfterTabChange", 0); 350 // Pretend we just synced 351 await tracker.clearChangedIDs(); 352 clearQuickWriteTimer(tracker); 353 354 // Fire ontab event 355 evttype = "TabOpen"; 356 tracker.onTab({ 357 type: evttype, 358 originalTarget: evttype, 359 target: { entries: [], currentURI: "about:config" }, 360 }); 361 // Ensure the tracker fired 362 Assert.ok(tracker.modified); 363 364 // We should NOT be scheduled for a sync soon 365 Assert.ok(!tracker.tabsQuickWriteTimer); 366 367 scheduler.setDefaults(); 368 for (const pref of Svc.PrefBranch.getChildList("")) { 369 Svc.PrefBranch.clearUserPref(pref); 370 } 371 });