/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ const { TabProvider } = ChromeUtils.importESModule( "resource://services-sync/engines/tabs.sys.mjs" ); const { WBORecord } = ChromeUtils.importESModule( "resource://services-sync/record.sys.mjs" ); const { Service } = ChromeUtils.importESModule( "resource://services-sync/service.sys.mjs" ); let engine; // We'll need the clients engine for testing as tabs is closely related let clientsEngine; async function syncClientsEngine(server) { clientsEngine._lastFxADevicesFetch = 0; clientsEngine.lastModified = server.getCollection("foo", "clients").timestamp; await clientsEngine._sync(); } async function makeRemoteClients() { let server = await serverForFoo(clientsEngine); await configureIdentity({ username: "foo" }, server); await Service.login(); await SyncTestingInfrastructure(server); await generateNewKeys(Service.collectionKeys); let remoteId = Utils.makeGUID(); let remoteId2 = Utils.makeGUID(); let collection = server.getCollection("foo", "clients"); _("Create remote client records"); collection.insertRecord({ id: remoteId, name: "Remote client", type: "desktop", commands: [], version: "48", fxaDeviceId: remoteId, fxaDeviceName: "Fxa - Remote client", protocols: ["1.5"], }); collection.insertRecord({ id: remoteId2, name: "Remote client 2", type: "desktop", commands: [], version: "48", fxaDeviceId: remoteId2, fxaDeviceName: "Fxa - Remote client 2", protocols: ["1.5"], }); let fxAccounts = clientsEngine.fxAccounts; clientsEngine.fxAccounts = { notifyDevices() { return Promise.resolve(true); }, device: { getLocalId() { return fxAccounts.device.getLocalId(); }, getLocalName() { return fxAccounts.device.getLocalName(); }, getLocalType() { return fxAccounts.device.getLocalType(); }, recentDeviceList: [{ id: remoteId, name: "remote device" }], refreshDeviceList() { return Promise.resolve(true); }, }, _internal: { now() { return Date.now(); }, }, }; await syncClientsEngine(server); } add_task(async function setup() { clientsEngine = Service.clientsEngine; // Make some clients to test with await makeRemoteClients(); // Make the tabs engine for all the tests to use engine = Service.engineManager.get("tabs"); await engine.initialize(); // Since these are xpcshell tests, we'll need to mock this TabProvider.shouldSkipWindow = mockShouldSkipWindow; }); add_task(async function test_tab_engine_skips_incoming_local_record() { _("Ensure incoming records that match local client ID are never applied."); let localID = clientsEngine.localID; let collection = new ServerCollection(); _("Creating remote tab record with local client ID"); let localRecord = encryptPayload({ id: localID, clientName: "local", tabs: [ { title: "title", urlHistory: ["http://foo.com/"], icon: "", lastUsed: 2000, }, ], }); collection.insert(localID, localRecord); _("Creating remote tab record with a different client ID"); let remoteID = "fake-guid-00"; // remote should match one of the test clients let remoteRecord = encryptPayload({ id: remoteID, clientName: "not local", tabs: [ { title: "title2", urlHistory: ["http://bar.com/"], icon: "", lastUsed: 3000, }, ], }); collection.insert(remoteID, remoteRecord); _("Setting up Sync server"); let server = sync_httpd_setup({ "/1.1/foo/storage/tabs": collection.handler(), }); await SyncTestingInfrastructure(server); let syncID = await engine.resetLocalSyncID(); let meta_global = Service.recordManager.set( engine.metaURL, new WBORecord(engine.metaURL) ); meta_global.payload.engines = { tabs: { version: engine.version, syncID }, }; await generateNewKeys(Service.collectionKeys); let promiseFinished = new Promise(resolve => { let syncFinish = engine._syncFinish; engine._syncFinish = async function () { let remoteTabs = await engine._rustStore.getAll(); equal( remoteTabs.length, 1, "Remote client record was applied and local wasn't" ); let record = remoteTabs[0]; equal(record.clientId, remoteID, "Remote client ID matches"); _("Ensure getAllClients returns the correct shape"); let clients = await engine.getAllClients(); equal(clients.length, 1); let client = clients[0]; equal(client.id, "fake-guid-00"); equal(client.name, "Remote client"); equal(client.type, "desktop"); Assert.ok(client.lastModified); // lastModified should be filled in once serverModified is populated from the server deepEqual(client.tabs, [ { title: "title2", urlHistory: ["http://bar.com/"], icon: "", inactive: false, lastUsed: 3000, }, ]); await syncFinish.call(engine); resolve(); }; }); _("Start sync"); Service.scheduler.hasIncomingItems = false; await engine._sync(); await promiseFinished; // Bug 1800185 - we don't want the sync scheduler to see these records as incoming. Assert.ok(!Service.scheduler.hasIncomingItems); }); // Ensure we trim tabs in the case of going past the max payload size allowed add_task(async function test_too_many_tabs() { let a_lot_of_tabs = []; for (let i = 0; i < 4000; ++i) { a_lot_of_tabs.push( `http://example${i}.com/some-super-long-url-chain-to-help-with-bytes` ); } TabProvider.getWindowEnumerator = mockGetWindowEnumerator.bind( this, a_lot_of_tabs ); let encoder = Utils.utf8Encoder; // see tryfitItems(..) in util.js const computeSerializedSize = records => encoder.encode(JSON.stringify(records)).byteLength; const maxPayloadSize = Service.getMaxRecordPayloadSize(); const maxSerializedSize = (maxPayloadSize / 4) * 3 - 1500; // We are over max payload size Assert.ok(computeSerializedSize(a_lot_of_tabs) > maxSerializedSize); let tabs = await engine.getTabsWithinPayloadSize(); // We are now under max payload size Assert.ok(computeSerializedSize(tabs) < maxSerializedSize); });