--- a/toolkit/components/extensions/Schemas.jsm
+++ b/toolkit/components/extensions/Schemas.jsm
@@ -2214,20 +2214,23 @@ const LOADERS = {
types: "loadType",
};
class Namespace extends Map {
constructor(name, path) {
super();
this._lazySchemas = [];
+ this.initialized = false;
this.name = name;
this.path = name ? [...path, name] : [...path];
+ this.superNamespace = null;
+
this.permissions = null;
this.allowedContexts = [];
this.defaultContexts = [];
}
/**
* Adds a JSON Schema object to the set of schemas that represent this
* namespace.
@@ -2239,27 +2242,35 @@ class Namespace extends Map {
addSchema(schema) {
this._lazySchemas.push(schema);
for (let prop of ["permissions", "allowedContexts", "defaultContexts"]) {
if (schema[prop]) {
this[prop] = schema[prop];
}
}
+
+ if (schema.$import) {
+ this.superNamespace = Schemas.getNamespace(schema.$import);
+ }
}
/**
* Initializes the keys of this namespace based on the schema objects
* added via previous `addSchema` calls.
*/
- init() { // eslint-disable-line complexity
- if (!this._lazySchemas) {
+ init() {
+ if (this.initialized) {
return;
}
+ if (this.superNamespace) {
+ this._lazySchemas.unshift(...this.superNamespace._lazySchemas);
+ }
+
for (let type of Object.keys(LOADERS)) {
this[type] = new DefaultMap(() => []);
}
for (let schema of this._lazySchemas) {
for (let type of schema.types || []) {
if (!type.unsupported) {
this.types.get(type.$extend || type.id).push(type);
@@ -2291,17 +2302,17 @@ class Namespace extends Map {
// are later used to instantiate an Entry object based on the actual
// schema object.
for (let type of Object.keys(LOADERS)) {
for (let key of this[type].keys()) {
this.set(key, type);
}
}
- this._lazySchemas = null;
+ this.initialized = true;
if (DEBUG) {
for (let key of this.keys()) {
this.get(key);
}
}
}
--- a/toolkit/components/extensions/test/xpcshell/test_ext_schemas.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_schemas.js
@@ -1294,16 +1294,94 @@ add_task(async function testNestedNamesp
// "Got the expected property defined in the CustomType instance)
//
// ok(instanceOfCustomType.onEvent &&
// instanceOfCustomType.onEvent.addListener &&
// typeof instanceOfCustomType.onEvent.addListener == "function",
// "Got the expected event defined in the CustomType instance");
});
+let $importJson = [
+ {
+ namespace: "from_the",
+ $import: "future",
+ },
+ {
+ namespace: "future",
+ properties: {
+ PROP1: {value: "original value"},
+ PROP2: {value: "second original"},
+ },
+ types: [
+ {
+ id: "Colour",
+ type: "string",
+ enum: ["red", "white", "blue"],
+ },
+ ],
+ functions: [
+ {
+ name: "dye",
+ type: "function",
+ parameters: [
+ {name: "arg", $ref: "Colour"},
+ ],
+ },
+ ],
+ },
+ {
+ namespace: "embrace",
+ $import: "future",
+ properties: {
+ PROP2: {value: "overridden value"},
+ },
+ types: [
+ {
+ id: "Colour",
+ type: "string",
+ enum: ["blue", "orange"],
+ },
+ ],
+ },
+];
+
+add_task(async function test_$import() {
+ let url = "data:," + JSON.stringify($importJson);
+ await Schemas.load(url);
+
+ let root = {};
+ tallied = null;
+ Schemas.inject(root, wrapper);
+ equal(tallied, null);
+
+ equal(root.from_the.PROP1, "original value", "imported property");
+ equal(root.from_the.PROP2, "second original", "second imported property");
+ equal(root.from_the.Colour.RED, "red", "imported enum type");
+ equal(typeof root.from_the.dye, "function", "imported function");
+
+ root.from_the.dye("white");
+ verify("call", "from_the", "dye", ["white"]);
+
+ Assert.throws(() => root.from_the.dye("orange"),
+ /Invalid enumeration value/,
+ "original imported argument type Colour doesn't include 'orange'");
+
+ equal(root.embrace.PROP1, "original value", "imported property");
+ equal(root.embrace.PROP2, "overridden value", "overridden property");
+ equal(root.embrace.Colour.ORANGE, "orange", "overridden enum type");
+ equal(typeof root.embrace.dye, "function", "imported function");
+
+ root.embrace.dye("orange");
+ verify("call", "embrace", "dye", ["orange"]);
+
+ Assert.throws(() => root.embrace.dye("white"),
+ /Invalid enumeration value/,
+ "overridden argument type Colour doesn't include 'white'");
+});
+
add_task(async function testLocalAPIImplementation() {
let countGet2 = 0;
let countProp3 = 0;
let countProp3SubFoo = 0;
let testingApiObj = {
get PROP1() {
// PROP1 is a schema-defined constant.