Merge m-c to inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 28 Dec 2012 12:57:30 -0500
changeset 126271 e19528d96c1aa51a5270f6e012ecf36a459136e7
parent 126270 83c45c2f712b3866800fe973b4ccf9a6f195f59c (current diff)
parent 126221 0bb4773db082c0ecf241e9dac3d8ca3402efbb13 (diff)
child 126272 52daedcf1dbbc9065faa3529dee6256772608d21
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)
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
Merge m-c to inbound.
content/html/content/test/test_bug549475.html
layout/base/nsCSSFrameConstructor.cpp
--- a/b2g/chrome/content/forms.js
+++ b/b2g/chrome/content/forms.js
@@ -277,24 +277,22 @@ function getJSON(element) {
   // Treat contenteditble element as a special text field
   if (element.contentEditable && element.contentEditable == "true") {
     type = "text";
     value = element.textContent;
   }
 
   // Until the input type=date/datetime/time have been implemented
   // let's return their real type even if the platform returns 'text'
-  // Related to Bug 769352 - Implement <input type=date>
   // Related to Bug 777279 - Implement <input type=time>
   let attributeType = element.getAttribute("type") || "";
 
   if (attributeType) {
     var typeLowerCase = attributeType.toLowerCase(); 
     switch (typeLowerCase) {
-      case "date":
       case "time":
       case "datetime":
       case "datetime-local":
         type = typeLowerCase;
         break;
     }
   }
 
--- a/content/events/src/nsEventStateManager.cpp
+++ b/content/events/src/nsEventStateManager.cpp
@@ -1894,16 +1894,17 @@ nsEventStateManager::FireContextClick()
         allowedToDispatch = (type == NS_FORM_INPUT_TEXT ||
                              type == NS_FORM_INPUT_EMAIL ||
                              type == NS_FORM_INPUT_SEARCH ||
                              type == NS_FORM_INPUT_TEL ||
                              type == NS_FORM_INPUT_URL ||
                              type == NS_FORM_INPUT_PASSWORD ||
                              type == NS_FORM_INPUT_FILE ||
                              type == NS_FORM_INPUT_NUMBER ||
+                             type == NS_FORM_INPUT_DATE ||
                              type == NS_FORM_TEXTAREA);
       }
       else if (tag == nsGkAtoms::applet ||
                tag == nsGkAtoms::embed  ||
                tag == nsGkAtoms::object) {
         allowedToDispatch = false;
       }
     }
