diff options
Diffstat (limited to 'dom/html/input')
-rw-r--r-- | dom/html/input/ButtonInputTypes.h | 73 | ||||
-rw-r--r-- | dom/html/input/CheckableInputTypes.cpp | 36 | ||||
-rw-r--r-- | dom/html/input/CheckableInputTypes.h | 55 | ||||
-rw-r--r-- | dom/html/input/ColorInputType.h | 28 | ||||
-rw-r--r-- | dom/html/input/DateTimeInputTypes.cpp | 501 | ||||
-rw-r--r-- | dom/html/input/DateTimeInputTypes.h | 154 | ||||
-rw-r--r-- | dom/html/input/FileInputType.cpp | 26 | ||||
-rw-r--r-- | dom/html/input/FileInputType.h | 32 | ||||
-rw-r--r-- | dom/html/input/HiddenInputType.h | 28 | ||||
-rw-r--r-- | dom/html/input/InputType.cpp | 354 | ||||
-rw-r--r-- | dom/html/input/InputType.h | 240 | ||||
-rw-r--r-- | dom/html/input/NumericInputTypes.cpp | 166 | ||||
-rw-r--r-- | dom/html/input/NumericInputTypes.h | 76 | ||||
-rw-r--r-- | dom/html/input/SingleLineTextInputTypes.cpp | 289 | ||||
-rw-r--r-- | dom/html/input/SingleLineTextInputTypes.h | 158 | ||||
-rw-r--r-- | dom/html/input/moz.build | 36 |
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" |