Bug 769370 - Add valueAsDate attribute and implement valueAsNumber/valueAsDate for date. r=mounir
authorRaphael Catolino <rcatolino@mozilla.com>
Thu, 27 Dec 2012 18:55:31 +0000
changeset 126217 78970aaa8008f602dcbb622b8c573fdc5d1036d1
parent 126216 d44a92b2cbf774a0fdc4b0ce33add7a7dd608a59
child 126218 739b8e307069d3f8f1a23e3cab2887c4e98507ac
push id2151
push userlsblakk@mozilla.com
push dateTue, 19 Feb 2013 18:06:57 +0000
treeherdermozilla-beta@4952e88741ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmounir
bugs769370
milestone20.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 769370 - Add valueAsDate attribute and implement valueAsNumber/valueAsDate for date. r=mounir
content/html/content/src/nsHTMLInputElement.cpp
content/html/content/src/nsHTMLInputElement.h
content/html/content/test/forms/Makefile.in
content/html/content/test/forms/test_input_attributes_reflection.html
content/html/content/test/forms/test_valueasdate_attribute.html
content/html/content/test/forms/test_valueasnumber_attribute.html
dom/interfaces/html/nsIDOMHTMLInputElement.idl
--- a/content/html/content/src/nsHTMLInputElement.cpp
+++ b/content/html/content/src/nsHTMLInputElement.cpp
@@ -88,16 +88,19 @@
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/Util.h" // DebugOnly
 #include "mozilla/Preferences.h"
 
 #include "nsIIDNService.h"
 
 #include <limits>
 
+// input type=date
+#include "jsapi.h"
+
 using namespace mozilla;
 using namespace mozilla::dom;
 
 // XXX align=left, hspace, vspace, border? other nav4 attrs
 
 static NS_DEFINE_CID(kXULControllersCID,  NS_XULCONTROLLERS_CID);
 
 // First bits are needed for the control type.
@@ -1052,27 +1055,82 @@ bool
 nsHTMLInputElement::IsValueEmpty() const
 {
   nsAutoString value;
   GetValueInternal(value);
 
   return value.IsEmpty();
 }
 
