11 prop2: { type: "string" },
25 enum: ["value1", "value2", "value3"],
32 prop1: { type: "integer" },
33 prop2: { type: "array", items: { $ref: "type1" } },
41 prop1: { type: "string" },
47 choices: [{ type: "integer" }],
53 prop2: { type: "string" },
59 choices: [{ type: "string" }],
66 baseprop: { type: "string" },
75 derivedprop: { type: "string" },
84 derivedprop: { type: "integer" },
96 returns: { type: "integer" },
107 { name: "arg1", type: "integer", optional: true, default: 99 },
108 { name: "arg2", type: "boolean", optional: true },
116 { name: "arg1", type: "integer", optional: true },
117 { name: "arg2", type: "boolean" },
129 prop1: { type: "string" },
130 prop2: { type: "integer", optional: true },
131 prop3: { type: "integer", unsupported: true },
140 parameters: [{ name: "arg1", $ref: "type1" }],
146 parameters: [{ name: "arg1", $ref: "type2" }],
152 parameters: [{ name: "arg1", type: "function" }],
159 { name: "arg1", type: "integer", optional: true },
160 { name: "arg2", type: "integer" },
179 additionalProperties: { type: "integer" },
194 parameters: [{ name: "x", type: "integer" }],
208 additionalProperties: { type: "any" },
220 properties: { prop1: { type: "string", pattern: "^\\d+$" } },
222 "(?i)^prop\\d+$": { type: "string" },
223 "^foo\\d+$": { type: "string" },
233 { name: "arg", type: "string", pattern: "(?i)^[0-9a-f]+$" },
245 hostname: { type: "string", format: "hostname", optional: true },
246 url: { type: "string", format: "url", optional: true },
247 origin: { type: "string", format: "origin", optional: true },
250 format: "relativeUrl",
255 format: "strictRelativeUrl",
258 imageDataOrStrictRelativeUrl: {
260 format: "imageDataOrStrictRelativeUrl",
276 date: { type: "string", format: "date", optional: true },
301 required: { type: "integer" },
302 optional: { type: "string", optional: true },
353 foo: { type: "string", preprocess: "localize", optional: true },
354 bar: { type: "string", optional: true },
357 preprocess: "localize",
369 parameters: [{ name: "val", $ref: "basetype1" }],
375 parameters: [{ name: "val", $ref: "basetype2" }],
379 name: "callderived1",
381 parameters: [{ name: "value", $ref: "derivedtype1" }],
385 name: "callderived2",
387 parameters: [{ name: "value", $ref: "derivedtype2" }],
412 namespace: "foreign",
414 foreignRef: { $ref: "testing.submodule" },
420 PROP1: { value: "should inject" },
424 namespace: "do-not-inject",
426 PROP1: { value: "should not inject" },
431 add_task(async function() {
432 let wrapper = getContextWrapper();
433 let url = "data:," + JSON.stringify(json);
434 Schemas._rootSchema = null;
435 await Schemas.load(url);
438 Schemas.inject(root, wrapper);
440 Assert.equal(root.testing.PROP1, 20, "simple value property");
441 Assert.equal(root.testing.type1.VALUE1, "value1", "enum type");
442 Assert.equal(root.testing.type1.VALUE2, "value2", "enum type");
444 Assert.equal("inject" in root, true, "namespace 'inject' should be injected");
446 root["do-not-inject"],
448 "namespace 'do-not-inject' should not be injected"
451 root.testing.foo(11, true);
452 wrapper.verify("call", "testing", "foo", [11, true]);
454 root.testing.foo(true);
455 wrapper.verify("call", "testing", "foo", [99, true]);
457 root.testing.foo(null, true);
458 wrapper.verify("call", "testing", "foo", [99, true]);
460 root.testing.foo(undefined, true);
461 wrapper.verify("call", "testing", "foo", [99, true]);
463 root.testing.foo(11);
464 wrapper.verify("call", "testing", "foo", [11, null]);
467 () => root.testing.bar(11),
468 /Incorrect argument types/,
469 "should throw without required arg"
473 () => root.testing.bar(11, true, 10),
474 /Incorrect argument types/,
475 "should throw with too many arguments"
478 root.testing.bar(true);
479 wrapper.verify("call", "testing", "bar", [null, true]);
481 root.testing.baz({ prop1: "hello", prop2: 22 });
482 wrapper.verify("call", "testing", "baz", [{ prop1: "hello", prop2: 22 }]);
484 root.testing.baz({ prop1: "hello" });
485 wrapper.verify("call", "testing", "baz", [{ prop1: "hello", prop2: null }]);
487 root.testing.baz({ prop1: "hello", prop2: null });
488 wrapper.verify("call", "testing", "baz", [{ prop1: "hello", prop2: null }]);
491 () => root.testing.baz({ prop2: 12 }),
492 /Property "prop1" is required/,
493 "should throw without required property"
497 () => root.testing.baz({ prop1: "hi", prop3: 12 }),
498 /Property "prop3" is unsupported by Firefox/,
499 "should throw with unsupported property"
503 () => root.testing.baz({ prop1: "hi", prop4: 12 }),
504 /Unexpected property "prop4"/,
505 "should throw with unexpected property"
509 () => root.testing.baz({ prop1: 12 }),
510 /Expected string instead of 12/,
511 "should throw with wrong type"
514 root.testing.qux("value2");
515 wrapper.verify("call", "testing", "qux", ["value2"]);
518 () => root.testing.qux("value4"),
519 /Invalid enumeration value "value4"/,
520 "should throw for invalid enum value"
523 root.testing.quack({ prop1: 12, prop2: ["value1", "value3"] });
524 wrapper.verify("call", "testing", "quack", [
525 { prop1: 12, prop2: ["value1", "value3"] },
530 root.testing.quack({ prop1: 12, prop2: ["value1", "value3", "value4"] }),
531 /Invalid enumeration value "value4"/,
532 "should throw for invalid array type"
536 root.testing.quora(f);
538 JSON.stringify(wrapper.tallied.slice(0, -1)),
539 JSON.stringify(["call", "testing", "quora"])
541 Assert.equal(wrapper.tallied[3][0], f);
542 wrapper.tallied = null;
545 root.testing.quora(g);
547 JSON.stringify(wrapper.tallied.slice(0, -1)),
548 JSON.stringify(["call", "testing", "quora"])
550 Assert.equal(wrapper.tallied[3][0], g);
551 wrapper.tallied = null;
553 root.testing.quileute(10);
554 wrapper.verify("call", "testing", "quileute", [null, 10]);
557 () => root.testing.queets(),
558 /queets is not a function/,
559 "should throw for unsupported functions"
562 root.testing.quintuplets({ a: 10, b: 20, c: 30 });
563 wrapper.verify("call", "testing", "quintuplets", [{ a: 10, b: 20, c: 30 }]);
566 () => root.testing.quintuplets({ a: 10, b: 20, c: 30, d: "hi" }),
567 /Expected integer instead of "hi"/,
568 "should throw for wrong additionalProperties type"
571 root.testing.quasar({ func: f });
573 JSON.stringify(wrapper.tallied.slice(0, -1)),
574 JSON.stringify(["call", "testing", "quasar"])
576 Assert.equal(wrapper.tallied[3][0].func, f);
578 root.testing.quosimodo({ a: 10, b: 20, c: 30 });
579 wrapper.verify("call", "testing", "quosimodo", [{ a: 10, b: 20, c: 30 }]);
582 () => root.testing.quosimodo(10),
583 /Incorrect argument types/,
584 "should throw for wrong type"
587 root.testing.patternprop({
593 wrapper.verify("call", "testing", "patternprop", [
594 { prop1: "12", prop2: "42", Prop3: "43", foo1: "x" },
597 root.testing.patternprop({ prop1: "12" });
598 wrapper.verify("call", "testing", "patternprop", [{ prop1: "12" }]);
601 () => root.testing.patternprop({ prop1: "12", foo1: null }),
602 /Expected string instead of null/,
603 "should throw for wrong property type"
607 () => root.testing.patternprop({ prop1: "xx", prop2: "yy" }),
608 /String "xx" must match \/\^\\d\+\$\//,
609 "should throw for wrong property type"
613 () => root.testing.patternprop({ prop1: "12", prop2: 42 }),
614 /Expected string instead of 42/,
615 "should throw for wrong property type"
619 () => root.testing.patternprop({ prop1: "12", prop2: null }),
620 /Expected string instead of null/,
621 "should throw for wrong property type"
625 () => root.testing.patternprop({ prop1: "12", propx: "42" }),
626 /Unexpected property "propx"/,
627 "should throw for unexpected property"
631 () => root.testing.patternprop({ prop1: "12", Foo1: "x" }),
632 /Unexpected property "Foo1"/,
633 "should throw for unexpected property"
636 root.testing.pattern("DEADbeef");
637 wrapper.verify("call", "testing", "pattern", ["DEADbeef"]);
640 () => root.testing.pattern("DEADcow"),
641 /String "DEADcow" must match \/\^\[0-9a-f\]\+\$\/i/,
642 "should throw for non-match"
645 root.testing.format({ hostname: "foo" });
646 wrapper.verify("call", "testing", "format", [
649 imageDataOrStrictRelativeUrl: null,
652 strictRelativeUrl: null,
657 for (let invalid of ["", " ", "http://foo", "foo/bar", "foo.com/", "foo?"]) {
659 () => root.testing.format({ hostname: invalid }),
661 "should throw for invalid hostname"
666 "https://example.com",
667 "http://example.com",
668 "https://foo.bar.æ ƒæœ¨.jp",
670 root.testing.format({ origin: valid });
673 for (let invalid of [
674 "https://example.com/testing",
679 "https://foo.bar.æ ƒæœ¨.jp/",
680 "https://user:pass@example.com",
681 "https://*.example.com",
682 "https://example.com#test",
683 "https://example.com?test",
686 () => root.testing.format({ origin: invalid }),
688 "should throw for invalid origin"
692 root.testing.format({ url: "http://foo/bar", relativeUrl: "http://foo/bar" });
693 wrapper.verify("call", "testing", "format", [
696 imageDataOrStrictRelativeUrl: null,
698 relativeUrl: "http://foo/bar",
699 strictRelativeUrl: null,
700 url: "http://foo/bar",
704 root.testing.format({
705 relativeUrl: "foo.html",
706 strictRelativeUrl: "foo.html",
708 wrapper.verify("call", "testing", "format", [
711 imageDataOrStrictRelativeUrl: null,
713 relativeUrl: `${wrapper.url}foo.html`,
714 strictRelativeUrl: `${wrapper.url}foo.html`,
719 root.testing.format({
720 imageDataOrStrictRelativeUrl: "data:image/png;base64,A",
722 wrapper.verify("call", "testing", "format", [
725 imageDataOrStrictRelativeUrl: "data:image/png;base64,A",
728 strictRelativeUrl: null,
733 root.testing.format({
734 imageDataOrStrictRelativeUrl: "data:image/jpeg;base64,A",
736 wrapper.verify("call", "testing", "format", [
739 imageDataOrStrictRelativeUrl: "data:image/jpeg;base64,A",
742 strictRelativeUrl: null,
747 root.testing.format({ imageDataOrStrictRelativeUrl: "foo.html" });
748 wrapper.verify("call", "testing", "format", [
751 imageDataOrStrictRelativeUrl: `${wrapper.url}foo.html`,
754 strictRelativeUrl: null,
759 for (let format of ["url", "relativeUrl"]) {
761 () => root.testing.format({ [format]: "chrome://foo/content/" }),
763 "should throw for access denied"
767 for (let urlString of ["//foo.html", "http://foo/bar.html"]) {
769 () => root.testing.format({ strictRelativeUrl: urlString }),
770 /must be a relative URL/,
771 "should throw for non-relative URL"
777 root.testing.format({
778 imageDataOrStrictRelativeUrl: "data:image/svg+xml;utf8,A",
780 /must be a relative or PNG or JPG data:image URL/,
781 "should throw for non-relative or non PNG/JPG data URL"
786 "2016-03-04T08:00:00Z",
787 "2016-03-04T08:00:00.000Z",
788 "2016-03-04T08:00:00-08:00",
789 "2016-03-04T08:00:00.000-08:00",
790 "2016-03-04T08:00:00+08:00",
791 "2016-03-04T08:00:00.000+08:00",
792 "2016-03-04T08:00:00+0800",
793 "2016-03-04T08:00:00-0800",
795 dates.forEach(str => {
796 root.testing.formatDate({ date: str });
797 wrapper.verify("call", "testing", "formatDate", [{ date: str }]);
800 // Make sure that a trivial change to a valid date invalidates it.
801 dates.forEach(str => {
803 () => root.testing.formatDate({ date: "0" + str }),
804 /Invalid date string/,
805 "should throw for invalid iso date string"
808 () => root.testing.formatDate({ date: str + "0" }),
809 /Invalid date string/,
810 "should throw for invalid iso date string"
815 "I do not look anything like a date string",
817 "2016-03-04T25:00:00Z",
819 badDates.forEach(str => {
821 () => root.testing.formatDate({ date: str }),
822 /Invalid date string/,
823 "should throw for invalid iso date string"
828 foo: { bar: [{ baz: { required: 12, optional: "42" } }] },
830 wrapper.verify("call", "testing", "deep", [
831 { foo: { bar: [{ baz: { optional: "42", required: 12 } }] } },
835 () => root.testing.deep({ foo: { bar: [{ baz: { optional: "42" } }] } }),
836 /Type error for parameter arg \(Error processing foo\.bar\.0\.baz: Property "required" is required\) for testing\.deep/,
837 "should throw with the correct object path"
843 foo: { bar: [{ baz: { optional: 42, required: 12 } }] },
845 /Type error for parameter arg \(Error processing foo\.bar\.0\.baz\.optional: Expected string instead of 42\) for testing\.deep/,
846 "should throw with the correct object path"
849 wrapper.talliedErrors.length = 0;
851 root.testing.errors({ default: "0123", ignore: "0123", warn: "0123" });
852 wrapper.verify("call", "testing", "errors", [
853 { default: "0123", ignore: "0123", warn: "0123" },
855 wrapper.checkErrors([]);
857 root.testing.errors({ default: "0123", ignore: "x123", warn: "0123" });
858 wrapper.verify("call", "testing", "errors", [
859 { default: "0123", ignore: null, warn: "0123" },
861 wrapper.checkErrors([]);
863 ExtensionTestUtils.failOnSchemaWarnings(false);
864 root.testing.errors({ default: "0123", ignore: "0123", warn: "x123" });
865 ExtensionTestUtils.failOnSchemaWarnings(true);
866 wrapper.verify("call", "testing", "errors", [
867 { default: "0123", ignore: "0123", warn: null },
869 wrapper.checkErrors(['String "x123" must match /^\\d+$/']);
871 root.testing.onFoo.addListener(f);
873 JSON.stringify(wrapper.tallied.slice(0, -1)),
874 JSON.stringify(["addListener", "testing", "onFoo"])
876 Assert.equal(wrapper.tallied[3][0], f);
877 Assert.equal(JSON.stringify(wrapper.tallied[3][1]), JSON.stringify([]));
878 wrapper.tallied = null;
880 root.testing.onFoo.removeListener(f);
882 JSON.stringify(wrapper.tallied.slice(0, -1)),
883 JSON.stringify(["removeListener", "testing", "onFoo"])
885 Assert.equal(wrapper.tallied[3][0], f);
886 wrapper.tallied = null;
888 root.testing.onFoo.hasListener(f);
890 JSON.stringify(wrapper.tallied.slice(0, -1)),
891 JSON.stringify(["hasListener", "testing", "onFoo"])
893 Assert.equal(wrapper.tallied[3][0], f);
894 wrapper.tallied = null;
897 () => root.testing.onFoo.addListener(10),
899 "addListener with non-function should throw"
902 root.testing.onBar.addListener(f, 10);
904 JSON.stringify(wrapper.tallied.slice(0, -1)),
905 JSON.stringify(["addListener", "testing", "onBar"])
907 Assert.equal(wrapper.tallied[3][0], f);
908 Assert.equal(JSON.stringify(wrapper.tallied[3][1]), JSON.stringify([10]));
909 wrapper.tallied = null;
911 root.testing.onBar.addListener(f);
913 JSON.stringify(wrapper.tallied.slice(0, -1)),
914 JSON.stringify(["addListener", "testing", "onBar"])
916 Assert.equal(wrapper.tallied[3][0], f);
917 Assert.equal(JSON.stringify(wrapper.tallied[3][1]), JSON.stringify([1]));
918 wrapper.tallied = null;
921 () => root.testing.onBar.addListener(f, "hi"),
922 /Incorrect argument types/,
923 "addListener with wrong extra parameter should throw"
926 let target = { prop1: 12, prop2: ["value1", "value3"] };
927 let proxy = new Proxy(target, {});
929 () => root.testing.quack(proxy),
930 /Expected a plain JavaScript object, got a Proxy/,
931 "should throw when passing a Proxy"
934 if (Symbol.toStringTag) {
935 let stringTarget = { prop1: 12, prop2: ["value1", "value3"] };
936 stringTarget[Symbol.toStringTag] = () => "[object Object]";
937 let stringProxy = new Proxy(stringTarget, {});
939 () => root.testing.quack(stringProxy),
940 /Expected a plain JavaScript object, got a Proxy/,
941 "should throw when passing a Proxy"
945 root.testing.localize({
948 url: "__MSG_http://example.com/__",
950 wrapper.verify("call", "testing", "localize", [
951 { bar: "__MSG_foo__", foo: "FOO", url: "http://example.com/" },
955 () => root.testing.localize({ url: "__MSG_/foo/bar__" }),
956 /\/FOO\/BAR is not a valid URL\./,
957 "should throw for invalid URL"
960 root.testing.extended1({ prop1: "foo", prop2: "bar" });
961 wrapper.verify("call", "testing", "extended1", [
962 { prop1: "foo", prop2: "bar" },
966 () => root.testing.extended1({ prop1: "foo", prop2: 12 }),
967 /Expected string instead of 12/,
968 "should throw for wrong property type"
972 () => root.testing.extended1({ prop1: "foo" }),
973 /Property "prop2" is required/,
974 "should throw for missing property"
978 () => root.testing.extended1({ prop1: "foo", prop2: "bar", prop3: "xxx" }),
979 /Unexpected property "prop3"/,
980 "should throw for extra property"
983 root.testing.extended2("foo");
984 wrapper.verify("call", "testing", "extended2", ["foo"]);
986 root.testing.extended2(12);
987 wrapper.verify("call", "testing", "extended2", [12]);
990 () => root.testing.extended2(true),
991 /Incorrect argument types/,
992 "should throw for wrong argument type"
995 root.testing.prop3.sub_foo();
996 wrapper.verify("call", "testing.prop3", "sub_foo", []);
999 () => root.testing.prop4.sub_foo(),
1000 /root.testing.prop4 is undefined/,
1001 "should throw for unsupported submodule"
1004 root.foreign.foreignRef.sub_foo();
1005 wrapper.verify("call", "foreign.foreignRef", "sub_foo", []);
1007 root.testing.callderived1({ baseprop: "s1", derivedprop: "s2" });
1008 wrapper.verify("call", "testing", "callderived1", [
1009 { baseprop: "s1", derivedprop: "s2" },
1013 () => root.testing.callderived1({ baseprop: "s1", derivedprop: 42 }),
1014 /Error processing derivedprop: Expected string/,
1015 "Two different objects may $import the same base object"
1018 () => root.testing.callderived1({ baseprop: "s1" }),
1019 /Property "derivedprop" is required/,
1020 "Object using $import has its local properites"
1023 () => root.testing.callderived1({ derivedprop: "s2" }),
1024 /Property "baseprop" is required/,
1025 "Object using $import has imported properites"
1028 root.testing.callderived2({ baseprop: "s1", derivedprop: 42 });
1029 wrapper.verify("call", "testing", "callderived2", [
1030 { baseprop: "s1", derivedprop: 42 },
1034 () => root.testing.callderived2({ baseprop: "s1", derivedprop: "s2" }),
1035 /Error processing derivedprop: Expected integer/,
1036 "Two different objects may $import the same base object"
1039 () => root.testing.callderived2({ baseprop: "s1" }),
1040 /Property "derivedprop" is required/,
1041 "Object using $import has its local properites"
1044 () => root.testing.callderived2({ derivedprop: 42 }),
1045 /Property "baseprop" is required/,
1046 "Object using $import has imported properites"
1050 let deprecatedJson = [
1052 namespace: "deprecated",
1058 deprecated: "This is not the property you are looking for",
1082 additionalProperties: {
1084 deprecated: "Unknown property",
1102 deprecated: "Please use an integer, not ${value}",
1115 deprecated: "You have no choices",
1134 deprecated: "Deprecated alias",
1144 deprecated: "Do not call this method",
1151 name: "onDeprecated",
1153 deprecated: "This event does not work",
1159 add_task(async function testDeprecation() {
1160 let wrapper = getContextWrapper();
1161 // This whole test expects deprecation warnings.
1162 ExtensionTestUtils.failOnSchemaWarnings(false);
1164 let url = "data:," + JSON.stringify(deprecatedJson);
1165 Schemas._rootSchema = null;
1166 await Schemas.load(url);
1169 Schemas.inject(root, wrapper);
1171 root.deprecated.property({ foo: "bar", xxx: "any", yyy: "property" });
1172 wrapper.verify("call", "deprecated", "property", [
1173 { foo: "bar", xxx: "any", yyy: "property" },
1175 wrapper.checkErrors([
1176 "Warning processing xxx: Unknown property",
1177 "Warning processing yyy: Unknown property",
1180 root.deprecated.value(12);
1181 wrapper.verify("call", "deprecated", "value", [12]);
1182 wrapper.checkErrors([]);
1184 root.deprecated.value("12");
1185 wrapper.verify("call", "deprecated", "value", ["12"]);
1186 wrapper.checkErrors(['Please use an integer, not "12"']);
1188 root.deprecated.choices(12);
1189 wrapper.verify("call", "deprecated", "choices", [12]);
1190 wrapper.checkErrors(["You have no choices"]);
1192 root.deprecated.ref("12");
1193 wrapper.verify("call", "deprecated", "ref", ["12"]);
1194 wrapper.checkErrors(["Deprecated alias"]);
1196 root.deprecated.method();
1197 wrapper.verify("call", "deprecated", "method", []);
1198 wrapper.checkErrors(["Do not call this method"]);
1200 void root.deprecated.accessor;
1201 wrapper.verify("get", "deprecated", "accessor", null);
1202 wrapper.checkErrors(["This is not the property you are looking for"]);
1204 root.deprecated.accessor = "x";
1205 wrapper.verify("set", "deprecated", "accessor", "x");
1206 wrapper.checkErrors(["This is not the property you are looking for"]);
1208 root.deprecated.onDeprecated.addListener(() => {});
1209 wrapper.checkErrors(["This event does not work"]);
1211 root.deprecated.onDeprecated.removeListener(() => {});
1212 wrapper.checkErrors(["This event does not work"]);
1214 root.deprecated.onDeprecated.hasListener(() => {});
1215 wrapper.checkErrors(["This event does not work"]);
1217 ExtensionTestUtils.failOnSchemaWarnings(true);
1220 () => root.deprecated.onDeprecated.hasListener(() => {}),
1221 /This event does not work/,
1222 "Deprecation warning with extensions.webextensions.warnings-as-errors=true"
1228 namespace: "choices",
1242 enum: ["foo", "bar", "baz"],
1246 pattern: "florg.*meh",
1274 additionalProperties: {
1323 add_task(async function testChoices() {
1324 let wrapper = getContextWrapper();
1325 let url = "data:," + JSON.stringify(choicesJson);
1326 Schemas._rootSchema = null;
1327 await Schemas.load(url);
1330 Schemas.inject(root, wrapper);
1333 () => root.choices.meh("frog"),
1334 /Value "frog" must either: be one of \["foo", "bar", "baz"\], match the pattern \/florg\.\*meh\/, or be an integer value/
1338 () => root.choices.meh(4),
1339 /be a string value, or be at least 12/
1343 () => root.choices.meh(43),
1344 /be a string value, or be no greater than 42/
1348 () => root.choices.foo([]),
1349 /be an object value, be a string value, or have at least 2 items/
1353 () => root.choices.foo([1, 2, 3, 4]),
1354 /be an object value, be a string value, or have at most 3 items/
1358 () => root.choices.foo({ foo: 12 }),
1359 /.foo must be a string value, be a string value, or be an array value/
1363 () => root.choices.foo({ blurg: "foo" }),
1364 /not contain an unsupported "blurg" property, be a string value, or be an array value/
1368 () => root.choices.bar({}),
1369 /contain the required "baz" property, or be an array value/
1373 () => root.choices.bar({ baz: "x", quux: "y" }),
1374 /not contain an unexpected "quux" property, or be an array value/
1378 () => root.choices.bar({ baz: "x", quux: "y", foo: "z" }),
1379 /not contain the unexpected properties \[foo, quux\], or be an array value/
1383 let permissionsJson = [
1385 namespace: "noPerms",
1399 permissions: ["foo"],
1406 namespace: "fooPerm",
1408 permissions: ["foo"],
1422 permissions: ["foo.bar"],
1429 add_task(async function testPermissions() {
1430 let url = "data:," + JSON.stringify(permissionsJson);
1431 Schemas._rootSchema = null;
1432 await Schemas.load(url);
1434 let wrapper = getContextWrapper();
1437 Schemas.inject(root, wrapper);
1439 equal(typeof root.noPerms, "object", "noPerms namespace should exist");
1441 typeof root.noPerms.noPerms,
1443 "noPerms.noPerms method should exist"
1447 root.noPerms.fooPerm,
1449 "noPerms.fooPerm should not method exist"
1452 equal(root.fooPerm, undefined, "fooPerm namespace should not exist");
1454 info('Add "foo" permission');
1455 wrapper.permissions.add("foo");
1458 Schemas.inject(root, wrapper);
1460 equal(typeof root.noPerms, "object", "noPerms namespace should exist");
1462 typeof root.noPerms.noPerms,
1464 "noPerms.noPerms method should exist"
1467 typeof root.noPerms.fooPerm,
1469 "noPerms.fooPerm method should exist"
1472 equal(typeof root.fooPerm, "object", "fooPerm namespace should exist");
1474 typeof root.fooPerm.noPerms,
1476 "noPerms.noPerms method should exist"
1480 root.fooPerm.fooBarPerm,
1482 "fooPerm.fooBarPerm method should not exist"
1485 info('Add "foo.bar" permission');
1486 wrapper.permissions.add("foo.bar");
1489 Schemas.inject(root, wrapper);
1491 equal(typeof root.noPerms, "object", "noPerms namespace should exist");
1493 typeof root.noPerms.noPerms,
1495 "noPerms.noPerms method should exist"
1498 typeof root.noPerms.fooPerm,
1500 "noPerms.fooPerm method should exist"
1503 equal(typeof root.fooPerm, "object", "fooPerm namespace should exist");
1505 typeof root.fooPerm.noPerms,
1507 "noPerms.noPerms method should exist"
1510 typeof root.fooPerm.fooBarPerm,
1512 "noPerms.fooBarPerm method should exist"
1516 let nestedNamespaceJson = [
1518 namespace: "nested.namespace",
1536 name: "functionOnCustomType",
1549 instanceOfCustomType: {
1568 add_task(async function testNestedNamespace() {
1569 let url = "data:," + JSON.stringify(nestedNamespaceJson);
1570 let wrapper = getContextWrapper();
1572 Schemas._rootSchema = null;
1573 await Schemas.load(url);
1576 Schemas.inject(root, wrapper);
1578 ok(root.nested, "The root object contains the first namespace level");
1580 root.nested.namespace,
1581 "The first level object contains the second namespace level"
1585 root.nested.namespace.create,
1586 "Got the expected function in the nested namespace"
1589 typeof root.nested.namespace.create,
1591 "The property is a function as expected"
1594 let { instanceOfCustomType } = root.nested.namespace;
1597 instanceOfCustomType,
1598 "Got the expected instance of the CustomType defined in the schema"
1601 instanceOfCustomType.functionOnCustomType,
1602 "Got the expected method in the CustomType instance"
1605 instanceOfCustomType.onEvent &&
1606 instanceOfCustomType.onEvent.addListener &&
1607 typeof instanceOfCustomType.onEvent.addListener == "function",
1608 "Got the expected event defined in the CustomType instance"
1611 instanceOfCustomType.functionOnCustomType("param_value");
1614 "nested.namespace.instanceOfCustomType",
1615 "functionOnCustomType",
1619 let fakeListener = () => {};
1620 instanceOfCustomType.onEvent.addListener(fakeListener);
1623 "nested.namespace.instanceOfCustomType",
1627 instanceOfCustomType.onEvent.removeListener(fakeListener);
1630 "nested.namespace.instanceOfCustomType",
1635 // TODO: test support properties in a SubModuleType defined in the schema,
1636 // once implemented, e.g.:
1637 // ok("url" in instanceOfCustomType,
1638 // "Got the expected property defined in the CustomType instance");
1643 namespace: "from_the",
1647 namespace: "future",
1649 PROP1: { value: "original value" },
1650 PROP2: { value: "second original" },
1656 enum: ["red", "white", "blue"],
1663 parameters: [{ name: "arg", $ref: "Colour" }],
1668 namespace: "embrace",
1671 PROP2: { value: "overridden value" },
1677 enum: ["blue", "orange"],
1683 add_task(async function test_$import() {
1684 let wrapper = getContextWrapper();
1685 let url = "data:," + JSON.stringify($importJson);
1686 Schemas._rootSchema = null;
1687 await Schemas.load(url);
1690 Schemas.inject(root, wrapper);
1692 equal(root.from_the.PROP1, "original value", "imported property");
1693 equal(root.from_the.PROP2, "second original", "second imported property");
1694 equal(root.from_the.Colour.RED, "red", "imported enum type");
1695 equal(typeof root.from_the.dye, "function", "imported function");
1697 root.from_the.dye("white");
1698 wrapper.verify("call", "from_the", "dye", ["white"]);
1701 () => root.from_the.dye("orange"),
1702 /Invalid enumeration value/,
1703 "original imported argument type Colour doesn't include 'orange'"
1706 equal(root.embrace.PROP1, "original value", "imported property");
1707 equal(root.embrace.PROP2, "overridden value", "overridden property");
1708 equal(root.embrace.Colour.ORANGE, "orange", "overridden enum type");
1709 equal(typeof root.embrace.dye, "function", "imported function");
1711 root.embrace.dye("orange");
1712 wrapper.verify("call", "embrace", "dye", ["orange"]);
1715 () => root.embrace.dye("white"),
1716 /Invalid enumeration value/,
1717 "overridden argument type Colour doesn't include 'white'"
1721 add_task(async function testLocalAPIImplementation() {
1724 let countProp3SubFoo = 0;
1726 let testingApiObj = {
1728 // PROP1 is a schema-defined constant.
1729 throw new Error("Unexpected get PROP1");
1736 throw new Error("Unexpected get prop3");
1739 // prop3 is a submodule, defined as a function, so the API should not pass
1740 // through assignment to prop3.
1741 throw new Error("Unexpected set prop3");
1744 let submoduleApiObj = {
1748 return ++countProp3SubFoo;
1753 let localWrapper = {
1756 shouldInject(ns, name) {
1757 return name == "testing" || ns == "testing" || ns == "testing.prop3";
1759 getImplementation(ns, name) {
1760 Assert.ok(ns == "testing" || ns == "testing.prop3");
1761 if (ns == "testing.prop3" && name == "sub_foo") {
1762 // It is fine to use `null` here because we don't call async functions.
1763 return new LocalAPIImplementation(submoduleApiObj, name, null);
1765 // It is fine to use `null` here because we don't call async functions.
1766 return new LocalAPIImplementation(testingApiObj, name, null);
1771 Schemas.inject(root, localWrapper);
1772 Assert.equal(countGet2, 0);
1773 Assert.equal(countProp3, 0);
1774 Assert.equal(countProp3SubFoo, 0);
1776 Assert.equal(root.testing.PROP1, 20);
1778 Assert.equal(root.testing.prop2, "prop2 val");
1779 Assert.equal(countGet2, 1);
1781 Assert.equal(root.testing.prop2, "prop2 val");
1782 Assert.equal(countGet2, 2);
1784 info(JSON.stringify(root.testing));
1785 Assert.equal(root.testing.prop3.sub_foo(), 1);
1786 Assert.equal(countProp3, 1);
1787 Assert.equal(countProp3SubFoo, 1);
1789 Assert.equal(root.testing.prop3.sub_foo(), 2);
1790 Assert.equal(countProp3, 2);
1791 Assert.equal(countProp3SubFoo, 2);
1793 root.testing.prop3.sub_foo = () => {
1794 return "overwritten";
1796 Assert.equal(root.testing.prop3.sub_foo(), "overwritten");
1798 root.testing.prop3 = {
1800 return "overwritten again";
1803 Assert.equal(root.testing.prop3.sub_foo(), "overwritten again");
1804 Assert.equal(countProp3SubFoo, 2);
1807 let defaultsJson = [
1809 namespace: "defaultsJson",
1823 prop1: { type: "integer", optional: true },
1825 default: { prop1: 1 },
1830 additionalProperties: true,
1837 add_task(async function testDefaults() {
1838 let url = "data:," + JSON.stringify(defaultsJson);
1839 Schemas._rootSchema = null;
1840 await Schemas.load(url);
1842 let testingApiObj = {
1843 defaultFoo: function(arg) {
1844 if (Object.keys(arg) != "prop1") {
1846 `Received the expected default object, default: ${JSON.stringify(
1856 let localWrapper = {
1862 getImplementation(ns, name) {
1863 return new LocalAPIImplementation(testingApiObj, name, null);
1868 Schemas.inject(root, localWrapper);
1870 deepEqual(root.defaultsJson.defaultFoo(), { prop1: 1, newProp: 1 });
1871 deepEqual(root.defaultsJson.defaultFoo({ prop1: 2 }), {
1875 deepEqual(root.defaultsJson.defaultFoo(), { prop1: 1, newProp: 1 });
1880 namespace: "returns",
1886 size: { type: "integer" },
1887 colour: { type: "string", optional: true },
1895 returns: { $ref: "Widget" },
1901 returns: { $ref: "Widget" },
1907 returns: { $ref: "Widget" },
1914 add_task(async function testReturns() {
1915 const url = "data:," + JSON.stringify(returnsJson);
1916 Schemas._rootSchema = null;
1917 await Schemas.load(url);
1921 return { size: 3, colour: "orange" };
1931 const localWrapper = {
1937 getImplementation(ns, name) {
1938 return new LocalAPIImplementation(apiObject, name, null);
1943 Schemas.inject(root, localWrapper);
1945 deepEqual(root.returns.complete(), { size: 3, colour: "orange" });
1947 root.returns.optional(),
1949 "Missing optional properties is allowed"
1952 if (AppConstants.DEBUG) {
1954 () => root.returns.invalid(),
1955 /Type error for result value \(Property "size" is required\)/,
1956 "Should throw for invalid result in DEBUG builds"
1960 root.returns.invalid(),
1962 "Doesn't throw for invalid result value in release builds"
1967 let booleanEnumJson = [
1969 namespace: "booleanEnum",
1980 name: "paramMustBeTrue",
1982 parameters: [{ name: "arg", $ref: "enumTrue" }],
1988 add_task(async function testBooleanEnum() {
1989 let wrapper = getContextWrapper();
1991 let url = "data:," + JSON.stringify(booleanEnumJson);
1992 Schemas._rootSchema = null;
1993 await Schemas.load(url);
1996 Schemas.inject(root, wrapper);
1998 ok(root.booleanEnum, "namespace exists");
1999 root.booleanEnum.paramMustBeTrue(true);
2000 wrapper.verify("call", "booleanEnum", "paramMustBeTrue", [true]);
2002 () => root.booleanEnum.paramMustBeTrue(false),
2003 /Type error for parameter arg \(Invalid value false\) for booleanEnum\.paramMustBeTrue\./,
2004 "should throw because enum of the type restricts parameter to true"
2010 namespace: "xorigin",
2026 allowCrossOriginArguments: true,
2038 add_task(async function testCrossOriginArguments() {
2039 let url = "data:," + JSON.stringify(xoriginJson);
2040 Schemas._rootSchema = null;
2041 await Schemas.load(url);
2043 let sandbox = new Cu.Sandbox("http://test.com");
2045 let testingApiObj = {
2047 sandbox.result = JSON.stringify(arg);
2050 sandbox.xResult = JSON.stringify(arg);
2054 let localWrapper = {
2056 cloneScope: sandbox,
2060 getImplementation(ns, name) {
2061 return new LocalAPIImplementation(testingApiObj, name, null);
2066 Schemas.inject(root, localWrapper);
2069 () => root.xorigin.foo({ key: 13 }),
2070 /Permission denied to pass object/
2072 equal(sandbox.result, undefined, "Foo can't read cross origin object.");
2074 root.xorigin.crossFoo({ answer: 42 });
2075 equal(sandbox.xResult, '{"answer":42}', "Can read cross origin object.");