summaryrefslogtreecommitdiffstats
path: root/dom/html/input
diff options
context:
space:
mode:
Diffstat (limited to 'dom/html/input')
-rw-r--r--dom/html/input/ButtonInputTypes.h73
-rw-r--r--dom/html/input/CheckableInputTypes.cpp36
-rw-r--r--dom/html/input/CheckableInputTypes.h55
-rw-r--r--dom/html/input/ColorInputType.h28
-rw-r--r--dom/html/input/DateTimeInputTypes.cpp501
-rw-r--r--dom/html/input/DateTimeInputTypes.h154
-rw-r--r--dom/html/input/FileInputType.cpp26
-rw-r--r--dom/html/input/FileInputType.h32
-rw-r--r--dom/html/input/HiddenInputType.h28
-rw-r--r--dom/html/input/InputType.cpp354
-rw-r--r--dom/html/input/InputType.h240
-rw-r--r--dom/html/input/NumericInputTypes.cpp166
-rw-r--r--dom/html/input/NumericInputTypes.h76
-rw-r--r--dom/html/input/SingleLineTextInputTypes.cpp289
-rw-r--r--dom/html/input/SingleLineTextInputTypes.h158
-rw-r--r--dom/html/input/moz.build36
16 files changed, 2252 insertions, 0 deletions
diff --git a/dom/html/input/ButtonInputTypes.h b/dom/html/input/ButtonInputTypes.h
new file mode 100644
index 0000000000..e891db00e0
--- /dev/null
+++ b/dom/html/input/ButtonInputTypes.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ButtonInputTypes_h__
+#define mozilla_dom_ButtonInputTypes_h__
+
+#include "mozilla/dom/InputType.h"
+
+namespace mozilla::dom {
+
+class ButtonInputTypeBase : public InputType {
+ public:
+ ~ButtonInputTypeBase() override = default;
+
+ protected:
+ explicit ButtonInputTypeBase(HTMLInputElement* aInputElement)
+ : InputType(aInputElement) {}
+};
+
+// input type=button
+class ButtonInputType : public ButtonInputTypeBase {
+ public:
+ static InputType* Create(HTMLInputElement* aInputElement, void* aMemory) {
+ return new (aMemory) ButtonInputType(aInputElement);
+ }
+
+ private:
+ explicit ButtonInputType(HTMLInputElement* aInputElement)
+ : ButtonInputTypeBase(aInputElement) {}
+};
+
+// input type=image
+class ImageInputType : public ButtonInputTypeBase {
+ public:
+ static InputType* Create(HTMLInputElement* aInputElement, void* aMemory) {
+ return new (aMemory) ImageInputType(aInputElement);
+ }
+
+ private:
+ explicit ImageInputType(HTMLInputElement* aInputElement)
+ : ButtonInputTypeBase(aInputElement) {}
+};
+
+// input type=reset
+class ResetInputType : public ButtonInputTypeBase {
+ public:
+ static InputType* Create(HTMLInputElement* aInputElement, void* aMemory) {
+ return new (aMemory) ResetInputType(aInputElement);
+ }
+
+ private:
+ explicit ResetInputType(HTMLInputElement* aInputElement)
+ : ButtonInputTypeBase(aInputElement) {}
+};
+
+// input type=submit
+class SubmitInputType : public ButtonInputTypeBase {
+ public:
+ static InputType* Create(HTMLInputElement* aInputElement, void* aMemory) {
+ return new (aMemory) SubmitInputType(aInputElement);
+ }
+
+ private:
+ explicit SubmitInputType(HTMLInputElement* aInputElement)
+ : ButtonInputTypeBase(aInputElement) {}
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_ButtonInputTypes_h__ */
diff --git a/dom/html/input/CheckableInputTypes.cpp b/dom/html/input/CheckableInputTypes.cpp
new file mode 100644
index 0000000000..f9d4d69d19
--- /dev/null
+++ b/dom/html/input/CheckableInputTypes.cpp
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/CheckableInputTypes.h"
+
+#include "mozilla/dom/HTMLInputElement.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+/* input type=checkbox */
+
+bool CheckboxInputType::IsValueMissing() const {
+ if (!mInputElement->IsRequired()) {
+ return false;
+ }
+
+ return !mInputElement->Checked();
+}
+
+nsresult CheckboxInputType::GetValueMissingMessage(nsAString& aMessage) {
+ return nsContentUtils::GetMaybeLocalizedString(
+ nsContentUtils::eDOM_PROPERTIES, "FormValidationCheckboxMissing",
+ mInputElement->OwnerDoc(), aMessage);
+}
+
+/* input type=radio */
+
+nsresult RadioInputType::GetValueMissingMessage(nsAString& aMessage) {
+ return nsContentUtils::GetMaybeLocalizedString(
+ nsContentUtils::eDOM_PROPERTIES, "FormValidationRadioMissing",
+ mInputElement->OwnerDoc(), aMessage);
+}
diff --git a/dom/html/input/CheckableInputTypes.h b/dom/html/input/CheckableInputTypes.h
new file mode 100644
index 0000000000..98c673685b
--- /dev/null
+++ b/dom/html/input/CheckableInputTypes.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_CheckableInputTypes_h__
+#define mozilla_dom_CheckableInputTypes_h__
+
+#include "mozilla/dom/InputType.h"
+
+namespace mozilla::dom {
+
+class CheckableInputTypeBase : public InputType {
+ public:
+ ~CheckableInputTypeBase() override = default;
+
+ protected:
+ explicit CheckableInputTypeBase(HTMLInputElement* aInputElement)
+ : InputType(aInputElement) {}
+};
+
+// input type=checkbox
+class CheckboxInputType : public CheckableInputTypeBase {
+ public:
+ static InputType* Create(HTMLInputElement* aInputElement, void* aMemory) {
+ return new (aMemory) CheckboxInputType(aInputElement);
+ }
+
+ bool IsValueMissing() const override;
+
+ nsresult GetValueMissingMessage(nsAString& aMessage) override;
+
+ private:
+ explicit CheckboxInputType(HTMLInputElement* aInputElement)
+ : CheckableInputTypeBase(aInputElement) {}
+};
+
+// input type=radio
+class RadioInputType : public CheckableInputTypeBase {
+ public:
+ static InputType* Create(HTMLInputElement* aInputElement, void* aMemory) {
+ return new (aMemory) RadioInputType(aInputElement);
+ }
+
+ nsresult GetValueMissingMessage(nsAString& aMessage) override;
+
+ private:
+ explicit RadioInputType(HTMLInputElement* aInputElement)
+ : CheckableInputTypeBase(aInputElement) {}
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_CheckableInputTypes_h__ */
diff --git a/dom/html/input/ColorInputType.h b/dom/html/input/ColorInputType.h
new file mode 100644
index 0000000000..b6749849b7
--- /dev/null
+++ b/dom/html/input/ColorInputType.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ColorInputType_h__
+#define mozilla_dom_ColorInputType_h__
+
+#include "mozilla/dom/InputType.h"
+
+namespace mozilla::dom {
+
+// input type=color
+class ColorInputType : public InputType {
+ public:
+ static InputType* Create(HTMLInputElement* aInputElement, void* aMemory) {
+ return new (aMemory) ColorInputType(aInputElement);
+ }
+
+ private:
+ explicit ColorInputType(HTMLInputElement* aInputElement)
+ : InputType(aInputElement) {}
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_ColorInputType_h__ */
diff --git a/dom/html/input/DateTimeInputTypes.cpp b/dom/html/input/DateTimeInputTypes.cpp
new file mode 100644
index 0000000000..56d6c57c49
--- /dev/null
+++ b/dom/html/input/DateTimeInputTypes.cpp
@@ -0,0 +1,501 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/DateTimeInputTypes.h"
+
+#include "js/Date.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "nsDOMTokenList.h"
+
+namespace mozilla::dom {
+
+const double DateTimeInputTypeBase::kMinimumYear = 1;
+const double DateTimeInputTypeBase::kMaximumYear = 275760;
+const double DateTimeInputTypeBase::kMaximumMonthInMaximumYear = 9;
+const double DateTimeInputTypeBase::kMaximumWeekInMaximumYear = 37;
+const double DateTimeInputTypeBase::kMsPerDay = 24 * 60 * 60 * 1000;
+
+bool DateTimeInputTypeBase::IsMutable() const {
+ return !mInputElement->IsDisabledOrReadOnly();
+}
+
+bool DateTimeInputTypeBase::IsValueMissing() const {
+ if (!mInputElement->IsRequired()) {
+ return false;
+ }
+
+ if (!IsMutable()) {
+ return false;
+ }
+
+ return IsValueEmpty();
+}
+
+bool DateTimeInputTypeBase::IsRangeOverflow() const {
+ Decimal maximum = mInputElement->GetMaximum();
+ if (maximum.isNaN()) {
+ return false;
+ }
+
+ Decimal value = mInputElement->GetValueAsDecimal();
+ if (value.isNaN()) {
+ return false;
+ }
+
+ return value > maximum;
+}
+
+bool DateTimeInputTypeBase::IsRangeUnderflow() const {
+ Decimal minimum = mInputElement->GetMinimum();
+ if (minimum.isNaN()) {
+ return false;
+ }
+
+ Decimal value = mInputElement->GetValueAsDecimal();
+ if (value.isNaN()) {
+ return false;
+ }
+
+ return value < minimum;
+}
+
+bool DateTimeInputTypeBase::HasStepMismatch() const {
+ Decimal value = mInputElement->GetValueAsDecimal();
+ return mInputElement->ValueIsStepMismatch(value);
+}
+
+bool DateTimeInputTypeBase::HasBadInput() const {
+ ShadowRoot* shadow = mInputElement->GetShadowRoot();
+ if (!shadow) {
+ return false;
+ }
+
+ Element* editWrapperElement = shadow->GetElementById(u"edit-wrapper"_ns);
+ if (!editWrapperElement) {
+ return false;
+ }
+
+ bool allEmpty = true;
+ // Empty field does not imply bad input, but incomplete field does.
+ for (Element* child = editWrapperElement->GetFirstElementChild(); child;
+ child = child->GetNextElementSibling()) {
+ if (!child->ClassList()->Contains(u"datetime-edit-field"_ns)) {
+ continue;
+ }
+ nsAutoString value;
+ child->GetAttr(nsGkAtoms::value, value);
+ if (!value.IsEmpty()) {
+ allEmpty = false;
+ break;
+ }
+ }
+
+ // If some fields are available but input element's value is empty implies it
+ // has been sanitized.
+ return !allEmpty && IsValueEmpty();
+}
+
+nsresult DateTimeInputTypeBase::GetRangeOverflowMessage(nsAString& aMessage) {
+ nsAutoString maxStr;
+ mInputElement->GetAttr(nsGkAtoms::max, maxStr);
+
+ return nsContentUtils::FormatMaybeLocalizedString(
+ aMessage, nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationDateTimeRangeOverflow", mInputElement->OwnerDoc(), maxStr);
+}
+
+nsresult DateTimeInputTypeBase::GetRangeUnderflowMessage(nsAString& aMessage) {
+ nsAutoString minStr;
+ mInputElement->GetAttr(nsGkAtoms::min, minStr);
+
+ return nsContentUtils::FormatMaybeLocalizedString(
+ aMessage, nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationDateTimeRangeUnderflow", mInputElement->OwnerDoc(),
+ minStr);
+}
+
+void DateTimeInputTypeBase::MinMaxStepAttrChanged() {
+ if (Element* dateTimeBoxElement = mInputElement->GetDateTimeBoxElement()) {
+ AsyncEventDispatcher::RunDOMEventWhenSafe(
+ *dateTimeBoxElement, u"MozNotifyMinMaxStepAttrChanged"_ns,
+ CanBubble::eNo, ChromeOnlyDispatch::eNo);
+ }
+}
+
+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
+
+nsresult DateInputType::GetBadInputMessage(nsAString& aMessage) {
+ return nsContentUtils::GetMaybeLocalizedString(
+ nsContentUtils::eDOM_PROPERTIES, "FormValidationInvalidDate",
+ mInputElement->OwnerDoc(), aMessage);
+}
+
+auto DateInputType::ConvertStringToNumber(const nsAString& aValue) const
+ -> StringToNumberResult {
+ uint32_t year, month, day;
+ if (!ParseDate(aValue, &year, &month, &day)) {
+ return {};
+ }
+ JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, day));
+ if (!time.isValid()) {
+ return {};
+ }
+ return {Decimal::fromDouble(time.toDouble())};
+}
+
+bool DateInputType::ConvertNumberToString(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 (std::isnan(year) || std::isnan(month) || std::isnan(day)) {
+ return false;
+ }
+
+ aResultString.AppendPrintf("%04.0f-%02.0f-%02.0f", year, month + 1, day);
+ return true;
+}
+
+// input type=time
+
+nsresult TimeInputType::GetBadInputMessage(nsAString& aMessage) {
+ return nsContentUtils::GetMaybeLocalizedString(
+ nsContentUtils::eDOM_PROPERTIES, "FormValidationInvalidTime",
+ mInputElement->OwnerDoc(), aMessage);
+}
+
+auto TimeInputType::ConvertStringToNumber(const nsAString& aValue) const
+ -> StringToNumberResult {
+ uint32_t milliseconds;
+ if (!ParseTime(aValue, &milliseconds)) {
+ return {};
+ }
+ return {Decimal(int32_t(milliseconds))};
+}
+
+bool TimeInputType::ConvertNumberToString(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, 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;
+}
+
+bool TimeInputType::HasReversedRange() const {
+ mozilla::Decimal maximum = mInputElement->GetMaximum();
+ if (maximum.isNaN()) {
+ return false;
+ }
+
+ mozilla::Decimal minimum = mInputElement->GetMinimum();
+ if (minimum.isNaN()) {
+ return false;
+ }
+
+ return maximum < minimum;
+}
+
+bool TimeInputType::IsReversedRangeUnderflowAndOverflow() const {
+ mozilla::Decimal maximum = mInputElement->GetMaximum();
+ mozilla::Decimal minimum = mInputElement->GetMinimum();
+ mozilla::Decimal value = mInputElement->GetValueAsDecimal();
+
+ MOZ_ASSERT(HasReversedRange(), "Must have reserved range.");
+
+ if (value.isNaN()) {
+ return false;
+ }
+
+ // When an element has a reversed range, and the value is more than the
+ // maximum and less than the minimum the element is simultaneously suffering
+ // from an underflow and suffering from an overflow.
+ return value > maximum && value < minimum;
+}
+
+bool TimeInputType::IsRangeOverflow() const {
+ return HasReversedRange() ? IsReversedRangeUnderflowAndOverflow()
+ : DateTimeInputTypeBase::IsRangeOverflow();
+}
+
+bool TimeInputType::IsRangeUnderflow() const {
+ return HasReversedRange() ? IsReversedRangeUnderflowAndOverflow()
+ : DateTimeInputTypeBase::IsRangeUnderflow();
+}
+
+nsresult TimeInputType::GetReversedRangeUnderflowAndOverflowMessage(
+ nsAString& aMessage) {
+ nsAutoString maxStr;
+ mInputElement->GetAttr(nsGkAtoms::max, maxStr);
+
+ nsAutoString minStr;
+ mInputElement->GetAttr(nsGkAtoms::min, minStr);
+
+ return nsContentUtils::FormatMaybeLocalizedString(
+ aMessage, nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationTimeReversedRangeUnderflowAndOverflow",
+ mInputElement->OwnerDoc(), minStr, maxStr);
+}
+
+nsresult TimeInputType::GetRangeOverflowMessage(nsAString& aMessage) {
+ return HasReversedRange()
+ ? GetReversedRangeUnderflowAndOverflowMessage(aMessage)
+ : DateTimeInputTypeBase::GetRangeOverflowMessage(aMessage);
+}
+
+nsresult TimeInputType::GetRangeUnderflowMessage(nsAString& aMessage) {
+ return HasReversedRange()
+ ? GetReversedRangeUnderflowAndOverflowMessage(aMessage)
+ : DateTimeInputTypeBase::GetRangeUnderflowMessage(aMessage);
+}
+
+// input type=week
+
+nsresult WeekInputType::GetBadInputMessage(nsAString& aMessage) {
+ return nsContentUtils::GetMaybeLocalizedString(
+ nsContentUtils::eDOM_PROPERTIES, "FormValidationInvalidWeek",
+ mInputElement->OwnerDoc(), aMessage);
+}
+
+auto WeekInputType::ConvertStringToNumber(const nsAString& aValue) const
+ -> StringToNumberResult {
+ uint32_t year, week;
+ if (!ParseWeek(aValue, &year, &week)) {
+ return {};
+ }
+ if (year < kMinimumYear || year > kMaximumYear) {
+ return {};
+ }
+ // Maximum week is 275760-W37, the week of 275760-09-13.
+ if (year == kMaximumYear && week > kMaximumWeekInMaximumYear) {
+ return {};
+ }
+ double days = DaysSinceEpochFromWeek(year, week);
+ return {Decimal::fromDouble(days * kMsPerDay)};
+}
+
+bool WeekInputType::ConvertNumberToString(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;
+
+ // Return if aValue is outside the valid JS date-time range.
+ if (std::isnan(year) || std::isnan(month) || std::isnan(day) ||
+ std::isnan(dayInYear)) {
+ return false;
+ }
+
+ // DayOfWeek requires the year to be non-negative.
+ if (year < 0) {
+ return false;
+ }
+
+ // 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
+
+nsresult MonthInputType::GetBadInputMessage(nsAString& aMessage) {
+ return nsContentUtils::GetMaybeLocalizedString(
+ nsContentUtils::eDOM_PROPERTIES, "FormValidationInvalidMonth",
+ mInputElement->OwnerDoc(), aMessage);
+}
+
+auto MonthInputType::ConvertStringToNumber(const nsAString& aValue) const
+ -> StringToNumberResult {
+ uint32_t year, month;
+ if (!ParseMonth(aValue, &year, &month)) {
+ return {};
+ }
+
+ if (year < kMinimumYear || year > kMaximumYear) {
+ return {};
+ }
+
+ // Maximum valid month is 275760-09.
+ if (year == kMaximumYear && month > kMaximumMonthInMaximumYear) {
+ return {};
+ }
+
+ int32_t months = MonthsSinceJan1970(year, month);
+ return {Decimal(int32_t(months))};
+}
+
+bool MonthInputType::ConvertNumberToString(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, 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
+
+nsresult DateTimeLocalInputType::GetBadInputMessage(nsAString& aMessage) {
+ return nsContentUtils::GetMaybeLocalizedString(
+ nsContentUtils::eDOM_PROPERTIES, "FormValidationInvalidDateTime",
+ mInputElement->OwnerDoc(), aMessage);
+}
+
+auto DateTimeLocalInputType::ConvertStringToNumber(
+ const nsAString& aValue) const -> StringToNumberResult {
+ uint32_t year, month, day, timeInMs;
+ if (!ParseDateTimeLocal(aValue, &year, &month, &day, &timeInMs)) {
+ return {};
+ }
+ JS::ClippedTime time =
+ JS::TimeClip(JS::MakeDate(year, month - 1, day, timeInMs));
+ if (!time.isValid()) {
+ return {};
+ }
+ return {Decimal::fromDouble(time.toDouble())};
+}
+
+bool DateTimeLocalInputType::ConvertNumberToString(
+ 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, 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 (std::isnan(year) || std::isnan(month) || std::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;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/html/input/DateTimeInputTypes.h b/dom/html/input/DateTimeInputTypes.h
new file mode 100644
index 0000000000..fa8805f67e
--- /dev/null
+++ b/dom/html/input/DateTimeInputTypes.h
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_DateTimeInputTypes_h__
+#define mozilla_dom_DateTimeInputTypes_h__
+
+#include "mozilla/dom/InputType.h"
+
+namespace mozilla::dom {
+
+class DateTimeInputTypeBase : public InputType {
+ public:
+ ~DateTimeInputTypeBase() override = default;
+
+ bool IsValueMissing() const override;
+ bool IsRangeOverflow() const override;
+ bool IsRangeUnderflow() const override;
+ bool HasStepMismatch() const override;
+ bool HasBadInput() const override;
+
+ nsresult GetRangeOverflowMessage(nsAString& aMessage) override;
+ nsresult GetRangeUnderflowMessage(nsAString& aMessage) override;
+
+ void MinMaxStepAttrChanged() override;
+
+ protected:
+ explicit DateTimeInputTypeBase(HTMLInputElement* aInputElement)
+ : InputType(aInputElement) {}
+
+ bool IsMutable() const override;
+
+ nsresult GetBadInputMessage(nsAString& aMessage) override = 0;
+
+ /**
+ * 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;
+ // Milliseconds in a day.
+ static const double kMsPerDay;
+};
+
+// input type=date
+class DateInputType : public DateTimeInputTypeBase {
+ public:
+ static InputType* Create(HTMLInputElement* aInputElement, void* aMemory) {
+ return new (aMemory) DateInputType(aInputElement);
+ }
+
+ nsresult GetBadInputMessage(nsAString& aMessage) override;
+
+ StringToNumberResult ConvertStringToNumber(const nsAString&) const override;
+ bool ConvertNumberToString(Decimal aValue,
+ nsAString& aResultString) const override;
+
+ private:
+ explicit DateInputType(HTMLInputElement* aInputElement)
+ : DateTimeInputTypeBase(aInputElement) {}
+};
+
+// input type=time
+class TimeInputType : public DateTimeInputTypeBase {
+ public:
+ static InputType* Create(HTMLInputElement* aInputElement, void* aMemory) {
+ return new (aMemory) TimeInputType(aInputElement);
+ }
+
+ nsresult GetBadInputMessage(nsAString& aMessage) override;
+
+ StringToNumberResult ConvertStringToNumber(const nsAString&) const override;
+ bool ConvertNumberToString(Decimal aValue,
+ nsAString& aResultString) const override;
+ bool IsRangeOverflow() const override;
+ bool IsRangeUnderflow() const override;
+ nsresult GetRangeOverflowMessage(nsAString& aMessage) override;
+ nsresult GetRangeUnderflowMessage(nsAString& aMessage) override;
+
+ private:
+ explicit TimeInputType(HTMLInputElement* aInputElement)
+ : DateTimeInputTypeBase(aInputElement) {}
+
+ // https://html.spec.whatwg.org/multipage/input.html#has-a-reversed-range
+ bool HasReversedRange() const;
+ bool IsReversedRangeUnderflowAndOverflow() const;
+ nsresult GetReversedRangeUnderflowAndOverflowMessage(nsAString& aMessage);
+};
+
+// input type=week
+class WeekInputType : public DateTimeInputTypeBase {
+ public:
+ static InputType* Create(HTMLInputElement* aInputElement, void* aMemory) {
+ return new (aMemory) WeekInputType(aInputElement);
+ }
+
+ nsresult GetBadInputMessage(nsAString& aMessage) override;
+ StringToNumberResult ConvertStringToNumber(const nsAString&) const override;
+ bool ConvertNumberToString(Decimal aValue,
+ nsAString& aResultString) const override;
+
+ private:
+ explicit WeekInputType(HTMLInputElement* aInputElement)
+ : DateTimeInputTypeBase(aInputElement) {}
+};
+
+// input type=month
+class MonthInputType : public DateTimeInputTypeBase {
+ public:
+ static InputType* Create(HTMLInputElement* aInputElement, void* aMemory) {
+ return new (aMemory) MonthInputType(aInputElement);
+ }
+
+ nsresult GetBadInputMessage(nsAString& aMessage) override;
+ StringToNumberResult ConvertStringToNumber(const nsAString&) const override;
+ bool ConvertNumberToString(Decimal aValue,
+ nsAString& aResultString) const override;
+
+ private:
+ explicit MonthInputType(HTMLInputElement* aInputElement)
+ : DateTimeInputTypeBase(aInputElement) {}
+};
+
+// input type=datetime-local
+class DateTimeLocalInputType : public DateTimeInputTypeBase {
+ public:
+ static InputType* Create(HTMLInputElement* aInputElement, void* aMemory) {
+ return new (aMemory) DateTimeLocalInputType(aInputElement);
+ }
+
+ nsresult GetBadInputMessage(nsAString& aMessage) override;
+ StringToNumberResult ConvertStringToNumber(const nsAString&) const override;
+ bool ConvertNumberToString(Decimal aValue,
+ nsAString& aResultString) const override;
+
+ private:
+ explicit DateTimeLocalInputType(HTMLInputElement* aInputElement)
+ : DateTimeInputTypeBase(aInputElement) {}
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_DateTimeInputTypes_h__ */
diff --git a/dom/html/input/FileInputType.cpp b/dom/html/input/FileInputType.cpp
new file mode 100644
index 0000000000..ed14aaa48d
--- /dev/null
+++ b/dom/html/input/FileInputType.cpp
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/FileInputType.h"
+
+#include "mozilla/dom/HTMLInputElement.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+bool FileInputType::IsValueMissing() const {
+ if (!mInputElement->IsRequired()) {
+ return false;
+ }
+
+ return mInputElement->GetFilesOrDirectoriesInternal().IsEmpty();
+}
+
+nsresult FileInputType::GetValueMissingMessage(nsAString& aMessage) {
+ return nsContentUtils::GetMaybeLocalizedString(
+ nsContentUtils::eDOM_PROPERTIES, "FormValidationFileMissing",
+ mInputElement->OwnerDoc(), aMessage);
+}
diff --git a/dom/html/input/FileInputType.h b/dom/html/input/FileInputType.h
new file mode 100644
index 0000000000..870ef93136
--- /dev/null
+++ b/dom/html/input/FileInputType.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_FileInputType_h__
+#define mozilla_dom_FileInputType_h__
+
+#include "mozilla/dom/InputType.h"
+
+namespace mozilla::dom {
+
+// input type=file
+class FileInputType : public InputType {
+ public:
+ static InputType* Create(HTMLInputElement* aInputElement, void* aMemory) {
+ return new (aMemory) FileInputType(aInputElement);
+ }
+
+ bool IsValueMissing() const override;
+
+ nsresult GetValueMissingMessage(nsAString& aMessage) override;
+
+ private:
+ explicit FileInputType(HTMLInputElement* aInputElement)
+ : InputType(aInputElement) {}
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_FileInputType_h__ */
diff --git a/dom/html/input/HiddenInputType.h b/dom/html/input/HiddenInputType.h
new file mode 100644
index 0000000000..ac7c9c571a
--- /dev/null
+++ b/dom/html/input/HiddenInputType.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_HiddenInputType_h__
+#define mozilla_dom_HiddenInputType_h__
+
+#include "mozilla/dom/InputType.h"
+
+namespace mozilla::dom {
+
+// input type=hidden
+class HiddenInputType : public InputType {
+ public:
+ static InputType* Create(HTMLInputElement* aInputElement, void* aMemory) {
+ return new (aMemory) HiddenInputType(aInputElement);
+ }
+
+ private:
+ explicit HiddenInputType(HTMLInputElement* aInputElement)
+ : InputType(aInputElement) {}
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_HiddenInputType_h__ */
diff --git a/dom/html/input/InputType.cpp b/dom/html/input/InputType.cpp
new file mode 100644
index 0000000000..1d6254c2e2
--- /dev/null
+++ b/dom/html/input/InputType.cpp
@@ -0,0 +1,354 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/InputType.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Likely.h"
+#include "nsIFormControl.h"
+#include "mozilla/dom/ButtonInputTypes.h"
+#include "mozilla/dom/CheckableInputTypes.h"
+#include "mozilla/dom/ColorInputType.h"
+#include "mozilla/dom/DateTimeInputTypes.h"
+#include "mozilla/dom/FileInputType.h"
+#include "mozilla/dom/HiddenInputType.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/dom/NumericInputTypes.h"
+#include "mozilla/dom/SingleLineTextInputTypes.h"
+
+#include "nsContentUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+constexpr Decimal InputType::kStepAny;
+
+/* static */ UniquePtr<InputType, InputType::DoNotDelete> InputType::Create(
+ HTMLInputElement* aInputElement, FormControlType aType, void* aMemory) {
+ UniquePtr<InputType, InputType::DoNotDelete> inputType;
+ switch (aType) {
+ // Single line text
+ case FormControlType::InputText:
+ inputType.reset(TextInputType::Create(aInputElement, aMemory));
+ break;
+ case FormControlType::InputTel:
+ inputType.reset(TelInputType::Create(aInputElement, aMemory));
+ break;
+ case FormControlType::InputEmail:
+ inputType.reset(EmailInputType::Create(aInputElement, aMemory));
+ break;
+ case FormControlType::InputSearch:
+ inputType.reset(SearchInputType::Create(aInputElement, aMemory));
+ break;
+ case FormControlType::InputPassword:
+ inputType.reset(PasswordInputType::Create(aInputElement, aMemory));
+ break;
+ case FormControlType::InputUrl:
+ inputType.reset(URLInputType::Create(aInputElement, aMemory));
+ break;
+ // Button
+ case FormControlType::InputButton:
+ inputType.reset(ButtonInputType::Create(aInputElement, aMemory));
+ break;
+ case FormControlType::InputSubmit:
+ inputType.reset(SubmitInputType::Create(aInputElement, aMemory));
+ break;
+ case FormControlType::InputImage:
+ inputType.reset(ImageInputType::Create(aInputElement, aMemory));
+ break;
+ case FormControlType::InputReset:
+ inputType.reset(ResetInputType::Create(aInputElement, aMemory));
+ break;
+ // Checkable
+ case FormControlType::InputCheckbox:
+ inputType.reset(CheckboxInputType::Create(aInputElement, aMemory));
+ break;
+ case FormControlType::InputRadio:
+ inputType.reset(RadioInputType::Create(aInputElement, aMemory));
+ break;
+ // Numeric
+ case FormControlType::InputNumber:
+ inputType.reset(NumberInputType::Create(aInputElement, aMemory));
+ break;
+ case FormControlType::InputRange:
+ inputType.reset(RangeInputType::Create(aInputElement, aMemory));
+ break;
+ // DateTime
+ case FormControlType::InputDate:
+ inputType.reset(DateInputType::Create(aInputElement, aMemory));
+ break;
+ case FormControlType::InputTime:
+ inputType.reset(TimeInputType::Create(aInputElement, aMemory));
+ break;
+ case FormControlType::InputMonth:
+ inputType.reset(MonthInputType::Create(aInputElement, aMemory));
+ break;
+ case FormControlType::InputWeek:
+ inputType.reset(WeekInputType::Create(aInputElement, aMemory));
+ break;
+ case FormControlType::InputDatetimeLocal:
+ inputType.reset(DateTimeLocalInputType::Create(aInputElement, aMemory));
+ break;
+ // Others
+ case FormControlType::InputColor:
+ inputType.reset(ColorInputType::Create(aInputElement, aMemory));
+ break;
+ case FormControlType::InputFile:
+ inputType.reset(FileInputType::Create(aInputElement, aMemory));
+ break;
+ case FormControlType::InputHidden:
+ inputType.reset(HiddenInputType::Create(aInputElement, aMemory));
+ break;
+ default:
+ inputType.reset(TextInputType::Create(aInputElement, aMemory));
+ }
+
+ return inputType;
+}
+
+bool InputType::IsMutable() const { return !mInputElement->IsDisabled(); }
+
+bool InputType::IsValueEmpty() const { return mInputElement->IsValueEmpty(); }
+
+void InputType::GetNonFileValueInternal(nsAString& aValue) const {
+ return mInputElement->GetNonFileValueInternal(aValue);
+}
+
+nsresult InputType::SetValueInternal(const nsAString& aValue,
+ const ValueSetterOptions& aOptions) {
+ RefPtr<HTMLInputElement> inputElement(mInputElement);
+ return inputElement->SetValueInternal(aValue, aOptions);
+}
+
+nsIFrame* InputType::GetPrimaryFrame() const {
+ return mInputElement->GetPrimaryFrame();
+}
+
+void InputType::DropReference() {
+ // Drop our (non ref-counted) reference.
+ mInputElement = nullptr;
+}
+
+bool InputType::IsTooLong() const { return false; }
+
+bool InputType::IsTooShort() const { return false; }
+
+bool InputType::IsValueMissing() const { return false; }
+
+bool InputType::HasTypeMismatch() const { return false; }
+
+Maybe<bool> InputType::HasPatternMismatch() const { return Some(false); }
+
+bool InputType::IsRangeOverflow() const { return false; }
+
+bool InputType::IsRangeUnderflow() const { return false; }
+
+bool InputType::HasStepMismatch() const { return false; }
+
+bool InputType::HasBadInput() const { return false; }
+
+nsresult InputType::GetValidationMessage(
+ nsAString& aValidationMessage,
+ nsIConstraintValidation::ValidityStateType aType) {
+ aValidationMessage.Truncate();
+
+ switch (aType) {
+ case nsIConstraintValidation::VALIDITY_STATE_TOO_LONG: {
+ int32_t maxLength = mInputElement->MaxLength();
+ int32_t textLength = mInputElement->InputTextLength(CallerType::System);
+ nsAutoString strMaxLength;
+ nsAutoString strTextLength;
+
+ strMaxLength.AppendInt(maxLength);
+ strTextLength.AppendInt(textLength);
+
+ return nsContentUtils::FormatMaybeLocalizedString(
+ aValidationMessage, nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationTextTooLong", mInputElement->OwnerDoc(), strMaxLength,
+ strTextLength);
+ }
+ case nsIConstraintValidation::VALIDITY_STATE_TOO_SHORT: {
+ int32_t minLength = mInputElement->MinLength();
+ int32_t textLength = mInputElement->InputTextLength(CallerType::System);
+ nsAutoString strMinLength;
+ nsAutoString strTextLength;
+
+ strMinLength.AppendInt(minLength);
+ strTextLength.AppendInt(textLength);
+
+ return nsContentUtils::FormatMaybeLocalizedString(
+ aValidationMessage, nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationTextTooShort", mInputElement->OwnerDoc(), strMinLength,
+ strTextLength);
+ }
+ case nsIConstraintValidation::VALIDITY_STATE_VALUE_MISSING:
+ return GetValueMissingMessage(aValidationMessage);
+ case nsIConstraintValidation::VALIDITY_STATE_TYPE_MISMATCH: {
+ return GetTypeMismatchMessage(aValidationMessage);
+ }
+ case nsIConstraintValidation::VALIDITY_STATE_PATTERN_MISMATCH: {
+ nsAutoString title;
+ mInputElement->GetAttr(nsGkAtoms::title, title);
+
+ if (title.IsEmpty()) {
+ return nsContentUtils::GetMaybeLocalizedString(
+ nsContentUtils::eDOM_PROPERTIES, "FormValidationPatternMismatch",
+ mInputElement->OwnerDoc(), aValidationMessage);
+ }
+
+ if (title.Length() >
+ nsIConstraintValidation::sContentSpecifiedMaxLengthMessage) {
+ title.Truncate(
+ nsIConstraintValidation::sContentSpecifiedMaxLengthMessage);
+ }
+ return nsContentUtils::FormatMaybeLocalizedString(
+ aValidationMessage, nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationPatternMismatchWithTitle", mInputElement->OwnerDoc(),
+ title);
+ }
+ case nsIConstraintValidation::VALIDITY_STATE_RANGE_OVERFLOW:
+ return GetRangeOverflowMessage(aValidationMessage);
+ case nsIConstraintValidation::VALIDITY_STATE_RANGE_UNDERFLOW:
+ return GetRangeUnderflowMessage(aValidationMessage);
+ case nsIConstraintValidation::VALIDITY_STATE_STEP_MISMATCH: {
+ Decimal value = mInputElement->GetValueAsDecimal();
+ if (MOZ_UNLIKELY(NS_WARN_IF(value.isNaN()))) {
+ // TODO(bug 1651070): This should ideally never happen, but we don't
+ // deal with lang changes correctly, so it could.
+ return GetBadInputMessage(aValidationMessage);
+ }
+
+ Decimal step = mInputElement->GetStep();
+ MOZ_ASSERT(step != kStepAny && step > Decimal(0));
+
+ Decimal stepBase = mInputElement->GetStepBase();
+
+ Decimal valueLow = value - NS_floorModulo(value - stepBase, step);
+ Decimal valueHigh = value + step - NS_floorModulo(value - stepBase, step);
+
+ Decimal maximum = mInputElement->GetMaximum();
+
+ if (maximum.isNaN() || valueHigh <= maximum) {
+ nsAutoString valueLowStr, valueHighStr;
+ ConvertNumberToString(valueLow, valueLowStr);
+ ConvertNumberToString(valueHigh, valueHighStr);
+
+ if (valueLowStr.Equals(valueHighStr)) {
+ return nsContentUtils::FormatMaybeLocalizedString(
+ aValidationMessage, nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationStepMismatchOneValue", mInputElement->OwnerDoc(),
+ valueLowStr);
+ }
+ return nsContentUtils::FormatMaybeLocalizedString(
+ aValidationMessage, nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationStepMismatch", mInputElement->OwnerDoc(),
+ valueLowStr, valueHighStr);
+ }
+
+ nsAutoString valueLowStr;
+ ConvertNumberToString(valueLow, valueLowStr);
+
+ return nsContentUtils::FormatMaybeLocalizedString(
+ aValidationMessage, nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationStepMismatchOneValue", mInputElement->OwnerDoc(),
+ valueLowStr);
+ }
+ case nsIConstraintValidation::VALIDITY_STATE_BAD_INPUT:
+ return GetBadInputMessage(aValidationMessage);
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown validity state");
+ return NS_ERROR_UNEXPECTED;
+ }
+}
+
+nsresult InputType::GetValueMissingMessage(nsAString& aMessage) {
+ return nsContentUtils::GetMaybeLocalizedString(
+ nsContentUtils::eDOM_PROPERTIES, "FormValidationValueMissing",
+ mInputElement->OwnerDoc(), aMessage);
+}
+
+nsresult InputType::GetTypeMismatchMessage(nsAString& aMessage) {
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult InputType::GetRangeOverflowMessage(nsAString& aMessage) {
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult InputType::GetRangeUnderflowMessage(nsAString& aMessage) {
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult InputType::GetBadInputMessage(nsAString& aMessage) {
+ return NS_ERROR_UNEXPECTED;
+}
+
+auto InputType::ConvertStringToNumber(const nsAString& aValue) const
+ -> StringToNumberResult {
+ NS_WARNING("InputType::ConvertStringToNumber called");
+ return {};
+}
+
+bool InputType::ConvertNumberToString(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);
+}
+
+bool InputType::ParseTime(const nsAString& aValue, uint32_t* aResult) const {
+ // see comment in InputType::ParseDate().
+ return HTMLInputElement::ParseTime(aValue, aResult);
+}
+
+bool InputType::ParseMonth(const nsAString& aValue, uint32_t* aYear,
+ uint32_t* aMonth) const {
+ // see comment in InputType::ParseDate().
+ return mInputElement->ParseMonth(aValue, aYear, aMonth);
+}
+
+bool InputType::ParseWeek(const nsAString& aValue, uint32_t* aYear,
+ uint32_t* aWeek) const {
+ // see comment in InputType::ParseDate().
+ return mInputElement->ParseWeek(aValue, aYear, aWeek);
+}
+
+bool InputType::ParseDateTimeLocal(const nsAString& aValue, uint32_t* aYear,
+ uint32_t* aMonth, uint32_t* aDay,
+ uint32_t* aTime) const {
+ // see comment in InputType::ParseDate().
+ return mInputElement->ParseDateTimeLocal(aValue, aYear, aMonth, aDay, aTime);
+}
+
+int32_t InputType::MonthsSinceJan1970(uint32_t aYear, uint32_t aMonth) const {
+ // see comment in InputType::ParseDate().
+ return mInputElement->MonthsSinceJan1970(aYear, aMonth);
+}
+
+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);
+}
diff --git a/dom/html/input/InputType.h b/dom/html/input/InputType.h
new file mode 100644
index 0000000000..a3599feadd
--- /dev/null
+++ b/dom/html/input/InputType.h
@@ -0,0 +1,240 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_InputType_h__
+#define mozilla_dom_InputType_h__
+
+#include <stdint.h>
+#include "mozilla/Decimal.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/TextControlState.h"
+#include "mozilla/UniquePtr.h"
+#include "nsIConstraintValidation.h"
+#include "nsString.h"
+#include "nsError.h"
+
+// This must come outside of any namespace, or else it won't overload with the
+// double based version in nsMathUtils.h
+inline mozilla::Decimal NS_floorModulo(mozilla::Decimal x, mozilla::Decimal y) {
+ return (x - y * (x / y).floor());
+}
+
+class nsIFrame;
+
+namespace mozilla::dom {
+class HTMLInputElement;
+
+/**
+ * A common superclass for different types of a HTMLInputElement.
+ */
+class InputType {
+ public:
+ using ValueSetterOption = TextControlState::ValueSetterOption;
+ using ValueSetterOptions = TextControlState::ValueSetterOptions;
+
+ // 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(); }
+ };
+
+ static UniquePtr<InputType, DoNotDelete> Create(
+ HTMLInputElement* aInputElement, FormControlType, void* aMemory);
+
+ virtual ~InputType() = default;
+
+ // Float value returned by GetStep() when the step attribute is set to 'any'.
+ static constexpr Decimal kStepAny = Decimal(0_d);
+
+ /**
+ * Drop the reference to the input element.
+ */
+ void DropReference();
+
+ virtual bool MinAndMaxLengthApply() const { return false; }
+ virtual bool IsTooLong() const;
+ virtual bool IsTooShort() const;
+ virtual bool IsValueMissing() const;
+ virtual bool HasTypeMismatch() const;
+ // May return Nothing() if the JS engine failed to evaluate the regex.
+ virtual Maybe<bool> HasPatternMismatch() const;
+ virtual bool IsRangeOverflow() const;
+ virtual bool IsRangeUnderflow() const;
+ virtual bool HasStepMismatch() const;
+ virtual bool HasBadInput() const;
+
+ nsresult GetValidationMessage(
+ nsAString& aValidationMessage,
+ nsIConstraintValidation::ValidityStateType aType);
+ virtual nsresult GetValueMissingMessage(nsAString& aMessage);
+ virtual nsresult GetTypeMismatchMessage(nsAString& aMessage);
+ virtual nsresult GetRangeOverflowMessage(nsAString& aMessage);
+ virtual nsresult GetRangeUnderflowMessage(nsAString& aMessage);
+ virtual nsresult GetBadInputMessage(nsAString& aMessage);
+
+ MOZ_CAN_RUN_SCRIPT virtual void MinMaxStepAttrChanged() {}
+
+ /**
+ * Convert a string to a Decimal 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.
+ */
+ struct StringToNumberResult {
+ // The result decimal. Successfully parsed if it's finite.
+ Decimal mResult = Decimal::nan();
+ // Whether the result required reading locale-dependent data (for input
+ // type=number), or the value parses using the regular HTML rules.
+ bool mLocalized = false;
+ };
+ virtual StringToNumberResult ConvertStringToNumber(
+ const nsAString& aValue) 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(Decimal aValue,
+ nsAString& aResultString) const;
+
+ protected:
+ explicit InputType(HTMLInputElement* aInputElement)
+ : mInputElement(aInputElement) {}
+
+ /**
+ * Get the mutable state of the element.
+ * When the element isn't mutable (immutable), the value or checkedness
+ * should not be changed by the user.
+ *
+ * See:
+ * https://html.spec.whatwg.org/multipage/forms.html#the-input-element:concept-fe-mutable
+ */
+ virtual bool IsMutable() const;
+
+ /**
+ * Returns whether the input element's current value is the empty string.
+ * This only makes sense for some input types; does NOT make sense for file
+ * inputs.
+ *
+ * @return whether the input element's current value is the empty string.
+ */
+ bool IsValueEmpty() const;
+
+ // A getter for callers that know we're not dealing with a file input, so they
+ // don't have to think about the caller type.
+ void GetNonFileValueInternal(nsAString& aValue) const;
+
+ /**
+ * Setting the input element's value.
+ *
+ * @param aValue String to set.
+ * @param aOptions See TextControlState::ValueSetterOption.
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult
+ SetValueInternal(const nsAString& aValue, const ValueSetterOptions& aOptions);
+
+ /**
+ * Get the primary frame for the input element.
+ */
+ nsIFrame* GetPrimaryFrame() const;
+
+ /**
+ * Parse a date string of the form yyyy-mm-dd
+ *
+ * @param aValue the string to be parsed.
+ * @return the date in aYear, aMonth, aDay.
+ * @return whether the parsing was successful.
+ */
+ bool ParseDate(const nsAString& aValue, uint32_t* aYear, uint32_t* aMonth,
+ uint32_t* aDay) const;
+
+ /**
+ * Returns the time expressed in milliseconds of |aValue| being parsed as a
+ * time following the HTML specifications:
+ * https://html.spec.whatwg.org/multipage/infrastructure.html#parse-a-time-string
+ *
+ * Note: |aResult| can be null.
+ *
+ * @param aValue the string to be parsed.
+ * @param aResult the time expressed in milliseconds representing the time
+ * [out]
+ * @return whether the parsing was successful.
+ */
+ bool ParseTime(const nsAString& aValue, uint32_t* aResult) const;
+
+ /**
+ * Parse a month string of the form yyyy-mm
+ *
+ * @param the string to be parsed.
+ * @return the year and month in aYear and aMonth.
+ * @return whether the parsing was successful.
+ */
+ bool ParseMonth(const nsAString& aValue, uint32_t* aYear,
+ uint32_t* aMonth) const;
+
+ /**
+ * Parse a week string of the form yyyy-Www
+ *
+ * @param the string to be parsed.
+ * @return the year and week in aYear and aWeek.
+ * @return whether the parsing was successful.
+ */
+ bool ParseWeek(const nsAString& aValue, uint32_t* aYear,
+ uint32_t* aWeek) const;
+
+ /**
+ * Parse a datetime-local string of the form yyyy-mm-ddThh:mm[:ss.s] or
+ * yyyy-mm-dd hh:mm[:ss.s], where fractions of seconds can be 1 to 3 digits.
+ *
+ * @param the string to be parsed.
+ * @return the date in aYear, aMonth, aDay and time expressed in milliseconds
+ * in aTime.
+ * @return whether the parsing was successful.
+ */
+ bool ParseDateTimeLocal(const nsAString& aValue, uint32_t* aYear,
+ uint32_t* aMonth, uint32_t* aDay,
+ uint32_t* aTime) const;
+
+ /**
+ * This methods returns the number of months between January 1970 and the
+ * given year and month.
+ */
+ 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;
+
+ HTMLInputElement* mInputElement;
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_InputType_h__ */
diff --git a/dom/html/input/NumericInputTypes.cpp b/dom/html/input/NumericInputTypes.cpp
new file mode 100644
index 0000000000..93941b30f7
--- /dev/null
+++ b/dom/html/input/NumericInputTypes.cpp
@@ -0,0 +1,166 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/NumericInputTypes.h"
+
+#include "mozilla/TextControlState.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "ICUUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+bool NumericInputTypeBase::IsRangeOverflow() const {
+ Decimal maximum = mInputElement->GetMaximum();
+ if (maximum.isNaN()) {
+ return false;
+ }
+
+ Decimal value = mInputElement->GetValueAsDecimal();
+ if (value.isNaN()) {
+ return false;
+ }
+
+ return value > maximum;
+}
+
+bool NumericInputTypeBase::IsRangeUnderflow() const {
+ Decimal minimum = mInputElement->GetMinimum();
+ if (minimum.isNaN()) {
+ return false;
+ }
+
+ Decimal value = mInputElement->GetValueAsDecimal();
+ if (value.isNaN()) {
+ return false;
+ }
+
+ return value < minimum;
+}
+
+bool NumericInputTypeBase::HasStepMismatch() const {
+ Decimal value = mInputElement->GetValueAsDecimal();
+ return mInputElement->ValueIsStepMismatch(value);
+}
+
+nsresult NumericInputTypeBase::GetRangeOverflowMessage(nsAString& aMessage) {
+ // We want to show the value as parsed when it's a number
+ Decimal maximum = mInputElement->GetMaximum();
+ MOZ_ASSERT(!maximum.isNaN());
+
+ nsAutoString maxStr;
+ ConvertNumberToString(maximum, maxStr);
+ return nsContentUtils::FormatMaybeLocalizedString(
+ aMessage, nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationNumberRangeOverflow", mInputElement->OwnerDoc(), maxStr);
+}
+
+nsresult NumericInputTypeBase::GetRangeUnderflowMessage(nsAString& aMessage) {
+ Decimal minimum = mInputElement->GetMinimum();
+ MOZ_ASSERT(!minimum.isNaN());
+
+ nsAutoString minStr;
+ ConvertNumberToString(minimum, minStr);
+ return nsContentUtils::FormatMaybeLocalizedString(
+ aMessage, nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationNumberRangeUnderflow", mInputElement->OwnerDoc(), minStr);
+}
+
+auto NumericInputTypeBase::ConvertStringToNumber(const nsAString& aValue) const
+ -> StringToNumberResult {
+ return {HTMLInputElement::StringToDecimal(aValue)};
+}
+
+bool NumericInputTypeBase::ConvertNumberToString(
+ 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, ArrayLength(buf));
+ aResultString.AssignASCII(buf);
+ MOZ_ASSERT(ok, "buf not big enough");
+
+ return ok;
+}
+
+/* input type=number */
+
+bool NumberInputType::IsValueMissing() const {
+ if (!mInputElement->IsRequired()) {
+ return false;
+ }
+
+ if (!IsMutable()) {
+ return false;
+ }
+
+ return IsValueEmpty();
+}
+
+bool NumberInputType::HasBadInput() const {
+ nsAutoString value;
+ GetNonFileValueInternal(value);
+ return !value.IsEmpty() && mInputElement->GetValueAsDecimal().isNaN();
+}
+
+auto NumberInputType::ConvertStringToNumber(const nsAString& aValue) const
+ -> StringToNumberResult {
+ auto result = NumericInputTypeBase::ConvertStringToNumber(aValue);
+ if (result.mResult.isFinite()) {
+ return result;
+ }
+ // Try to read the localized value from the user.
+ ICUUtils::LanguageTagIterForContent langTagIter(mInputElement);
+ result.mLocalized = true;
+ result.mResult =
+ Decimal::fromDouble(ICUUtils::ParseNumber(aValue, langTagIter));
+ return result;
+}
+
+bool NumberInputType::ConvertNumberToString(Decimal aValue,
+ nsAString& aResultString) const {
+ MOZ_ASSERT(aValue.isFinite(), "aValue must be a valid non-Infinite number.");
+
+ aResultString.Truncate();
+ ICUUtils::LanguageTagIterForContent langTagIter(mInputElement);
+ ICUUtils::LocalizeNumber(aValue.toDouble(), langTagIter, aResultString);
+ return true;
+}
+
+nsresult NumberInputType::GetValueMissingMessage(nsAString& aMessage) {
+ return nsContentUtils::GetMaybeLocalizedString(
+ nsContentUtils::eDOM_PROPERTIES, "FormValidationBadInputNumber",
+ mInputElement->OwnerDoc(), aMessage);
+}
+
+nsresult NumberInputType::GetBadInputMessage(nsAString& aMessage) {
+ return nsContentUtils::GetMaybeLocalizedString(
+ nsContentUtils::eDOM_PROPERTIES, "FormValidationBadInputNumber",
+ mInputElement->OwnerDoc(), aMessage);
+}
+
+bool NumberInputType::IsMutable() const {
+ return !mInputElement->IsDisabledOrReadOnly();
+}
+
+/* input type=range */
+void RangeInputType::MinMaxStepAttrChanged() {
+ // The value may need to change when @min/max/step changes since the value may
+ // have been invalid and can now change to a valid value, or vice versa. For
+ // example, consider: <input type=range value=-1 max=1 step=3>. The valid
+ // range is 0 to 1 while the nearest valid steps are -1 and 2 (the max value
+ // having prevented there being a valid step in range). Changing @max to/from
+ // 1 and a number greater than on equal to 3 should change whether we have a
+ // step mismatch or not.
+ // The value may also need to change between a value that results in a step
+ // mismatch and a value that results in overflow. For example, if @max in the
+ // example above were to change from 1 to -1.
+ nsAutoString value;
+ GetNonFileValueInternal(value);
+ SetValueInternal(value, TextControlState::ValueSetterOption::ByInternalAPI);
+}
diff --git a/dom/html/input/NumericInputTypes.h b/dom/html/input/NumericInputTypes.h
new file mode 100644
index 0000000000..a541961f8d
--- /dev/null
+++ b/dom/html/input/NumericInputTypes.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_NumericInputTypes_h__
+#define mozilla_dom_NumericInputTypes_h__
+
+#include "mozilla/dom/InputType.h"
+
+namespace mozilla::dom {
+
+class NumericInputTypeBase : public InputType {
+ public:
+ ~NumericInputTypeBase() override = default;
+
+ bool IsRangeOverflow() const override;
+ bool IsRangeUnderflow() const override;
+ bool HasStepMismatch() const override;
+
+ nsresult GetRangeOverflowMessage(nsAString& aMessage) override;
+ nsresult GetRangeUnderflowMessage(nsAString& aMessage) override;
+
+ StringToNumberResult ConvertStringToNumber(
+ const nsAString& aValue) const override;
+ bool ConvertNumberToString(Decimal aValue,
+ nsAString& aResultString) const override;
+
+ protected:
+ explicit NumericInputTypeBase(HTMLInputElement* aInputElement)
+ : InputType(aInputElement) {}
+};
+
+// input type=number
+class NumberInputType final : public NumericInputTypeBase {
+ public:
+ static InputType* Create(HTMLInputElement* aInputElement, void* aMemory) {
+ return new (aMemory) NumberInputType(aInputElement);
+ }
+
+ bool IsValueMissing() const override;
+ bool HasBadInput() const override;
+
+ nsresult GetValueMissingMessage(nsAString& aMessage) override;
+ nsresult GetBadInputMessage(nsAString& aMessage) override;
+
+ StringToNumberResult ConvertStringToNumber(const nsAString&) const override;
+ bool ConvertNumberToString(Decimal aValue,
+ nsAString& aResultString) const override;
+
+ protected:
+ bool IsMutable() const override;
+
+ private:
+ explicit NumberInputType(HTMLInputElement* aInputElement)
+ : NumericInputTypeBase(aInputElement) {}
+};
+
+// input type=range
+class RangeInputType : public NumericInputTypeBase {
+ public:
+ static InputType* Create(HTMLInputElement* aInputElement, void* aMemory) {
+ return new (aMemory) RangeInputType(aInputElement);
+ }
+
+ MOZ_CAN_RUN_SCRIPT void MinMaxStepAttrChanged() override;
+
+ private:
+ explicit RangeInputType(HTMLInputElement* aInputElement)
+ : NumericInputTypeBase(aInputElement) {}
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_NumericInputTypes_h__ */
diff --git a/dom/html/input/SingleLineTextInputTypes.cpp b/dom/html/input/SingleLineTextInputTypes.cpp
new file mode 100644
index 0000000000..18cb12f520
--- /dev/null
+++ b/dom/html/input/SingleLineTextInputTypes.cpp
@@ -0,0 +1,289 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/SingleLineTextInputTypes.h"
+
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/TextUtils.h"
+#include "HTMLSplitOnSpacesTokenizer.h"
+#include "nsContentUtils.h"
+#include "nsCRTGlue.h"
+#include "nsIIDNService.h"
+#include "nsIIOService.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+bool SingleLineTextInputTypeBase::IsMutable() const {
+ return !mInputElement->IsDisabledOrReadOnly();
+}
+
+bool SingleLineTextInputTypeBase::IsTooLong() const {
+ int32_t maxLength = mInputElement->MaxLength();
+
+ // Maxlength of -1 means attribute isn't set or parsing error.
+ if (maxLength == -1) {
+ return false;
+ }
+
+ int32_t textLength = mInputElement->InputTextLength(CallerType::System);
+
+ return textLength > maxLength;
+}
+
+bool SingleLineTextInputTypeBase::IsTooShort() const {
+ int32_t minLength = mInputElement->MinLength();
+
+ // Minlength of -1 means attribute isn't set or parsing error.
+ if (minLength == -1) {
+ return false;
+ }
+
+ int32_t textLength = mInputElement->InputTextLength(CallerType::System);
+
+ return textLength && textLength < minLength;
+}
+
+bool SingleLineTextInputTypeBase::IsValueMissing() const {
+ if (!mInputElement->IsRequired()) {
+ return false;
+ }
+
+ if (!IsMutable()) {
+ return false;
+ }
+
+ return IsValueEmpty();
+}
+
+Maybe<bool> SingleLineTextInputTypeBase::HasPatternMismatch() const {
+ if (!mInputElement->HasPatternAttribute()) {
+ return Some(false);
+ }
+
+ nsAutoString pattern;
+ if (!mInputElement->GetAttr(nsGkAtoms::pattern, pattern)) {
+ return Some(false);
+ }
+
+ nsAutoString value;
+ GetNonFileValueInternal(value);
+
+ if (value.IsEmpty()) {
+ return Some(false);
+ }
+
+ Document* doc = mInputElement->OwnerDoc();
+ Maybe<bool> result = nsContentUtils::IsPatternMatching(
+ value, std::move(pattern), doc,
+ mInputElement->HasAttr(nsGkAtoms::multiple));
+ return result ? Some(!*result) : Nothing();
+}
+
+/* input type=url */
+
+bool URLInputType::HasTypeMismatch() const {
+ nsAutoString value;
+ GetNonFileValueInternal(value);
+
+ if (value.IsEmpty()) {
+ return false;
+ }
+
+ /**
+ * TODO:
+ * The URL is not checked as the HTML5 specifications want it to be because
+ * there is no code to check for a valid URI/IRI according to 3986 and 3987
+ * RFC's at the moment, see bug 561586.
+ *
+ * RFC 3987 (IRI) implementation: bug 42899
+ *
+ * HTML5 specifications:
+ * http://dev.w3.org/html5/spec/infrastructure.html#valid-url
+ */
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService();
+ nsCOMPtr<nsIURI> uri;
+
+ return !NS_SUCCEEDED(ioService->NewURI(NS_ConvertUTF16toUTF8(value), nullptr,
+ nullptr, getter_AddRefs(uri)));
+}
+
+nsresult URLInputType::GetTypeMismatchMessage(nsAString& aMessage) {
+ return nsContentUtils::GetMaybeLocalizedString(
+ nsContentUtils::eDOM_PROPERTIES, "FormValidationInvalidURL",
+ mInputElement->OwnerDoc(), aMessage);
+}
+
+/* input type=email */
+
+bool EmailInputType::HasTypeMismatch() const {
+ nsAutoString value;
+ GetNonFileValueInternal(value);
+
+ if (value.IsEmpty()) {
+ return false;
+ }
+
+ return mInputElement->HasAttr(nsGkAtoms::multiple)
+ ? !IsValidEmailAddressList(value)
+ : !IsValidEmailAddress(value);
+}
+
+bool EmailInputType::HasBadInput() const {
+ // With regards to suffering from bad input the spec says that only the
+ // punycode conversion works, so we don't care whether the email address is
+ // valid or not here. (If the email address is invalid then we will be
+ // suffering from a type mismatch.)
+ nsAutoString value;
+ nsAutoCString unused;
+ uint32_t unused2;
+ GetNonFileValueInternal(value);
+ HTMLSplitOnSpacesTokenizer tokenizer(value, ',');
+ while (tokenizer.hasMoreTokens()) {
+ if (!PunycodeEncodeEmailAddress(tokenizer.nextToken(), unused, &unused2)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+nsresult EmailInputType::GetTypeMismatchMessage(nsAString& aMessage) {
+ return nsContentUtils::GetMaybeLocalizedString(
+ nsContentUtils::eDOM_PROPERTIES, "FormValidationInvalidEmail",
+ mInputElement->OwnerDoc(), aMessage);
+}
+
+nsresult EmailInputType::GetBadInputMessage(nsAString& aMessage) {
+ return nsContentUtils::GetMaybeLocalizedString(
+ nsContentUtils::eDOM_PROPERTIES, "FormValidationInvalidEmail",
+ mInputElement->OwnerDoc(), aMessage);
+}
+
+/* static */
+bool EmailInputType::IsValidEmailAddressList(const nsAString& aValue) {
+ HTMLSplitOnSpacesTokenizer tokenizer(aValue, ',');
+
+ while (tokenizer.hasMoreTokens()) {
+ if (!IsValidEmailAddress(tokenizer.nextToken())) {
+ return false;
+ }
+ }
+
+ return !tokenizer.separatorAfterCurrentToken();
+}
+
+/* static */
+bool EmailInputType::IsValidEmailAddress(const nsAString& aValue) {
+ // Email addresses can't be empty and can't end with a '.' or '-'.
+ if (aValue.IsEmpty() || aValue.Last() == '.' || aValue.Last() == '-') {
+ return false;
+ }
+
+ uint32_t atPos;
+ nsAutoCString value;
+ if (!PunycodeEncodeEmailAddress(aValue, value, &atPos) ||
+ atPos == (uint32_t)kNotFound || atPos == 0 ||
+ atPos == value.Length() - 1) {
+ // Could not encode, or "@" was not found, or it was at the start or end
+ // of the input - in all cases, not a valid email address.
+ return false;
+ }
+
+ uint32_t length = value.Length();
+ uint32_t i = 0;
+
+ // Parsing the username.
+ for (; i < atPos; ++i) {
+ char16_t c = value[i];
+
+ // The username characters have to be in this list to be valid.
+ if (!(IsAsciiAlpha(c) || IsAsciiDigit(c) || c == '.' || c == '!' ||
+ c == '#' || c == '$' || c == '%' || c == '&' || c == '\'' ||
+ c == '*' || c == '+' || c == '-' || c == '/' || c == '=' ||
+ c == '?' || c == '^' || c == '_' || c == '`' || c == '{' ||
+ c == '|' || c == '}' || c == '~')) {
+ return false;
+ }
+ }
+
+ // Skip the '@'.
+ ++i;
+
+ // The domain name can't begin with a dot or a dash.
+ if (value[i] == '.' || value[i] == '-') {
+ return false;
+ }
+
+ // Parsing the domain name.
+ for (; i < length; ++i) {
+ char16_t c = value[i];
+
+ if (c == '.') {
+ // A dot can't follow a dot or a dash.
+ if (value[i - 1] == '.' || value[i - 1] == '-') {
+ return false;
+ }
+ } else if (c == '-') {
+ // A dash can't follow a dot.
+ if (value[i - 1] == '.') {
+ return false;
+ }
+ } else if (!(IsAsciiAlpha(c) || IsAsciiDigit(c) || c == '-')) {
+ // The domain characters have to be in this list to be valid.
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/* static */
+bool EmailInputType::PunycodeEncodeEmailAddress(const nsAString& aEmail,
+ nsAutoCString& aEncodedEmail,
+ uint32_t* aIndexOfAt) {
+ nsAutoCString value = NS_ConvertUTF16toUTF8(aEmail);
+ *aIndexOfAt = (uint32_t)value.FindChar('@');
+
+ if (*aIndexOfAt == (uint32_t)kNotFound || *aIndexOfAt == value.Length() - 1) {
+ aEncodedEmail = value;
+ return true;
+ }
+
+ nsCOMPtr<nsIIDNService> idnSrv = do_GetService(NS_IDNSERVICE_CONTRACTID);
+ if (!idnSrv) {
+ NS_ERROR("nsIIDNService isn't present!");
+ return false;
+ }
+
+ uint32_t indexOfDomain = *aIndexOfAt + 1;
+
+ const nsDependentCSubstring domain = Substring(value, indexOfDomain);
+ bool ace;
+ if (NS_SUCCEEDED(idnSrv->IsACE(domain, &ace)) && !ace) {
+ nsAutoCString domainACE;
+ if (NS_FAILED(idnSrv->ConvertUTF8toACE(domain, domainACE))) {
+ return false;
+ }
+
+ // Bug 1788115 removed the 63 character limit from the
+ // IDNService::ConvertUTF8toACE so we check for that limit here as required
+ // by the spec: https://html.spec.whatwg.org/#valid-e-mail-address
+ nsCCharSeparatedTokenizer tokenizer(domainACE, '.');
+ while (tokenizer.hasMoreTokens()) {
+ if (tokenizer.nextToken().Length() > 63) {
+ return false;
+ }
+ }
+
+ value.Replace(indexOfDomain, domain.Length(), domainACE);
+ }
+
+ aEncodedEmail = value;
+ return true;
+}
diff --git a/dom/html/input/SingleLineTextInputTypes.h b/dom/html/input/SingleLineTextInputTypes.h
new file mode 100644
index 0000000000..afacc917a3
--- /dev/null
+++ b/dom/html/input/SingleLineTextInputTypes.h
@@ -0,0 +1,158 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_SingleLineTextInputTypes_h__
+#define mozilla_dom_SingleLineTextInputTypes_h__
+
+#include "mozilla/dom/InputType.h"
+
+namespace mozilla::dom {
+
+class SingleLineTextInputTypeBase : public InputType {
+ public:
+ ~SingleLineTextInputTypeBase() override = default;
+
+ bool MinAndMaxLengthApply() const final { return true; }
+ bool IsTooLong() const final;
+ bool IsTooShort() const final;
+ bool IsValueMissing() const final;
+ // Can return Nothing() if the JS engine failed to evaluate the pattern.
+ Maybe<bool> HasPatternMismatch() const final;
+
+ protected:
+ explicit SingleLineTextInputTypeBase(HTMLInputElement* aInputElement)
+ : InputType(aInputElement) {}
+
+ bool IsMutable() const override;
+};
+
+// input type=text
+class TextInputType : public SingleLineTextInputTypeBase {
+ public:
+ static InputType* Create(HTMLInputElement* aInputElement, void* aMemory) {
+ return new (aMemory) TextInputType(aInputElement);
+ }
+
+ private:
+ explicit TextInputType(HTMLInputElement* aInputElement)
+ : SingleLineTextInputTypeBase(aInputElement) {}
+};
+
+// input type=search
+class SearchInputType : public SingleLineTextInputTypeBase {
+ public:
+ static InputType* Create(HTMLInputElement* aInputElement, void* aMemory) {
+ return new (aMemory) SearchInputType(aInputElement);
+ }
+
+ private:
+ explicit SearchInputType(HTMLInputElement* aInputElement)
+ : SingleLineTextInputTypeBase(aInputElement) {}
+};
+
+// input type=tel
+class TelInputType : public SingleLineTextInputTypeBase {
+ public:
+ static InputType* Create(HTMLInputElement* aInputElement, void* aMemory) {
+ return new (aMemory) TelInputType(aInputElement);
+ }
+
+ private:
+ explicit TelInputType(HTMLInputElement* aInputElement)
+ : SingleLineTextInputTypeBase(aInputElement) {}
+};
+
+// input type=url
+class URLInputType : public SingleLineTextInputTypeBase {
+ public:
+ static InputType* Create(HTMLInputElement* aInputElement, void* aMemory) {
+ return new (aMemory) URLInputType(aInputElement);
+ }
+
+ bool HasTypeMismatch() const override;
+
+ nsresult GetTypeMismatchMessage(nsAString& aMessage) override;
+
+ private:
+ explicit URLInputType(HTMLInputElement* aInputElement)
+ : SingleLineTextInputTypeBase(aInputElement) {}
+};
+
+// input type=email
+class EmailInputType : public SingleLineTextInputTypeBase {
+ public:
+ static InputType* Create(HTMLInputElement* aInputElement, void* aMemory) {
+ return new (aMemory) EmailInputType(aInputElement);
+ }
+
+ bool HasTypeMismatch() const override;
+ bool HasBadInput() const override;
+
+ nsresult GetTypeMismatchMessage(nsAString& aMessage) override;
+ nsresult GetBadInputMessage(nsAString& aMessage) override;
+
+ private:
+ explicit EmailInputType(HTMLInputElement* aInputElement)
+ : SingleLineTextInputTypeBase(aInputElement) {}
+
+ /**
+ * This helper method returns true if aValue is a valid email address.
+ * This is following the HTML5 specification:
+ * http://dev.w3.org/html5/spec/forms.html#valid-e-mail-address
+ *
+ * @param aValue the email address to check.
+ * @result whether the given string is a valid email address.
+ */
+ static bool IsValidEmailAddress(const nsAString& aValue);
+
+ /**
+ * This helper method returns true if aValue is a valid email address list.
+ * Email address list is a list of email address separated by comas (,) which
+ * can be surrounded by space charecters.
+ * This is following the HTML5 specification:
+ * http://dev.w3.org/html5/spec/forms.html#valid-e-mail-address-list
+ *
+ * @param aValue the email address list to check.
+ * @result whether the given string is a valid email address list.
+ */
+ static bool IsValidEmailAddressList(const nsAString& aValue);
+
+ /**
+ * Takes aEmail and attempts to convert everything after the first "@"
+ * character (if anything) to punycode before returning the complete result
+ * via the aEncodedEmail out-param. The aIndexOfAt out-param is set to the
+ * index of the "@" character.
+ *
+ * If no "@" is found in aEmail, aEncodedEmail is simply set to aEmail and
+ * the aIndexOfAt out-param is set to kNotFound.
+ *
+ * Returns true in all cases unless an attempt to punycode encode fails. If
+ * false is returned, aEncodedEmail has not been set.
+ *
+ * This function exists because ConvertUTF8toACE() splits on ".", meaning that
+ * for 'user.name@sld.tld' it would treat "name@sld" as a label. We want to
+ * encode the domain part only.
+ */
+ static bool PunycodeEncodeEmailAddress(const nsAString& aEmail,
+ nsAutoCString& aEncodedEmail,
+ uint32_t* aIndexOfAt);
+};
+
+// input type=password
+class PasswordInputType : public SingleLineTextInputTypeBase {
+ public:
+ static InputType* Create(HTMLInputElement* aInputElement, void* aMemory) {
+ return new (aMemory) PasswordInputType(aInputElement);
+ }
+
+ private:
+ explicit PasswordInputType(HTMLInputElement* aInputElement)
+ : SingleLineTextInputTypeBase(aInputElement) {}
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_SingleLineTextInputTypes_h__ */
diff --git a/dom/html/input/moz.build b/dom/html/input/moz.build
new file mode 100644
index 0000000000..fa468f11e7
--- /dev/null
+++ b/dom/html/input/moz.build
@@ -0,0 +1,36 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.mozilla.dom += [
+ "ButtonInputTypes.h",
+ "CheckableInputTypes.h",
+ "ColorInputType.h",
+ "DateTimeInputTypes.h",
+ "FileInputType.h",
+ "HiddenInputType.h",
+ "InputType.h",
+ "NumericInputTypes.h",
+ "SingleLineTextInputTypes.h",
+]
+
+UNIFIED_SOURCES += [
+ "CheckableInputTypes.cpp",
+ "DateTimeInputTypes.cpp",
+ "FileInputType.cpp",
+ "InputType.cpp",
+ "NumericInputTypes.cpp",
+ "SingleLineTextInputTypes.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+LOCAL_INCLUDES += [
+ "/dom/base",
+ "/dom/html",
+ "/layout/forms",
+]
+
+FINAL_LIBRARY = "xul"