--- a/toolkit/components/extensions/Schemas.jsm
+++ b/toolkit/components/extensions/Schemas.jsm
@@ -4,16 +4,18 @@
"use strict";
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
const Cr = Components.results;
+const global = this;
+
Cu.importGlobalProperties(["URL"]);
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
var {
@@ -1284,19 +1286,20 @@ class FunctionType extends Type {
if ("parameters" in schema) {
parameters = [];
for (let param of schema.parameters) {
// Callbacks default to optional for now, because of promise
// handling.
let isCallback = isAsync && param.name == schema.async;
parameters.push({
- type: Schemas.parseSchema(param, path, ["name", "optional"]),
+ type: Schemas.parseSchema(param, path, ["name", "optional", "default"]),
name: param.name,
optional: param.optional == null ? isCallback : param.optional,
+ default: param.default == undefined ? null : param.default,
});
}
}
let hasAsyncCallback = false;
if (isAsync) {
if (parameters && parameters.length && parameters[parameters.length - 1].name == schema.async) {
hasAsyncCallback = true;
@@ -1454,43 +1457,44 @@ class CallEntry extends Entry {
throwError(context, msg) {
throw context.makeError(`${msg} for ${this.path.join(".")}.${this.name}.`);
}
checkParameters(args, context) {
let fixedArgs = [];
// First we create a new array, fixedArgs, that is the same as
- // |args| but with null values in place of omitted optional
- // parameters.
+ // |args| but with default values in place of omitted optional parameters.
let check = (parameterIndex, argIndex) => {
if (parameterIndex == this.parameters.length) {
if (argIndex == args.length) {
return true;
}
return false;
}
let parameter = this.parameters[parameterIndex];
if (parameter.optional) {
// Try skipping it.
- fixedArgs[parameterIndex] = null;
+ fixedArgs[parameterIndex] = parameter.default;
if (check(parameterIndex + 1, argIndex)) {
return true;
}
}
if (argIndex == args.length) {
return false;
}
let arg = args[argIndex];
if (!parameter.type.checkBaseType(getValueBaseType(arg))) {
+ // For Chrome compatibility, use the default value if null or undefined
+ // is explicitly passed but is not a valid argument in this position.
if (parameter.optional && (arg === null || arg === undefined)) {
- fixedArgs[parameterIndex] = null;
+ fixedArgs[parameterIndex] = Cu.cloneInto(parameter.default, global);
} else {
return false;
}
} else {
fixedArgs[parameterIndex] = arg;
}
return check(parameterIndex + 1, argIndex + 1);
@@ -1750,19 +1754,20 @@ this.Schemas = {
let f = this.parseFunction([namespaceName], fun);
this.register(namespaceName, fun.name, f);
},
loadEvent(namespaceName, event) {
let extras = event.extraParameters || [];
extras = extras.map(param => {
return {
- type: this.parseSchema(param, [namespaceName], ["name", "optional"]),
+ type: this.parseSchema(param, [namespaceName], ["name", "optional", "default"]),
name: param.name,
optional: param.optional || false,
+ default: param.default == undefined ? null : param.default,
};
});
// We ignore these properties for now.
/* eslint-disable no-unused-vars */
let returns = event.returns;
let filters = event.filters;
/* eslint-enable no-unused-vars */
--- a/toolkit/components/extensions/test/xpcshell/test_ext_schemas.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_schemas.js
@@ -80,17 +80,17 @@ let json = [
},
],
functions: [
{
name: "foo",
type: "function",
parameters: [
- {name: "arg1", type: "integer", optional: true},
+ {name: "arg1", type: "integer", optional: true, default: 99},
{name: "arg2", type: "boolean", optional: true},
],
},
{
name: "bar",
type: "function",
parameters: [
@@ -336,16 +336,18 @@ let json = [
},
{
name: "onBar",
type: "function",
extraParameters: [{
name: "filter",
type: "integer",
+ optional: true,
+ default: 1,
}],
},
],
},
{
namespace: "foreign",
properties: {
foreignRef: {$ref: "testing.submodule"},
@@ -471,23 +473,23 @@ add_task(function* () {
do_check_eq("inject" in root, true, "namespace 'inject' should be injected");
do_check_eq("do-not-inject" in root, false, "namespace 'do-not-inject' should not be injected");
root.testing.foo(11, true);
verify("call", "testing", "foo", [11, true]);
root.testing.foo(true);
- verify("call", "testing", "foo", [null, true]);
+ verify("call", "testing", "foo", [99, true]);
root.testing.foo(null, true);
- verify("call", "testing", "foo", [null, true]);
+ verify("call", "testing", "foo", [99, true]);
root.testing.foo(undefined, true);
- verify("call", "testing", "foo", [null, true]);
+ verify("call", "testing", "foo", [99, true]);
root.testing.foo(11);
verify("call", "testing", "foo", [11, null]);
Assert.throws(() => root.testing.bar(11),
/Incorrect argument types/,
"should throw without required arg");
@@ -729,16 +731,22 @@ add_task(function* () {
"addListener with non-function should throw");
root.testing.onBar.addListener(f, 10);
do_check_eq(JSON.stringify(tallied.slice(0, -1)), JSON.stringify(["addListener", "testing", "onBar"]));
do_check_eq(tallied[3][0], f);
do_check_eq(JSON.stringify(tallied[3][1]), JSON.stringify([10]));
tallied = null;
+ root.testing.onBar.addListener(f);
+ do_check_eq(JSON.stringify(tallied.slice(0, -1)), JSON.stringify(["addListener", "testing", "onBar"]));
+ do_check_eq(tallied[3][0], f);
+ do_check_eq(JSON.stringify(tallied[3][1]), JSON.stringify([1]));
+ tallied = null;
+
Assert.throws(() => root.testing.onBar.addListener(f, "hi"),
/Incorrect argument types/,
"addListener with wrong extra parameter should throw");
let target = {prop1: 12, prop2: ["value1", "value3"]};
let proxy = new Proxy(target, {});
Assert.throws(() => root.testing.quack(proxy),
/Expected a plain JavaScript object, got a Proxy/,
@@ -1359,8 +1367,61 @@ add_task(function* testLocalAPIImplement
root.testing.prop3.sub_foo = () => { return "overwritten"; };
do_check_eq(root.testing.prop3.sub_foo(), "overwritten");
root.testing.prop3 = {sub_foo() { return "overwritten again"; }};
do_check_eq(root.testing.prop3.sub_foo(), "overwritten again");
do_check_eq(countProp3SubFoo, 2);
});
+
+
+let defaultsJson = [
+ {namespace: "defaultsJson",
+
+ types: [],
+
+ functions: [
+ {
+ name: "defaultFoo",
+ type: "function",
+ parameters: [
+ {name: "arg", type: "object", optional: true, properties: {
+ prop1: {type: "integer", optional: true},
+ }, default: {prop1: 1}},
+ ],
+ returns: {
+ type: "object",
+ },
+ },
+ ]},
+];
+
+add_task(function* testDefaults() {
+ let url = "data:," + JSON.stringify(defaultsJson);
+ yield Schemas.load(url);
+
+ let testingApiObj = {
+ defaultFoo: function(arg) {
+ if (Object.keys(arg) != "prop1") {
+ throw new Error(`Received the expected default object, default: ${JSON.stringify(arg)}`);
+ }
+ arg.newProp = 1;
+ return arg;
+ },
+ };
+
+ let localWrapper = {
+ shouldInject(ns) {
+ return true;
+ },
+ getImplementation(ns, name) {
+ return new LocalAPIImplementation(testingApiObj, name, null);
+ },
+ };
+
+ let root = {};
+ Schemas.inject(root, localWrapper);
+
+ deepEqual(root.defaultsJson.defaultFoo(), {prop1: 1, newProp: 1});
+ deepEqual(root.defaultsJson.defaultFoo({prop1: 2}), {prop1: 2, newProp: 1});
+ deepEqual(root.defaultsJson.defaultFoo(), {prop1: 1, newProp: 1});
+});