Bug 1363258 - Part 2: Factor ConvertNumberToString out of HTMLInputElement. r?smaug draft
authorJessica Jong <jjong@mozilla.com>
Wed, 10 May 2017 17:03:46 +0800
changeset 577750 8b0b7248f7e09f71cce2fe668ca27f1cf6508040
parent 577749 987e6b1fb9ddf61fbaf39caba4a46884c8b4858e
child 628574 3f5ebfd32a41560f27aa971320ad99e88e2296d7
push id58771
push userjjong@mozilla.com
push dateMon, 15 May 2017 09:24:10 +0000
reviewerssmaug
bugs1363258
milestone55.0a1
Bug 1363258 - Part 2: Factor ConvertNumberToString out of HTMLInputElement. r?smaug MozReview-Commit-ID: 8ZHrdvpjr0m
dom/html/HTMLInputElement.cpp
dom/html/HTMLInputElement.h
dom/html/input/DateTimeInputTypes.cpp
dom/html/input/DateTimeInputTypes.h
dom/html/input/InputType.cpp
dom/html/input/InputType.h
dom/html/input/NumericInputTypes.cpp
dom/html/input/NumericInputTypes.h
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -1938,185 +1938,21 @@ HTMLInputElement::SetValue(Decimal aValu
 
   if (aValue.isNaN()) {
     IgnoredErrorResult rv;
     SetValue(EmptyString(), aCallerType, rv);
     return;
   }
 
   nsAutoString value;
-  ConvertNumberToString(aValue, value);
+  mInputType->ConvertNumberToString(aValue, value);
   IgnoredErrorResult rv;
   SetValue(value, aCallerType, rv);
 }
 
