Bug 1440782 Part 1 - Move PolicyEngine's JSON schema validator to toolkit r=Felipe
authorMike Cooper <mcooper@mozilla.com>
Tue, 17 Apr 2018 16:36:05 -0700
changeset 468320 8350c61c629e186cf52be47afca865c0558b83f3
parent 468319 1382278840ed09f08a9ebf47576b190cd433dcbd
child 468321 c760daa73210e215fd864abc09483f706baa265b
push id9165
push userasasaki@mozilla.com
push dateThu, 26 Apr 2018 21:04:54 +0000
treeherdermozilla-beta@064c3804de2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersFelipe
bugs1440782
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1440782 Part 1 - Move PolicyEngine's JSON schema validator to toolkit r=Felipe MozReview-Commit-ID: 41K9xzry21w
browser/components/enterprisepolicies/EnterprisePolicies.js
browser/components/enterprisepolicies/PoliciesValidator.jsm
browser/components/enterprisepolicies/moz.build
browser/components/enterprisepolicies/tests/browser/browser.ini
browser/components/enterprisepolicies/tests/browser/browser_policies_validate_and_parse_API.js
toolkit/components/normandy/actions/BaseAction.jsm
toolkit/components/utils/JsonSchemaValidator.jsm
toolkit/components/utils/moz.build
toolkit/components/utils/test/browser/.eslintrc.js
toolkit/components/utils/test/browser/browser.ini
toolkit/components/utils/test/browser/browser_JsonSchemaValidator.js
--- a/browser/components/enterprisepolicies/EnterprisePolicies.js
+++ b/browser/components/enterprisepolicies/EnterprisePolicies.js
@@ -4,17 +4,17 @@
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   WindowsGPOParser: "resource:///modules/policies/WindowsGPOParser.jsm",
   Policies: "resource:///modules/policies/Policies.jsm",
-  PoliciesValidator: "resource:///modules/policies/PoliciesValidator.jsm",
+  JsonSchemaValidator: "resource://gre/modules/components-utils/JsonSchemaValidator.jsm",
 });
 
 // This is the file that will be searched for in the
 // ${InstallDir}/distribution folder.
 const POLICIES_FILENAME = "policies.json";
 
 // For easy testing, modify the helpers/sample.json file,
 // and set PREF_ALTERNATE_PATH in firefox.js as:
@@ -122,18 +122,17 @@ EnterprisePoliciesManager.prototype = {
       }
 
       if (policySchema.enterprise_only && !areEnterpriseOnlyPoliciesAllowed()) {
         log.error(`Policy ${policyName} is only allowed on ESR`);
         continue;
       }
 
       let [parametersAreValid, parsedParameters] =
-        PoliciesValidator.validateAndParseParameters(policyParameters,
-                                                     policySchema);
+        JsonSchemaValidator.validateAndParseParameters(policyParameters, policySchema);
 
       if (!parametersAreValid) {
         log.error(`Invalid parameters specified for ${policyName}.`);
         continue;
       }
 
       let policyImpl = Policies[policyName];
 
