From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../test/xpcshell/test_ext_schemas_versioned.js | 714 +++++++++++++++++++++ 1 file changed, 714 insertions(+) create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_schemas_versioned.js (limited to 'toolkit/components/extensions/test/xpcshell/test_ext_schemas_versioned.js') 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 + ); +}); -- cgit v1.2.3