+bool
+nsHTMLInputElement::ConvertStringToNumber(nsAString& aValue,
+                                          double& aResultValue) const
+{
+  switch (mType) {
+    case NS_FORM_INPUT_NUMBER:
+      {
+        nsresult ec;
+        aResultValue = PromiseFlatString(aValue).ToDouble(&ec);
+        if (NS_FAILED(ec)) {
+          return false;
+        }
+
+        break;
+      }
+    case NS_FORM_INPUT_DATE:
+      {
+        JSContext* ctx = nsContentUtils::GetContextFromDocument(OwnerDoc());
+        if (!ctx) {
+          return false;
+        }
+
+        uint32_t year, month, day;
+        if (!GetValueAsDate(aValue, year, month, day)) {
+          return false;
+        }
+
+        JSObject* date = JS_NewDateObjectMsec(ctx, 0);
+        jsval rval;
+        jsval fullYear[3];
+        fullYear[0].setInt32(year);
+        fullYear[1].setInt32(month-1);
+        fullYear[2].setInt32(day);
+        if (!JS::Call(ctx, date, "setUTCFullYear", 3, fullYear, &rval)) {
+          return false;
+        }
+
+        jsval timestamp;
+        if (!JS::Call(ctx, date, "getTime", 0, nullptr, &timestamp)) {
+          return false;
+        }
+
+        if (!timestamp.isNumber()) {
+          return false;
+        }
+
+        aResultValue = timestamp.toNumber();
+      }
+      break;
+    default:
+      return false;
+  }
+
+  return true;
+}
+
 double
 nsHTMLInputElement::GetValueAsDouble() const
 {
   double doubleValue;
   nsAutoString stringValue;
-  nsresult ec;
 
   GetValueInternal(stringValue);
-  doubleValue = stringValue.ToDouble(&ec);
-
-  return NS_SUCCEEDED(ec) ? doubleValue : MOZ_DOUBLE_NaN();
+
+  return !ConvertStringToNumber(stringValue, doubleValue) ? MOZ_DOUBLE_NaN()
+                                                          : doubleValue;
 }
 
 NS_IMETHODIMP 
 nsHTMLInputElement::SetValue(const nsAString& aValue)
 {
   // check security.  Note that setting the value to the empty string is always
   // OK and gives pages a way to clear a file input if necessary.
   if (mType == NS_FORM_INPUT_FILE) {
@@ -1138,21 +1196,111 @@ nsHTMLInputElement::GetList(nsIDOMHTMLEl
   CallQueryInterface(element, aValue);
   return NS_OK;
 }
 
 void
 nsHTMLInputElement::SetValue(double aValue)
 {
   nsAutoString value;
-  value.AppendFloat(aValue);
+  switch (mType) {
+    case NS_FORM_INPUT_NUMBER:
+      value.AppendFloat(aValue);
+      break;
+    case NS_FORM_INPUT_DATE:
+    {
+      value.Truncate();
+      JSContext* ctx = nsContentUtils::GetContextFromDocument(OwnerDoc());
+      if (!ctx) {
+        break;
+      }
+
+      JSObject* date = JS_NewDateObjectMsec(ctx, aValue);
+      if (!date) {
+        break;
+      }
+
+      jsval year, month, day;
+      if(!JS::Call(ctx, date, "getUTCFullYear", 0, nullptr, &year)) {
+        break;
+      }
+
+      if(!JS::Call(ctx, date, "getUTCMonth", 0, nullptr, &month)) {
+        break;
+      }
+
+      if(!JS::Call(ctx, date, "getUTCDate", 0, nullptr, &day)) {
+        break;
+      }
+
+      value.AppendPrintf("%04.0f-%02.0f-%02.0f", year.toNumber(),
+                         month.toNumber() + 1, day.toNumber());
+    }
+    break;
+  }
+
   SetValue(value);
 }
 
 NS_IMETHODIMP
+nsHTMLInputElement::GetValueAsDate(JSContext* aCtx, jsval* aDate)
+{
+  if (mType != NS_FORM_INPUT_DATE) {
+    aDate->setNull();
+    return NS_OK;
+  }
+
+  uint32_t year, month, day;
+  nsAutoString value;
+  GetValueInternal(value);
+  if (!GetValueAsDate(value, year, month, day)) {
+    aDate->setNull();
+    return NS_OK;
+  }
+
+  JSObject* date = JS_NewDateObjectMsec(aCtx, 0);
+  jsval rval;
+  jsval fullYear[3];
+  fullYear[0].setInt32(year);
+  fullYear[1].setInt32(month-1);
+  fullYear[2].setInt32(day);
+  if(!JS::Call(aCtx, date, "setUTCFullYear", 3, fullYear, &rval)) {
+    aDate->setNull();
+    return NS_OK;
+  }
+
+  aDate->setObjectOrNull(date);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLInputElement::SetValueAsDate(JSContext* aCtx, const jsval& aDate)
+{
+  if (mType != NS_FORM_INPUT_DATE) {
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
+  if (!aDate.isObject() || !JS_ObjectIsDate(aCtx, &aDate.toObject())) {
+    SetValue(EmptyString());
+    return NS_OK;
+  }
+
+  JSObject& date = aDate.toObject();
+  jsval timestamp;
+  bool ret = JS::Call(aCtx, &date, "getTime", 0, nullptr, &timestamp);
+  if (!ret || !timestamp.isNumber() || MOZ_DOUBLE_IS_NaN(timestamp.toNumber())) {
+    SetValue(EmptyString());
+    return NS_OK;
+  }
+
+  SetValue(timestamp.toNumber());
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsHTMLInputElement::GetValueAsNumber(double* aValueAsNumber)
 {
   *aValueAsNumber = DoesValueAsNumberApply() ? GetValueAsDouble()
                                              : MOZ_DOUBLE_NaN();
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -2765,31 +2913,39 @@ nsHTMLInputElement::SanitizeValue(nsAStr
       }
       break;
   }
 }
 
 bool
 nsHTMLInputElement::IsValidDate(nsAString& aValue) const
 {
+  uint32_t year, month, day;
+  return GetValueAsDate(aValue, year, month, day);
+}
+
+bool
+nsHTMLInputElement::GetValueAsDate(nsAString& aValue,
+                                   uint32_t& aYear,
+                                   uint32_t& aMonth,
+                                   uint32_t& aDay) const
+{
+
 /*
  * Parse the year, month, day values out a date string formatted as 'yyy-mm-dd'.
  * -The year must be 4 or more digits long, and year > 0
  * -The month must be exactly 2 digits long, and 01 <= month <= 12
  * -The day must be exactly 2 digit long, and 01 <= day <= maxday
  *  Where maxday is the number of days in the month 'month' and year 'year'
  */
 
   if (aValue.IsEmpty()) {
     return false;
   }
 
-  uint32_t year = 0;
-  uint32_t month = 0;
-  uint32_t day = 0;
   int32_t fieldMaxSize = 0;
   int32_t fieldMinSize = 4;
   enum {
     YEAR, MONTH, DAY, NONE
   } field;
   int32_t fieldSize = 0;
   nsresult ec;
 
@@ -2813,51 +2969,51 @@ nsHTMLInputElement::IsValidDate(nsAStrin
 
     // Parse the field.
     if (fieldSize < fieldMinSize) {
       return false;
     }
 
     switch(field) {
       case YEAR:
-        year = PromiseFlatString(StringHead(aValue, offset)).ToInteger(&ec);
+        aYear = PromiseFlatString(StringHead(aValue, offset)).ToInteger(&ec);
         NS_ENSURE_SUCCESS(ec, false);
 
-        if (year <= 0) {
+        if (aYear <= 0) {
           return false;
         }
 
         // The field after year is month, which have a fixed size of 2 char.
         field = MONTH;
         fieldMaxSize = 2;
         fieldMinSize = 2;
         break;
       case MONTH:
-        month = PromiseFlatString(Substring(aValue,
+        aMonth = PromiseFlatString(Substring(aValue,
                                             offset-fieldSize,
                                             offset)).ToInteger(&ec);
         NS_ENSURE_SUCCESS(ec, false);
 
-        if (month < 1 || month > 12) {
+        if (aMonth < 1 || aMonth > 12) {
           return false;
         }
 
         // The next field is the last one, we won't parse a '-',
         // so the field size will be one char smaller.
         field = DAY;
         fieldMinSize = 1;
         fieldMaxSize = 1;
         break;
       case DAY:
-        day = PromiseFlatString(Substring(aValue,
+        aDay = PromiseFlatString(Substring(aValue,
                                           offset-fieldSize,
                                           offset + 1)).ToInteger(&ec);
         NS_ENSURE_SUCCESS(ec, false);
 
-        if (day <  1 || day > NumberOfDaysInMonth(month, year)) {
+        if (aDay <  1 || aDay > NumberOfDaysInMonth(aMonth, aYear)) {
           return false;
         }
 
         field = NONE;
         break;
       default:
         return false;
     }
--- a/content/html/content/src/nsHTMLInputElement.h
+++ b/content/html/content/src/nsHTMLInputElement.h
@@ -556,24 +556,46 @@ protected:
    * Returns the input element's value as a double-precision float.
    * Returns NaN if the current element's value is not a floating point number.
    *
    * @return the input element's value as a double-precision float.
    */
   double GetValueAsDouble() const;
 
   /**
+   * Convert a string to a number in a type specific way,
+   * http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#concept-input-value-string-number
+   * ie parse a date string to a timestamp if type=date,
+   * or parse a number string to its value if type=number.
+   * @param aValue the string to be parsed.
+   * @param aResultValue the timestamp as a double.
+   * @result whether the parsing was successful.
+   */
+  bool ConvertStringToNumber(nsAString& aValue, double& aResultValue) const;
+
+  /**
    * Parse a date string of the form yyyy-mm-dd
    * @param the string to be parsed.
    * @return whether the string is a valid date.
    * Note : this function does not consider the empty string as valid.
    */
   bool IsValidDate(nsAString& aValue) const;
 
   /**
+   * Parse a date string of the form yyyy-mm-dd
+   * @param the string to be parsed.
+   * @return the date in aYear, aMonth, aDay.
+   * @return whether the parsing was successful.
+   */
+  bool GetValueAsDate(nsAString& aValue,
+                      uint32_t& aYear,
+                      uint32_t& aMonth,
+                      uint32_t& aDay) const;
+
+  /**
    * This methods returns the number of days in a given month, for a given year.
    */
   uint32_t NumberOfDaysInMonth(uint32_t aMonth, uint32_t aYear) const;
 
   /**
    * Sets the value of the element to the string representation of the double.
    *
    * @param aValue The double that will be used to set the value.
--- a/content/html/content/test/forms/Makefile.in
+++ b/content/html/content/test/forms/Makefile.in
@@ -45,12 +45,13 @@ MOCHITEST_FILES = \
 		test_max_attribute.html \
 		test_min_attribute.html \
 		test_step_attribute.html \
 		test_stepup_stepdown.html \
 		test_valueasnumber_attribute.html \
 		test_experimental_forms_pref.html \
 		test_input_number_value.html \
 		test_input_sanitization.html \
+		test_valueasdate_attribute.html \
 		$(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
--- a/content/html/content/test/forms/test_input_attributes_reflection.html
+++ b/content/html/content/test/forms/test_input_attributes_reflection.html
@@ -213,18 +213,18 @@ reflectString({
   element: document.createElement("input"),
   attribute: { idl: "defaultValue", content: "value" },
   otherValues: [ "foo\nbar", "foo\rbar", "foo\r\nbar" ],
 });
 
 // .value doesn't reflect a content attribute.
 
 // .valueAsDate
-todo("valueAsDate" in document.createElement("input"),
-     "valueAsDate isn't implemented yet");
+is("valueAsDate" in document.createElement("input"), true,
+   "valueAsDate should be available");
 
 // Deeper check will be done with bug 763305.
 is('valueAsNumber' in document.createElement("input"), true,
    "valueAsNumber should be available");
 
 // .selectedOption
 todo("selectedOption" in document.createElement("input"),
      "selectedOption isn't implemented yet");
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/forms/test_valueasdate_attribute.html
@@ -0,0 +1,206 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=769370
+-->
+<head>
+  <title>Test for input.valueAsDate</title>
+  <script type="application/javascript" src="/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=769370">Mozilla Bug 769370</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 769370**/
+
+/**
+ * This test is checking .valueAsDate.
+ */
+
+var element = document.createElement("input");
+
+function checkAvailability()
+{
+  var testData =
+  [
+    ["text", false],
+    ["password", false],
+    ["search", false],
+    ["telephone", false],
+    ["email", false],
+    ["url", false],
+    ["hidden", false],
+    ["checkbox", false],
+    ["radio", false],
+    ["file", false],
+    ["submit", false],
+    ["image", false],
+    ["reset", false],
+    ["button", false],
+    ["number", false],
+    ["date", true],
+    // The next types have not been implemented but will fallback to "text"
+    // which has the same value.
+    ["range", false],
+    ["color", false],
+  ];
+
+  var todoList =
+  [
+    ["datetime", true],
+    ["month", true],
+    ["week", true],
+    ["time", true],
+    ["datetime-local", true],
+  ];
+
+  for (data of testData) {
+    var exceptionCatched = false;
+    element.type = data[0];
+    try {
+      element.valueAsDate;
+    } catch (e) {
+      exceptionCatched = true;
+    }
+    is(exceptionCatched, false,
+       "valueAsDate shouldn't throw exception on getting");
+
+    exceptionCatched = false;
+    try {
+      element.valueAsDate = new Date();
+    } catch (e) {
+      exceptionCatched = true;
+    }
+    is(exceptionCatched, !data[1], "valueAsDate for " + data[0] +
+                                   " availability is not correct");
+  }
+
+  for (data of todoList) {
+    var exceptionCatched = false;
+    element.type = data[0];
+    try {
+      element.valueAsDate;
+    } catch (e) {
+      exceptionCatched = true;
+    }
+    is(exceptionCatched, false,
+       "valueAsDate shouldn't throw exception on getting");
+
+    exceptionCatched = false;
+    try {
+      element.valueAsDate= 42;
+    } catch (e) {
+      exceptionCatched = true;
+    }
+    todo_is(exceptionCatched, !data[1],
+            "valueAsDate for " + data[0] + " availability is not correct");
+  }
+}
+
+function checkGet()
+{
+  var validData =
+  [
+    [ "2012-07-12", 1342051200000 ],
+    [ "1970-01-01", 0 ],
+    [ "1970-01-02", 86400000 ],
+    [ "1969-12-31", -86400000 ],
+    [ "0311-01-31", -52350451200000 ],
+    [ "275760-09-13", 8640000000000000 ],
+    [ "0001-01-01", -62135596800000 ],
+    [ "2012-02-29", 1330473600000 ],
+    [ "2011-02-28", 1298851200000 ],
+  ];
+
+  var invalidData =
+  [
+    [ "invaliddate" ],
+    [ "-001-12-31" ],
+    [ "901-12-31" ],
+    [ "1901-13-31" ],
+    [ "1901-12-32" ],
+    [ "1901-00-12" ],
+    [ "1901-01-00" ],
+    [ "1900-02-29" ],
+    [ "0000-01-01" ],
+    [ "" ],
+    // This date is valid for the input element, but is out of
+    // the date object range. In this case, on getting valueAsDate,
+    // a Date object will be created, but it will have a NaN internal value,
+    // and will return the string "Invalid Date".
+    [ "275760-09-14", true ],
+  ];
+
+  element.type = "date";
+  for (data of validData) {
+    element.value = data[0];
+    is(element.valueAsDate.valueOf(), data[1],
+       "valueAsDate should return the " +
+       "valid date object representing this date");
+  }
+
+  for (data of invalidData) {
+    element.value = data[0];
+    is(element.valueAsDate, data[1] ? "Invalid Date" : null,
+       "valueAsDate should return null "  +
+       "when the element value is not a valid date");
+  }
+
+}
+
+function checkSet()
+{
+  var testData =
+  [
+    [ 1342051200000,     "2012-07-12" ],
+    [ 0,                 "1970-01-01" ],
+    // Maximum valid date (limited by the ecma date object range).
+    [ 8640000000000000,  "275760-09-13" ],
+    // Minimum valid date (limited by the input element minimum valid value).
+    [ -62135596800000 ,   "0001-01-01" ],
+    [ 1330473600000,     "2012-02-29" ],
+    [ 1298851200000,     "2011-02-28" ],
+    // "Values must be truncated to valid dates"
+    [ 42.1234,           "1970-01-01" ],
+    [ 123.123456789123,  "1970-01-01" ],
+    [ 1e-1,              "1970-01-01" ],
+    [ 1298851200010,     "2011-02-28" ],
+    [ -1,                "1969-12-31" ],
+    [ -86400000,         "1969-12-31" ],
+    [ 86400000,          "1970-01-02" ],
+    // Negative years, this is out of range for the input element,
+    // the corresponding date string is the empty string
+    [ -62135596800001,   "" ],
+    // Invalid dates.
+    [ NaN,               "" ],
+  ];
+
+  element.type = "date";
+  for (data of testData) {
+    element.valueAsDate = new Date(data[0]);
+    is(element.value, data[1], "valueAsDate should set the value to "
+                                + data[1]);
+  }
+
+  element.valueAsDate = null;
+  is(element.value, "", "valueAsDate should set the value to the empty string");
+
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms", true]]}, function() {
+checkAvailability();
+checkGet();
+checkSet();
+
+SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
--- a/content/html/content/test/forms/test_valueasnumber_attribute.html
+++ b/content/html/content/test/forms/test_valueasnumber_attribute.html
@@ -1,15 +1,15 @@
 <!DOCTYPE HTML>
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=636737
 -->
 <head>
-  <title>Test for Bug 636737</title>
+  <title>Test for Bug input.valueAsNumber</title>
   <script type="application/javascript" src="/MochiKit/packed.js"></script>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=636737">Mozilla Bug 636737</a>
 <p id="display"></p>
 <pre id="test">
@@ -38,25 +38,25 @@ function checkAvailability()
     ["checkbox", false],
     ["radio", false],
     ["file", false],
     ["submit", false],
     ["image", false],
     ["reset", false],
     ["button", false],
     ["number", true],
+    ["date", true],
     // The next types have not been implemented but will fallback to "text"
     // which has the same value.
     ["color", false],
   ];
 
   var todoList =
   [
     ["datetime", true],
-    ["date", true],
     ["month", true],
     ["week", true],
     ["time", true],
     ["datetime-local", true],
     ["range", true],
   ];
 
 
@@ -98,17 +98,17 @@ function checkAvailability()
     } catch (e) {
       exceptionCatched = true;
     }
     todo_is(exceptionCatched, !data[1],
             "valueAsNumber for " + data[0] + " availability is not correct");
   }
 }
 
-function checkGet()
+function checkNumberGet()
 {
   var testData =
   [
     ["42", 42],
     ["-42", -42], // should work for negative values
     ["42.1234", 42.1234],
     ["123.12345678912345", 123.12345678912345], // double precision
     ["1e2", 100], // e should be usable
@@ -131,17 +131,17 @@ function checkGet()
          "floating point representation of the value");
     } else {
       ok(isNaN(element.valueAsNumber), "valueAsNumber should return NaN "  +
          "when the element value is not a number");
     }
   }
 }
 
-function checkSet()
+function checkNumberSet()
 {
   var testData =
   [
     [42, "42"],
     [-42, "-42"], // should work for negative values
     [42.1234, "42.1234"],
     [123.123456789123, "123.123456789123"], // double precision
     [1e2, "100"], // e should be usable
@@ -161,21 +161,117 @@ function checkSet()
     } else {
       element.valueAsNumber = testData[0];
       isnot(element.value, data[1],
             "valueAsNumber should not set the value if it's not a number");
     }
   }
 }
 
+function checkDateGet()
+{
+  var validData =
+  [
+    [ "2012-07-12", 1342051200000 ],
+    [ "1970-01-01", 0 ],
+    // We are supposed to support at least until this date.
+    // (corresponding to the date object maximal value)
+    [ "275760-09-13", 8640000000000000 ],
+    // Minimum valid date (limited by the input element minimum valid value)
+    [ "0001-01-01", -62135596800000 ],
+    [ "2012-02-29", 1330473600000 ],
+    [ "2011-02-28", 1298851200000 ],
+  ];
+
+  var invalidData =
+  [
+    "invaliddate",
+    "",
+    "275760-09-14",
+    "999-12-31",
+    "-001-12-31",
+    "0000-01-01",
+    "2011-02-29",
+    "1901-13-31",
+    "1901-12-32",
+    "1901-00-12",
+    "1901-01-00",
+    "1900-02-29",
+  ];
+
+  element.type = "date";
+  for (data of validData) {
+    element.value = data[0];
+    is(element.valueAsNumber, data[1], "valueAsNumber should return the " +
+       "timestamp representing this date");
+  }
+
+  for (data of invalidData) {
+    element.value = data;
+    ok(isNaN(element.valueAsNumber), "valueAsNumber should return NaN "  +
+       "when the element value is not a valid date");
+  }
+}
+
+function checkDateSet()
+{
+  var testData =
+  [
+    [ 1342051200000,     "2012-07-12" ],
+    [ 0,                 "1970-01-01" ],
+    // Maximum valid date (limited by the ecma date object range).
+    [ 8640000000000000,  "275760-09-13" ],
+    // Minimum valid date (limited by the input element minimum valid value)
+    [ -62135596800000,   "0001-01-01" ],
+    [ 1330473600000,     "2012-02-29" ],
+    [ 1298851200000,     "2011-02-28" ],
+    // "Values must be truncated to valid dates"
+    [ 42.1234,           "1970-01-01" ],
+    [ 123.123456789123,  "1970-01-01" ],
+    [ 1e2,               "1970-01-01" ],
+    [ 1E9,               "1970-01-12" ],
+    [ 1e-1,              "1970-01-01" ],
+    [ 2e10,              "1970-08-20" ],
+    [ 1298851200010,     "2011-02-28" ],
+    [ -1,                "1969-12-31" ],
+    [ -86400000,         "1969-12-31" ],
+    [ 86400000,          "1970-01-02" ],
+    // Invalid numbers.
+    // Those are implicitly converted to numbers
+    [ "",                "1970-01-01" ],
+    [ true,              "1970-01-01" ],
+    [ false,             "1970-01-01" ],
+    [ null,              "1970-01-01" ],
+    // Those are converted to NaN, the corresponding date string is the empty string
+    [ "invaliddatenumber", "" ],
+    [ NaN,               "" ],
+    [ undefined,         "" ],
+    // Out of range, the corresponding date string is the empty string
+    [ -62135596800001,   "" ],
+  ];
+
+  element.type = "date";
+  for (data of testData) {
+    element.valueAsNumber = data[0];
+    is(element.value, data[1], "valueAsNumber should set the value to " + data[1]);
+  }
+
+}
+
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms", true]]}, function() {
 checkAvailability();
-checkGet();
-checkSet();
+
+// <input type='number'> test
+checkNumberGet();
+checkNumberSet();
+
+// <input type='date'> test
+checkDateGet();
+checkDateSet();
 
 SimpleTest.finish();
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/interfaces/html/nsIDOMHTMLInputElement.idl
+++ b/dom/interfaces/html/nsIDOMHTMLInputElement.idl
@@ -64,16 +64,17 @@ interface nsIDOMHTMLInputElement : nsIDO
            attribute unsigned long         size;
            attribute unsigned long width;
            attribute DOMString             src;
 
            attribute DOMString             type;
            attribute DOMString             defaultValue;
            attribute DOMString             value;
            attribute double                valueAsNumber;
+  [implicit_jscontext] attribute jsval     valueAsDate;
 
   [optional_argc] void stepDown([optional] in long n);
   [optional_argc] void stepUp([optional] in long n);
 
   // The following lines are part of the constraint validation API, see:
   // http://www.whatwg.org/specs/web-apps/current-work/#the-constraint-validation-api
   readonly attribute boolean             willValidate;
   readonly attribute nsIDOMValidityState validity;