summaryrefslogtreecommitdiffstats
path: root/toolkit/components/extensions/test/xpcshell/test_ext_schemas_versioned.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/extensions/test/xpcshell/test_ext_schemas_versioned.js')
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_schemas_versioned.js714
1 files changed, 714 insertions, 0 deletions
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_schemas_versioned.js b/toolkit/components/extensions/test/xpcshell/test_ext_schemas_versioned.js
new file mode 100644
index 0000000000..3dddbbc41b
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_schemas_versioned.js
@@ -0,0 +1,714 @@
+"use strict";
+
+let json = [
+ {
+ namespace: "MV2",
+ max_manifest_version: 2,
+
+ properties: {
+ PROP1: { value: 20 },
+ },
+ },
+ {
+ namespace: "MV3",
+ min_manifest_version: 3,
+ properties: {
+ PROP1: { value: 20 },
+ },
+ },
+ {
+ namespace: "mixed",
+
+ properties: {
+ PROP_any: { value: 20 },
+ PROP_mv3: {
+ $ref: "submodule",
+ },
+ },
+ types: [
+ {
+ id: "manifestTest",
+ type: "object",
+ properties: {
+ // An example of extending the base type for permissions
+ permissions: {
+ type: "array",
+ items: {
+ $ref: "BaseType",
+ },
+ optional: true,
+ default: [],
+ },
+ // An example of differentiating versions of a manifest entry
+ multiple_choice: {
+ optional: true,
+ choices: [
+ {
+ max_manifest_version: 2,
+ type: "array",
+ items: {
+ type: "string",
+ },
+ },
+ {
+ min_manifest_version: 3,
+ type: "array",
+ items: {
+ type: "boolean",
+ },
+ },
+ {
+ type: "array",
+ items: {
+ type: "object",
+ properties: {
+ value: { type: "boolean" },
+ },
+ },
+ },
+ ],
+ },
+ accepting_unrecognized_props: {
+ optional: true,
+ type: "object",
+ properties: {
+ mv2_only_prop: {
+ type: "string",
+ optional: true,
+ max_manifest_version: 2,
+ },
+ mv3_only_prop: {
+ type: "string",
+ optional: true,
+ min_manifest_version: 3,
+ },
+ mv2_only_prop_with_default: {
+ type: "string",
+ optional: true,
+ default: "only in MV2",
+ max_manifest_version: 2,
+ },
+ mv3_only_prop_with_default: {
+ type: "string",
+ optional: true,
+ default: "only in MV3",
+ min_manifest_version: 3,
+ },
+ },
+ additionalProperties: { $ref: "UnrecognizedProperty" },
+ },
+ },
+ },
+ {
+ id: "submodule",
+ type: "object",
+ min_manifest_version: 3,
+ functions: [
+ {
+ name: "sub_foo",
+ type: "function",
+ parameters: [],
+ returns: { type: "integer" },
+ },
+ {
+ name: "sub_no_match",
+ type: "function",
+ max_manifest_version: 2,
+ parameters: [],
+ returns: { type: "integer" },
+ },
+ ],
+ },
+ {
+ id: "BaseType",
+ choices: [
+ {
+ type: "string",
+ enum: ["base"],
+ },
+ ],
+ },
+ {
+ id: "type_any",
+ type: "string",
+ enum: ["value1", "value2", "value3"],
+ },
+ {
+ id: "type_mv2",
+ max_manifest_version: 2,
+ type: "string",
+ enum: ["value1", "value2", "value3"],
+ },
+ {
+ id: "type_mv3",
+ min_manifest_version: 3,
+ type: "string",
+ enum: ["value1", "value2", "value3"],
+ },
+ {
+ id: "param_type_changed",
+ type: "array",
+ items: {
+ choices: [
+ { max_manifest_version: 2, type: "string" },
+ {
+ min_manifest_version: 3,
+ type: "boolean",
+ },
+ ],
+ },
+ },
+ {
+ id: "object_type_changed",
+ type: "object",
+ properties: {
+ prop_mv2: {
+ type: "string",
+ max_manifest_version: 2,
+ },
+ prop_mv3: {
+ type: "string",
+ min_manifest_version: 3,
+ },
+ prop_any: {
+ type: "string",
+ },
+ },
+ },
+ {
+ id: "no_valid_choices",
+ type: "array",
+ items: {
+ choices: [
+ { max_manifest_version: 1, type: "string" },
+ {
+ min_manifest_version: 4,
+ type: "boolean",
+ },
+ ],
+ },
+ },
+ ],
+
+ functions: [
+ {
+ name: "fun_param_type_versioned",
+ type: "function",
+ parameters: [{ name: "arg1", $ref: "param_type_changed" }],
+ },
+ {
+ name: "fun_mv2",
+ max_manifest_version: 2,
+ type: "function",
+ parameters: [
+ { name: "arg1", type: "integer", optional: true, default: 99 },
+ { name: "arg2", type: "boolean", optional: true },
+ ],
+ },
+ {
+ name: "fun_mv3",
+ min_manifest_version: 3,
+ type: "function",
+ parameters: [
+ { name: "arg1", type: "integer", optional: true, default: 99 },
+ { name: "arg2", type: "boolean", optional: true },
+ ],
+ },
+ {
+ name: "fun_param_change",
+ type: "function",
+ parameters: [{ name: "arg1", $ref: "object_type_changed" }],
+ },
+ {
+ name: "fun_no_valid_param",
+ type: "function",
+ parameters: [{ name: "arg1", $ref: "no_valid_choices" }],
+ },
+ ],
+ events: [
+ {
+ name: "onEvent_any",
+ type: "function",
+ },
+ {
+ name: "onEvent_mv2",
+ max_manifest_version: 2,
+ type: "function",
+ },
+ {
+ name: "onEvent_mv3",
+ min_manifest_version: 3,
+ type: "function",
+ },
+ ],
+ },
+ {
+ namespace: "mixed",
+ types: [
+ {
+ $extend: "BaseType",
+ choices: [
+ {
+ min_manifest_version: 3,
+ type: "string",
+ enum: ["extended"],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ namespace: "mixed",
+ types: [
+ {
+ $extend: "manifestTest",
+ properties: {
+ versioned_extend: {
+ optional: true,
+ // just a simple type here
+ type: "string",
+ max_manifest_version: 2,
+ },
+ },
+ },
+ ],
+ },
+];
+
+add_task(async function setup() {
+ let url = "data:," + JSON.stringify(json);
+ Schemas._rootSchema = null;
+ await Schemas.load(url);
+
+ // We want the actual errors thrown here, and not warnings recast as errors.
+ ExtensionTestUtils.failOnSchemaWarnings(false);
+});
+
+add_task(async function test_inject_V2() {
+ // Test injecting into a V2 context.
+ let wrapper = getContextWrapper(2);
+
+ let root = {};
+ Schemas.inject(root, wrapper);
+
+ // Test elements available to both
+ Assert.equal(root.mixed.type_any.VALUE1, "value1", "type_any exists");
+ Assert.equal(root.mixed.PROP_any, 20, "mixed value property");
+
+ // Test elements available to MV2
+ Assert.equal(root.MV2.PROP1, 20, "MV2 value property");
+ Assert.equal(root.mixed.type_mv2.VALUE2, "value2", "type_mv2 exists");
+
+ // Test MV3 elements not available
+ Assert.equal(root.MV3, undefined, "MV3 not injected");
+ Assert.ok(!("MV3" in root), "MV3 not enumerable");
+ Assert.equal(
+ root.mixed.PROP_mv3,
+ undefined,
+ "mixed submodule property does not exist"
+ );
+ Assert.ok(
+ !("PROP_mv3" in root.mixed),
+ "mixed submodule property not enumerable"
+ );
+ Assert.equal(root.mixed.type_mv3, undefined, "type_mv3 does not exist");
+
+ // Function tests
+ Assert.ok(
+ "fun_param_type_versioned" in root.mixed,
+ "fun_param_type_versioned exists"
+ );
+ Assert.ok(
+ !!root.mixed.fun_param_type_versioned,
+ "fun_param_type_versioned exists"
+ );
+ Assert.ok("fun_mv2" in root.mixed, "fun_mv2 exists");
+ Assert.ok(!!root.mixed.fun_mv2, "fun_mv2 exists");
+ Assert.ok(!("fun_mv3" in root.mixed), "fun_mv3 does not exist");
+ Assert.ok(!root.mixed.fun_mv3, "fun_mv3 does not exist");
+
+ // Event tests
+ Assert.ok("onEvent_any" in root.mixed, "onEvent_any exists");
+ Assert.ok(!!root.mixed.onEvent_any, "onEvent_any exists");
+ Assert.ok("onEvent_mv2" in root.mixed, "onEvent_mv2 exists");
+ Assert.ok(!!root.mixed.onEvent_mv2, "onEvent_mv2 exists");
+ Assert.ok(!("onEvent_mv3" in root.mixed), "onEvent_mv3 does not exist");
+ Assert.ok(!root.mixed.onEvent_mv3, "onEvent_mv3 does not exist");
+
+ // Function call tests
+ root.mixed.fun_param_type_versioned(["hello"]);
+ wrapper.verify("call", "mixed", "fun_param_type_versioned", [["hello"]]);
+ Assert.throws(
+ () => root.mixed.fun_param_type_versioned([true]),
+ /Expected string instead of true/,
+ "fun_param_type_versioned should throw for invalid type"
+ );
+
+ let propObj = { prop_any: "prop_any", prop_mv2: "prop_mv2" };
+ root.mixed.fun_param_change(propObj);
+ wrapper.verify("call", "mixed", "fun_param_change", [propObj]);
+
+ // Still throw same error as we did before we knew of the MV3 property.
+ Assert.throws(
+ () => root.mixed.fun_param_change({ prop_mv3: "prop_mv3", ...propObj }),
+ /Type error for parameter arg1 \(Unexpected property "prop_mv3"\)/,
+ "generic unexpected property message for MV3 property in MV2 extension"
+ );
+
+ // But print the more specific and descriptive warning message to console.
+ wrapper.checkErrors([
+ `Property "prop_mv3" is unsupported in Manifest Version 2`,
+ ]);
+
+ Assert.throws(
+ () => root.mixed.fun_no_valid_param("anything"),
+ /Incorrect argument types for mixed.fun_no_valid_param/,
+ "fun_no_valid_param should throw for versioned type"
+ );
+});
+
+function normalizeTest(manifest, test, wrapper) {
+ let normalized = Schemas.normalize(manifest, "mixed.manifestTest", wrapper);
+ test(normalized);
+ // The test function should call wrapper.checkErrors if it expected errors.
+ // Here we call checkErrors again to ensure that there are not any unexpected
+ // errors left.
+ wrapper.checkErrors([]);
+}
+
+add_task(async function test_normalize_V2() {
+ let wrapper = getContextWrapper(2);
+
+ // Test normalize additions to the manifest structure
+ normalizeTest(
+ {
+ versioned_extend: "test",
+ },
+ normalized => {
+ Assert.equal(
+ normalized.value.versioned_extend,
+ "test",
+ "resources normalized"
+ );
+ },
+ wrapper
+ );
+
+ // Test normalizing baseType
+ normalizeTest(
+ {
+ permissions: ["base"],
+ },
+ normalized => {
+ Assert.equal(
+ normalized.value.permissions[0],
+ "base",
+ "resources normalized"
+ );
+ },
+ wrapper
+ );
+
+ normalizeTest(
+ {
+ permissions: ["extended"],
+ },
+ normalized => {
+ Assert.ok(
+ normalized.error.startsWith("Error processing permissions.0"),
+ `manifest normalized error ${normalized.error}`
+ );
+ },
+ wrapper
+ );
+
+ // Test normalizing a value
+ normalizeTest(
+ {
+ multiple_choice: ["foo.html"],
+ },
+ normalized => {
+ Assert.equal(
+ normalized.value.multiple_choice[0],
+ "foo.html",
+ "resources normalized"
+ );
+ },
+ wrapper
+ );
+
+ normalizeTest(
+ {
+ multiple_choice: [true],
+ },
+ normalized => {
+ Assert.ok(
+ normalized.error.startsWith("Error processing multiple_choice"),
+ "manifest error"
+ );
+ },
+ wrapper
+ );
+
+ normalizeTest(
+ {
+ multiple_choice: [
+ {
+ value: true,
+ },
+ ],
+ },
+ normalized => {
+ Assert.ok(
+ normalized.value.multiple_choice[0].value,
+ "resources normalized"
+ );
+ },
+ wrapper
+ );
+
+ // Tests that object definitions including additionalProperties can
+ // successfully accept objects from another manifest version, while ignoring
+ // the actual value from the non-matching manifest value.
+ normalizeTest(
+ {
+ accepting_unrecognized_props: {
+ mv2_only_prop: "mv2 here",
+ mv3_only_prop: "mv3 here",
+ },
+ },
+ normalized => {
+ equal(normalized.error, undefined, "no normalization error");
+ Assert.deepEqual(
+ normalized.value.accepting_unrecognized_props,
+ {
+ mv2_only_prop: "mv2 here",
+ mv2_only_prop_with_default: "only in MV2",
+ },
+ "Normalized object for MV2, without MV3-specific props"
+ );
+ wrapper.checkErrors([
+ `Property "mv3_only_prop" is unsupported in Manifest Version 2`,
+ ]);
+ },
+ wrapper
+ );
+});
+
+add_task(async function test_inject_V3() {
+ // Test injecting into a V3 context.
+ let wrapper = getContextWrapper(3);
+
+ let root = {};
+ Schemas.inject(root, wrapper);
+
+ // Test elements available to both
+ Assert.equal(root.mixed.type_any.VALUE1, "value1", "type_any exists");
+ Assert.equal(root.mixed.PROP_any, 20, "mixed value property");
+
+ // Test elements available to MV2
+ Assert.equal(root.MV2, undefined, "MV2 value property");
+ Assert.ok(!("MV2" in root), "MV2 not enumerable");
+ Assert.equal(root.mixed.type_mv2, undefined, "type_mv2 does not exist");
+ Assert.ok(!("type_mv2" in root.mixed), "type_mv2 not enumerable");
+
+ // Test MV3 elements not available
+ Assert.equal(root.MV3.PROP1, 20, "MV3 injected");
+ Assert.ok(!!root.mixed.PROP_mv3, "mixed submodule property exists");
+ Assert.equal(root.mixed.type_mv3.VALUE3, "value3", "type_mv3 exists");
+
+ // Versioned submodule
+ Assert.ok(!!root.mixed.PROP_mv3.sub_foo, "mixed submodule sub_foo exists");
+ Assert.ok(
+ !root.mixed.PROP_mv3.sub_no_match,
+ "mixed submodule sub_no_match does not exist"
+ );
+ Assert.ok(
+ !("sub_no_match" in root.mixed.PROP_mv3),
+ "mixed submodule sub_no_match is not enumerable"
+ );
+
+ // Function tests
+ Assert.ok(
+ "fun_param_type_versioned" in root.mixed,
+ "fun_param_type_versioned exists"
+ );
+ Assert.ok(
+ !!root.mixed.fun_param_type_versioned,
+ "fun_param_type_versioned exists"
+ );
+ Assert.ok(!("fun_mv2" in root.mixed), "fun_mv2 does not exist");
+ Assert.ok(!root.mixed.fun_mv2, "fun_mv2 does not exist");
+ Assert.ok("fun_mv3" in root.mixed, "fun_mv3 exists");
+ Assert.ok(!!root.mixed.fun_mv3, "fun_mv3 exists");
+
+ // Event tests
+ Assert.ok("onEvent_any" in root.mixed, "onEvent_any exists");
+ Assert.ok(!!root.mixed.onEvent_any, "onEvent_any exists");
+ Assert.ok(!("onEvent_mv2" in root.mixed), "onEvent_mv2 not enumerable");
+ Assert.ok(!root.mixed.onEvent_mv2, "onEvent_mv2 does not exist");
+ Assert.ok("onEvent_mv3" in root.mixed, "onEvent_mv3 exists");
+ Assert.ok(!!root.mixed.onEvent_mv3, "onEvent_mv3 exists");
+
+ // Function call tests
+ root.mixed.fun_param_type_versioned([true]);
+ wrapper.verify("call", "mixed", "fun_param_type_versioned", [[true]]);
+ Assert.throws(
+ () => root.mixed.fun_param_type_versioned(["hello"]),
+ /Expected boolean instead of "hello"/,
+ "should throw for invalid type"
+ );
+
+ let propObj = { prop_any: "prop_any", prop_mv3: "prop_mv3" };
+ root.mixed.fun_param_change(propObj);
+ wrapper.verify("call", "mixed", "fun_param_change", [propObj]);
+ Assert.throws(
+ () => root.mixed.fun_param_change({ prop_mv2: "prop_mv2", ...propObj }),
+ /Unexpected property "prop_mv2"/,
+ "should throw for versioned type"
+ );
+ wrapper.checkErrors([
+ `Property "prop_mv2" is unsupported in Manifest Version 3`,
+ ]);
+
+ root.mixed.PROP_mv3.sub_foo();
+ wrapper.verify("call", "mixed.PROP_mv3", "sub_foo", []);
+ Assert.throws(
+ () => root.mixed.PROP_mv3.sub_no_match(),
+ /TypeError: root.mixed.PROP_mv3.sub_no_match is not a function/,
+ "sub_no_match should throw"
+ );
+});
+
+add_task(async function test_normalize_V3() {
+ let wrapper = getContextWrapper(3);
+
+ // Test normalize additions to the manifest structure
+ normalizeTest(
+ {
+ versioned_extend: "test",
+ },
+ normalized => {
+ Assert.equal(
+ normalized.error,
+ `Unexpected property "versioned_extend"`,
+ "expected manifest error"
+ );
+ wrapper.checkErrors([
+ `Property "versioned_extend" is unsupported in Manifest Version 3`,
+ ]);
+ },
+ wrapper
+ );
+
+ // Test normalizing baseType
+ normalizeTest(
+ {
+ permissions: ["base"],
+ },
+ normalized => {
+ Assert.equal(
+ normalized.value.permissions[0],
+ "base",
+ "resources normalized"
+ );
+ },
+ wrapper
+ );
+
+ normalizeTest(
+ {
+ permissions: ["extended"],
+ },
+ normalized => {
+ Assert.equal(
+ normalized.value.permissions[0],
+ "extended",
+ "resources normalized"
+ );
+ },
+ wrapper
+ );
+
+ // Test normalizing a value
+ normalizeTest(
+ {
+ multiple_choice: ["foo.html"],
+ },
+ normalized => {
+ Assert.ok(
+ normalized.error.startsWith("Error processing multiple_choice"),
+ "manifest error"
+ );
+ },
+ wrapper
+ );
+
+ normalizeTest(
+ {
+ multiple_choice: [true],
+ },
+ normalized => {
+ Assert.equal(
+ normalized.value.multiple_choice[0],
+ true,
+ "resources normalized"
+ );
+ },
+ wrapper
+ );
+
+ normalizeTest(
+ {
+ multiple_choice: [
+ {
+ value: true,
+ },
+ ],
+ },
+ normalized => {
+ Assert.ok(
+ normalized.value.multiple_choice[0].value,
+ "resources normalized"
+ );
+ },
+ wrapper
+ );
+
+ wrapper.tallied = null;
+
+ normalizeTest(
+ {},
+ normalized => {
+ ok(!normalized.error, "manifest normalized");
+ },
+ wrapper
+ );
+
+ // Tests that object definitions including additionalProperties can
+ // successfully accept objects from another manifest version, while ignoring
+ // the actual value from the non-matching manifest value.
+ normalizeTest(
+ {
+ accepting_unrecognized_props: {
+ mv2_only_prop: "mv2 here",
+ mv3_only_prop: "mv3 here",
+ },
+ },
+ normalized => {
+ equal(normalized.error, undefined, "no normalization error");
+ Assert.deepEqual(
+ normalized.value.accepting_unrecognized_props,
+ {
+ mv3_only_prop: "mv3 here",
+ mv3_only_prop_with_default: "only in MV3",
+ },
+ "Normalized object for MV3, without MV2-specific props"
+ );
+ wrapper.checkErrors([
+ `Property "mv2_only_prop" is unsupported in Manifest Version 3`,
+ ]);
+ },
+ wrapper
+ );
+});