Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
/* globals browser BigInt */
"use strict";
add_setup(async function () {
// Always on top mode mess up with toolbox focus and openStoragePanelForAddon would timeout
// waiting for toolbox focus.
await pushPref("devtools.toolbox.alwaysOnTop", false);
});
/**
* Since storage item values are represented in the client as strings in textboxes, not all
* JavaScript object types supported by the WE storage local API and its IndexedDB backend
* can be successfully stringified for display in the table much less parsed correctly when
* the user tries to edit a value in the panel. This test is expected to change over time
* as more and more value types are supported.
*/
add_task(
async function test_extension_toolbox_only_supported_values_editable() {
async function background() {
browser.test.onMessage.addListener(async (msg, ...args) => {
switch (msg) {
case "storage-local-set":
await browser.storage.local.set(args[0]);
break;
case "storage-local-get": {
const items = await browser.storage.local.get(args[0]);
for (const [key, val] of Object.entries(items)) {
browser.test.assertTrue(
val === args[1],
`New value ${val} is set for key ${key}.`
);
}
break;
}
case "storage-local-fireOnChanged": {
const listener = () => {
browser.storage.onChanged.removeListener(listener);
browser.test.sendMessage("storage-local-onChanged");
};
browser.storage.onChanged.addListener(listener);
// Call an API method implemented in the parent process
// to ensure that the listener has been registered
// in the main process before the test proceeds.
await browser.runtime.getPlatformInfo();
break;
}
default:
browser.test.fail(`Unexpected test message: ${msg}`);
}
browser.test.sendMessage(`${msg}:done`);
});
browser.test.sendMessage("extension-origin", window.location.origin);
}
const extension = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["storage"],
},
background,
useAddonManager: "temporary",
});
await extension.startup();
const host = await extension.awaitMessage("extension-origin");
const itemsSupported = {
arr: [1, 2],
bool: true,
null: null,
num: 4,
obj: { a: 123 },
str: "hi",
// Nested objects or arrays at most 2 levels deep should be editable
nestedArr: [
{
a: "b",
},
"c",
],
nestedObj: {
a: [1, 2, "long-".repeat(10000)],
b: 3,
},
};
const itemsUnsupported = {
arrBuffer: new ArrayBuffer(8),
bigint: BigInt(1),
blob: new Blob(
[
JSON.stringify(
{
hello: "world",
},
null,
2
),
],
{
type: "application/json",
}
),
date: new Date(0),
map: new Map().set("a", "b"),
regexp: /regexp/,
set: new Set().add(1).add("a"),
undef: undefined,
// Arrays and object literals with non-JSONifiable values should not be editable
arrWithMap: [1, new Map().set("a", 1)],
objWithArrayBuffer: { a: new ArrayBuffer(8) },
// Nested objects or arrays more than 2 levels deep should not be editable
deepNestedArr: [[{ a: "b" }, 3], 4],
deepNestedObj: {
a: {
b: [1, 2],
},
},
};
info("Add storage items from the extension");
const allItems = { ...itemsSupported, ...itemsUnsupported };
extension.sendMessage("storage-local-fireOnChanged");
await extension.awaitMessage("storage-local-fireOnChanged:done");
extension.sendMessage("storage-local-set", allItems);
info(
"Wait for the extension to add storage items and receive the 'onChanged' event"
);
await extension.awaitMessage("storage-local-set:done");
await extension.awaitMessage("storage-local-onChanged");
info("Open the addon toolbox storage panel");
const { toolbox } = await openStoragePanelForAddon(extension.id);
await selectTreeItem(["extensionStorage", host]);
await waitForStorageData("str", "hi");
info("Verify that values are displayed as expected in the sidebar");
const expectedRenderedData = {
arr: {
sidebarItems: [
{ name: "arr", value: "Array" },
{ name: "arr.0", value: "1" },
{ name: "arr.1", value: "2" },
],
parsed: true,
},
arrBuffer: {
sidebarItems: [{ name: "arrBuffer", value: "Object" }],
parsed: true,
},
arrWithMap: {
sidebarItems: [
{ name: "arrWithMap", value: "Array" },
{ name: "arrWithMap.0", value: "1" },
{ name: "arrWithMap.1", value: "Object" },
],
parsed: true,
},
bigint: { sidebarItems: [{ name: "bigint", value: "1n" }] },
blob: { sidebarItems: [{ name: "blob", value: "Object" }], parsed: true },
bool: {
sidebarItems: [{ name: "bool", value: "true" }],
},
date: {
sidebarItems: [{ name: "date", value: "1970-01-01T00:00:00.000Z" }],
},
deepNestedArr: {
sidebarItems: [
{ name: "deepNestedArr", value: "Array" },
{ name: "deepNestedArr.0", value: "Array" },
{ name: "deepNestedArr.1", value: "4" },
{ name: "deepNestedArr.length", value: "2" },
],
parsed: true,
},
deepNestedObj: {
sidebarItems: [
{ name: "deepNestedObj", value: "Object" },
{ name: "deepNestedObj.a", value: "Object" },
],
parsed: true,
},
map: { sidebarItems: [{ name: "map", value: "Object" }], parsed: true },
nestedArr: {
sidebarItems: [
{ name: "nestedArr", value: "Array" },
{ name: "nestedArr.0", value: "Object" },
{ name: "nestedArr.0.a", value: "b" },
{ name: "nestedArr.1", value: "c" },
],
parsed: true,
},
nestedObj: {
sidebarItems: [
{ name: "nestedObj", value: "Object" },
{ name: "nestedObj.a", value: "Array" },
{ name: "nestedObj.a.0", value: "1" },
{ name: "nestedObj.a.1", value: "2" },
{ name: "nestedObj.a.2", value: "long-".repeat(10000) },
{ name: "nestedObj.b", value: "3" },
],
parsed: true,
},
null: {
sidebarItems: [{ name: "null", value: "null" }],
},
num: {
sidebarItems: [{ name: "num", value: itemsSupported.num }],
},
obj: {
sidebarItems: [
{ name: "obj", value: "Object" },
{ name: "obj.a", value: "123" },
],
parsed: true,
},
objWithArrayBuffer: {
sidebarItems: [
{ name: "objWithArrayBuffer", value: "Object" },
{ name: "objWithArrayBuffer.a", value: "Object" },
],
parsed: true,
},
regexp: {
sidebarItems: [{ name: "regexp", value: "Object" }],
parsed: true,
},
set: { sidebarItems: [{ name: "set", value: "Object" }], parsed: true },
str: {
sidebarItems: [{ name: "str", value: itemsSupported.str }],
},
undef: { sidebarItems: [{ name: "undef", value: "undefined" }] },
};
for (const [id, { sidebarItems, parsed }] of Object.entries(
expectedRenderedData
)) {
info(`Verify "${id}" entry`);
await selectTableItem(id);
await findVariableViewProperties(sidebarItems, parsed);
}
info("Verify that value types supported by the storage actor are editable");
let validate = true;
const newValue = "anotherValue";
const supportedIds = Object.keys(itemsSupported);
for (const id of supportedIds) {
startCellEdit(id, "value", newValue);
await editCell(id, "value", newValue, validate);
}
info("Verify that associated values have been changed in the extension");
extension.sendMessage(
"storage-local-get",
Object.keys(itemsSupported),
newValue
);
await extension.awaitMessage("storage-local-get:done");
info(
"Verify that value types not supported by the storage actor are uneditable"
);
const expectedValStrings = {
arrBuffer: "{}",
bigint: "1n",
blob: "{}",
date: "1970-01-01T00:00:00.000Z",
map: "{}",
regexp: "{}",
set: "{}",
undef: "undefined",
arrWithMap: "[1,{}]",
objWithArrayBuffer: '{"a":{}}',
deepNestedArr: '[[{"a":"b"},3],4]',
deepNestedObj: '{"a":{"b":[1,2]}}',
};
validate = false;
for (const id of Object.keys(itemsUnsupported)) {
startCellEdit(id, "value", validate);
checkCellUneditable(id, "value");
checkCell(id, "value", expectedValStrings[id]);
}
info("Shut down the test");
await toolbox.destroy();
await extension.unload();
}
);