1
0
Fork 0
firefox/toolkit/components/extensions/test/xpcshell/test_StorageSyncService.js
Daniel Baumann 5e9a113729
Adding upstream version 140.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-25 09:37:52 +02:00

262 lines
7.1 KiB
JavaScript

/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
ChromeUtils.defineESModuleGetters(this, {
extensionStorageSync: "resource://gre/modules/ExtensionStorageSync.sys.mjs",
Service: "resource://services-sync/service.sys.mjs",
QuotaError:
"moz-src:///toolkit/components/uniffi-bindgen-gecko-js/components/generated/RustWebextstorage.sys.mjs",
});
const { ExtensionStorageEngineBridge } = ChromeUtils.importESModule(
"resource://services-sync/engines/extension-storage.sys.mjs"
);
const SYNC_QUOTA_BYTES = 102400;
add_task(async function setup_storage_sync() {
// So that we can write to the profile directory.
do_get_profile();
});
add_task(async function test_storage_sync_service() {
const service = extensionStorageSync;
{
// mocking notifyListeners so we have access to the return value of `service.set`
service.notifyListeners = (extId, changeSet) => {
equal(extId, "ext-1");
let expected = {
hi: {
newValue: "hello! 💖",
},
bye: {
newValue: "adiós",
},
};
deepEqual(
[changeSet],
[expected],
"`set` should notify listeners about changes"
);
};
let newValue = {
hi: "hello! 💖",
bye: "adiós",
};
// finalling calling `service.set` which asserts the deepEqual in the above mocked `notifyListeners`
await service.set({ id: "ext-1" }, newValue);
}
{
service.notifyListeners = (_extId, _changeSet) => {
console.log(`NOTIFY LISTENERS`);
};
let expected = {
hi: "hello! 💖",
};
let value = await service.get({ id: "ext-1" }, ["hi"]);
deepEqual(value, expected, "`get` with key should return value");
let expected2 = {
hi: "hello! 💖",
bye: "adiós",
};
let allValues = await service.get({ id: "ext-1" }, null);
deepEqual(
allValues,
expected2,
"`get` without a key should return all values"
);
}
{
service.notifyListeners = (extId, changeSet) => {
console.log("notifyListeners", extId, changeSet);
};
let newValue = {
hi: "hola! 👋",
};
await service.set({ id: "ext-2" }, newValue);
await service.clear({ id: "ext-1" });
let allValues = await service.get({ id: "ext-1" }, null);
deepEqual(allValues, {}, "clear removed ext-1");
let allValues2 = await service.get({ id: "ext-2" }, null);
let expected = { hi: "hola! 👋" };
deepEqual(allValues2, expected, "clear didn't remove ext-2");
// We need to clear data for ext-2 too, so later tests don't fail due to
// this data.
await service.clear({ id: "ext-2" });
}
});
add_task(async function test_storage_sync_bridged_engine() {
let engine = new ExtensionStorageEngineBridge(Service);
await engine.initialize();
let area = engine._rustStore;
info("Add some local items");
await area.set("ext-1", JSON.stringify({ a: "abc" }));
await area.set("ext-2", JSON.stringify({ b: "xyz" }));
info("Start a sync");
await engine._bridge.syncStarted();
info("Store some incoming synced items");
let incomingEnvelopesAsJSON = [
{
id: "guidAAA",
modified: 0.1,
payload: JSON.stringify({
extId: "ext-2",
data: JSON.stringify({
c: 1234,
}),
}),
},
{
id: "guidBBB",
modified: 0.1,
payload: JSON.stringify({
extId: "ext-3",
data: JSON.stringify({
d: "new! ✨",
}),
}),
},
].map(e => JSON.stringify(e));
await engine._bridge.storeIncoming(incomingEnvelopesAsJSON);
info("Merge");
// Three levels of JSON wrapping: each outgoing envelope, the cleartext in
// each envelope, and the extension storage data in each cleartext payload.
let outgoingEnvelopesAsJSON = await engine._bridge.apply();
let outgoingEnvelopes = outgoingEnvelopesAsJSON.map(json => JSON.parse(json));
let parsedCleartexts = outgoingEnvelopes.map(e => JSON.parse(e.payload));
let parsedData = parsedCleartexts.map(c => JSON.parse(c.data));
let changes = (await area.getSyncedChanges()).map(change => {
return {
extId: change.extId,
changes: JSON.parse(change.changes),
};
});
deepEqual(
changes,
[
{
extId: "ext-2",
changes: {
c: { newValue: 1234 },
},
},
{
extId: "ext-3",
changes: {
d: { newValue: "new! ✨" },
},
},
],
"Should return pending synced changes for observers"
);
// ext-1 doesn't exist remotely yet, so the Rust sync layer will generate
// a GUID for it. We don't know what it is, so we find it by the extension
// ID.
let ext1Index = parsedCleartexts.findIndex(c => c.extId == "ext-1");
greater(ext1Index, -1, "Should find envelope for ext-1");
let ext1Guid = outgoingEnvelopes[ext1Index].id;
// ext-2 has a remote GUID that we set in the test above.
let ext2Index = outgoingEnvelopes.findIndex(c => c.id == "guidAAA");
greater(ext2Index, -1, "Should find envelope for ext-2");
equal(outgoingEnvelopes.length, 2, "Should upload ext-1 and ext-2");
deepEqual(
parsedData[ext1Index],
{
a: "abc",
},
"Should upload new data for ext-1"
);
deepEqual(
parsedData[ext2Index],
{
b: "xyz",
c: 1234,
},
"Should merge local and remote data for ext-2"
);
info("Mark all extensions as uploaded");
await engine._bridge.setUploaded(0, [ext1Guid, "guidAAA"]);
info("Finish sync");
await engine._bridge.syncFinished();
// Try fetching values for the remote-only extension we just synced.
let ext3Value = await area.get("ext-3", "null");
deepEqual(
JSON.parse(ext3Value),
{
d: "new! ✨",
},
"Should return new keys for ext-3"
);
info("Try applying a second time");
let secondApply = await engine._bridge.apply();
deepEqual(secondApply, {}, "Shouldn't merge anything on second apply");
info("Wipe all items");
await engine._bridge.wipe();
for (let extId of ["ext-1", "ext-2", "ext-3"]) {
// `get` always returns an object, even if there are no keys for the
// extension ID.
let value = await area.get(extId, "null");
deepEqual(
JSON.parse(value),
{},
`Wipe should remove all values for ${extId}`
);
}
});
add_task(async function test_storage_sync_quota() {
let engine = new ExtensionStorageEngineBridge(Service);
await engine.initialize();
let service = engine._rustStore;
await engine._bridge.wipe();
await service.set("ext-1", JSON.stringify({ x: "hi" }));
await service.set("ext-1", JSON.stringify({ longer: "value" }));
let v1 = await service.getBytesInUse("ext-1", '"x"');
Assert.equal(v1, 5); // key len without quotes, value len with quotes.
let v2 = await service.getBytesInUse("ext-1", "null");
// 5 from 'x', plus 'longer' (6 for key, 7 for value = 13) = 18.
Assert.equal(v2, 18);
await Assert.rejects(
service.set(
"ext-1",
JSON.stringify({
big: "x".repeat(SYNC_QUOTA_BYTES),
})
),
QuotaError,
"should reject with QuotaError"
);
});