-bool
-HTMLInputElement::ConvertNumberToString(Decimal aValue,
-                                        nsAString& aResultString) const
-{
-  MOZ_ASSERT(DoesValueAsNumberApply(),
-             "ConvertNumberToString is only implemented for types implementing .valueAsNumber");
-  MOZ_ASSERT(aValue.isFinite(),
-             "aValue must be a valid non-Infinite number.");
-
-  aResultString.Truncate();
-
-  switch (mType) {
-    case NS_FORM_INPUT_NUMBER:
-    case NS_FORM_INPUT_RANGE:
-      {
-        char buf[32];
-        bool ok = aValue.toString(buf, ArrayLength(buf));
-        aResultString.AssignASCII(buf);
-        MOZ_ASSERT(ok, "buf not big enough");
-        return ok;
-      }
-    case NS_FORM_INPUT_DATE:
-      {
-        // The specs (and our JS APIs) require |aValue| to be truncated.
-        aValue = aValue.floor();
-
-        double year = JS::YearFromTime(aValue.toDouble());
-        double month = JS::MonthFromTime(aValue.toDouble());
-        double day = JS::DayFromTime(aValue.toDouble());
-
-        if (IsNaN(year) || IsNaN(month) || IsNaN(day)) {
-          return false;
-        }
-
-        aResultString.AppendPrintf("%04.0f-%02.0f-%02.0f", year,
-                                   month + 1, day);
-
-        return true;
-      }
-    case NS_FORM_INPUT_TIME:
-      {
-        aValue = aValue.floor();
-        // Per spec, we need to truncate |aValue| and we should only represent
-        // times inside a day [00:00, 24:00[, which means that we should do a
-        // modulo on |aValue| using the number of milliseconds in a day (86400000).
-        uint32_t value =
-          NS_floorModulo(aValue, Decimal::fromDouble(kMsPerDay)).toDouble();
-
-        uint16_t milliseconds, seconds, minutes, hours;
-        if (!GetTimeFromMs(value, &hours, &minutes, &seconds, &milliseconds)) {
-          return false;
-        }
-
-        if (milliseconds != 0) {
-          aResultString.AppendPrintf("%02d:%02d:%02d.%03d",
-                                     hours, minutes, seconds, milliseconds);
-        } else if (seconds != 0) {
-          aResultString.AppendPrintf("%02d:%02d:%02d",
-                                     hours, minutes, seconds);
-        } else {
-          aResultString.AppendPrintf("%02d:%02d", hours, minutes);
-        }
-
-        return true;
-      }
-    case NS_FORM_INPUT_MONTH:
-      {
-        aValue = aValue.floor();
-
-        double month = NS_floorModulo(aValue, Decimal(12)).toDouble();
-        month = (month < 0 ? month + 12 : month);
-
-        double year = 1970 + (aValue.toDouble() - month) / 12;
-
-        // Maximum valid month is 275760-09.
-        if (year < kMinimumYear || year > kMaximumYear) {
-          return false;
-        }
-
-        if (year == kMaximumYear && month > 8) {
-          return false;
-        }
-
-        aResultString.AppendPrintf("%04.0f-%02.0f", year, month + 1);
-        return true;
-      }
-    case NS_FORM_INPUT_WEEK:
-      {
-        aValue = aValue.floor();
-
-        // Based on ISO 8601 date.
-        double year = JS::YearFromTime(aValue.toDouble());
-        double month = JS::MonthFromTime(aValue.toDouble());
-        double day = JS::DayFromTime(aValue.toDouble());
-        // Adding 1 since day starts from 0.
-        double dayInYear = JS::DayWithinYear(aValue.toDouble(), year) + 1;
-
-        // Adding 1 since month starts from 0.
-        uint32_t isoWeekday = DayOfWeek(year, month + 1, day, true);
-        // Target on Wednesday since ISO 8601 states that week 1 is the week
-        // with the first Thursday of that year.
-        uint32_t week = (dayInYear - isoWeekday + 10) / 7;
-
-        if (week < 1) {
-          year--;
-          if (year < 1) {
-            return false;
-          }
-          week = MaximumWeekInYear(year);
-        } else if (week > MaximumWeekInYear(year)) {
-          year++;
-          if (year > kMaximumYear ||
-              (year == kMaximumYear && week > kMaximumWeekInMaximumYear)) {
-            return false;
-          }
-          week = 1;
-        }
-
-        aResultString.AppendPrintf("%04.0f-W%02d", year, week);
-        return true;
-      }
-    case NS_FORM_INPUT_DATETIME_LOCAL:
-      {
-        aValue = aValue.floor();
-
-        uint32_t timeValue =
-          NS_floorModulo(aValue, Decimal::fromDouble(kMsPerDay)).toDouble();
-
-        uint16_t milliseconds, seconds, minutes, hours;
-        if (!GetTimeFromMs(timeValue,
-                           &hours, &minutes, &seconds, &milliseconds)) {
-          return false;
-        }
-
-        double year = JS::YearFromTime(aValue.toDouble());
-        double month = JS::MonthFromTime(aValue.toDouble());
-        double day = JS::DayFromTime(aValue.toDouble());
-
-        if (IsNaN(year) || IsNaN(month) || IsNaN(day)) {
-          return false;
-        }
-
-        if (milliseconds != 0) {
-          aResultString.AppendPrintf("%04.0f-%02.0f-%02.0fT%02d:%02d:%02d.%03d",
-                                     year, month + 1, day, hours, minutes,
-                                     seconds, milliseconds);
-        } else if (seconds != 0) {
-          aResultString.AppendPrintf("%04.0f-%02.0f-%02.0fT%02d:%02d:%02d",
-                                     year, month + 1, day, hours, minutes,
-                                     seconds);
-        } else {
-          aResultString.AppendPrintf("%04.0f-%02.0f-%02.0fT%02d:%02d",
-                                     year, month + 1, day, hours, minutes);
-        }
-
-        return true;
-      }
-    default:
-      MOZ_ASSERT(false, "Unrecognized input type");
-      return false;
-  }
-}
-
-
 Nullable<Date>
 HTMLInputElement::GetValueAsDate(ErrorResult& aRv)
 {
   if (!IsDateTimeInputType(mType)) {
     return Nullable<Date>();
   }
 
   switch (mType) {
@@ -4141,17 +3977,17 @@ HTMLInputElement::CancelRangeThumbDrag(b
   }
   if (aIsForUserEvent) {
     SetValueOfRangeForUserEvent(mRangeThumbDragStartValue);
   } else {
     // Don't dispatch an 'input' event - at least not using
     // DispatchTrustedEvent.
     // TODO: decide what we should do here - bug 851782.
     nsAutoString val;
-    ConvertNumberToString(mRangeThumbDragStartValue, val);
+    mInputType->ConvertNumberToString(mRangeThumbDragStartValue, val);
     // TODO: What should we do if SetValueInternal fails?  (The allocation
     // is small, so we should be fine here.)
     SetValueInternal(val, nsTextEditorState::eSetValue_BySetUserInput |
                           nsTextEditorState::eSetValue_Notify);
     nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
     if (frame) {
       frame->UpdateForValueChange();
     }
@@ -4164,17 +4000,17 @@ HTMLInputElement::CancelRangeThumbDrag(b
 void
 HTMLInputElement::SetValueOfRangeForUserEvent(Decimal aValue)
 {
   MOZ_ASSERT(aValue.isFinite());
 
   Decimal oldValue = GetValueAsDecimal();
 
   nsAutoString val;
-  ConvertNumberToString(aValue, val);
+  mInputType->ConvertNumberToString(aValue, val);
   // TODO: What should we do if SetValueInternal fails?  (The allocation
   // is small, so we should be fine here.)
   SetValueInternal(val, nsTextEditorState::eSetValue_BySetUserInput |
                         nsTextEditorState::eSetValue_Notify);
   nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
   if (frame) {
     frame->UpdateForValueChange();
   }
@@ -4267,17 +4103,17 @@ HTMLInputElement::StepNumberControlForUs
 
   nsresult rv = GetValueIfStepped(aDirection, CALLED_FOR_USER_EVENT, &newValue);
 
   if (NS_FAILED(rv) || !newValue.isFinite()) {
     return; // value should not or will not change
   }
 
   nsAutoString newVal;
-  ConvertNumberToString(newValue, newVal);
+  mInputType->ConvertNumberToString(newValue, newVal);
   // TODO: What should we do if SetValueInternal fails?  (The allocation
   // is small, so we should be fine here.)
   SetValueInternal(newVal, nsTextEditorState::eSetValue_BySetUserInput |
                            nsTextEditorState::eSetValue_Notify);
 
   nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
                                        static_cast<nsIDOMHTMLInputElement*>(this),
                                        NS_LITERAL_STRING("input"), true,
@@ -5415,39 +5251,16 @@ HTMLInputElement::MaximumWeekInYear(uint
   int day = DayOfWeek(aYear, 1, 1, true); // January 1.
   // A year starting on Thursday or a leap year starting on Wednesday has 53
   // weeks. All other years have 52 weeks.
   return day == 4 || (day == 3 && IsLeapYear(aYear)) ?
     kMaximumWeekInYear : kMaximumWeekInYear - 1;
 }
 
 bool
-HTMLInputElement::GetTimeFromMs(double aValue, uint16_t* aHours,
-                                uint16_t* aMinutes, uint16_t* aSeconds,
-                                uint16_t* aMilliseconds) const {
-  MOZ_ASSERT(aValue >= 0 && aValue < kMsPerDay,
-             "aValue must be milliseconds within a day!");
-
-  uint32_t value = floor(aValue);
-
-  *aMilliseconds = value % 1000;
-  value /= 1000;
-
-  *aSeconds = value % 60;
-  value /= 60;
-
-  *aMinutes = value % 60;
-  value /= 60;
-
-  *aHours = value;
-
-  return true;
-}
-
-bool
 HTMLInputElement::IsValidWeek(const nsAString& aValue) const
 {
   uint32_t year, week;
   return ParseWeek(aValue, &year, &week);
 }
 
 bool
 HTMLInputElement::IsValidMonth(const nsAString& aValue) const
@@ -7766,33 +7579,33 @@ HTMLInputElement::GetValidationMessage(n
 
       Decimal valueLow = value - NS_floorModulo(value - stepBase, step);
       Decimal valueHigh = value + step - NS_floorModulo(value - stepBase, step);
 
       Decimal maximum = GetMaximum();
 
       if (maximum.isNaN() || valueHigh <= maximum) {
         nsAutoString valueLowStr, valueHighStr;
-        ConvertNumberToString(valueLow, valueLowStr);
-        ConvertNumberToString(valueHigh, valueHighStr);
+        mInputType->ConvertNumberToString(valueLow, valueLowStr);
+        mInputType->ConvertNumberToString(valueHigh, valueHighStr);
 
         if (valueLowStr.Equals(valueHighStr)) {
           const char16_t* params[] = { valueLowStr.get() };
           rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
                                                      "FormValidationStepMismatchOneValue",
                                                      params, message);
         } else {
           const char16_t* params[] = { valueLowStr.get(), valueHighStr.get() };
           rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
                                                      "FormValidationStepMismatch",
                                                      params, message);
         }
       } else {
         nsAutoString valueLowStr;
-        ConvertNumberToString(valueLow, valueLowStr);
+        mInputType->ConvertNumberToString(valueLow, valueLowStr);
 
         const char16_t* params[] = { valueLowStr.get() };
         rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
                                                    "FormValidationStepMismatchOneValue",
                                                    params, message);
       }
 
       aValidationMessage = message;
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -1162,29 +1162,16 @@ protected:
    * Returns the radio group container if the element has one, null otherwise.
    * The radio group container will be the form owner if there is one.
    * The current document otherwise.
    * @return the radio group container if the element has one, null otherwise.
    */
   nsIRadioGroupContainer* GetRadioGroupContainer() const;
 
   /**
-   * Convert a Decimal 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 Decimal to be converted
-   * @param aResultString [out] the string representing the Decimal
-   * @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(Decimal aValue, nsAString& aResultString) const;
-
-  /**
    * Parse a color string of the form #XXXXXX where X should be hexa characters
    * @param the string to be parsed.
    * @return whether the string is a valid simple color.
    * Note : this function does not consider the empty string as valid.
    */
   bool IsValidSimpleColor(const nsAString& aValue) const;
 
   /**
@@ -1311,23 +1298,16 @@ protected:
 
   /**
    * This methods returns the maximum number of week in a given year, the
    * result is either 52 or 53.
    */
   uint32_t MaximumWeekInYear(uint32_t aYear) const;
 
   /**
-   * This method converts aValue (milliseconds within a day) to hours, minutes,
-   * seconds and milliseconds.
-   */
-  bool GetTimeFromMs(double aValue, uint16_t* aHours, uint16_t* aMinutes,
-                     uint16_t* aSeconds, uint16_t* aMilliseconds) const;
-
-  /**
    * This methods returns true if it's a leap year.
    */
   bool IsLeapYear(uint32_t aYear) const;
 
   /**
    * Returns whether aValue is a valid time as described by HTML specifications:
    * http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#valid-time-string
    *
--- a/dom/html/input/DateTimeInputTypes.cpp
+++ b/dom/html/input/DateTimeInputTypes.cpp
@@ -85,16 +85,39 @@ DateTimeInputTypeBase::HasStepMismatch(b
   if (step == kStepAny) {
     return false;
   }
 
   // Value has to be an integral multiple of step.
   return NS_floorModulo(value - GetStepBase(), step) != mozilla::Decimal(0);
 }
 
+bool
+DateTimeInputTypeBase::GetTimeFromMs(double aValue, uint16_t* aHours,
+                                     uint16_t* aMinutes, uint16_t* aSeconds,
+                                     uint16_t* aMilliseconds) const {
+  MOZ_ASSERT(aValue >= 0 && aValue < kMsPerDay,
+             "aValue must be milliseconds within a day!");
+
+  uint32_t value = floor(aValue);
+
+  *aMilliseconds = value % 1000;
+  value /= 1000;
+
+  *aSeconds = value % 60;
+  value /= 60;
+
+  *aMinutes = value % 60;
+  value /= 60;
+
+  *aHours = value;
+
+  return true;
+}
+
 // input type=date
 
 bool
 DateInputType::ConvertStringToNumber(nsAString& aValue,
                                      mozilla::Decimal& aResultValue) const
 {
   uint32_t year, month, day;
   if (!ParseDate(aValue, &year, &month, &day)) {
@@ -105,31 +128,87 @@ DateInputType::ConvertStringToNumber(nsA
   if (!time.isValid()) {
     return false;
   }
 
   aResultValue = mozilla::Decimal::fromDouble(time.toDouble());
   return true;
 }
 
+bool
+DateInputType::ConvertNumberToString(mozilla::Decimal aValue,
+                                     nsAString& aResultString) const
+{
+  MOZ_ASSERT(aValue.isFinite(), "aValue must be a valid non-Infinite number.");
+
+  aResultString.Truncate();
+
+  // The specs (and our JS APIs) require |aValue| to be truncated.
+  aValue = aValue.floor();
+
+  double year = JS::YearFromTime(aValue.toDouble());
+  double month = JS::MonthFromTime(aValue.toDouble());
+  double day = JS::DayFromTime(aValue.toDouble());
+
+  if (mozilla::IsNaN(year) || mozilla::IsNaN(month) || mozilla::IsNaN(day)) {
+    return false;
+  }
+
+  aResultString.AppendPrintf("%04.0f-%02.0f-%02.0f", year, month + 1, day);
+  return true;
+}
+
 // input type=time
 
 bool
 TimeInputType::ConvertStringToNumber(nsAString& aValue,
                                      mozilla::Decimal& aResultValue) const
 {
   uint32_t milliseconds;
   if (!ParseTime(aValue, &milliseconds)) {
     return false;
   }
 
   aResultValue = mozilla::Decimal(int32_t(milliseconds));
   return true;
 }
 
+bool
+TimeInputType::ConvertNumberToString(mozilla::Decimal aValue,
+                                     nsAString& aResultString) const
+{
+  MOZ_ASSERT(aValue.isFinite(), "aValue must be a valid non-Infinite number.");
+
+  aResultString.Truncate();
+
+  aValue = aValue.floor();
+  // Per spec, we need to truncate |aValue| and we should only represent
+  // times inside a day [00:00, 24:00[, which means that we should do a
+  // modulo on |aValue| using the number of milliseconds in a day (86400000).
+  uint32_t value =
+    NS_floorModulo(aValue, mozilla::Decimal::fromDouble(kMsPerDay)).toDouble();
+
+  uint16_t milliseconds, seconds, minutes, hours;
+  if (!GetTimeFromMs(value, &hours, &minutes, &seconds, &milliseconds)) {
+    return false;
+  }
+
+  if (milliseconds != 0) {
+    aResultString.AppendPrintf("%02d:%02d:%02d.%03d",
+                               hours, minutes, seconds, milliseconds);
+  } else if (seconds != 0) {
+    aResultString.AppendPrintf("%02d:%02d:%02d",
+                               hours, minutes, seconds);
+  } else {
+    aResultString.AppendPrintf("%02d:%02d", hours, minutes);
+  }
+
+  return true;
+}
+
 // input type=week
 
 bool
 WeekInputType::ConvertStringToNumber(nsAString& aValue,
                                      mozilla::Decimal& aResultValue) const
 {
   uint32_t year, week;
   if (!ParseWeek(aValue, &year, &week)) {
@@ -145,16 +224,58 @@ WeekInputType::ConvertStringToNumber(nsA
     return false;
   }
 
   double days = DaysSinceEpochFromWeek(year, week);
   aResultValue = mozilla::Decimal::fromDouble(days * kMsPerDay);
   return true;
 }
 
+bool
+WeekInputType::ConvertNumberToString(mozilla::Decimal aValue,
+                                     nsAString& aResultString) const
+{
+  MOZ_ASSERT(aValue.isFinite(), "aValue must be a valid non-Infinite number.");
+
+  aResultString.Truncate();
+
+  aValue = aValue.floor();
+
+  // Based on ISO 8601 date.
+  double year = JS::YearFromTime(aValue.toDouble());
+  double month = JS::MonthFromTime(aValue.toDouble());
+  double day = JS::DayFromTime(aValue.toDouble());
+  // Adding 1 since day starts from 0.
+  double dayInYear = JS::DayWithinYear(aValue.toDouble(), year) + 1;
+
+  // Adding 1 since month starts from 0.
+  uint32_t isoWeekday = DayOfWeek(year, month + 1, day, true);
+  // Target on Wednesday since ISO 8601 states that week 1 is the week
+  // with the first Thursday of that year.
+  uint32_t week = (dayInYear - isoWeekday + 10) / 7;
+
+  if (week < 1) {
+    year--;
+    if (year < 1) {
+      return false;
+    }
+    week = MaximumWeekInYear(year);
+  } else if (week > MaximumWeekInYear(year)) {
+    year++;
+    if (year > kMaximumYear ||
+        (year == kMaximumYear && week > kMaximumWeekInMaximumYear)) {
+      return false;
+    }
+    week = 1;
+  }
+
+  aResultString.AppendPrintf("%04.0f-W%02d", year, week);
+  return true;
+}
+
 // input type=month
 
 bool
 MonthInputType::ConvertStringToNumber(nsAString& aValue,
                                       mozilla::Decimal& aResultValue) const
 {
   uint32_t year, month;
   if (!ParseMonth(aValue, &year, &month)) {
@@ -170,16 +291,45 @@ MonthInputType::ConvertStringToNumber(ns
     return false;
   }
 
   int32_t months = MonthsSinceJan1970(year, month);
   aResultValue = mozilla::Decimal(int32_t(months));
   return true;
 }
 
+bool
+MonthInputType::ConvertNumberToString(mozilla::Decimal aValue,
+                                      nsAString& aResultString) const
+{
+  MOZ_ASSERT(aValue.isFinite(), "aValue must be a valid non-Infinite number.");
+
+  aResultString.Truncate();
+
+  aValue = aValue.floor();
+
+  double month = NS_floorModulo(aValue, mozilla::Decimal(12)).toDouble();
+  month = (month < 0 ? month + 12 : month);
+
+  double year = 1970 + (aValue.toDouble() - month) / 12;
+
+  // Maximum valid month is 275760-09.
+  if (year < kMinimumYear || year > kMaximumYear) {
+    return false;
+  }
+
+  if (year == kMaximumYear && month > 8) {
+    return false;
+  }
+
+  aResultString.AppendPrintf("%04.0f-%02.0f", year, month + 1);
+  return true;
+
+}
+
 // input type=datetime-local
 
 bool
 DateTimeLocalInputType::ConvertStringToNumber(
   nsAString& aValue, mozilla::Decimal& aResultValue) const
 {
   uint32_t year, month, day, timeInMs;
   if (!ParseDateTimeLocal(aValue, &year, &month, &day, &timeInMs)) {
@@ -190,8 +340,50 @@ DateTimeLocalInputType::ConvertStringToN
                                                    timeInMs));
   if (!time.isValid()) {
     return false;
   }
 
   aResultValue = mozilla::Decimal::fromDouble(time.toDouble());
   return true;
 }
+
+bool
+DateTimeLocalInputType::ConvertNumberToString(mozilla::Decimal aValue,
+                                              nsAString& aResultString) const
+{
+  MOZ_ASSERT(aValue.isFinite(), "aValue must be a valid non-Infinite number.");
+
+  aResultString.Truncate();
+
+  aValue = aValue.floor();
+
+  uint32_t timeValue =
+    NS_floorModulo(aValue, mozilla::Decimal::fromDouble(kMsPerDay)).toDouble();
+
+  uint16_t milliseconds, seconds, minutes, hours;
+  if (!GetTimeFromMs(timeValue, &hours, &minutes, &seconds, &milliseconds)) {
+    return false;
+  }
+
+  double year = JS::YearFromTime(aValue.toDouble());
+  double month = JS::MonthFromTime(aValue.toDouble());
+  double day = JS::DayFromTime(aValue.toDouble());
+
+  if (mozilla::IsNaN(year) || mozilla::IsNaN(month) || mozilla::IsNaN(day)) {
+    return false;
+  }
+
+  if (milliseconds != 0) {
+    aResultString.AppendPrintf("%04.0f-%02.0f-%02.0fT%02d:%02d:%02d.%03d",
+                               year, month + 1, day, hours, minutes,
+                               seconds, milliseconds);
+  } else if (seconds != 0) {
+    aResultString.AppendPrintf("%04.0f-%02.0f-%02.0fT%02d:%02d:%02d",
+                               year, month + 1, day, hours, minutes,
+                               seconds);
+  } else {
+    aResultString.AppendPrintf("%04.0f-%02.0f-%02.0fT%02d:%02d",
+                               year, month + 1, day, hours, minutes);
+  }
+
+  return true;
+}
--- a/dom/html/input/DateTimeInputTypes.h
+++ b/dom/html/input/DateTimeInputTypes.h
@@ -21,16 +21,23 @@ public:
 
 protected:
   explicit DateTimeInputTypeBase(mozilla::dom::HTMLInputElement* aInputElement)
     : InputType(aInputElement)
   {}
 
   bool IsMutable() const override;
 
+  /**
+   * This method converts aValue (milliseconds within a day) to hours, minutes,
+   * seconds and milliseconds.
+   */
+  bool GetTimeFromMs(double aValue, uint16_t* aHours, uint16_t* aMinutes,
+                     uint16_t* aSeconds, uint16_t* aMilliseconds) const;
+
   // Minimum year limited by HTML standard, year >= 1.
   static const double kMinimumYear;
   // Maximum year limited by ECMAScript date object range, year <= 275760.
   static const double kMaximumYear;
   // Maximum valid month is 275760-09.
   static const double kMaximumMonthInMaximumYear;
   // Maximum valid week is 275760-W37.
   static const double kMaximumWeekInMaximumYear;
@@ -45,16 +52,18 @@ public:
   static InputType*
   Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
   {
     return new (aMemory) DateInputType(aInputElement);
   }
 
   bool ConvertStringToNumber(nsAString& aValue,
                              mozilla::Decimal& aResultValue) const override;
+  bool ConvertNumberToString(mozilla::Decimal aValue,
+                             nsAString& aResultString) const override;
 
 private:
   explicit DateInputType(mozilla::dom::HTMLInputElement* aInputElement)
     : DateTimeInputTypeBase(aInputElement)
   {}
 };
 
 // input type=time
@@ -64,16 +73,18 @@ public:
   static InputType*
   Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
   {
     return new (aMemory) TimeInputType(aInputElement);
   }
 
   bool ConvertStringToNumber(nsAString& aValue,
                              mozilla::Decimal& aResultValue) const override;
+  bool ConvertNumberToString(mozilla::Decimal aValue,
+                             nsAString& aResultString) const override;
 
 private:
   explicit TimeInputType(mozilla::dom::HTMLInputElement* aInputElement)
     : DateTimeInputTypeBase(aInputElement)
   {}
 };
 
 // input type=week
@@ -83,16 +94,18 @@ public:
   static InputType*
   Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
   {
     return new (aMemory) WeekInputType(aInputElement);
   }
 
   bool ConvertStringToNumber(nsAString& aValue,
                              mozilla::Decimal& aResultValue) const override;
+  bool ConvertNumberToString(mozilla::Decimal aValue,
+                             nsAString& aResultString) const override;
 
 private:
   explicit WeekInputType(mozilla::dom::HTMLInputElement* aInputElement)
     : DateTimeInputTypeBase(aInputElement)
   {}
 };
 
 // input type=month