--- a/content/html/content/public/nsIFormControl.h
+++ b/content/html/content/public/nsIFormControl.h
@@ -43,16 +43,17 @@ enum ButtonElementTypes {
   NS_FORM_BUTTON_RESET,
   NS_FORM_BUTTON_SUBMIT,
   eButtonElementTypesMax
 };
 
 enum InputElementTypes {
   NS_FORM_INPUT_BUTTON = NS_FORM_INPUT_ELEMENT + 1,
   NS_FORM_INPUT_CHECKBOX,
+  NS_FORM_INPUT_DATE,
   NS_FORM_INPUT_EMAIL,
   NS_FORM_INPUT_FILE,
   NS_FORM_INPUT_HIDDEN,
   NS_FORM_INPUT_RESET,
   NS_FORM_INPUT_IMAGE,
   NS_FORM_INPUT_NUMBER,
   NS_FORM_INPUT_PASSWORD,
   NS_FORM_INPUT_RADIO,
@@ -227,16 +228,18 @@ nsIFormControl::IsSingleLineTextControl(
 {
   return aType == NS_FORM_INPUT_TEXT ||
          aType == NS_FORM_INPUT_EMAIL ||
          aType == NS_FORM_INPUT_SEARCH ||
          aType == NS_FORM_INPUT_TEL ||
          aType == NS_FORM_INPUT_URL ||
          // TODO: this is temporary until bug 635240 is fixed.
          aType == NS_FORM_INPUT_NUMBER ||
+         // TODO: this is temporary until bug 773205 is fixed.
+         aType == NS_FORM_INPUT_DATE ||
          (!aExcludePassword && aType == NS_FORM_INPUT_PASSWORD);
 }
 
 bool
 nsIFormControl::IsSubmittableControl() const
 {
   // TODO: keygen should be in that list, see bug 101019.
   uint32_t type = GetType();
--- a/content/html/content/src/nsHTMLFormElement.cpp
+++ b/content/html/content/src/nsHTMLFormElement.cpp
@@ -179,16 +179,17 @@ ShouldBeInElements(nsIFormControl* aForm
   case NS_FORM_INPUT_PASSWORD :
   case NS_FORM_INPUT_RADIO :
   case NS_FORM_INPUT_SEARCH :
   case NS_FORM_INPUT_SUBMIT :
   case NS_FORM_INPUT_TEXT :
   case NS_FORM_INPUT_TEL :
   case NS_FORM_INPUT_URL :
   case NS_FORM_INPUT_NUMBER :
+  case NS_FORM_INPUT_DATE :
   case NS_FORM_SELECT :
   case NS_FORM_TEXTAREA :
   case NS_FORM_FIELDSET :
   case NS_FORM_OBJECT :
   case NS_FORM_OUTPUT :
     return true;
   }
 
--- a/content/html/content/src/nsHTMLInputElement.cpp
+++ b/content/html/content/src/nsHTMLInputElement.cpp
@@ -83,21 +83,25 @@
 #include "nsContentCreatorFunctions.h"
 #include "nsContentUtils.h"
 #include "mozilla/dom/DirectionalityUtils.h"
 #include "nsRadioVisitor.h"
 
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/Util.h" // DebugOnly
 #include "mozilla/Preferences.h"
+#include "mozilla/MathAlgorithms.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.
@@ -112,16 +116,17 @@ static NS_DEFINE_CID(kXULControllersCID,
 // whether textfields should be selected once focused:
 //  -1: no, 1: yes, 0: uninitialized
 static int32_t gSelectTextFieldOnFocus;
 UploadLastDir* nsHTMLInputElement::gUploadLastDir;
 
 static const nsAttrValue::EnumTable kInputTypeTable[] = {
   { "button", NS_FORM_INPUT_BUTTON },
   { "checkbox", NS_FORM_INPUT_CHECKBOX },
+  { "date", NS_FORM_INPUT_DATE },
   { "email", NS_FORM_INPUT_EMAIL },
   { "file", NS_FORM_INPUT_FILE },
   { "hidden", NS_FORM_INPUT_HIDDEN },
   { "reset", NS_FORM_INPUT_RESET },
   { "image", NS_FORM_INPUT_IMAGE },
   { "number", NS_FORM_INPUT_NUMBER },
   { "password", NS_FORM_INPUT_PASSWORD },
   { "radio", NS_FORM_INPUT_RADIO },
@@ -129,17 +134,17 @@ static const nsAttrValue::EnumTable kInp
   { "submit", NS_FORM_INPUT_SUBMIT },
   { "tel", NS_FORM_INPUT_TEL },
   { "text", NS_FORM_INPUT_TEXT },
   { "url", NS_FORM_INPUT_URL },
   { 0 }
 };
 
 // Default type is 'text'.
-static const nsAttrValue::EnumTable* kInputDefaultType = &kInputTypeTable[13];
+static const nsAttrValue::EnumTable* kInputDefaultType = &kInputTypeTable[14];
 
 static const uint8_t NS_INPUT_AUTOCOMPLETE_OFF     = 0;
 static const uint8_t NS_INPUT_AUTOCOMPLETE_ON      = 1;
 static const uint8_t NS_INPUT_AUTOCOMPLETE_DEFAULT = 2;
 
 static const nsAttrValue::EnumTable kInputAutocompleteTable[] = {
   { "", NS_INPUT_AUTOCOMPLETE_DEFAULT },
   { "on", NS_INPUT_AUTOCOMPLETE_ON },
@@ -167,16 +172,18 @@ static const nsAttrValue::EnumTable kInp
   { "titlecase", NS_INPUT_INPUTMODE_TITLECASE },
   { "autocapitalized", NS_INPUT_INPUTMODE_AUTOCAPITALIZED },
   { 0 }
 };
 
 // Default inputmode value is "auto".
 static const nsAttrValue::EnumTable* kInputDefaultInputmode = &kInputInputmodeTable[0];
 
+const double nsHTMLInputElement::kStepScaleFactorDate = 86400000;
+const double nsHTMLInputElement::kStepScaleFactorNumber = 1;
 const double nsHTMLInputElement::kDefaultStepBase = 0;
 const double nsHTMLInputElement::kStepAny = 0;
 
 #define NS_INPUT_ELEMENT_STATE_IID                 \
 { /* dc3b3d14-23e2-4479-b513-7b369343e3a0 */       \
   0xdc3b3d14,                                      \
   0x23e2,                                          \
   0x4479,                                          \
@@ -691,16 +698,17 @@ nsHTMLInputElement::Clone(nsINodeInfo *a
   switch (mType) {
     case NS_FORM_INPUT_EMAIL:
     case NS_FORM_INPUT_SEARCH:
     case NS_FORM_INPUT_TEXT:
     case NS_FORM_INPUT_PASSWORD:
     case NS_FORM_INPUT_TEL:
     case NS_FORM_INPUT_URL:
     case NS_FORM_INPUT_NUMBER:
+    case NS_FORM_INPUT_DATE:
       if (mValueChanged) {
         // We don't have our default value anymore.  Set our value on
         // the clone.
         nsAutoString value;
         GetValueInternal(value);
         // SetValueInternal handles setting the VALUE_CHANGED bit for us
         it->SetValueInternal(value, false, true);
       }
@@ -1050,27 +1058,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) {
@@ -1135,74 +1198,188 @@ nsHTMLInputElement::GetList(nsIDOMHTMLEl
 
   CallQueryInterface(element, aValue);
   return NS_OK;
 }
 
 void
 nsHTMLInputElement::SetValue(double aValue)
 {
+  MOZ_ASSERT(!MOZ_DOUBLE_IS_INFINITE(aValue), "aValue must not be Infinity!");
+
+  if (MOZ_DOUBLE_IS_NaN(aValue)) {
+    SetValue(EmptyString());
+    return;
+  }
+
   nsAutoString value;
-  value.AppendFloat(aValue);
+  ConvertNumberToString(aValue, value);
   SetValue(value);
 }
 
+bool
+nsHTMLInputElement::ConvertNumberToString(double aValue,
+                                          nsAString& aResultString) const
+{
+  MOZ_ASSERT(mType == NS_FORM_INPUT_DATE || mType == NS_FORM_INPUT_NUMBER,
+             "ConvertNumberToString is only implemented for type='{number,date}'");
+  MOZ_ASSERT(!MOZ_DOUBLE_IS_NaN(aValue) && !MOZ_DOUBLE_IS_INFINITE(aValue),
+             "aValue must be a valid non-Infinite number.");
+
+  aResultString.Truncate();
+
+  switch (mType) {
+    case NS_FORM_INPUT_NUMBER:
+      aResultString.AppendFloat(aValue);
+      return true;
+    case NS_FORM_INPUT_DATE:
+      {
+        JSContext* ctx = nsContentUtils::GetContextFromDocument(OwnerDoc());
+        if (!ctx) {
+          return false;
+        }
+
+        // The specs require |aValue| to be truncated.
+        aValue = floor(aValue);
+
+        JSObject* date = JS_NewDateObjectMsec(ctx, aValue);
+        if (!date) {
+          return false;
+        }
+
+        jsval year, month, day;
+        if (!JS::Call(ctx, date, "getUTCFullYear", 0, nullptr, &year) ||
+            !JS::Call(ctx, date, "getUTCMonth", 0, nullptr, &month) ||
+            !JS::Call(ctx, date, "getUTCDate", 0, nullptr, &day)) {
+          return false;
+        }
+
+        aResultString.AppendPrintf("%04.0f-%02.0f-%02.0f", year.toNumber(),
+                                   month.toNumber() + 1, day.toNumber());
+
+	return true;
+      }
+    default:
+      MOZ_NOT_REACHED();
+      return false;
+  }
+}
+
+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
 nsHTMLInputElement::SetValueAsNumber(double aValueAsNumber)
 {
+  // TODO: return TypeError when HTMLInputElement is converted to WebIDL, see
+  // bug 825197.
+  if (MOZ_DOUBLE_IS_INFINITE(aValueAsNumber)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
   if (!DoesValueAsNumberApply()) {
     return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
   SetValue(aValueAsNumber);
   return NS_OK;
 }
 
 double
 nsHTMLInputElement::GetMinAsDouble() const
 {
-  // Should only be used for <input type='number'> for the moment.
-  MOZ_ASSERT(mType == NS_FORM_INPUT_NUMBER);
+  // Should only be used for <input type='number'/'date'> for the moment.
+  MOZ_ASSERT(mType == NS_FORM_INPUT_NUMBER || mType == NS_FORM_INPUT_DATE);
 
   if (!HasAttr(kNameSpaceID_None, nsGkAtoms::min)) {
     return MOZ_DOUBLE_NaN();
   }
 
   nsAutoString minStr;
   GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr);
 
-  nsresult ec;
-  double min = minStr.ToDouble(&ec);
-  return NS_SUCCEEDED(ec) ? min : MOZ_DOUBLE_NaN();
+  double min;
+  return ConvertStringToNumber(minStr, min) ? min : MOZ_DOUBLE_NaN();
 }
 
 double
 nsHTMLInputElement::GetMaxAsDouble() const
 {
-  // Should only be used for <input type='number'> for the moment.
-  MOZ_ASSERT(mType == NS_FORM_INPUT_NUMBER);
+  // Should only be used for <input type='number'/'date'> for the moment.
+  MOZ_ASSERT(mType == NS_FORM_INPUT_NUMBER || mType == NS_FORM_INPUT_DATE);
 
   if (!HasAttr(kNameSpaceID_None, nsGkAtoms::max)) {
     return MOZ_DOUBLE_NaN();
   }
 
   nsAutoString maxStr;
   GetAttr(kNameSpaceID_None, nsGkAtoms::max, maxStr);
 
-  nsresult ec;
-  double max = maxStr.ToDouble(&ec);
-  return NS_SUCCEEDED(ec) ? max : MOZ_DOUBLE_NaN();
+  double max;
+  return ConvertStringToNumber(maxStr, max) ? max : MOZ_DOUBLE_NaN();
 }
 
 double
 nsHTMLInputElement::GetStepBase() const
 {
   double stepBase = GetMinAsDouble();
 
   // If @min is not a double, we should use defaultValue.
@@ -1262,16 +1439,31 @@ nsHTMLInputElement::ApplyStep(int32_t aS
     } else if (aStep < 0) {
       value -= NS_floorModulo(value - GetStepBase(), step);
       value += step;
     }
   }
 
   value += aStep * step;
 
+  // For date inputs, the value can hold a string that is not a day. We do not
+  // want to round it, as it might result in a step mismatch. Instead we want to
+  // clamp to the next valid value.
+  if (mType == NS_FORM_INPUT_DATE &&
+      NS_floorModulo(value - GetStepBase(), GetStepScaleFactor()) != 0) {
+    double validStep = EuclidLCM<uint64_t>(static_cast<uint64_t>(step),
+                                           static_cast<uint64_t>(GetStepScaleFactor()));
+    if (aStep > 0) {
+      value -= NS_floorModulo(value - GetStepBase(), validStep);
+      value += validStep;
+    } else if (aStep < 0) {
+      value -= NS_floorModulo(value - GetStepBase(), validStep);
+    }
+  }
+
   // When stepUp() is called and the value is below min, we should clamp on
   // min unless stepUp() moves us higher than min.
   if (GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW) && aStep > 0 &&
       value <= min) {
     MOZ_ASSERT(!MOZ_DOUBLE_IS_NaN(min)); // min can't be NaN if we are here!
     value = min;
   // Same goes for stepDown() and max.
   } else if (GetValidityState(VALIDITY_STATE_RANGE_OVERFLOW) && aStep < 0 &&
@@ -1368,18 +1560,18 @@ nsHTMLInputElement::MozSetFileNameArray(
   SetFiles(files, true);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHTMLInputElement::MozIsTextField(bool aExcludePassword, bool* aResult)
 {
-  // TODO: temporary until bug 635240 is fixed.
-  if (mType == NS_FORM_INPUT_NUMBER) {
+  // TODO: temporary until bug 635240 and 773205 are fixed.
+  if (mType == NS_FORM_INPUT_NUMBER || mType == NS_FORM_INPUT_DATE) {
     *aResult = false;
     return NS_OK;
   }
 
   *aResult = IsSingleLineTextControl(aExcludePassword);
 
   return NS_OK;
 }
@@ -2455,17 +2647,18 @@ nsHTMLInputElement::PostHandleEvent(nsEv
            * (c) if there is more than one text input and no submit buttons, do
            *     not submit, period.
            */
 
           if (aVisitor.mEvent->message == NS_KEY_PRESS &&
               (keyEvent->keyCode == NS_VK_RETURN ||
                keyEvent->keyCode == NS_VK_ENTER) &&
                (IsSingleLineTextControl(false, mType) ||
-                mType == NS_FORM_INPUT_NUMBER)) {
+                mType == NS_FORM_INPUT_NUMBER ||
+                mType == NS_FORM_INPUT_DATE)) {
             FireChangeEventIfNeeded();   
             rv = MaybeSubmitForm(aVisitor.mPresContext);
             NS_ENSURE_SUCCESS(rv, rv);
           }
 
         } break; // NS_KEY_PRESS || NS_KEY_UP
 
         case NS_MOUSE_BUTTON_DOWN:
@@ -2749,35 +2942,179 @@ nsHTMLInputElement::SanitizeValue(nsAStr
       {
         nsresult ec;
         PromiseFlatString(aValue).ToDouble(&ec);
         if (NS_FAILED(ec)) {
           aValue.Truncate();
         }
       }
       break;
+    case NS_FORM_INPUT_DATE:
+      {
+        if (!aValue.IsEmpty() && !IsValidDate(aValue)) {
+          aValue.Truncate();
+        }
+      }
+      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;
+  }
+
+  int32_t fieldMaxSize = 0;
+  int32_t fieldMinSize = 4;
+  enum {
+    YEAR, MONTH, DAY, NONE
+  } field;
+  int32_t fieldSize = 0;
+  nsresult ec;
+
+  field = YEAR;
+  for (uint32_t offset = 0; offset < aValue.Length(); ++offset) {
+    // Test if the fied size is superior to its maximum size.
+    if (fieldMaxSize && fieldSize > fieldMaxSize) {
+      return false;
+    }
+
+    // Illegal char.
+    if (aValue[offset] != '-' && !NS_IsAsciiDigit(aValue[offset])) {
+      return false;
+    }
+
+    // There are more characters in this field.
+    if (aValue[offset] != '-' && offset != aValue.Length()-1) {
+      fieldSize++;
+      continue;
+    }
+
+    // Parse the field.
+    if (fieldSize < fieldMinSize) {
+      return false;
+    }
+
+    switch(field) {
+      case YEAR:
+        aYear = PromiseFlatString(StringHead(aValue, offset)).ToInteger(&ec);
+        NS_ENSURE_SUCCESS(ec, false);
+
+        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:
+        aMonth = PromiseFlatString(Substring(aValue,
+                                            offset-fieldSize,
+                                            offset)).ToInteger(&ec);
+        NS_ENSURE_SUCCESS(ec, false);
+
+        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:
+        aDay = PromiseFlatString(Substring(aValue,
+                                          offset-fieldSize,
+                                          offset + 1)).ToInteger(&ec);
+        NS_ENSURE_SUCCESS(ec, false);
+
+        if (aDay <  1 || aDay > NumberOfDaysInMonth(aMonth, aYear)) {
+          return false;
+        }
+
+        field = NONE;
+        break;
+      default:
+        return false;
+    }
+
+    fieldSize = 0;
+  }
+
+  return field == NONE;
+}
+
+uint32_t
+nsHTMLInputElement::NumberOfDaysInMonth(uint32_t aMonth, uint32_t aYear) const
+{
+/*
+ * Returns the number of days in a month.
+ * Months that are |longMonths| always have 31 days.
+ * Months that are not |longMonths| have 30 days except February (month 2).
+ * February has 29 days during leap years which are years that are divisible by 400.
+ * or divisible by 100 and 4. February has 28 days otherwise.
+ */
+
+  static const bool longMonths[] = { true, false, true, false, true, false,
+                                     true, true, false, true, false, true };
+  MOZ_ASSERT(aMonth <= 12 && aMonth > 0);
+
+  if (longMonths[aMonth-1]) {
+    return 31;
+  }
+
+  if (aMonth != 2) {
+    return 30;
+  }
+
+  return (aYear % 400 == 0 || (aYear % 100 != 0 && aYear % 4 == 0))
+          ? 29 : 28;
+}
+ 
+bool
 nsHTMLInputElement::ParseAttribute(int32_t aNamespaceID,
                                    nsIAtom* aAttribute,
                                    const nsAString& aValue,
                                    nsAttrValue& aResult)
 {
   if (aNamespaceID == kNameSpaceID_None) {
     if (aAttribute == nsGkAtoms::type) {
       // XXX ARG!! This is major evilness. ParseAttribute
       // shouldn't set members. Override SetAttr instead
       int32_t newType;
       bool success = aResult.ParseEnumValue(aValue, kInputTypeTable, false);
       if (success) {
         newType = aResult.GetEnumValue();
-        if (newType == NS_FORM_INPUT_NUMBER && 
-          !Preferences::GetBool("dom.experimental_forms", false)) {
+        if ((newType == NS_FORM_INPUT_NUMBER ||
+             newType == NS_FORM_INPUT_DATE) && 
+            !Preferences::GetBool("dom.experimental_forms", false)) {
           newType = kInputDefaultType->value;
           aResult.SetTo(newType, &aValue);
         }
       } else {
         newType = kInputDefaultType->value;
       }
 
       if (newType != mType) {
@@ -3392,16 +3729,17 @@ nsHTMLInputElement::SaveState()
       break;
     case NS_FORM_INPUT_EMAIL:
     case NS_FORM_INPUT_SEARCH:
     case NS_FORM_INPUT_TEXT:
     case NS_FORM_INPUT_TEL:
     case NS_FORM_INPUT_URL:
     case NS_FORM_INPUT_HIDDEN:
     case NS_FORM_INPUT_NUMBER:
+    case NS_FORM_INPUT_DATE:
       {
         if (mValueChanged) {
           inputState = new nsHTMLInputElementState();
           nsAutoString value;
           GetValue(value);
           DebugOnly<nsresult> rv =
             nsLinebreakConverter::ConvertStringLineBreaks(
                  value,
@@ -3576,16 +3914,17 @@ nsHTMLInputElement::RestoreState(nsPresS
 
       case NS_FORM_INPUT_EMAIL:
       case NS_FORM_INPUT_SEARCH:
       case NS_FORM_INPUT_TEXT:
       case NS_FORM_INPUT_TEL:
       case NS_FORM_INPUT_URL:
       case NS_FORM_INPUT_HIDDEN:
       case NS_FORM_INPUT_NUMBER:
+      case NS_FORM_INPUT_DATE:
         {
           SetValueInternal(inputState->GetValue(), false, true);
           break;
         }
       case NS_FORM_INPUT_FILE:
         {
           const nsCOMArray<nsIDOMFile>& files = inputState->GetFiles();
           SetFiles(files, true);
@@ -3800,16 +4139,17 @@ nsHTMLInputElement::GetValueMode() const
 #ifdef DEBUG
     case NS_FORM_INPUT_TEXT:
     case NS_FORM_INPUT_PASSWORD:
     case NS_FORM_INPUT_SEARCH:
     case NS_FORM_INPUT_TEL:
     case NS_FORM_INPUT_EMAIL:
     case NS_FORM_INPUT_URL:
     case NS_FORM_INPUT_NUMBER:
+    case NS_FORM_INPUT_DATE:
       return VALUE_MODE_VALUE;
     default:
       NS_NOTYETIMPLEMENTED("Unexpected input type in GetValueMode()");
       return VALUE_MODE_VALUE;
 #else // DEBUG
     default:
       return VALUE_MODE_VALUE;
 #endif // DEBUG
@@ -3844,16 +4184,17 @@ nsHTMLInputElement::DoesReadOnlyApply() 
 #ifdef DEBUG
     case NS_FORM_INPUT_TEXT:
     case NS_FORM_INPUT_PASSWORD:
     case NS_FORM_INPUT_SEARCH:
     case NS_FORM_INPUT_TEL:
     case NS_FORM_INPUT_EMAIL:
     case NS_FORM_INPUT_URL:
     case NS_FORM_INPUT_NUMBER:
+    case NS_FORM_INPUT_DATE:
       return true;
     default:
       NS_NOTYETIMPLEMENTED("Unexpected input type in DoesReadOnlyApply()");
       return true;
 #else // DEBUG
     default:
       return true;
 #endif // DEBUG
@@ -3880,44 +4221,56 @@ nsHTMLInputElement::DoesRequiredApply() 
     case NS_FORM_INPUT_FILE:
     case NS_FORM_INPUT_TEXT:
     case NS_FORM_INPUT_PASSWORD:
     case NS_FORM_INPUT_SEARCH:
     case NS_FORM_INPUT_TEL:
     case NS_FORM_INPUT_EMAIL:
     case NS_FORM_INPUT_URL:
     case NS_FORM_INPUT_NUMBER:
+    case NS_FORM_INPUT_DATE:
       return true;
     default:
       NS_NOTYETIMPLEMENTED("Unexpected input type in DoesRequiredApply()");
       return true;
 #else // DEBUG
     default:
       return true;
 #endif // DEBUG
   }
 }
 
 bool
+nsHTMLInputElement::PlaceholderApplies() const
+{
+  if (mType == NS_FORM_INPUT_DATE) {
+    return false;
+  }
+
+  return IsSingleLineTextControl(false);
+}
+
+bool
 nsHTMLInputElement::DoesPatternApply() const
 {
   // TODO: temporary until bug 635240 is fixed.
-  if (mType == NS_FORM_INPUT_NUMBER) {
+  if (mType == NS_FORM_INPUT_NUMBER || mType == NS_FORM_INPUT_DATE) {
     return false;
   }
 
   return IsSingleLineTextControl(false);
 }
 
 bool
 nsHTMLInputElement::DoesMinMaxApply() const
 {
   switch (mType)
   {
     case NS_FORM_INPUT_NUMBER:
+    case NS_FORM_INPUT_DATE:
     // TODO:
     // case NS_FORM_INPUT_RANGE:
     // All date/time types.
       return true;
 #ifdef DEBUG
     case NS_FORM_INPUT_RESET:
     case NS_FORM_INPUT_SUBMIT:
     case NS_FORM_INPUT_IMAGE:
@@ -3941,44 +4294,42 @@ nsHTMLInputElement::DoesMinMaxApply() co
       return false;
 #endif // DEBUG
   }
 }
 
 double
 nsHTMLInputElement::GetStep() const
 {
-  NS_ASSERTION(mType == NS_FORM_INPUT_NUMBER,
-               "We can't be there if type!=number!");
-
-  // NOTE: should be defaultStep * defaultStepScaleFactor,
-  // which is 1 for type=number.
+  MOZ_ASSERT(mType == NS_FORM_INPUT_NUMBER || mType == NS_FORM_INPUT_DATE,
+             "We can't be there if type!=number or date!");
+
+  // NOTE: should be defaultStep, which is 1 for type=number and date.
   double step = 1;
 
   if (HasAttr(kNameSpaceID_None, nsGkAtoms::step)) {
     nsAutoString stepStr;
     GetAttr(kNameSpaceID_None, nsGkAtoms::step, stepStr);
 
     if (stepStr.LowerCaseEqualsLiteral("any")) {
       // The element can't suffer from step mismatch if there is no step.
       return kStepAny;
     }
 
     nsresult ec;
-    // NOTE: should be multiplied by defaultStepScaleFactor,
-    // which is 1 for type=number.
     step = stepStr.ToDouble(&ec);
     if (NS_FAILED(ec) || step <= 0) {
-      // NOTE: we should use defaultStep * defaultStepScaleFactor,
-      // which is 1 for type=number.
+      // NOTE: we should use defaultStep, which is 1 for type=number and date.
       step = 1;
     }
   }
 
-  return step;
+  // TODO: This multiplication can lead to inexact results, we should use a
+  // type that supports a better precision than double. Bug 783607.
+  return step * GetStepScaleFactor();
 }
 
 // nsIConstraintValidation
 
 NS_IMETHODIMP
 nsHTMLInputElement::SetCustomValidity(const nsAString& aError)
 {
   nsIConstraintValidation::SetCustomValidity(aError);
@@ -4159,16 +4510,25 @@ nsHTMLInputElement::HasStepMismatch() co
     return false;
   }
 
   double step = GetStep();
   if (step == kStepAny) {
     return false;
   }
 
+  if (mType == NS_FORM_INPUT_DATE) {
+    // The multiplication by the stepScaleFactor for date can easily lead
+    // to precision loss, since in most use cases this value should be
+    // an integer (millisecond precision), we can get rid of the precision
+    // loss by rounding step. This will however lead to erroneous results
+    // when step was intented to have a precision superior to a millisecond.
+    step = NS_round(step);
+  }
+
   // Value has to be an integral multiple of step.
   return NS_floorModulo(value - GetStepBase(), step) != 0;
 }
 
 void
 nsHTMLInputElement::UpdateTooLongValidityState()
 {
   // TODO: this code will be re-enabled with bug 613016 and bug 613019.
@@ -4381,38 +4741,51 @@ nsHTMLInputElement::GetValidationMessage
       }
       aValidationMessage = message;
       break;
     }
     case VALIDITY_STATE_RANGE_OVERFLOW:
     {
       nsXPIDLString message;
 
-      double max = GetMaxAsDouble();
-      MOZ_ASSERT(!MOZ_DOUBLE_IS_NaN(max));
-
       nsAutoString maxStr;
-      maxStr.AppendFloat(max);
+      if (mType == NS_FORM_INPUT_NUMBER) {
+        //We want to show the value as parsed when it's a number
+        double max = GetMaxAsDouble();
+        MOZ_ASSERT(!MOZ_DOUBLE_IS_NaN(max));
+
+        maxStr.AppendFloat(max);
+      } else if (mType == NS_FORM_INPUT_DATE) {
+        GetAttr(kNameSpaceID_None, nsGkAtoms::max, maxStr);
+      } else {
+        NS_NOTREACHED("Unexpected input type");
+      }
 
       const PRUnichar* params[] = { maxStr.get() };
       rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
                                                  "FormValidationRangeOverflow",
                                                  params, message);
       aValidationMessage = message;
       break;
     }
     case VALIDITY_STATE_RANGE_UNDERFLOW:
     {
       nsXPIDLString message;
 
-      double min = GetMinAsDouble();
-      MOZ_ASSERT(!MOZ_DOUBLE_IS_NaN(min));
-
       nsAutoString minStr;
-      minStr.AppendFloat(min);
+      if (mType == NS_FORM_INPUT_NUMBER) {
+        double min = GetMinAsDouble();
+        MOZ_ASSERT(!MOZ_DOUBLE_IS_NaN(min));
+
+        minStr.AppendFloat(min);
+      } else if (mType == NS_FORM_INPUT_DATE) {
+        GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr);
+      } else {
+        NS_NOTREACHED("Unexpected input type");
+      }
 
       const PRUnichar* params[] = { minStr.get() };
       rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
                                                  "FormValidationRangeUnderflow",
                                                  params, message);
       aValidationMessage = message;
       break;
     }
@@ -4421,35 +4794,45 @@ nsHTMLInputElement::GetValidationMessage
       nsXPIDLString message;
 
       double value = GetValueAsDouble();
       MOZ_ASSERT(!MOZ_DOUBLE_IS_NaN(value));
 
       double step = GetStep();
       MOZ_ASSERT(step != kStepAny);
 
+      // In case this is a date and the step is not an integer, we don't want to
+      // display the dates corresponding to the truncated timestamps of valueLow
+      // and valueHigh because they might suffer from a step mismatch as well.
+      // Instead we want the timestamps to correspond to a rounded day. That is,
+      // we want a multiple of the step scale factor (1 day) as well as of step.
+      if (mType == NS_FORM_INPUT_DATE) {
+        step = EuclidLCM<uint64_t>(static_cast<uint64_t>(step),
+                                   static_cast<uint64_t>(GetStepScaleFactor()));
+      }
+
       double stepBase = GetStepBase();
 
       double valueLow = value - NS_floorModulo(value - stepBase, step);
       double valueHigh = value + step - NS_floorModulo(value - stepBase, step);
 
       double max = GetMaxAsDouble();
 
       if (MOZ_DOUBLE_IS_NaN(max) || valueHigh <= max) {
         nsAutoString valueLowStr, valueHighStr;
-        valueLowStr.AppendFloat(valueLow);
-        valueHighStr.AppendFloat(valueHigh);
+        ConvertNumberToString(valueLow, valueLowStr);
+        ConvertNumberToString(valueHigh, valueHighStr);
 
         const PRUnichar* params[] = { valueLowStr.get(), valueHighStr.get() };
         rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
                                                    "FormValidationStepMismatch",
                                                    params, message);
       } else {
         nsAutoString valueLowStr;
-        valueLowStr.AppendFloat(valueLow);
+        ConvertNumberToString(valueLow, valueLowStr);
 
         const PRUnichar* params[] = { valueLowStr.get() };
         rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
                                                    "FormValidationStepMismatchWithoutMax",
                                                    params, message);
       }
 
       aValidationMessage = message;
@@ -4856,16 +5239,32 @@ nsHTMLInputElement::GetFilterFromAccept(
       }
       filter = tokenFilter;
     }
   }
 
   return filter;
 }
 
+double
+nsHTMLInputElement::GetStepScaleFactor() const
+{
+  MOZ_ASSERT(DoesStepApply());
+
+  switch (mType) {
+    case NS_FORM_INPUT_DATE:
+      return kStepScaleFactorDate;
+    case NS_FORM_INPUT_NUMBER:
+      return kStepScaleFactorNumber;
+    default:
+      MOZ_NOT_REACHED();
+      return MOZ_DOUBLE_NaN();
+  }
+}
+
 void
 nsHTMLInputElement::UpdateValidityUIBits(bool aIsFocused)
 {
   if (aIsFocused) {
     // If the invalid UI is shown, we should show it while focusing (and
     // update). Otherwise, we should not.
     mCanShowInvalidUI = !IsValid() && ShouldShowValidityUI();
 
@@ -4878,17 +5277,17 @@ nsHTMLInputElement::UpdateValidityUIBits
   }
 }
 
 void
 nsHTMLInputElement::UpdateHasRange()
 {
   mHasRange = false;
 
-  if (mType != NS_FORM_INPUT_NUMBER) {
+  if (mType != NS_FORM_INPUT_NUMBER && mType != NS_FORM_INPUT_DATE) {
     return;
   }
 
   // <input type=number> has a range if min or max is a valid double.
 
   double min = GetMinAsDouble();
   if (!MOZ_DOUBLE_IS_NaN(min)) {
     mHasRange = true;
--- a/content/html/content/src/nsHTMLInputElement.h
+++ b/content/html/content/src/nsHTMLInputElement.h
@@ -497,17 +497,17 @@ protected:
    * Sanitize the value of the element depending of its current type.
    * See: http://www.whatwg.org/specs/web-apps/current-work/#value-sanitization-algorithm
    */
   void SanitizeValue(nsAString& aValue);
 
   /**
    * Returns whether the placeholder attribute applies for the current type.
    */
-  bool PlaceholderApplies() const { return IsSingleLineTextControl(false, mType); }
+  bool PlaceholderApplies() const;
 
   /**
    * Set the current default value to the value of the input element.
    * @note You should not call this method if GetValueMode() doesn't return
    * VALUE_MODE_VALUE.
    */
   nsresult SetDefaultValueAsValue();
 
@@ -556,16 +556,64 @@ 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;
+
+  /**
+   * Convert a double to a string in a type specific way, ie convert a timestamp
+   * to a date string if type=date or append the number string representing the
+   * value if type=number.
+   *
+   * @param aValue the double to be converted
+   * @param aResultString [out] the string representing the double
+   * @return whether the function succeded, it will fail if the current input's
+   *         type is not supported or the number can't be converted to a string
+   *         as expected by the type.
+   */
+  bool ConvertNumberToString(double aValue, nsAString& aResultString) 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.
    */
   void SetValue(double aValue);
 
   /**
    * Update the HAS_RANGE bit field value.
@@ -579,16 +627,23 @@ protected:
   double GetMinAsDouble() const;
 
   /**
    * Returns the max attribute as a double.
    * Returns NaN if the max attribute isn't a valid floating point number.
    */
   double GetMaxAsDouble() const;
 
+   /**
+    * Get the step scale value for the current type.
+    * See:
+    * http://www.whatwg.org/specs/web-apps/current-work/multipage/common-input-element-attributes.html#concept-input-step-scale
+    */
+  double GetStepScaleFactor() const;
+
   /**
    * Returns the current step value.
    * Returns kStepAny if the current step is "any" string.
    *
    * @return the current step value.
    */
   double GetStep() const;
 
@@ -648,16 +703,20 @@ protected:
    * The value of the input element when first initialized and it is updated
    * when the element is either changed through a script, focused or dispatches   
    * a change event. This is to ensure correct future change event firing.
    * NB: This is ONLY applicable where the element is a text control. ie,
    * where type= "text", "email", "search", "tel", "url" or "password".
    */
   nsString mFocusedValue;  
 
+  // Step scale factor values, for input types that have one.
+  static const double kStepScaleFactorDate;
+  static const double kStepScaleFactorNumber;
+
   // Default step base value when a type do not have specific one.
   static const double kDefaultStepBase;
   // Float alue returned by GetStep() when the step attribute is set to 'any'.
   static const double kStepAny;
 
   /**
    * The type of this input (<input type=...>) as an integer.
    * @see nsIFormControl.h (specifically NS_FORM_INPUT_*)
--- a/content/html/content/test/Makefile.in
+++ b/content/html/content/test/Makefile.in
@@ -154,17 +154,16 @@ MOCHITEST_FILES = \
 		test_bug564001.html \
 		test_bug566046.html \
 		test_bug567938-1.html \
 		test_bug567938-2.html \
 		test_bug567938-3.html \
 		test_bug567938-4.html \
 		test_bug569955.html \
 		test_bug573969.html \
-		test_bug549475.html \
 		test_bug585508.html \
 		test_bug561640.html \
 		test_bug566064.html \
 		test_bug582412-1.html \
 		test_bug582412-2.html \
 		test_bug558788-1.html \
 		test_bug558788-2.html \
 		test_bug561634.html \
--- a/content/html/content/test/forms/Makefile.in
+++ b/content/html/content/test/forms/Makefile.in
@@ -44,12 +44,14 @@ MOCHITEST_FILES = \
 		test_meter_pseudo-classes.html \
 		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
@@ -196,35 +196,35 @@ reflectString({
 });
 
 // .type
 reflectLimitedEnumerated({
   element: document.createElement("input"),
   attribute: "type",
   validValues: [ "hidden", "text", "search", "tel", "url", "email", "password",
                  "checkbox", "radio", "file", "submit", "image", "reset",
-                 "button", "number" ],
+                 "button", "date", "number" ],
   invalidValues: [ "this-is-probably-a-wrong-type", "", "tulip" ],
   defaultValue: "text",
-  unsupportedValues: [ "datetime", "date", "month", "week", "time",
+  unsupportedValues: [ "datetime", "month", "week", "time",
                        "datetime-local", "range", "color" ]
 });
 
 // .defaultValue
 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");
rename from content/html/content/test/test_bug549475.html
rename to content/html/content/test/forms/test_input_sanitization.html
--- a/content/html/content/test/test_bug549475.html
+++ b/content/html/content/test/forms/test_input_sanitization.html
@@ -20,65 +20,139 @@ https://bugzilla.mozilla.org/show_bug.cg
 
 /** Test for Bug 549475 **/
 
 // We are excluding "file" because it's too different from the other types.
 // And it has no sanitizing algorithm.
 var inputTypes =
 [
   "text", "password", "search", "tel", "hidden", "checkbox", "radio",
-  "submit", "image", "reset", "button", "email", "url", "number",
+  "submit", "image", "reset", "button", "email", "url", "number", "date",
 ];
 
 var todoTypes =
 [
   "range", "color",
-  "date", "month", "week", "time", "datetime", "datetime-local",
+  "month", "week", "time", "datetime", "datetime-local",
 ];
 
 var valueModeValue =
 [
   "text", "search", "url", "tel", "email", "password", "date", "datetime",
   "month", "week", "time", "datetime-local", "number", "range", "color",
 ];
 
+function checkDateSanitizing(element) {
+  var invalidData =
+  [
+    "1234",
+    "1234-",
+    "12345",
+    "1234-01",
+    "1234-012",
+    "1234-01-",
+    "12-12",
+    "999-01-01",
+    "1234-56-78-91",
+    "1234-567-78",
+    "1234--7-78",
+    "abcd-12-12",
+    "thisinotadate",
+    "2012-13-01",
+    "1234-12-42",
+    " 2012-13-01",
+    " 123-01-01",
+    "2012- 3-01",
+    "12- 10- 01",
+    "  12-0-1",
+    "2012-3-001",
+    "2012-12-00",
+    "2012-12-1r",
+    "2012-11-31",
+    "2011-02-29",
+    "2100-02-29",
+    "a2000-01-01",
+    "2000a-01-0'",
+    "20aa00-01-01",
+    "2000a2000-01-01",
+    "2000-1-1",
+    "2000-1-01",
+    "2000-01-1",
+    "2000-01-01 ",
+    "2000- 01-01",
+    "-1970-01-01",
+    "0000-00-00",
+    "0001-00-00",
+    "0000-01-01",
+    "1234-12 12",
+    "1234 12-12",
+    "1234 12 12",
+  ];
+
+  var validData =
+  [
+    "1970-01-01",
+    "1234-12-12",
+    "1234567890-01-02",
+    "2012-12-31",
+    "2012-02-29",
+    "2000-02-29",
+  ];
+
+  for (data of validData) {
+    element.value = data;
+    is(element.value, data, "valid date should not be sanitized");
+  }
+
+  for (data of invalidData) {
+    element.value = data;
+    is(element.value, "", "invalid date should be sanitized");
+  }
+}
+
 function sanitizeValue(aType, aValue)
 {
-  switch (aType) {
-    case "text":
-    case "password":
-    case "search":
-    case "tel":
-      return aValue.replace(/[\n\r]/g, "");
-    case "url":
-    case "email":
-      return aValue.replace(/[\n\r]/g, "").replace(/^\s+|\s+$/g, "");
-    case "date":
-    case "month":
-    case "week":
-    case "time":
-    case "datetime":
-    case "datetime-local":
-      // TODO: write the sanitize algorithm.
-      return "";
-    case "number":
-      return (parseFloat(aValue) + "" === aValue) ? aValue : "";
-    case "range":
-      // TODO: write the sanitize algorithm.
-      return "";
-    case "color":
-      // TODO: write the sanitize algorithm.
-      return "";
-    default:
-      return aValue;
-  }
+	switch (aType) {
+		case "text":
+		case "password":
+		case "search":
+		case "tel":
+			return aValue.replace(/[\n\r]/g, "");
+		case "url":
+		case "email":
+			return aValue.replace(/[\n\r]/g, "").replace(/^\s+|\s+$/g, "");
+		case "date":
+			return "";
+		case "month":
+		case "week":
+		case "time":
+		case "datetime":
+		case "datetime-local":
+			// TODO: write the sanitize algorithm.
+			return "";
+		case "number":
+			return (parseFloat(aValue) + "" === aValue) ? aValue : "";
+		case "range":
+			// TODO: write the sanitize algorithm.
+			return "";
+		case "color":
+			// TODO: write the sanitize algorithm.
+			return "";
+		default:
+			return aValue;
+	}
 }
 
 function checkSanitizing(element)
 {
+  if (element.type == 'date') {
+    checkDateSanitizing(element);
+    return;
+  }
+
   var testData =
   [
     // For text, password, search, tel, email:
     "\n\rfoo\n\r",
     "foo\n\rbar",
     "  foo  ",
     "  foo\n\r  bar  ",
     // For url:
--- a/content/html/content/test/forms/test_max_attribute.html
+++ b/content/html/content/test/forms/test_max_attribute.html
@@ -23,17 +23,17 @@ var types = [
   [ 'hidden',         false ],
   [ 'text',           false ],
   [ 'search',         false ],
   [ 'tel',            false ],
   [ 'url',            false ],
   [ 'email',          false ],
   [ 'password',       false ],
   [ 'datetime',       true,  true ],
-  [ 'date',           true,  true ],
+  [ 'date',           true ],
   [ 'month',          true,  true ],
   [ 'week',           true,  true ],
   [ 'time',           true,  true ],
   [ 'datetime-local', true,  true ],
   [ 'number',         true ],
   [ 'range',          true,  true ],
   [ 'color',          false, true ],
   [ 'checkbox',       false ],
@@ -85,17 +85,22 @@ for (var data of types) {
 
   if (data[2]) {
     todo_is(input.type, data[0], data[0] + " isn't implemented yet");
     continue;
   }
 
   checkValidity(input, true, apply, false);
 
-  input.max = '2';
+  if (input.type == 'date') {
+    input.max = '2012-06-27';
+  } else {
+    input.max = '2';
+  }
+
   checkValidity(input, true, apply, apply);
 
   if (input.type == 'url') {
     input.value = 'http://mozilla.org';
     checkValidity(input, true, apply, apply);
   } else if (input.type == 'email') {
     input.value = 'foo@bar.com';
     checkValidity(input, true, apply, apply);
@@ -113,16 +118,46 @@ for (var data of types) {
                    0666, 0);
     outStream.write("foo", 3);
     outStream.close();
 
     input.value = file.path;
     checkValidity(input, true, apply, apply);
 
     file.remove(false);
+  } else if (input.type == 'date') {
+    input.value = '2012-06-26';
+    checkValidity(input, true, apply, apply);
+
+    input.value = '2012-06-27';
+    checkValidity(input, true, apply, apply);
+
+    input.value = 'foo';
+    checkValidity(input, true, apply, apply);
+
+    input.value = '2012-06-28';
+    checkValidity(input, false, apply, apply);
+
+    input.max = '2012-06-30';
+    checkValidity(input, true, apply, apply);
+
+    input.value = '2012-07-05';
+    checkValidity(input, false, apply, apply);
+
+    input.value = '1000-01-01';
+    checkValidity(input, true, apply, apply);
+
+    input.value = '20120-01-01';
+    checkValidity(input, false, apply, apply);
+
+    input.max = '0050-01-01';
+    checkValidity(input, false, apply, apply);
+
+    input.value = '0049-01-01';
+    checkValidity(input, true, apply, apply);
   } else {
     input.value = '1';
     checkValidity(input, true, apply, apply);
 
     input.value = '2';
     checkValidity(input, true, apply, apply);
 
     input.value = 'foo';
--- a/content/html/content/test/forms/test_min_attribute.html
+++ b/content/html/content/test/forms/test_min_attribute.html
@@ -23,17 +23,17 @@ var types = [
   [ 'hidden',         false ],
   [ 'text',           false ],
   [ 'search',         false ],
   [ 'tel',            false ],
   [ 'url',            false ],
   [ 'email',          false ],
   [ 'password',       false ],
   [ 'datetime',       true,  true ],
-  [ 'date',           true,  true ],
+  [ 'date',           true ],
   [ 'month',          true,  true ],
   [ 'week',           true,  true ],
   [ 'time',           true,  true ],
   [ 'datetime-local', true,  true ],
   [ 'number',         true ],
   [ 'range',          true,  true ],
   [ 'color',          false, true ],
   [ 'checkbox',       false ],
@@ -85,17 +85,22 @@ for (var data of types) {
 
   if (data[2]) {
     todo_is(input.type, data[0], data[0] + " isn't implemented yet");
     continue;
   }
 
   checkValidity(input, true, apply, false);
 
-  input.min = '0';
+  if (input.type == 'date') {
+    input.min = '2012-06-27';
+  } else {
+    input.min = '0';
+  }
+
   checkValidity(input, true, apply, apply);
 
   if (input.type == 'url') {
     input.value = 'http://mozilla.org';
     checkValidity(input, true, apply, apply);
   } else if (input.type == 'email') {
     input.value = 'foo@bar.com';
     checkValidity(input, true, apply, apply);
@@ -113,16 +118,46 @@ for (var data of types) {
                    0666, 0);
     outStream.write("foo", 3);
     outStream.close();
 
     input.value = file.path;
     checkValidity(input, true, apply, apply);
 
     file.remove(false);
+  } else if (input.type == 'date') {
+    input.value = '2012-06-28';
+    checkValidity(input, true, apply, apply);
+
+    input.value = '2012-06-27';
+    checkValidity(input, true, apply, apply);
+
+    input.value = 'foo';
+    checkValidity(input, true, apply, apply);
+
+    input.value = '2012-06-26';
+    checkValidity(input, false, apply, apply);
+
+    input.min = '2012-02-29';
+    checkValidity(input, true, apply, apply);
+
+    input.value = '2012-02-28';
+    checkValidity(input, false, apply, apply);
+
+    input.value = '1000-01-01';
+    checkValidity(input, false, apply, apply);
+
+    input.value = '20120-01-01';
+    checkValidity(input, true, apply, apply);
+
+    input.min = '0050-01-01';
+    checkValidity(input, true, apply, apply);
+
+    input.value = '0049-01-01';
+    checkValidity(input, false, apply, apply);
   } else {
     input.value = '1';
     checkValidity(input, true, apply, apply);
 
     input.value = '0';
     checkValidity(input, true, apply, apply);
 
     input.value = 'foo';
--- a/content/html/content/test/forms/test_mozistextfield.html
+++ b/content/html/content/test/forms/test_mozistextfield.html
@@ -43,27 +43,27 @@ var gInputTestData = [
   ['reset',    false],
   ['image',    false],
   ['radio',    false],
   ['submit',   false],
   ['search',   true],
   ['email',    true],
   ['url',      true],
   ['number',   false],
+  ['date',     false],
 ];
 
 /**
  * TODO: the next types are not yet in the tree.
  * The value is only a suggestion.
  */
 var gInputTodoData = [
 /* type        expected result */
   ['range',    false],
   ['color',    false],
-  ['date',     false],
   ['datetime', false],
   ['month',    false],
   ['week',     false],
   ['time',     false],
   ['datetime-local', false],
 ];
 
 function checkMozIsTextFieldDefined(aElement, aResult)
--- a/content/html/content/test/forms/test_pattern_attribute.html
+++ b/content/html/content/test/forms/test_pattern_attribute.html
@@ -256,18 +256,18 @@ function checkPatternValidity(element)
 }
 
 var input = document.getElementById('i');
 
 // |validTypes| are the types which accept @pattern
 // and |invalidTypes| are the ones which do not accept it.
 var validTypes = Array('text', 'password', 'search', 'tel', 'email', 'url');
 var barredTypes = Array('hidden', 'reset', 'button', 'submit', 'image');
-var invalidTypes = Array('checkbox', 'radio', 'file', 'number');
-// TODO: 'datetime', 'date', 'month', 'week', 'time', 'datetime-local',
+var invalidTypes = Array('checkbox', 'radio', 'file', 'number', 'date');
+// TODO: 'datetime', 'month', 'week', 'time', 'datetime-local',
 //       'range', and 'color' do not accept the @pattern too but are not
 //       implemented yet.
 
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms", true]]}, function() {
 for (type of validTypes) {
   input.type = type;
   completeValidityCheck(input, false);
--- a/content/html/content/test/forms/test_required_attribute.html
+++ b/content/html/content/test/forms/test_required_attribute.html
@@ -159,16 +159,18 @@ function checkInputRequiredValidity(type
   checkSufferingFromBeingMissing(element, true);
 
   if (element.type == 'email') {
     element.value = 'foo@bar.com';
   } else if (element.type == 'url') {
     element.value = 'http://mozilla.org/';
   } else if (element.type == 'number') {
     element.value = '42';
+  } else if (element.type == 'date') {
+    element.value = '2010-10-10';
   } else {
     element.value = 'foo';
   }
   checkNotSufferingFromBeingMissing(element);
 
   element.value = '';
   checkSufferingFromBeingMissing(element, true);
 
@@ -347,16 +349,19 @@ function checkInputRequiredValidityForFi
 
   element.required = true;
   element.value = '';
   file.remove(false);
   document.forms[0].removeChild(element);
   checkNotSufferingFromBeingMissing(element);
 }
 
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms", true]]}, function() {
+
 checkTextareaRequiredValidity();
 
 // The require attribute behavior depend of the input type.
 // First of all, checks for types that make the element barred from
 // constraint validation.
 var typeBarredFromConstraintValidation = ["hidden", "button", "reset", "submit", "image"];
 for (type of typeBarredFromConstraintValidation) {
   checkInputRequiredNotApply(type, true);
@@ -365,25 +370,28 @@ for (type of typeBarredFromConstraintVal
 // Then, checks for the types which do not use the required attribute.
 // TODO: check 'color' and 'range' when they will be implemented.
 var typeRequireNotApply = [];
 for (type of typeRequireNotApply) {
   checkInputRequiredNotApply(type, false);
 }
 
 // Now, checking for all types which accept the required attribute.
-// TODO: check 'datetime', 'date', 'month', 'week', 'time' and 'datetime-local'
+// TODO: check 'datetime', 'month', 'week', 'time' and 'datetime-local'
 //       when they will be implemented.
 var typeRequireApply = ["text", "password", "search", "tel", "email", "url",
-                        "number"];
+                        "number", "date"];
 
 for (type of typeRequireApply) {
   checkInputRequiredValidity(type);
 }
 
 checkInputRequiredValidityForCheckbox();
 checkInputRequiredValidityForRadio();
 checkInputRequiredValidityForFile();
 
+SimpleTest.finish();
+});
+
 </script>
 </pre>
 </body>
 </html>
--- a/content/html/content/test/forms/test_step_attribute.html
+++ b/content/html/content/test/forms/test_step_attribute.html
@@ -23,17 +23,17 @@ var types = [
   [ 'hidden',         false ],
   [ 'text',           false ],
   [ 'search',         false ],
   [ 'tel',            false ],
   [ 'url',            false ],
   [ 'email',          false ],
   [ 'password',       false ],
   [ 'datetime',       true,  true ],
-  [ 'date',           true,  true ],
+  [ 'date',           true ],
   [ 'month',          true,  true ],
   [ 'week',           true,  true ],
   [ 'time',           true,  true ],
   [ 'datetime-local', true,  true ],
   [ 'number',         true ],
   [ 'range',          true,  true ],
   [ 'color',          false, true ],
   [ 'checkbox',       false ],
@@ -110,16 +110,142 @@ for (var data of types) {
                    0666, 0);
     outStream.write("foo", 3);
     outStream.close();
 
     input.value = file.path;
     checkValidity(input, true, apply);
 
     file.remove(false);
+  } else if (input.type == 'date') {
+    // For date, the step is calulated on the timestamp since 1970-01-01
+    // which mean that for all dates prior to the epoch, this timestamp is < 0
+    // and the behavior might differ, therefore we have to test for these cases.
+
+    // When step is 1 every date is valid
+    input.value = '2012-07-05';
+    checkValidity(input, true, apply);
+
+    input.step = 'foo';
+    input.value = '1970-01-01';
+    checkValidity(input, true, apply);
+
+    input.step = '-1';
+    input.value = '1969-12-12';
+    checkValidity(input, true, apply);
+
+    input.removeAttribute('step');
+    input.value = '1500-01-01';
+    checkValidity(input, true, apply);
+
+    input.step = 'any';
+    checkValidity(input, true, apply);
+
+    input.step = 'aNy';
+    checkValidity(input, true, apply);
+
+    input.step = 'AnY';
+    checkValidity(input, true, apply);
+
+    input.step = 'ANY';
+    checkValidity(input, true, apply);
+
+    // When min is set to a valid date, there is a step base.
+    input.min = '2008-02-28';
+    input.step = '2';
+    input.value = '2008-03-01';
+    checkValidity(input, true, apply);
+
+    input.value = '2008-02-29';
+    checkValidity(input, false, apply, { low: "2008-02-28", high: "2008-03-01" });
+
+    input.min = '2008-02-27';
+    input.value = '2008-02-28';
+    checkValidity(input, false, apply, { low: "2008-02-27", high: "2008-02-29" });
+
+    input.min = '2009-02-27';
+    input.value = '2009-02-28';
+    checkValidity(input, false, apply, { low: "2009-02-27", high: "2009-03-01" });
+
+    input.min = '2009-02-01';
+    input.step = '1.1';
+    input.value = '2009-02-02';
+    checkValidity(input, false, apply, { low: "2009-02-01", high: "2009-02-12" });
+
+    // Without any step attribute the date is valid
+    input.removeAttribute('step');
+    checkValidity(input, true, apply);
+
+    input.min = '1950-01-01';
+    input.step = '366';
+    input.value = '1951-01-01';
+    checkValidity(input, false, apply, { low: "1950-01-01", high: "1951-01-02" });
+
+    input.min = '1951-01-01';
+    input.step = '365';
+    input.value = '1952-01-01';
+    checkValidity(input, true, apply);
+
+    input.step = '0.9';
+    input.value = '1951-01-02';
+    checkValidity(input, false, apply, { low: "1951-01-01", high: "1951-01-10" });
+
+    input.value = '1951-01-10'
+    checkValidity(input, true, apply);
+
+    input.step = '0.5';
+    input.value = '1951-01-02';
+    checkValidity(input, true, apply);
+
+    input.step = '1.5';
+    input.value = '1951-01-03';
+    checkValidity(input, false, apply, { low: "1951-01-01", high: "1951-01-04" });
+
+    input.value = '1951-01-08';
+    checkValidity(input, false, apply, { low: "1951-01-07", high: "1951-01-10" });
+
+    input.step = '3000';
+    input.min= '1968-01-01';
+    input.value = '1968-05-12';
+    checkValidity(input, false, apply, { low: "1968-01-01", high: "1976-03-19" });
+
+    input.value = '1971-01-01';
+    checkValidity(input, false, apply, { low: "1968-01-01", high: "1976-03-19" });
+
+    input.value = '1991-01-01';
+    checkValidity(input, false, apply, { low: "1984-06-05", high: "1992-08-22" });
+
+    input.value = '1984-06-05';
+    checkValidity(input, true, apply);
+
+    input.value = '1992-08-22';
+    checkValidity(input, true, apply);
+
+    input.step = '1.1';
+    input.min = '1991-01-01';
+    input.value = '1991-01-01';
+    checkValidity(input, true, apply);
+
+    input.value = '1991-01-02';
+    checkValidity(input, false, apply, { low: "1991-01-01", high: "1991-01-12" });
+
+    input.value = '1991-01-12';
+    checkValidity(input, true, apply);
+
+    input.step = '1.1';
+    input.min = '1969-12-20';
+    input.value = '1969-12-20';
+    checkValidity(input, true, apply);
+
+    input.value = '1969-12-21';
+    checkValidity(input, false, apply, { low: "1969-12-20", high: "1969-12-31" });
+
+    input.value = '1969-12-31';
+    checkValidity(input, true, apply);
+
   } else {
     // When step=0, the allowed step is 1.
     input.value = '1.2';
     checkValidity(input, false, apply, { low: 1, high: 2 });
 
     input.value = '1';
     checkValidity(input, true, apply);
 
--- a/content/html/content/test/forms/test_stepup_stepdown.html
+++ b/content/html/content/test/forms/test_stepup_stepdown.html
@@ -42,25 +42,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],
   ];
 
   var element = document.createElement("input");
@@ -102,22 +102,23 @@ function checkAvailability()
     } catch (e) {
       exceptionCaught = true;
     }
     todo_is(exceptionCaught, !data[1],
             "stepUp() availability is not correct");
   }
 }
 
-function checkStepDownForNumber()
+function checkStepDown()
 {
-  // This testData is very similar to the one in checkStepUpForNumber
-  // with some changes relative to stepDown.
+  // This testData is very similar to the one in checkStepUp with some changes
+  // relative to stepDown.
   var testData = [
   /* Initial value | step | min | max | stepDown arg | final value | exception */
+  { type: 'number', data: [
     // Regular case.
     [ '1',   null,  null,  null,  null, '0',    false ],
     // Argument testing.
     [ '1',   null,  null,  null,  1,    '0',    false ],
     [ '9',   null,  null,  null,  9,    '0',    false ],
     [ '1',   null,  null,  null,  -1,   '2',    false ],
     [ '1',   null,  null,  null,  0,    '1',    false ],
     // Float values are rounded to integer (1.1 -> 1).
@@ -173,64 +174,134 @@ function checkStepDownForNumber()
     [ '',   null,   null,  null,  null, '',     false ],
     // With step = 'any'.
     [ '0',  'any',  null,  null,  1,    null,   true ],
     [ '0',  'ANY',  null,  null,  1,    null,   true ],
     [ '0',  'AnY',  null,  null,  1,    null,   true ],
     [ '0',  'aNy',  null,  null,  1,    null,   true ],
     // With @value = step base.
     [ '1',  '2',    null,  null,  null, '-1',   false ],
+  ]},
+  { type: 'date', data: [
+    // Regular case.
+    [ '2012-07-09',  null,  null,  null,  null, '2012-07-08',   false ],
+    // Argument testing.
+    [ '2012-07-09',  null,  null,  null,  1,    '2012-07-08',   false ],
+    [ '2012-07-09',  null,  null,  null,  5,    '2012-07-04',   false ],
+    [ '2012-07-09',  null,  null,  null,  -1,   '2012-07-10',   false ],
+    [ '2012-07-09',  null,  null,  null,  0,    '2012-07-09',   false ],
+    // Month/Year wrapping.
+    [ '2012-08-01',  null,  null,  null,  1,    '2012-07-31',   false ],
+    [ '1969-01-02',  null,  null,  null,  4,    '1968-12-29',   false ],
+    [ '1969-01-01',  null,  null,  null,  -365, '1970-01-01',   false ],
+    [ '2012-02-29',  null,  null,  null,  -1,   '2012-03-01',   false ],
+    // Float values are rounded to integer (1.1 -> 1).
+    [ '2012-01-02',  null,  null,  null,  1.1,  '2012-01-01',   false ],
+    [ '2012-01-02',  null,  null,  null,  1.9,  '2012-01-01',   false ],
+    // With step values.
+    [ '2012-01-03',  '0.5', null,  null,  null, '2012-01-02',   false ],
+    [ '2012-01-02',  '0.5', null,  null,  null, '2012-01-01',   false ],
+    [ '2012-01-01',  '2',   null,  null,  null, '2011-12-30',   false ],
+    [ '2012-01-02',  '0.25',null,  null,  4,    '2012-01-01',   false ],
+    [ '2012-01-15',  '1.1',  '2012-01-01', null,  1,    '2012-01-12',   false ],
+    [ '2012-01-12',  '1.1',  '2012-01-01', null,  2,    '2012-01-01',   false ],
+    [ '2012-01-23',  '1.1',  '2012-01-01', null,  10,   '2012-01-12',   false ],
+    [ '2012-01-23',  '1.1',  '2012-01-01', null,  11,   '2012-01-01',   false ],
+    [ '1968-01-12',  '1.1',  '1968-01-01', null,  8,    '1968-01-01',   false ],
+    // step = 0 isn't allowed (-> step = 1).
+    [ '2012-01-02',  '0',   null,  null,  null, '2012-01-01',   false ],
+    // step < 0 isn't allowed (-> step = 1).
+    [ '2012-01-02',  '-1',  null,  null,  null, '2012-01-01',   false ],
+    // step = NaN isn't allowed (-> step = 1).
+    [ '2012-01-02',  'foo', null,  null,  null, '2012-01-01',   false ],
+    // Min values testing.
+    [ '2012-01-03',  '1',    'foo',        null,  2,     '2012-01-01',  false ],
+    [ '2012-01-02',  '1',    '2012-01-01', null,  null,  '2012-01-01',  false ],
+    [ '2012-01-01',  '1',    '2012-01-01', null,  null,  '2012-01-01',  false ],
+    [ '2012-01-01',  '1',    '2012-01-10', null,  1,     '2012-01-01',  false ],
+    [ '2012-01-05',  '3',    '2012-01-01', null,  null,  '2012-01-04',  false ],
+    [ '1969-01-01',  '5',    '1969-01-01', '1969-01-02', null,  '1969-01-01',  false ],
+    // Max values testing.
+    [ '2012-01-02',  '1',    null,  'foo',         null,  '2012-01-01',  false ],
+    [ '2012-01-02',  null,   null,  '2012-01-05',  null,  '2012-01-01',  false ],
+    [ '2012-01-03',  null,   null,  '2012-01-03',  null,  '2012-01-02',  false ],
+    [ '2012-01-07',  null,   null,  '2012-01-04',  4,     '2012-01-03',  false ],
+    [ '2012-01-07',  '2',    null,  '2012-01-04',  3,     '2012-01-01',  false ],
+    // Step mismatch.
+    [ '2012-01-04',  '2',    '2012-01-01',  null,         null,  '2012-01-03',  false ],
+    [ '2012-01-06',  '2',    '2012-01-01',  null,         2,     '2012-01-03',  false ],
+    [ '2012-01-05',  '2',    '2012-01-04',  '2012-01-08', null,  '2012-01-04',  false ],
+    [ '1970-01-04',  '2',    null,          null,         null,  '1970-01-03',  false ],
+    [ '1970-01-09',  '3',    null,          null,         null,  '1970-01-07',  false ],
+    // Clamping.
+    [ '2012-05-01',  null,   null,          '2012-01-05', null,  '2012-01-05',  false ],
+    [ '1970-01-05',  '2',    '1970-01-02',  '1970-01-05', null,  '1970-01-04',  false ],
+    [ '1970-01-01',  '5',    '1970-01-02',  '1970-01-09', 10,    '1970-01-01',  false ],
+    [ '1970-01-07',  '5',    '1969-12-27',  '1970-01-06', 2,     '1970-01-01',  false ],
+    [ '1970-03-08',  '3',    '1970-02-01',  '1970-02-07', 15,    '1970-02-01',  false ],
+    [ '1970-01-10',  '3',    '1970-01-01',  '1970-01-06', 2,     '1970-01-04',  false ],
+    // value = "" (NaN).
+    [ '',   null,   null,  null,  null, '',    false ],
+    // With step = 'any'.
+    [ '2012-01-01',  'any',  null,  null,  1,  null,  true ],
+    [ '2012-01-01',  'ANY',  null,  null,  1,  null,  true ],
+    [ '2012-01-01',  'AnY',  null,  null,  1,  null,  true ],
+    [ '2012-01-01',  'aNy',  null,  null,  1,  null,  true ],
+  ]},
   ];
 
-  for (var data of testData) {
-    var element = document.createElement("input");
-    element.type = 'number';
-
-    if (data[0] != null) {
-      element.setAttribute('value', data[0]);
-    }
-
-    if (data[1] != null) {
-      element.step = data[1];
-    }
+  for (var test of testData) {
+    for (var data of test.data) {
+      var element = document.createElement("input");
+      element.type = test.type;
 
-    if (data[2] != null) {
-      element.min = data[2];
-    }
+      if (data[0] != null) {
+        element.setAttribute('value', data[0]);
+      }
 
-    if (data[3] != null) {
-      element.max = data[3];
-    }
+      if (data[1] != null) {
+        element.step = data[1];
+      }
 
-    var exceptionCaught = false;
-    try {
-      if (data[4] != null) {
-        element.stepDown(data[4]);
-      } else {
-        element.stepDown();
+      if (data[2] != null) {
+        element.min = data[2];
       }
 
-      is(element.value, data[5], "The value should be " + data[5]);
-    } catch (e) {
-      exceptionCaught = true;
-      is(element.value, data[0], e.name + "The value should not have changed");
-      is(e.name, 'InvalidStateError',
-         "It should be a InvalidStateError exception.");
-    } finally {
-      is(exceptionCaught, data[6], "exception status should be " + data[6]);
+      if (data[3] != null) {
+        element.max = data[3];
+      }
+
+      var exceptionCaught = false;
+      try {
+        if (data[4] != null) {
+          element.stepDown(data[4]);
+        } else {
+          element.stepDown();
+        }
+
+        is(element.value, data[5], "The value should be " + data[5]);
+      } catch (e) {
+        exceptionCaught = true;
+        is(element.value, data[0], e.name + "The value should not have changed");
+        is(e.name, 'InvalidStateError',
+           "It should be a InvalidStateError exception.");
+      } finally {
+        is(exceptionCaught, data[6], "exception status should be " + data[6]);
+      }
     }
   }
 }
 
-function checkStepUpForNumber()
+function checkStepUp()
 {
-  // This testData is very similar to the one in checkStepDownForNumber
-  // with some changes relative to stepUp.
+  // This testData is very similar to the one in checkStepDown with some changes
+  // relative to stepUp.
   var testData = [
   /* Initial value | step | min | max | stepUp arg | final value | exception */
+  { type: 'number', data: [
     // Regular case.
     [ '1',   null,  null,  null,  null, '2',   false ],
     // Argument testing.
     [ '1',   null,  null,  null,  1,    '2',   false ],
     [ '9',   null,  null,  null,  9,    '18',  false ],
     [ '1',   null,  null,  null,  -1,   '0',   false ],
     [ '1',   null,  null,  null,  0,    '1',   false ],
     // Float values are rounded to integer (1.1 -> 1).
@@ -283,65 +354,138 @@ function checkStepUpForNumber()
     [ '',   null,   null,  null,  null, '',    false ],
     // With step = 'any'.
     [ '0',  'any',  null,  null,  1,    null,  true ],
     [ '0',  'ANY',  null,  null,  1,    null,  true ],
     [ '0',  'AnY',  null,  null,  1,    null,  true ],
     [ '0',  'aNy',  null,  null,  1,    null,  true ],
     // With @value = step base.
     [ '1',  '2',    null,  null,  null, '3',   false ],
+  ]},
+  { type: 'date', data: [
+    // Regular case.
+    [ '2012-07-09',  null,  null,  null,  null, '2012-07-10',   false ],
+    // Argument testing.
+    [ '2012-07-09',  null,  null,  null,  1,    '2012-07-10',   false ],
+    [ '2012-07-09',  null,  null,  null,  9,    '2012-07-18',   false ],
+    [ '2012-07-09',  null,  null,  null,  -1,   '2012-07-08',   false ],
+    [ '2012-07-09',  null,  null,  null,  0,    '2012-07-09',   false ],
+    // Month/Year wrapping.
+    [ '2012-07-31',  null,  null,  null,  1,    '2012-08-01',   false ],
+    [ '1968-12-29',  null,  null,  null,  4,    '1969-01-02',   false ],
+    [ '1970-01-01',  null,  null,  null,  -365, '1969-01-01',   false ],
+    [ '2012-03-01',  null,  null,  null,  -1,   '2012-02-29',   false ],
+    // Float values are rounded to integer (1.1 -> 1).
+    [ '2012-01-01',  null,  null,  null,  1.1,  '2012-01-02',   false ],
+    [ '2012-01-01',  null,  null,  null,  1.9,  '2012-01-02',   false ],
+    // With step values.
+    [ '2012-01-01',  '0.5',  null,         null,  null, '2012-01-02',   false ],
+    [ '2012-01-01',  '0.5',  null,         null,  null, '2012-01-02',   false ],
+    [ '2012-01-01',  '2',    null,         null,  null, '2012-01-03',   false ],
+    [ '2012-01-01',  '0.25', null,         null,  4,    '2012-01-02',   false ],
+    [ '2012-01-01',  '1.1',  '2012-01-01', null,  1,    '2012-01-12',   false ],
+    [ '2012-01-01',  '1.1',  '2012-01-01', null,  2,    '2012-01-12',   false ],
+    [ '2012-01-01',  '1.1',  '2012-01-01', null,  10,   '2012-01-12',   false ],
+    [ '2012-01-01',  '1.1',  '2012-01-01', null,  11,   '2012-01-23',   false ],
+    [ '1968-01-01',  '1.1',  '1968-01-01', null,  8,    '1968-01-12',   false ],
+    // step = 0 isn't allowed (-> step = 1).
+    [ '2012-01-01',  '0',   null,  null,  null, '2012-01-02',   false ],
+    // step < 0 isn't allowed (-> step = 1).
+    [ '2012-01-01',  '-1',  null,  null,  null, '2012-01-02',   false ],
+    // step = NaN isn't allowed (-> step = 1).
+    [ '2012-01-01',  'foo', null,  null,  null, '2012-01-02',   false ],
+    // Min values testing.
+    [ '2012-01-01',  '1',   'foo',         null,  null,  '2012-01-02',  false ],
+    [ '2012-01-01',  null,  '2011-12-01',  null,  null,  '2012-01-02',  false ],
+    [ '2012-01-01',  null,  '2012-01-02',  null,  null,  '2012-01-02',  false ],
+    [ '2012-01-01',  null,  '2012-01-01',  null,  null,  '2012-01-02',  false ],
+    [ '2012-01-01',  null,  '2012-01-04',  null,  4,     '2012-01-05',  false ],
+    [ '2012-01-01',  '2',   '2012-01-04',  null,  3,     '2012-01-06',  false ],
+    // Max values testing.
+    [ '2012-01-01',  '1',    null,  'foo',        2,     '2012-01-03',  false ],
+    [ '2012-01-01',  '1',    null,  '2012-01-10', 1,     '2012-01-02',  false ],
+    [ '2012-01-02',  null,   null,  '2012-01-01', null,  '2012-01-02',  false ],
+    [ '2012-01-02',  null,   null,  '2012-01-02', null,  '2012-01-02',  false ],
+    [ '2012-01-02',  null,   null,  '2012-01-02', null,  '2012-01-02',  false ],
+    [ '1969-01-02',  '5',    '1969-01-01',  '1969-01-02', null,  '1969-01-02',  false ],
+    // Step mismatch.
+    [ '2012-01-02',  '2',    '2012-01-01',  null,         null,  '2012-01-03',  false ],
+    [ '2012-01-02',  '2',    '2012-01-01',  null,         2,     '2012-01-05',  false ],
+    [ '2012-01-05',  '2',    '2012-01-01',  '2012-01-06', null,  '2012-01-05',  false ],
+    [ '1970-01-02',  '2',    null,          null,         null,  '1970-01-03',  false ],
+    [ '1970-01-05',  '3',    null,          null,         null,  '1970-01-07',  false ],
+    [ '1970-01-03',  '3',    null,          null,         null,  '1970-01-04',  false ],
+    [ '1970-01-03',  '3',    '1970-01-02',  null,         null,  '1970-01-05',  false ],
+    // Clamping.
+    [ '2012-01-01',  null,   '2012-01-31',  null,         null,  '2012-01-31',  false ],
+    [ '1970-01-02',  '2',    '1970-01-01',  '1970-01-04', null,  '1970-01-03',  false ],
+    [ '1970-01-01',  '5',    '1970-01-02',  '1970-01-09', 10,    '1970-01-07',  false ],
+    [ '1969-12-28',  '5',    '1969-12-29',  '1970-01-06', 3,     '1970-01-03',  false ],
+    [ '1970-01-01',  '3',    '1970-02-01',  '1970-02-07', 15,    '1970-02-07',  false ],
+    [ '1970-01-01',  '3',    '1970-01-01',  '1970-01-06', 2,     '1970-01-04',  false ],
+    // value = "" (NaN).
+    [ '',   null,   null,  null,  null, '',    false ],
+    // With step = 'any'.
+    [ '2012-01-01',  'any',  null,  null,  1,  null,  true ],
+    [ '2012-01-01',  'ANY',  null,  null,  1,  null,  true ],
+    [ '2012-01-01',  'AnY',  null,  null,  1,  null,  true ],
+    [ '2012-01-01',  'aNy',  null,  null,  1,  null,  true ],
+  ]},
   ];
 
-  for (var data of testData) {
-    var element = document.createElement("input");
-    element.type = 'number';
-
-    if (data[0] != null) {
-      element.setAttribute('value', data[0]);
-    }
-
-    if (data[1] != null) {
-      element.step = data[1];
-    }
+  for (var test of testData) {
+    for (var data of test.data) {
+      var element = document.createElement("input");
+      element.type = test.type;
 
-    if (data[2] != null) {
-      element.min = data[2];
-    }
+      if (data[0] != null) {
+        element.setAttribute('value', data[0]);
+      }
 
-    if (data[3] != null) {
-      element.max = data[3];
-    }
+      if (data[1] != null) {
+        element.step = data[1];
+      }
 
-    var exceptionCaught = false;
-    try {
-      if (data[4] != null) {
-        element.stepUp(data[4]);
-      } else {
-        element.stepUp();
+      if (data[2] != null) {
+        element.min = data[2];
       }
 
-      is(element.value, data[5], "The value should be " + data[5]);
-    } catch (e) {
-      exceptionCaught = true;
-      is(element.value, data[0], e.name + "The value should not have changed");
-      is(e.name, 'InvalidStateError',
-         "It should be a InvalidStateError exception.");
-    } finally {
-      is(exceptionCaught, data[6], "exception status should be " + data[6]);
+      if (data[3] != null) {
+        element.max = data[3];
+      }
+
+      var exceptionCaught = false;
+      try {
+        if (data[4] != null) {
+          element.stepUp(data[4]);
+        } else {
+          element.stepUp();
+        }
+
+        is(element.value, data[5], "The value should be " + data[5]);
+      } catch (e) {
+        exceptionCaught = true;
+        is(element.value, data[0], e.name + "The value should not have changed");
+        is(e.name, 'InvalidStateError',
+           "It should be a InvalidStateError exception.");
+      } finally {
+        is(exceptionCaught, data[6], "exception status should be " + data[6]);
+      }
     }
   }
 }
 
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms", true]]}, function() {
 
 checkPresence();
 checkAvailability();
-checkStepDownForNumber();
-checkStepUpForNumber();
+
+checkStepDown();
+checkStepUp();
 
 SimpleTest.finish();
 });
 
 </script>
 </pre>
 </body>
 </html>
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
@@ -121,61 +121,190 @@ function checkGet()
     ["", null], // the empty string is not a number
     ["foo", null],
     ["42,13", null], // comma can't be used as a decimal separator
   ];
 
   element.type = "number";
   for (data of testData) {
     element.value = data[0];
+
+    // Given that NaN != NaN, we have to use null when the expected value is NaN.
     if (data[1] != null) {
       is(element.valueAsNumber, data[1], "valueAsNumber should return the " +
          "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
     [2e1, "20"],
     [1e-1, "0.1"], // value after e can be negative
     [1E2, "100"], // E can be used instead of e
-    ["", null], // the empty string is not a number
-    ["foo", null],
+    // Setting a string will set NaN.
+    ["foo", ""],
+    // "" is converted to 0.
+    ["", 0],
+    [42, "42"], // Keep this here, it is used by the next test.
+    // Setting Infinity should throw and not change the current value.
+    [Infinity, 42, true],
+    [-Infinity, 42, true],
+    // Setting NaN should change the value to the empty string.
+    [NaN, ""],
   ];
 
   element.type = "number";
   for (data of testData) {
-    element.valueAsNumber = data[0];
-    if (data[1] != null) {
+    var caught = false;
+    try {
+      element.valueAsNumber = data[0];
       is(element.value, data[1],
          "valueAsNumber should be able to set the value");
+    } catch (e) {
+      caught = true;
+    }
+
+    if (data[2]) {
+      ok(caught, "valueAsNumber should have thrown");
+      is(element.value, data[1], "value should not have changed");
     } else {
-      element.valueAsNumber = testData[0];
-      isnot(element.value, data[1],
-            "valueAsNumber should not set the value if it's not a number");
+      ok(!caught, "valueAsNumber should not have thrown");
     }
   }
 }
 
+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,   "" ],
+    // Infinity will keep the current value and throw (so we need to set a current value).
+    [ 1298851200010, "2011-02-28" ],
+    [ Infinity, "2011-02-28", true ],
+    [ -Infinity, "2011-02-28", true ],
+  ];
+
+  element.type = "date";
+  for (data of testData) {
+    var caught = false;
+
+    try {
+      element.valueAsNumber = data[0];
+      is(element.value, data[1], "valueAsNumber should set the value to " + data[1]);
+    } catch(e) {
+      caught = true;
+    }
+
+    if (data[2]) {
+      ok(caught, "valueAsNumber should have trhown"); 
+      is(element.value, data[1], "the value should not have changed");
+    } else {
+      ok(!caught, "valueAsNumber should not have thrown");
+    }
+  }
+
+}
+
 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/content/html/content/test/test_bug590363.html
+++ b/content/html/content/test/test_bug590363.html
@@ -30,32 +30,41 @@ var testData = [
   [ "submit",   false ],
   [ "tel",      true ],
   [ "text",     true ],
   [ "url",      true ],
   [ "email",    true ],
   [ "search",   true ],
   [ "password", true ],
   [ "number",   true ],
+  [ "date",     true ],
   // 'file' is treated separatly.
 ];
 
 var todoTypes = [
-  "datetime", "date", "month", "week", "time", "datetime-local", "range", "color"
+  "datetime", "month", "week", "time", "datetime-local", "range", "color"
 ];
 
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms", true]]}, function() {
+
 var length = testData.length;
 for (var i=0; i<length; ++i) {
   for (var j=0; j<length; ++j) {
     var e = document.createElement('input');
     e.type = testData[i][0];
 
     var expectedValue;
-    if (testData[i][0] == 'number' || testData[j][0] == 'number') {
+    if ((testData[i][0] == 'number' && testData[j][0] == 'date') ||
+        (testData[i][0] == 'date' && testData[j][0] == 'number')) {
+      expectedValue = '';
+    } else if (testData[i][0] == 'number' || testData[j][0] == 'number') {
       expectedValue = '42';
+    } else if (testData[i][0] == 'date' || testData[j][0] == 'date') {
+      expectedValue = '2012-12-21';
     } else {
       expectedValue = "foo";
     }
     e.value = expectedValue;
 
     e.type = testData[j][0];
     is(e.value, expectedValue, ".value should still return the same value after " +
        "changing type from " + testData[i][0] + " to " + testData[j][0]);
@@ -82,12 +91,15 @@ for (var data of testData) {
 
 // TODO checks
 for (var type of todoTypes) {
   var e = document.createElement('input');
   e.type = type;
   todo_is(e.type, type, type + " type isn't supported yet");
 }
 
+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;
--- a/embedding/components/webbrowserpersist/src/nsWebBrowserPersist.cpp
+++ b/embedding/components/webbrowserpersist/src/nsWebBrowserPersist.cpp
@@ -3259,16 +3259,17 @@ nsWebBrowserPersist::CloneNodeWithFixedU
             nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(*aNodeOut);
             switch (formControl->GetType()) {
                 case NS_FORM_INPUT_EMAIL:
                 case NS_FORM_INPUT_SEARCH:
                 case NS_FORM_INPUT_TEXT:
                 case NS_FORM_INPUT_TEL:
                 case NS_FORM_INPUT_URL:
                 case NS_FORM_INPUT_NUMBER:
+                case NS_FORM_INPUT_DATE:
                     nodeAsInput->GetValue(valueStr);
                     // Avoid superfluous value="" serialization
                     if (valueStr.IsEmpty())
                       outElt->RemoveAttribute(valueAttr);
                     else
                       outElt->SetAttribute(valueAttr, valueStr);
                     break;
                 case NS_FORM_INPUT_CHECKBOX:
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -3427,16 +3427,18 @@ nsCSSFrameConstructor::FindInputData(Ele
     SIMPLE_INT_CREATE(NS_FORM_INPUT_EMAIL, NS_NewTextControlFrame),
     SIMPLE_INT_CREATE(NS_FORM_INPUT_SEARCH, NS_NewTextControlFrame),
     SIMPLE_INT_CREATE(NS_FORM_INPUT_TEXT, NS_NewTextControlFrame),
     SIMPLE_INT_CREATE(NS_FORM_INPUT_TEL, NS_NewTextControlFrame),
     SIMPLE_INT_CREATE(NS_FORM_INPUT_URL, NS_NewTextControlFrame),
     SIMPLE_INT_CREATE(NS_FORM_INPUT_PASSWORD, NS_NewTextControlFrame),
     // TODO: this is temporary until a frame is written: bug 635240.
     SIMPLE_INT_CREATE(NS_FORM_INPUT_NUMBER, NS_NewTextControlFrame),
+    // TODO: this is temporary until a frame is written: bug 773205.
+    SIMPLE_INT_CREATE(NS_FORM_INPUT_DATE, NS_NewTextControlFrame),
     { NS_FORM_INPUT_SUBMIT,
       FCDATA_WITH_WRAPPING_BLOCK(0, NS_NewGfxButtonControlFrame,
                                  nsCSSAnonBoxes::buttonContent) },
     { NS_FORM_INPUT_RESET,
       FCDATA_WITH_WRAPPING_BLOCK(0, NS_NewGfxButtonControlFrame,
                                  nsCSSAnonBoxes::buttonContent) },
     { NS_FORM_INPUT_BUTTON,
       FCDATA_WITH_WRAPPING_BLOCK(0, NS_NewGfxButtonControlFrame,
--- a/toolkit/components/satchel/test/test_form_autocomplete.html
+++ b/toolkit/components/satchel/test/test_form_autocomplete.html
@@ -87,16 +87,22 @@ Form History test: form field autocomple
   </form>
 
   <!-- normal, basic form (with fieldname='searchbar-history') -->
   <form id="form13" onsubmit="return false;">
     <input  type="text" name="searchbar-history">
     <button type="submit">Submit</button>
   </form>
 
+  <!-- form with input type='date' -->
+  <form id="form14" onsubmit="return false;">
+    <input  type="date" name="field11">
+    <button type="submit">Submit</button>
+  </form>
+
 </div>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Form History autocomplete **/
 
 netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
@@ -129,20 +135,21 @@ fh.addEntry("field5", "1");
 fh.addEntry("field5", "12");
 fh.addEntry("field5", "123");
 fh.addEntry("field5", "1234");
 fh.addEntry("field6", "value");
 fh.addEntry("field7", "value");
 fh.addEntry("field8", "value");
 fh.addEntry("field9", "value");
 fh.addEntry("field10", "42");
+fh.addEntry("field11", "2010-10-10");
 fh.addEntry("searchbar-history", "blacklist test");
 
 // All these non-implemeted types might need autocomplete tests in the future.
-var todoTypes = [ "datetime", "date", "month", "week", "time", "datetime-local",
+var todoTypes = [ "datetime", "month", "week", "time", "datetime-local",
                   "range", "color" ];
 var todoInput = document.createElement("input");
 for (var type of todoTypes) {
   todoInput.type = type;
   todo_is(todoInput.type, type, type + " type shouldn't be implemented");
 }
 
 
@@ -725,16 +732,27 @@ function runTest(testNum) {
         break;
 
     case 404:
         checkMenuEntries(["42"]);
         doKey("down");
         doKey("return");
         checkForm("42");
 
+        input = $_(14, "field11");
+        restoreForm();
+        doKey("down");
+        break;
+
+    case 405:
+        checkMenuEntries(["2010-10-10"]);
+        doKey("down");
+        doKey("return");
+        checkForm("2010-10-10");
+
         // Go to test 500.
         fh.addEntry("field1", "value1");
         input = $_(1, "field1");
         testNum = 499;
 
         restoreForm();
         doKey("down");
         break;