Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test gets skipped with pattern: os == 'android' OR appname == 'thunderbird'
- Manifest: toolkit/components/nimbus/test/browser/browser.toml
/* Any copyright is dedicated to the Public Domain.
"use strict";
const { FileTestUtils } = ChromeUtils.importESModule(
);
const { SharedDataMap } = ChromeUtils.importESModule(
"resource://nimbus/lib/SharedDataMap.sys.mjs"
);
const KEY = "browser_shareddatamap.js";
const PATH = FileTestUtils.getTempFile("shared-data-map").path;
add_setup(() => {
SpecialPowers.addTaskImport(
"SharedDataMap",
"resource://nimbus/lib/SharedDataMap.sys.mjs"
);
SpecialPowers.addTaskImport(
"TestUtils",
);
SpecialPowers.addTaskImport(
"sinon",
);
Services.ppmm.releaseCachedProcesses();
});
function setupTest({ multiprocess = false } = {}) {
const sdm = new SharedDataMap(KEY, { path: PATH });
return {
sdm,
async cleanup() {
sdm._removeEntriesByKeys(Object.keys(sdm._store.data));
// Wait for the store to finish writing to disk, then delete the file on disk.
await sdm._store.finalize();
await IOUtils.remove(PATH);
if (multiprocess) {
// Ensure we shut down any cached processes for the next test.
Services.ppmm.releaseCachedProcesses();
}
},
};
}
add_task(async function testSetSaves() {
const { sdm, cleanup } = setupTest();
await sdm.init();
sinon.spy(sdm._store, "saveSoon");
sdm.set("foo", "bar");
Assert.ok(
sdm._store.saveSoon.calledOnce,
"Should call saveSoon when setting a value"
);
await cleanup();
});
add_task(async function testUpdate() {
const { sdm, cleanup } = setupTest();
await sdm.init();
sdm.set("foo", "foo");
sdm.set("bar", "bar");
Assert.equal(sdm.get("foo"), "foo");
Assert.equal(sdm.get("bar"), "bar");
sdm.set("foo", "baz");
sdm.set("bar", "qux");
Assert.equal(sdm.get("foo"), "baz");
Assert.equal(sdm.get("bar"), "qux");
sdm._removeEntriesByKeys(["bar", "qux"]);
Assert.equal(sdm.get("foo"), "baz");
Assert.equal(typeof sdm.get("bar"), "undefined");
await cleanup();
});
add_task(async function testInitSafe() {
const { sdm, cleanup } = setupTest();
await sdm.init();
sinon.stub(sdm._store, "load");
sinon.replaceGetter(sdm._store, "data", () => {
throw new Error("uh oh");
});
await sdm.init();
Assert.ok(sdm._store.load.calledOnce, "should have called load");
await cleanup();
}).skip();
add_task(async function testInitMultiple() {
const { sdm, cleanup } = setupTest();
sinon.spy(sdm._store, "load");
await sdm.init();
await sdm.ready();
Assert.ok(sdm._store.load.calledOnce, "load called");
await sdm.init();
Assert.ok(sdm._store.load.calledOnce, "load called only once");
await cleanup();
});
add_task(async function testChildInit() {
const browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
const tab = await BrowserTestUtils.openNewForegroundTab({
gBrowser: browserWindow.gBrowser,
forceNewProcess: true,
});
await SpecialPowers.spawn(tab.linkedBrowser, [KEY], async key => {
const childSdm = new SharedDataMap(key);
Assert.ok(
!Object.hasOwn(childSdm, "_store"),
"child SharedDataMap does not have a store"
);
});
Services.ppmm.releaseCachedProcesses();
BrowserTestUtils.removeTab(tab);
});
add_task(async function testSetNotifiesParent() {
const { sdm, cleanup } = setupTest();
await sdm.init();
const onUpdate = sinon.stub();
sdm.on("parent-store-update:foo", onUpdate);
sdm.set("foo", "bar");
Assert.ok(
onUpdate.calledOnceWithExactly("parent-store-update:foo", "bar"),
"update event sent"
);
await cleanup();
});
add_task(async function testSetNotifiesChild() {
const { sdm, cleanup } = setupTest({ multiprocess: true });
await sdm.init();
const browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
// Open a tab so we have a content process.
const tab = await BrowserTestUtils.openNewForegroundTab({
gBrowser: browserWindow.gBrowser,
forceNewProcess: true,
});
const messagePrefix = "testSetNotifiesChild";
const complete = SpecialPowers.spawn(
tab.linkedBrowser,
[KEY, messagePrefix],
async (key, prefix) => {
function waitForSharedDataChanged() {
return new Promise(resolve => {
const listener = () => {
resolve();
Services.cpmm.sharedData.removeEventListener("change", listener);
};
Services.cpmm.sharedData.addEventListener("change", listener);
});
}
const childSdm = new SharedDataMap(key);
const onUpdate = sinon.stub();
childSdm.on("child-store-update:foo", onUpdate);
await childSdm.ready();
Assert.equal(
typeof childSdm.get("foo"),
"undefined",
"child does not see value"
);
// Send the begin message and wait for the SharedData update.
let sharedDataChanged = waitForSharedDataChanged();
Services.cpmm.sendAsyncMessage(`${prefix}:begin`);
await sharedDataChanged;
// Wait for the update event to fire.
await TestUtils.waitForCondition(
() => onUpdate.callCount,
"Child received an update event"
);
Assert.ok(
onUpdate.calledOnceWith("child-store-update:foo", { bar: 1 }),
"child received update event"
);
Assert.deepEqual(
childSdm.get("foo"),
{ bar: 1 },
"child sees updated value"
);
onUpdate.resetHistory();
// Send the delete message and wait for the SharedData update.
sharedDataChanged = waitForSharedDataChanged();
Services.cpmm.sendAsyncMessage(`${prefix}:delete`);
await sharedDataChanged;
await TestUtils.waitForCondition(() => !childSdm.get("foo"));
Assert.ok(onUpdate.notCalled, "onUpdate not called for deletions");
Assert.equal(
typeof childSdm.get("foo"),
"undefined",
"After deletion, foo is undefined"
);
}
);
// Wait for the child process to register its listeners.
await waitForChildMessage(`${messagePrefix}:begin`);
// Set the value in the parent and wait for the child to finish its assertions.
const continuePromise = waitForChildMessage(`${messagePrefix}:delete`);
// Flush immediately. Do not wait for idle dispatch.
sdm.set("foo", { bar: 1 });
Services.ppmm.sharedData.flush();
await continuePromise;
// Delete the entry. _deleteForTests flushes so we don't have to.
sdm._deleteForTests("foo");
// Wait for the child task to complete.
await complete;
BrowserTestUtils.removeTab(tab);
await cleanup();
});
add_task(async function testGetFromChildExisting() {
const { sdm, cleanup } = setupTest({ multiprocess: true });
await sdm.init();
sdm.set("foo", { bar: "baz", qux: ["quux"] });
// Ensure that we re-serialize SharedData to make the content immediately
// available to new processes.
Services.ppmm.sharedData.flush();
const browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
const tab = await BrowserTestUtils.openNewForegroundTab({
gBrowser: browserWindow.gBrowser,
forceNewProcess: true,
});
await SpecialPowers.spawn(tab.linkedBrowser, [KEY], async key => {
const childSdm = new SharedDataMap(key);
await childSdm.ready();
Assert.deepEqual(
childSdm.get("foo"),
{ bar: "baz", qux: ["quux"] },
"child sees correct value"
);
});
BrowserTestUtils.removeTab(tab);
await cleanup();
});