@@ -102,16 +115,18 @@ public:
   static InputType*
   Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
   {
     return new (aMemory) MonthInputType(aInputElement);
   }
 
   bool ConvertStringToNumber(nsAString& aValue,
                              mozilla::Decimal& aResultValue) const override;
+  bool ConvertNumberToString(mozilla::Decimal aValue,
+                             nsAString& aResultString) const override;
 
 private:
   explicit MonthInputType(mozilla::dom::HTMLInputElement* aInputElement)
     : DateTimeInputTypeBase(aInputElement)
   {}
 };
 
 // input type=datetime-local
@@ -121,16 +136,18 @@ public:
   static InputType*
   Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
   {
     return new (aMemory) DateTimeLocalInputType(aInputElement);
   }
 
   bool ConvertStringToNumber(nsAString& aValue,
                              mozilla::Decimal& aResultValue) const override;
+  bool ConvertNumberToString(mozilla::Decimal aValue,
+                             nsAString& aResultString) const override;
 
 private:
   explicit DateTimeLocalInputType(mozilla::dom::HTMLInputElement* aInputElement)
     : DateTimeInputTypeBase(aInputElement)
   {}
 };
 
 
--- a/dom/html/input/InputType.cpp
+++ b/dom/html/input/InputType.cpp
@@ -211,16 +211,25 @@ InputType::ConvertStringToNumber(nsAStri
                                  mozilla::Decimal& aResultValue) const
 {
   NS_WARNING("InputType::ConvertStringToNumber called");
 
   return false;
 }
 
 bool
