Bug 1251766 - Add new Date type to webextensions schemas. r=kmag
authorAndrew Swan <aswan@mozilla.com>
Fri, 04 Mar 2016 13:08:19 -0800
changeset 323202 d298fc97819e99e21d242dd6ff1c143771985493
parent 323201 0c220ff8ce852e1c9a53cdbfb9989e8e4b7bbe69
child 323203 3644fa6e882a7bd01fdd63b353435f8a7632ddc2
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskmag
bugs1251766
milestone47.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 1251766 - Add new Date type to webextensions schemas. r=kmag MozReview-Commit-ID: EEX5FziiINo
toolkit/components/extensions/Schemas.jsm
toolkit/components/extensions/schemas/extension_types.json
toolkit/components/extensions/test/xpcshell/test_ext_schemas.js
--- a/toolkit/components/extensions/Schemas.jsm
+++ b/toolkit/components/extensions/Schemas.jsm
@@ -244,16 +244,31 @@ const FORMATS = {
         new URL(string);
       } catch (e) {
         return FORMATS.relativeUrl(string, context);
       }
     }
 
     throw new SyntaxError(`String ${JSON.stringify(string)} must be a relative URL`);
   },
+
+  date(string, context) {
+    // A valid ISO 8601 timestamp.
+    const PATTERN = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{3})?(Z|([-+]\d{2}:?\d{2})))?$/;
+    if (!PATTERN.test(string)) {
+      throw new Error(`Invalid date string ${string}`);
+    }
+    // Our pattern just checks the format, we could still have invalid
+    // values (e.g., month=99 or month=02 and day=31).  Let the Date
+    // constructor do the dirty work of validating.
+    if (isNaN(new Date(string))) {
+      throw new Error(`Invalid date string ${string}`);
+    }
+    return string;
+  },
 };
 
 // Schema files contain namespaces, and each namespace contains types,
 // properties, functions, and events. An Entry is a base class for
 // types, properties, functions, and events.
 class Entry {
   constructor(schema = {}) {
     /**
--- a/toolkit/components/extensions/schemas/extension_types.json
+++ b/toolkit/components/extensions/schemas/extension_types.json
@@ -54,12 +54,30 @@
             "description": "The ID of the frame to inject the script into. This may not be used in combination with <code>allFrames</code>."
           },
           "runAt": {
             "$ref": "RunAt",
             "optional": true,
             "description": "The soonest that the JavaScript or CSS will be injected into the tab. Defaults to \"document_idle\"."
           }
         }
+      },
+      {
+        "id": "Date",
+        "choices": [
+          {
+            "type": "string",
+            "format": "date"
+          },
+          {
+            "type": "integer",
+            "minimum": 0
+          },
+          {
+            "type": "object",
+            "isInstanceOf": "Date",
+            "additionalProperties": { "type": "any" }
+          }
+        ]
       }
     ]
   }
 ]
--- a/toolkit/components/extensions/test/xpcshell/test_ext_schemas.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_schemas.js
@@ -211,16 +211,30 @@ let json = [
              relativeUrl: {type: "string", "format": "relativeUrl", "optional": true},
              strictRelativeUrl: {type: "string", "format": "strictRelativeUrl", "optional": true},
            },
          },
        ],
      },
 
      {
+       name: "formatDate",
+       type: "function",
+       parameters: [
+         {
+           name: "arg",
+           type: "object",
+           properties: {
+             date: {type: "string", format: "date", optional: true},
+           },
+         },
+       ],
+     },
+
+     {
        name: "deep",
        type: "function",
        parameters: [
          {
            name: "arg",
            type: "object",
            properties: {
              foo: {
@@ -564,16 +578,53 @@ add_task(function* () {
   }
 
   for (let url of ["//foo.html", "http://foo/bar.html"]) {
     Assert.throws(() => root.testing.format({strictRelativeUrl: url}),
                   /must be a relative URL/,
                   "should throw for non-relative URL");
   }
 
+  const dates = [
+    "2016-03-04",
+    "2016-03-04T08:00:00Z",
+    "2016-03-04T08:00:00.000Z",
+    "2016-03-04T08:00:00-08:00",
+    "2016-03-04T08:00:00.000-08:00",
+    "2016-03-04T08:00:00+08:00",
+    "2016-03-04T08:00:00.000+08:00",
+    "2016-03-04T08:00:00+0800",
+    "2016-03-04T08:00:00-0800",
+  ];
+  dates.forEach(str => {
+    root.testing.formatDate({date: str});
+    verify("call", "testing", "formatDate", [{date: str}]);
+  });
+
+  // Make sure that a trivial change to a valid date invalidates it.
+  dates.forEach(str => {
+    Assert.throws(() => root.testing.formatDate({date: "0" + str}),
+                  /Invalid date string/,
+                  "should throw for invalid iso date string");
+    Assert.throws(() => root.testing.formatDate({date: str + "0"}),
+                  /Invalid date string/,
+                  "should throw for invalid iso date string");
+  });
+
+  const badDates = [
+    "I do not look anything like a date string",
+    "2016-99-99",
+    "2016-03-04T25:00:00Z",
+  ];
+  badDates.forEach(str => {
+    Assert.throws(() => root.testing.formatDate({date: str}),
+                  /Invalid date string/,
+                  "should throw for invalid iso date string");
+  });
+
   root.testing.deep({foo: {bar: [{baz: {required: 12, optional: "42"}}]}});
   verify("call", "testing", "deep", [{foo: {bar: [{baz: {required: 12, optional: "42"}}]}}]);
   tallied = null;
 
   Assert.throws(() => root.testing.deep({foo: {bar: [{baz: {optional: "42"}}]}}),
                 /Type error for parameter arg \(Error processing foo\.bar\.0\.baz: Property "required" is required\) for testing\.deep/,
                 "should throw with the correct object path");