--- a/browser/components/enterprisepolicies/moz.build
+++ b/browser/components/enterprisepolicies/moz.build
@@ -19,17 +19,16 @@ TEST_DIRS += [
 EXTRA_COMPONENTS += [
     'EnterprisePolicies.js',
     'EnterprisePolicies.manifest',
     'EnterprisePoliciesContent.js',
 ]
 
 EXTRA_JS_MODULES.policies += [
     'Policies.jsm',
-    'PoliciesValidator.jsm',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
     EXTRA_JS_MODULES.policies += [
         'WindowsGPOParser.jsm',
 ]
 
 FINAL_LIBRARY = 'browsercomps'
--- a/browser/components/enterprisepolicies/tests/browser/browser.ini
+++ b/browser/components/enterprisepolicies/tests/browser/browser.ini
@@ -13,17 +13,16 @@ support-files =
 [browser_policies_broken_json.js]
 [browser_policies_enterprise_only.js]
 [browser_policies_notice_in_aboutpreferences.js]
 [browser_policies_popups_cookies_addons_flash.js]
 [browser_policies_runOnce_helper.js]
 [browser_policies_setAndLockPref_API.js]
 [browser_policies_simple_pref_policies.js]
 [browser_policies_sorted_alphabetically.js]
-[browser_policies_validate_and_parse_API.js]
 [browser_policy_app_update.js]
 [browser_policy_block_about_addons.js]
 [browser_policy_block_about_config.js]
 [browser_policy_block_about_profiles.js]
 [browser_policy_block_about_support.js]
 [browser_policy_block_set_desktop_background.js]
 [browser_policy_bookmarks.js]
 [browser_policy_clear_blocked_cookies.js]
--- a/toolkit/components/normandy/actions/BaseAction.jsm
+++ b/toolkit/components/normandy/actions/BaseAction.jsm
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 ChromeUtils.defineModuleGetter(this, "LogManager", "resource://normandy/lib/LogManager.jsm");
 ChromeUtils.defineModuleGetter(this, "Uptake", "resource://normandy/lib/Uptake.jsm");
-ChromeUtils.defineModuleGetter(this, "PoliciesValidator", "resource:///modules/policies/PoliciesValidator.jsm");
+ChromeUtils.defineModuleGetter(this, "JsonSchemaValidator", "resource://gre/modules/components-utils/JsonSchemaValidator.jsm");
 
 var EXPORTED_SYMBOLS = ["BaseAction"];
 
 /**
  * Base class for local actions.
  *
  * This should be subclassed. Subclasses must implement _run() for
  * per-recipe behavior, and may implement _preExecution and _finalize
@@ -67,17 +67,17 @@ class BaseAction {
     }
 
     if (this.failed) {
       Uptake.reportRecipe(recipe.id, Uptake.RECIPE_ACTION_DISABLED);
       this.log.warn(`Skipping recipe ${recipe.name} because ${this.name} failed during preExecution.`);
       return;
     }
 
-    let [valid, validatedArguments] = PoliciesValidator.validateAndParseParameters(recipe.arguments, this.schema);
+    let [valid, validatedArguments] = JsonSchemaValidator.validateAndParseParameters(recipe.arguments, this.schema);
     if (!valid) {
       Cu.reportError(new Error(`Arguments do not match schema. arguments: ${JSON.stringify(recipe.arguments)}. schema: ${JSON.stringify(this.schema)}`));
       Uptake.reportRecipe(recipe.id, Uptake.RECIPE_EXECUTION_ERROR);
       return;
     }
 
     recipe.arguments = validatedArguments;
 
rename from browser/components/enterprisepolicies/PoliciesValidator.jsm
rename to toolkit/components/utils/JsonSchemaValidator.jsm
--- a/browser/components/enterprisepolicies/PoliciesValidator.jsm
+++ b/toolkit/components/utils/JsonSchemaValidator.jsm
@@ -1,47 +1,72 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+/* This file implements a not-quite standard JSON schema validator. It differs
+ * from the spec in a few ways:
+ *
+ *  - the spec doesn't allow custom types to be defined, but this validator
+ *    defines "URL", "URLorEmpty", "origin" etc.
+ * - Strings are automatically converted to nsIURIs for the appropriate types.
+ * - It doesn't support "pattern" when matching strings.
+ * - The boolean type accepts (and casts) 0 and 1 as valid values.
+ */
+
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
-const PREF_LOGLEVEL           = "browser.policies.loglevel";
-
 XPCOMUtils.defineLazyGetter(this, "log", () => {
   let { ConsoleAPI } = ChromeUtils.import("resource://gre/modules/Console.jsm", {});
   return new ConsoleAPI({
-    prefix: "PoliciesValidator.jsm",
+    prefix: "JsonSchemaValidator.jsm",
     // tip: set maxLogLevel to "debug" and use log.debug() to create detailed
     // messages during development. See LOG_LEVELS in Console.jsm for details.
     maxLogLevel: "error",
-    maxLogLevelPref: PREF_LOGLEVEL,
   });
 });
 
-var EXPORTED_SYMBOLS = ["PoliciesValidator"];
+var EXPORTED_SYMBOLS = ["JsonSchemaValidator"];
 
-var PoliciesValidator = {
+var JsonSchemaValidator = {
   validateAndParseParameters(param, properties) {
     return validateAndParseParamRecursive(param, properties);
   }
 };
 
 function validateAndParseParamRecursive(param, properties) {
   if (properties.enum) {
     if (properties.enum.includes(param)) {
       return [true, param];
     }
     return [false, null];
   }
 
   log.debug(`checking @${param}@ for type ${properties.type}`);
+
+  if (Array.isArray(properties.type)) {
+    log.debug("type is an array");
+    // For an array of types, the value is valid if it matches any of the listed
+    // types. To check this, make versions of the object definition that include
+    // only one type at a time, and check the value against each one.
+    for (const type of properties.type) {
+      let typeProperties = Object.assign({}, properties, {type});
+      log.debug(`checking subtype ${type}`);
+      let [valid, data] = validateAndParseParamRecursive(param, typeProperties);
+      if (valid) {
+        return [true, data];
+      }
+    }
+    // None of the types matched
+    return [false, null];
+  }
+
   switch (properties.type) {
     case "boolean":
     case "number":
     case "integer":
     case "string":
     case "URL":
     case "URLorEmpty":
     case "origin":
--- a/toolkit/components/utils/moz.build
+++ b/toolkit/components/utils/moz.build
@@ -8,10 +8,13 @@ with Files('**'):
     BUG_COMPONENT = ('Toolkit', 'General')
 
 EXTRA_COMPONENTS += [
     'simpleServices.js',
     'utils.manifest',
 ]
 
 EXTRA_JS_MODULES['components-utils'] = [
-    'ClientEnvironment.jsm'
+    'ClientEnvironment.jsm',
+    'JsonSchemaValidator.jsm',
 ]
+
+BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
copy from toolkit/components/normandy/test/browser/.eslintrc.js
copy to toolkit/components/utils/test/browser/.eslintrc.js
new file mode 100644
--- /dev/null
+++ b/toolkit/components/utils/test/browser/browser.ini
@@ -0,0 +1,1 @@
+[browser_JsonSchemaValidator.js]
rename from browser/components/enterprisepolicies/tests/browser/browser_policies_validate_and_parse_API.js
rename to toolkit/components/utils/test/browser/browser_JsonSchemaValidator.js
--- a/browser/components/enterprisepolicies/tests/browser/browser_policies_validate_and_parse_API.js
+++ b/toolkit/components/utils/test/browser/browser_JsonSchemaValidator.js
@@ -1,226 +1,222 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-/* This file will test the parameters parsing and validation directly through
-   the PoliciesValidator API.
- */
-
-const { PoliciesValidator } = ChromeUtils.import("resource:///modules/policies/PoliciesValidator.jsm", {});
+ChromeUtils.import("resource://gre/modules/components-utils/JsonSchemaValidator.jsm", this);
 
 add_task(async function test_boolean_values() {
   let schema = {
     type: "boolean"
   };
 
   let valid, parsed;
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters(true, schema);
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters(true, schema);
   ok(valid && parsed === true, "Parsed boolean value correctly");
 
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters(false, schema);
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters(false, schema);
   ok(valid && parsed === false, "Parsed boolean value correctly");
 
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters(0, schema);
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters(0, schema);
   ok(valid && parsed === false, "0 parsed as false correctly");
 
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters(1, schema);
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters(1, schema);
   ok(valid && parsed === true, "1 parsed as true correctly");
 
   // Invalid values:
-  ok(!PoliciesValidator.validateAndParseParameters("0", schema)[0], "No type coercion");
-  ok(!PoliciesValidator.validateAndParseParameters("true", schema)[0], "No type coercion");
-  ok(!PoliciesValidator.validateAndParseParameters(2, schema)[0], "Other number values are not valid");
-  ok(!PoliciesValidator.validateAndParseParameters(undefined, schema)[0], "Invalid value");
-  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
-  ok(!PoliciesValidator.validateAndParseParameters(null, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters("0", schema)[0], "No type coercion");
+  ok(!JsonSchemaValidator.validateAndParseParameters("true", schema)[0], "No type coercion");
+  ok(!JsonSchemaValidator.validateAndParseParameters(2, schema)[0], "Other number values are not valid");
+  ok(!JsonSchemaValidator.validateAndParseParameters(undefined, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters(null, schema)[0], "Invalid value");
 });
 
 add_task(async function test_number_values() {
   let schema = {
     type: "number"
   };
 
   let valid, parsed;
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters(1, schema);
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters(1, schema);
   ok(valid && parsed === 1, "Parsed number value correctly");
 
   // Invalid values:
-  ok(!PoliciesValidator.validateAndParseParameters("1", schema)[0], "No type coercion");
-  ok(!PoliciesValidator.validateAndParseParameters(true, schema)[0], "Invalid value");
-  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
-  ok(!PoliciesValidator.validateAndParseParameters(null, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters("1", schema)[0], "No type coercion");
+  ok(!JsonSchemaValidator.validateAndParseParameters(true, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters(null, schema)[0], "Invalid value");
 });
 
 add_task(async function test_integer_values() {
   // Integer is an alias for number
   let schema = {
     type: "integer"
   };
 
   let valid, parsed;
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters(1, schema);
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters(1, schema);
   ok(valid && parsed == 1, "Parsed integer value correctly");
 
   // Invalid values:
-  ok(!PoliciesValidator.validateAndParseParameters("1", schema)[0], "No type coercion");
-  ok(!PoliciesValidator.validateAndParseParameters(true, schema)[0], "Invalid value");
-  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
-  ok(!PoliciesValidator.validateAndParseParameters(null, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters("1", schema)[0], "No type coercion");
+  ok(!JsonSchemaValidator.validateAndParseParameters(true, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters(null, schema)[0], "Invalid value");
 });
 
 add_task(async function test_string_values() {
   let schema = {
     type: "string"
   };
 
   let valid, parsed;
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters("foobar", schema);
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters("foobar", schema);
   ok(valid && parsed == "foobar", "Parsed string value correctly");
 
   // Invalid values:
-  ok(!PoliciesValidator.validateAndParseParameters(1, schema)[0], "No type coercion");
-  ok(!PoliciesValidator.validateAndParseParameters(true, schema)[0], "No type coercion");
-  ok(!PoliciesValidator.validateAndParseParameters(undefined, schema)[0], "Invalid value");
-  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
-  ok(!PoliciesValidator.validateAndParseParameters(null, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters(1, schema)[0], "No type coercion");
+  ok(!JsonSchemaValidator.validateAndParseParameters(true, schema)[0], "No type coercion");
+  ok(!JsonSchemaValidator.validateAndParseParameters(undefined, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters(null, schema)[0], "Invalid value");
 });
 
 add_task(async function test_URL_values() {
   let schema = {
     type: "URL"
   };
 
   let valid, parsed;
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters("https://www.example.com/foo#bar", schema);
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters("https://www.example.com/foo#bar", schema);
   ok(valid, "URL is valid");
   ok(parsed instanceof Ci.nsIURI, "parsed is a nsIURI");
   is(parsed.prePath, "https://www.example.com", "prePath is correct");
   is(parsed.pathQueryRef, "/foo#bar", "pathQueryRef is correct");
 
   // Invalid values:
-  ok(!PoliciesValidator.validateAndParseParameters("", schema)[0], "Empty string is not accepted for URL");
-  ok(!PoliciesValidator.validateAndParseParameters("www.example.com", schema)[0], "Scheme is required for URL");
-  ok(!PoliciesValidator.validateAndParseParameters("https://:!$%", schema)[0], "Invalid URL");
-  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters("", schema)[0], "Empty string is not accepted for URL");
+  ok(!JsonSchemaValidator.validateAndParseParameters("www.example.com", schema)[0], "Scheme is required for URL");
+  ok(!JsonSchemaValidator.validateAndParseParameters("https://:!$%", schema)[0], "Invalid URL");
+  ok(!JsonSchemaValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
 });
 
 add_task(async function test_URLorEmpty_values() {
   let schema = {
     type: "URLorEmpty"
   };
 
   let valid, parsed;
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters("https://www.example.com/foo#bar", schema);
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters("https://www.example.com/foo#bar", schema);
   ok(valid, "URL is valid");
   ok(parsed instanceof Ci.nsIURI, "parsed is a nsIURI");
   is(parsed.prePath, "https://www.example.com", "prePath is correct");
   is(parsed.pathQueryRef, "/foo#bar", "pathQueryRef is correct");
 
   // Test that this type also accept empty strings
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters("", schema);
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters("", schema);
   ok(valid, "URLorEmpty is valid");
   ok(!parsed, "parsed value is falsy");
   is(typeof(parsed), "string", "parsed is a string");
   is(parsed, "", "parsed is an empty string");
 
   // Invalid values:
-  ok(!PoliciesValidator.validateAndParseParameters(" ", schema)[0], "Non-empty string is not accepted");
-  ok(!PoliciesValidator.validateAndParseParameters("www.example.com", schema)[0], "Scheme is required for URL");
-  ok(!PoliciesValidator.validateAndParseParameters("https://:!$%", schema)[0], "Invalid URL");
-  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters(" ", schema)[0], "Non-empty string is not accepted");
+  ok(!JsonSchemaValidator.validateAndParseParameters("www.example.com", schema)[0], "Scheme is required for URL");
+  ok(!JsonSchemaValidator.validateAndParseParameters("https://:!$%", schema)[0], "Invalid URL");
+  ok(!JsonSchemaValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
 });
 
 
 add_task(async function test_origin_values() {
   // Origin is a URL that doesn't contain a path/query string (i.e., it's only scheme + host + port)
   let schema = {
     type: "origin"
   };
 
   let valid, parsed;
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters("https://www.example.com", schema);
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters("https://www.example.com", schema);
   ok(valid, "Origin is valid");
   ok(parsed instanceof Ci.nsIURI, "parsed is a nsIURI");
   is(parsed.prePath, "https://www.example.com", "prePath is correct");
   is(parsed.pathQueryRef, "/", "pathQueryRef is corect");
 
   // Invalid values:
-  ok(!PoliciesValidator.validateAndParseParameters("https://www.example.com/foobar", schema)[0], "Origin cannot contain a path part");
-  ok(!PoliciesValidator.validateAndParseParameters("https://:!$%", schema)[0], "Invalid origin");
-  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters("https://www.example.com/foobar", schema)[0], "Origin cannot contain a path part");
+  ok(!JsonSchemaValidator.validateAndParseParameters("https://:!$%", schema)[0], "Invalid origin");
+  ok(!JsonSchemaValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
 });
 
 add_task(async function test_array_values() {
   // The types inside an array object must all be the same
   let schema = {
     type: "array",
     items: {
       type: "number"
     }
   };
 
   let valid, parsed;
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters([1, 2, 3], schema);
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters([1, 2, 3], schema);
   ok(valid, "Array is valid");
   ok(Array.isArray(parsed), "parsed is an array");
   is(parsed.length, 3, "array is correct");
 
   // An empty array is also valid
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters([], schema);
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters([], schema);
   ok(valid, "Array is valid");
   ok(Array.isArray(parsed), "parsed is an array");
   is(parsed.length, 0, "array is correct");
 
   // Invalid values:
-  ok(!PoliciesValidator.validateAndParseParameters([1, true, 3], schema)[0], "Mixed types");
-  ok(!PoliciesValidator.validateAndParseParameters(2, schema)[0], "Type is correct but not in an array");
-  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Object is not an array");
+  ok(!JsonSchemaValidator.validateAndParseParameters([1, true, 3], schema)[0], "Mixed types");
+  ok(!JsonSchemaValidator.validateAndParseParameters(2, schema)[0], "Type is correct but not in an array");
+  ok(!JsonSchemaValidator.validateAndParseParameters({}, schema)[0], "Object is not an array");
 });
 
 add_task(async function test_object_values() {
   let schema = {
     type: "object",
     properties: {
       url: {
         type: "URL"
       },
       title: {
         type: "string"
       }
     }
   };
 
   let valid, parsed;
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters(
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters(
     {
       url: "https://www.example.com/foo#bar",
       title: "Foo",
       alias: "Bar"
     },
     schema);
 
   ok(valid, "Object is valid");
   ok(typeof(parsed) == "object", "parsed in an object");
   ok(parsed.url instanceof Ci.nsIURI, "types inside the object are also parsed");
   is(parsed.url.spec, "https://www.example.com/foo#bar", "URL was correctly parsed");
   is(parsed.title, "Foo", "title was correctly parsed");
   is(parsed.alias, undefined, "property not described in the schema is not present in the parsed object");
 
   // Invalid values:
-  ok(!PoliciesValidator.validateAndParseParameters(
+  ok(!JsonSchemaValidator.validateAndParseParameters(
     {
       url: "https://www.example.com/foo#bar",
       title: 3,
     },
     schema)[0], "Mismatched type for title");
 
-  ok(!PoliciesValidator.validateAndParseParameters(
+  ok(!JsonSchemaValidator.validateAndParseParameters(
     {
       url: "www.example.com",
       title: 3,
     },
     schema)[0], "Invalid URL inside the object");
 });
 
 add_task(async function test_array_of_objects() {
@@ -236,17 +232,17 @@ add_task(async function test_array_of_ob
         title: {
           type: "string"
         }
       }
     }
   };
 
   let valid, parsed;
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters(
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters(
     [{
       url: "https://www.example.com/bookmark1",
       title: "Foo",
     },
     {
       url: "https://www.example.com/bookmark2",
       title: "Bar",
     }],
@@ -280,17 +276,17 @@ add_task(async function test_missing_arr
           type: "boolean"
         }
       }
 
     }
   };
 
   let valid, parsed;
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters({
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters({
     allow: [true, true, true]
   }, schema);
 
   ok(valid, "Object is valid");
   is(parsed.allow.length, 3, "Allow array is correct.");
   is(parsed.block, undefined, "Block array is undefined, as expected.");
 });
 
@@ -305,23 +301,71 @@ add_task(async function test_required_vs
       "required-property": {
         type: "number"
       }
     },
     required: ["required-property"]
   };
 
   let valid, parsed;
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters({
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters({
     "required-property": 5
   }, schema);
 
   ok(valid, "Object is valid since required property is present");
   is(parsed["required-property"], 5, "required property is correct");
   is(parsed["non-required-property"], undefined, "non-required property is undefined, as expected");
 
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters({
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters({
     "non-required-property": 5
   }, schema);
 
   ok(!valid, "Object is not valid since the required property is missing");
   is(parsed, null, "Nothing was returned as parsed");
 });
+
+add_task(async function test_number_or_string_values() {
+  let schema = {
+    type: ["number", "string"],
+  };
+
+  let valid, parsed;
+  // valid values
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters(1, schema);
+  ok(valid && parsed === 1, "Parsed number value correctly");
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters("foobar", schema);
+  ok(valid && parsed === "foobar", "Parsed string value correctly");
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters("1", schema);
+  ok(valid && parsed === "1", "Did not coerce string to number");
+
+  // Invalid values:
+  ok(!JsonSchemaValidator.validateAndParseParameters(true, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters(null, schema)[0], "Invalid value");
+});
+
+add_task(async function test_number_or_array_values() {
+  let schema = {
+    type: ["number", "array"],
+    items: {
+      type: "number",
+    }
+  };
+
+  let valid, parsed;
+  // valid values
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters(1, schema);
+  ok(valid, "Number is valid");
+  is(parsed, 1, "Parsed correctly");
+  ok(valid && parsed === 1, "Parsed number value correctly");
+
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters([1, 2, 3], schema);
+  ok(valid, "Array is valid");
+  Assert.deepEqual(parsed, [1, 2, 3], "Parsed correctly");
+
+  // Invalid values:
+  ok(!JsonSchemaValidator.validateAndParseParameters(true, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters(null, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters(["a", "b"], schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters([[]], schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters([0, 1, [2, 3]], schema)[0], "Invalid value");
+});