+InputType::ConvertNumberToString(mozilla::Decimal aValue,
+                                 nsAString& aResultString) const
+{
+  NS_WARNING("InputType::ConvertNumberToString called");
+
+  return false;
+}
+
+bool
 InputType::ParseDate(const nsAString& aValue, uint32_t* aYear, uint32_t* aMonth,
                      uint32_t* aDay) const
 {
   // TODO: move this function and implementation to DateTimeInpuTypeBase when
   // refactoring is completed. Now we can only call HTMLInputElement::ParseDate
   // from here, since the method is protected and only InputType is a friend
   // class.
   return mInputElement->ParseDate(aValue, aYear, aMonth, aDay);
@@ -266,8 +275,23 @@ InputType::MonthsSinceJan1970(uint32_t a
 }
 
 double
 InputType::DaysSinceEpochFromWeek(uint32_t aYear, uint32_t aWeek) const
 {
   // see comment in InputType::ParseDate().
   return mInputElement->DaysSinceEpochFromWeek(aYear, aWeek);
 }
+
+uint32_t
+InputType::DayOfWeek(uint32_t aYear, uint32_t aMonth, uint32_t aDay,
+                     bool isoWeek) const
+{
+  // see comment in InputType::ParseDate().
+  return mInputElement->DayOfWeek(aYear, aMonth, aDay, isoWeek);
+}
+
+uint32_t
+InputType::MaximumWeekInYear(uint32_t aYear) const
+{
+  // see comment in InputType::ParseDate().
+  return mInputElement->MaximumWeekInYear(aYear);
+}
--- a/dom/html/input/InputType.h
+++ b/dom/html/input/InputType.h
@@ -69,16 +69,30 @@ public:
    * or parse a number string to its value if type=number.
    * @param aValue the string to be parsed.
    * @param aResultValue the number as a Decimal.
    * @result whether the parsing was successful.
    */
   virtual bool ConvertStringToNumber(nsAString& aValue,
                                      mozilla::Decimal& aResultValue) const;
 
+  /**
+   * Convert a Decimal 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 Decimal to be converted
+   * @param aResultString [out] the string representing the Decimal
+   * @return whether the function succeeded, 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.
+   */
+  virtual bool ConvertNumberToString(mozilla::Decimal aValue,
+                                     nsAString& aResultString) const;
+
 protected:
   explicit InputType(mozilla::dom::HTMLInputElement* aInputElement)
     : mInputElement(aInputElement)
   {}
 
   /**
    * Get the mutable state of the element.
    * When the element isn't mutable (immutable), the value or checkedness
@@ -191,16 +205,29 @@ protected:
   int32_t MonthsSinceJan1970(uint32_t aYear, uint32_t aMonth) const;
 
   /**
    * This methods returns the number of days since epoch for a given year and
    * week.
    */
   double DaysSinceEpochFromWeek(uint32_t aYear, uint32_t aWeek) const;
 
+  /**
+   * This methods returns the day of the week given a date. If @isoWeek is true,
+   * 7=Sunday, otherwise, 0=Sunday.
+   */
+  uint32_t DayOfWeek(uint32_t aYear, uint32_t aMonth, uint32_t aDay,
+                     bool isoWeek) const;
+
+  /**
+   * This methods returns the maximum number of week in a given year, the
+   * result is either 52 or 53.
+   */
+  uint32_t MaximumWeekInYear(uint32_t aYear) const;
+
   mozilla::dom::HTMLInputElement* mInputElement;
 };
 
 // Custom deleter for UniquePtr<InputType> to avoid freeing memory pre-allocated
 // for InputType, but we still need to call the destructor explictly.
 struct DoNotDelete { void operator()(::InputType* p) { p->~InputType(); } };
 
 #endif /* InputType_h__ */
