Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

"use strict";
const { AppConstants } = ChromeUtils.importESModule(
);
const { XPCShellContentUtils } = ChromeUtils.importESModule(
);
const PROCESS_COUNT_PREF = "dom.ipc.processCount";
const remote = AppConstants.platform !== "android";
XPCShellContentUtils.init(this);
let contentPage;
async function readBlob(key, sharedData = Services.cpmm.sharedData) {
const { ExtensionUtils } = ChromeUtils.importESModule(
);
let reader = new FileReader();
reader.readAsText(sharedData.get(key));
await ExtensionUtils.promiseEvent(reader, "loadend");
return reader.result;
}
function getKey(key, sharedData = Services.cpmm.sharedData) {
return sharedData.get(key);
}
function hasKey(key, sharedData = Services.cpmm.sharedData) {
return sharedData.has(key);
}
function getContents(sharedMap = Services.cpmm.sharedData) {
return {
keys: Array.from(sharedMap.keys()),
values: Array.from(sharedMap.values()),
entries: Array.from(sharedMap.entries()),
getValues: Array.from(sharedMap.keys(), key => sharedMap.get(key)),
};
}
function checkMap(contents, expected) {
expected = Array.from(expected);
equal(contents.keys.length, expected.length, "Got correct number of keys");
equal(
contents.values.length,
expected.length,
"Got correct number of values"
);
equal(
contents.entries.length,
expected.length,
"Got correct number of entries"
);
for (let [i, [key, val]] of contents.entries.entries()) {
equal(key, contents.keys[i], `keys()[${i}] matches entries()[${i}]`);
deepEqual(
val,
contents.values[i],
`values()[${i}] matches entries()[${i}]`
);
}
expected.sort(([a], [b]) => a.localeCompare(b));
contents.entries.sort(([a], [b]) => a.localeCompare(b));
for (let [i, [key, val]] of contents.entries.entries()) {
equal(
key,
expected[i][0],
`expected[${i}].key matches entries()[${i}].key`
);
deepEqual(
val,
expected[i][1],
`expected[${i}].value matches entries()[${i}].value`
);
}
}
function checkParentMap(expected) {
info("Checking parent map");
checkMap(getContents(Services.ppmm.sharedData), expected);
}
async function checkContentMaps(expected, parentOnly = false) {
info("Checking in-process content map");
checkMap(getContents(Services.cpmm.sharedData), expected);
if (!parentOnly) {
info("Checking out-of-process content map");
let contents = await contentPage.spawn([], getContents);
checkMap(contents, expected);
}
}
async function loadContentPage() {
let page = await XPCShellContentUtils.loadContentPage("data:text/html,", {
remote,
});
registerCleanupFunction(() => page.close());
return page;
}
add_setup(async function () {
// Start with one content process so that we can increase the number
// later and test the behavior of a fresh content process.
Services.prefs.setIntPref(PROCESS_COUNT_PREF, 1);
contentPage = await loadContentPage();
});
add_task(async function test_sharedMap() {
let { sharedData } = Services.ppmm;
info("Check that parent and child maps are both initially empty");
checkParentMap([]);
await checkContentMaps([]);
let expected = [
["foo-a", { foo: "a" }],
["foo-b", { foo: "b" }],
["bar-c", null],
["bar-d", 42],
];
function setKey(key, val) {
sharedData.set(key, val);
expected = expected.filter(([k]) => k != key);
expected.push([key, val]);
}
function deleteKey(key) {
sharedData.delete(key);
expected = expected.filter(([k]) => k != key);
}
for (let [key, val] of expected) {
sharedData.set(key, val);
}
info(
"Add some entries, test that they are initially only available in the parent"
);
checkParentMap(expected);
await checkContentMaps([]);
info("Flush. Check that changes are visible in both parent and children");
sharedData.flush();
checkParentMap(expected);
await checkContentMaps(expected);
info(
"Add another entry. Check that it is initially only available in the parent"
);
let oldExpected = Array.from(expected);
setKey("baz-a", { meh: "meh" });
// When we do several checks in a row, we can't check the values in
// the content process, since the async checks may allow the idle
// flush task to run, and update it before we're ready.
checkParentMap(expected);
checkContentMaps(oldExpected, true);
info(
"Add another entry. Check that both new entries are only available in the parent"
);
setKey("baz-a", { meh: 12 });
checkParentMap(expected);
checkContentMaps(oldExpected, true);
info(
"Delete an entry. Check that all changes are only visible in the parent"
);
deleteKey("foo-b");
checkParentMap(expected);
checkContentMaps(oldExpected, true);
info(
"Flush. Check that all entries are available in both parent and children"
);
sharedData.flush();
checkParentMap(expected);
await checkContentMaps(expected);
info("Test that entries are automatically flushed on idle:");
info(
"Add a new entry. Check that it is initially only available in the parent"
);
// Test the idle flush task.
oldExpected = Array.from(expected);
setKey("thing", "stuff");
checkParentMap(expected);
checkContentMaps(oldExpected, true);
info(
"Wait for an idle timeout. Check that changes are now visible in all children"
);
await new Promise(resolve => ChromeUtils.idleDispatch(resolve));
checkParentMap(expected);
await checkContentMaps(expected);
// Test that has() rebuilds map after a flush.
sharedData.set("grick", true);
sharedData.flush();
equal(
await contentPage.spawn(["grick"], hasKey),
true,
"has() should see key after flush"
);
sharedData.set("grack", true);
sharedData.flush();
equal(
await contentPage.spawn(["gruck"], hasKey),
false,
"has() should return false for nonexistent key"
);
});
add_task(async function test_blobs() {
let { sharedData } = Services.ppmm;
let text = [
"The quick brown fox jumps over the lazy dog",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit",
"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
];
let blobs = text.map(str => new Blob([str]));
let data = { foo: { bar: "baz" } };
sharedData.set("blob0", blobs[0]);
sharedData.set("blob1", blobs[1]);
sharedData.set("data", data);
equal(
await readBlob("blob0", sharedData),
text[0],
"Expected text for blob0 in parent ppmm"
);
sharedData.flush();
equal(
await readBlob("blob0", sharedData),
text[0],
"Expected text for blob0 in parent ppmm"
);
equal(
await readBlob("blob1", sharedData),
text[1],
"Expected text for blob1 in parent ppmm"
);
equal(
await readBlob("blob0"),
text[0],
"Expected text for blob0 in parent cpmm"
);
equal(
await readBlob("blob1"),
text[1],
"Expected text for blob1 in parent cpmm"
);
equal(
await contentPage.spawn(["blob0"], readBlob),
text[0],
"Expected text for blob0 in child 1 cpmm"
);
equal(
await contentPage.spawn(["blob1"], readBlob),
text[1],
"Expected text for blob1 in child 1 cpmm"
);
// Start a second child process
Services.prefs.setIntPref(PROCESS_COUNT_PREF, 2);
let page2 = await loadContentPage();
equal(
await page2.spawn(["blob0"], readBlob),
text[0],
"Expected text for blob0 in child 2 cpmm"
);
equal(
await page2.spawn(["blob1"], readBlob),
text[1],
"Expected text for blob1 in child 2 cpmm"
);
sharedData.set("blob0", blobs[2]);
equal(
await readBlob("blob0", sharedData),
text[2],
"Expected text for blob0 in parent ppmm"
);
sharedData.flush();
equal(
await readBlob("blob0", sharedData),
text[2],
"Expected text for blob0 in parent ppmm"
);
equal(
await readBlob("blob1", sharedData),
text[1],
"Expected text for blob1 in parent ppmm"
);
equal(
await readBlob("blob0"),
text[2],
"Expected text for blob0 in parent cpmm"
);
equal(
await readBlob("blob1"),
text[1],
"Expected text for blob1 in parent cpmm"
);
equal(
await contentPage.spawn(["blob0"], readBlob),
text[2],
"Expected text for blob0 in child 1 cpmm"
);
equal(
await contentPage.spawn(["blob1"], readBlob),
text[1],
"Expected text for blob1 in child 1 cpmm"
);
equal(
await page2.spawn(["blob0"], readBlob),
text[2],
"Expected text for blob0 in child 2 cpmm"
);
equal(
await page2.spawn(["blob1"], readBlob),
text[1],
"Expected text for blob1 in child 2 cpmm"
);
deepEqual(
await page2.spawn(["data"], getKey),
data,
"Expected data for data key in child 2 cpmm"
);
});