--- a/dom/html/input/NumericInputTypes.cpp
+++ b/dom/html/input/NumericInputTypes.cpp
@@ -77,16 +77,32 @@ NumericInputTypeBase::ConvertStringToNum
 {
   aResultValue = mozilla::dom::HTMLInputElement::StringToDecimal(aValue);
   if (!aResultValue.isFinite()) {
     return false;
   }
   return true;
 }
 
+bool
+NumericInputTypeBase::ConvertNumberToString(mozilla::Decimal aValue,
+                                            nsAString& aResultString) const
+{
+  MOZ_ASSERT(aValue.isFinite(), "aValue must be a valid non-Infinite number.");
+
+  aResultString.Truncate();
+
+  char buf[32];
+  bool ok = aValue.toString(buf, mozilla::ArrayLength(buf));
+  aResultString.AssignASCII(buf);
+  MOZ_ASSERT(ok, "buf not big enough");
+
+  return ok;
+}
+
 /* input type=numer */
 
 bool
 NumberInputType::IsValueMissing() const
 {
   if (!mInputElement->HasAttr(kNameSpaceID_None, nsGkAtoms::required)) {
     return false;
   }
--- a/dom/html/input/NumericInputTypes.h
+++ b/dom/html/input/NumericInputTypes.h
@@ -15,16 +15,18 @@ public:
   ~NumericInputTypeBase() override {}
 
   bool IsRangeOverflow() const override;
   bool IsRangeUnderflow() const override;
   bool HasStepMismatch(bool aUseZeroIfValueNaN) const override;
 
   bool ConvertStringToNumber(nsAString& aValue,
                              mozilla::Decimal& aResultValue) const override;
+  bool ConvertNumberToString(mozilla::Decimal aValue,
+                             nsAString& aResultString) const override;
 
 protected:
   explicit NumericInputTypeBase(mozilla::dom::HTMLInputElement* aInputElement)
     : InputType(aInputElement)
   {}
 };
 
 // input type=number