tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

DateTimeInputTypes.cpp (15303B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "mozilla/dom/DateTimeInputTypes.h"
      8 
      9 #include "js/Date.h"
     10 #include "mozilla/AsyncEventDispatcher.h"
     11 #include "mozilla/StaticPrefs_dom.h"
     12 #include "mozilla/dom/HTMLInputElement.h"
     13 #include "mozilla/dom/ShadowRoot.h"
     14 #include "nsDOMTokenList.h"
     15 
     16 namespace mozilla::dom {
     17 
     18 const double DateTimeInputTypeBase::kMinimumYear = 1;
     19 const double DateTimeInputTypeBase::kMaximumYear = 275760;
     20 const double DateTimeInputTypeBase::kMaximumMonthInMaximumYear = 9;
     21 const double DateTimeInputTypeBase::kMaximumWeekInMaximumYear = 37;
     22 const double DateTimeInputTypeBase::kMsPerDay = 24 * 60 * 60 * 1000;
     23 
     24 bool DateTimeInputTypeBase::IsMutable() const {
     25  return !mInputElement->IsDisabledOrReadOnly();
     26 }
     27 
     28 bool DateTimeInputTypeBase::IsValueMissing() const {
     29  if (!mInputElement->IsRequired()) {
     30    return false;
     31  }
     32 
     33  if (!IsMutable()) {
     34    return false;
     35  }
     36 
     37  return IsValueEmpty();
     38 }
     39 
     40 bool DateTimeInputTypeBase::IsRangeOverflow() const {
     41  Decimal maximum = mInputElement->GetMaximum();
     42  if (maximum.isNaN()) {
     43    return false;
     44  }
     45 
     46  Decimal value = mInputElement->GetValueAsDecimal();
     47  if (value.isNaN()) {
     48    return false;
     49  }
     50 
     51  return value > maximum;
     52 }
     53 
     54 bool DateTimeInputTypeBase::IsRangeUnderflow() const {
     55  Decimal minimum = mInputElement->GetMinimum();
     56  if (minimum.isNaN()) {
     57    return false;
     58  }
     59 
     60  Decimal value = mInputElement->GetValueAsDecimal();
     61  if (value.isNaN()) {
     62    return false;
     63  }
     64 
     65  return value < minimum;
     66 }
     67 
     68 bool DateTimeInputTypeBase::HasStepMismatch() const {
     69  Decimal value = mInputElement->GetValueAsDecimal();
     70  return mInputElement->ValueIsStepMismatch(value);
     71 }
     72 
     73 bool DateTimeInputTypeBase::HasBadInput() const {
     74  ShadowRoot* shadow = mInputElement->GetShadowRoot();
     75  if (!shadow) {
     76    return false;
     77  }
     78 
     79  Element* editWrapperElement = shadow->GetElementById(u"edit-wrapper"_ns);
     80  if (!editWrapperElement) {
     81    return false;
     82  }
     83 
     84  bool allEmpty = true;
     85  // Empty field does not imply bad input, but incomplete field does.
     86  for (Element* child = editWrapperElement->GetFirstElementChild(); child;
     87       child = child->GetNextElementSibling()) {
     88    if (!child->ClassList()->Contains(u"datetime-edit-field"_ns)) {
     89      continue;
     90    }
     91    nsAutoString value;
     92    child->GetAttr(nsGkAtoms::value, value);
     93    if (!value.IsEmpty()) {
     94      allEmpty = false;
     95      break;
     96    }
     97  }
     98 
     99  // If some fields are available but input element's value is empty implies it
    100  // has been sanitized.
    101  return !allEmpty && IsValueEmpty();
    102 }
    103 
    104 nsresult DateTimeInputTypeBase::GetRangeOverflowMessage(nsAString& aMessage) {
    105  nsAutoString maxStr;
    106  mInputElement->GetAttr(nsGkAtoms::max, maxStr);
    107 
    108  return nsContentUtils::FormatMaybeLocalizedString(
    109      aMessage, nsContentUtils::eDOM_PROPERTIES,
    110      "FormValidationDateTimeRangeOverflow", mInputElement->OwnerDoc(), maxStr);
    111 }
    112 
    113 nsresult DateTimeInputTypeBase::GetRangeUnderflowMessage(nsAString& aMessage) {
    114  nsAutoString minStr;
    115  mInputElement->GetAttr(nsGkAtoms::min, minStr);
    116 
    117  return nsContentUtils::FormatMaybeLocalizedString(
    118      aMessage, nsContentUtils::eDOM_PROPERTIES,
    119      "FormValidationDateTimeRangeUnderflow", mInputElement->OwnerDoc(),
    120      minStr);
    121 }
    122 
    123 void DateTimeInputTypeBase::MinMaxStepAttrChanged() {
    124  if (Element* dateTimeBoxElement = mInputElement->GetDateTimeBoxElement()) {
    125    AsyncEventDispatcher::RunDOMEventWhenSafe(
    126        *dateTimeBoxElement, u"MozNotifyMinMaxStepAttrChanged"_ns,
    127        CanBubble::eNo, ChromeOnlyDispatch::eNo);
    128  }
    129 }
    130 
    131 bool DateTimeInputTypeBase::GetTimeFromMs(double aValue, uint16_t* aHours,
    132                                          uint16_t* aMinutes,
    133                                          uint16_t* aSeconds,
    134                                          uint16_t* aMilliseconds) const {
    135  MOZ_ASSERT(aValue >= 0 && aValue < kMsPerDay,
    136             "aValue must be milliseconds within a day!");
    137 
    138  uint32_t value = floor(aValue);
    139 
    140  *aMilliseconds = value % 1000;
    141  value /= 1000;
    142 
    143  *aSeconds = value % 60;
    144  value /= 60;
    145 
    146  *aMinutes = value % 60;
    147  value /= 60;
    148 
    149  *aHours = value;
    150 
    151  return true;
    152 }
    153 
    154 // input type=date
    155 
    156 nsresult DateInputType::GetBadInputMessage(nsAString& aMessage) {
    157  return nsContentUtils::GetMaybeLocalizedString(
    158      nsContentUtils::eDOM_PROPERTIES, "FormValidationInvalidDate",
    159      mInputElement->OwnerDoc(), aMessage);
    160 }
    161 
    162 auto DateInputType::ConvertStringToNumber(const nsAString& aValue) const
    163    -> StringToNumberResult {
    164  uint32_t year, month, day;
    165  if (!ParseDate(aValue, &year, &month, &day)) {
    166    return {};
    167  }
    168  JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, day));
    169  if (!time.isValid()) {
    170    return {};
    171  }
    172  return {Decimal::fromDouble(time.toDouble())};
    173 }
    174 
    175 bool DateInputType::ConvertNumberToString(Decimal aValue, Localized,
    176                                          nsAString& aResultString) const {
    177  MOZ_ASSERT(aValue.isFinite(), "aValue must be a valid non-Infinite number.");
    178 
    179  aResultString.Truncate();
    180 
    181  // The specs (and our JS APIs) require |aValue| to be truncated.
    182  aValue = aValue.floor();
    183 
    184  double year = JS::YearFromTime(aValue.toDouble());
    185  double month = JS::MonthFromTime(aValue.toDouble());
    186  double day = JS::DayFromTime(aValue.toDouble());
    187 
    188  if (std::isnan(year) || std::isnan(month) || std::isnan(day)) {
    189    return false;
    190  }
    191 
    192  aResultString.AppendPrintf("%04.0f-%02.0f-%02.0f", year, month + 1, day);
    193  return true;
    194 }
    195 
    196 // input type=time
    197 
    198 nsresult TimeInputType::GetBadInputMessage(nsAString& aMessage) {
    199  return nsContentUtils::GetMaybeLocalizedString(
    200      nsContentUtils::eDOM_PROPERTIES, "FormValidationInvalidTime",
    201      mInputElement->OwnerDoc(), aMessage);
    202 }
    203 
    204 auto TimeInputType::ConvertStringToNumber(const nsAString& aValue) const
    205    -> StringToNumberResult {
    206  uint32_t milliseconds;
    207  if (!ParseTime(aValue, &milliseconds)) {
    208    return {};
    209  }
    210  return {Decimal(int32_t(milliseconds))};
    211 }
    212 
    213 bool TimeInputType::ConvertNumberToString(Decimal aValue, Localized,
    214                                          nsAString& aResultString) const {
    215  MOZ_ASSERT(aValue.isFinite(), "aValue must be a valid non-Infinite number.");
    216 
    217  aResultString.Truncate();
    218 
    219  aValue = aValue.floor();
    220  // Per spec, we need to truncate |aValue| and we should only represent
    221  // times inside a day [00:00, 24:00[, which means that we should do a
    222  // modulo on |aValue| using the number of milliseconds in a day (86400000).
    223  uint32_t value =
    224      NS_floorModulo(aValue, Decimal::fromDouble(kMsPerDay)).toDouble();
    225 
    226  uint16_t milliseconds, seconds, minutes, hours;
    227  if (!GetTimeFromMs(value, &hours, &minutes, &seconds, &milliseconds)) {
    228    return false;
    229  }
    230 
    231  if (milliseconds != 0) {
    232    aResultString.AppendPrintf("%02d:%02d:%02d.%03d", hours, minutes, seconds,
    233                               milliseconds);
    234  } else if (seconds != 0) {
    235    aResultString.AppendPrintf("%02d:%02d:%02d", hours, minutes, seconds);
    236  } else {
    237    aResultString.AppendPrintf("%02d:%02d", hours, minutes);
    238  }
    239 
    240  return true;
    241 }
    242 
    243 bool TimeInputType::HasReversedRange() const {
    244  mozilla::Decimal maximum = mInputElement->GetMaximum();
    245  if (maximum.isNaN()) {
    246    return false;
    247  }
    248 
    249  mozilla::Decimal minimum = mInputElement->GetMinimum();
    250  if (minimum.isNaN()) {
    251    return false;
    252  }
    253 
    254  return maximum < minimum;
    255 }
    256 
    257 bool TimeInputType::IsReversedRangeUnderflowAndOverflow() const {
    258  mozilla::Decimal maximum = mInputElement->GetMaximum();
    259  mozilla::Decimal minimum = mInputElement->GetMinimum();
    260  mozilla::Decimal value = mInputElement->GetValueAsDecimal();
    261 
    262  MOZ_ASSERT(HasReversedRange(), "Must have reserved range.");
    263 
    264  if (value.isNaN()) {
    265    return false;
    266  }
    267 
    268  // When an element has a reversed range, and the value is more than the
    269  // maximum and less than the minimum the element is simultaneously suffering
    270  // from an underflow and suffering from an overflow.
    271  return value > maximum && value < minimum;
    272 }
    273 
    274 bool TimeInputType::IsRangeOverflow() const {
    275  return HasReversedRange() ? IsReversedRangeUnderflowAndOverflow()
    276                            : DateTimeInputTypeBase::IsRangeOverflow();
    277 }
    278 
    279 bool TimeInputType::IsRangeUnderflow() const {
    280  return HasReversedRange() ? IsReversedRangeUnderflowAndOverflow()
    281                            : DateTimeInputTypeBase::IsRangeUnderflow();
    282 }
    283 
    284 nsresult TimeInputType::GetReversedRangeUnderflowAndOverflowMessage(
    285    nsAString& aMessage) {
    286  nsAutoString maxStr;
    287  mInputElement->GetAttr(nsGkAtoms::max, maxStr);
    288 
    289  nsAutoString minStr;
    290  mInputElement->GetAttr(nsGkAtoms::min, minStr);
    291 
    292  return nsContentUtils::FormatMaybeLocalizedString(
    293      aMessage, nsContentUtils::eDOM_PROPERTIES,
    294      "FormValidationTimeReversedRangeUnderflowAndOverflow",
    295      mInputElement->OwnerDoc(), minStr, maxStr);
    296 }
    297 
    298 nsresult TimeInputType::GetRangeOverflowMessage(nsAString& aMessage) {
    299  return HasReversedRange()
    300             ? GetReversedRangeUnderflowAndOverflowMessage(aMessage)
    301             : DateTimeInputTypeBase::GetRangeOverflowMessage(aMessage);
    302 }
    303 
    304 nsresult TimeInputType::GetRangeUnderflowMessage(nsAString& aMessage) {
    305  return HasReversedRange()
    306             ? GetReversedRangeUnderflowAndOverflowMessage(aMessage)
    307             : DateTimeInputTypeBase::GetRangeUnderflowMessage(aMessage);
    308 }
    309 
    310 // input type=week
    311 
    312 nsresult WeekInputType::GetBadInputMessage(nsAString& aMessage) {
    313  return nsContentUtils::GetMaybeLocalizedString(
    314      nsContentUtils::eDOM_PROPERTIES, "FormValidationInvalidWeek",
    315      mInputElement->OwnerDoc(), aMessage);
    316 }
    317 
    318 auto WeekInputType::ConvertStringToNumber(const nsAString& aValue) const
    319    -> StringToNumberResult {
    320  uint32_t year, week;
    321  if (!ParseWeek(aValue, &year, &week)) {
    322    return {};
    323  }
    324  if (year < kMinimumYear || year > kMaximumYear) {
    325    return {};
    326  }
    327  // Maximum week is 275760-W37, the week of 275760-09-13.
    328  if (year == kMaximumYear && week > kMaximumWeekInMaximumYear) {
    329    return {};
    330  }
    331  double days = DaysSinceEpochFromWeek(year, week);
    332  return {Decimal::fromDouble(days * kMsPerDay)};
    333 }
    334 
    335 bool WeekInputType::ConvertNumberToString(Decimal aValue, Localized,
    336                                          nsAString& aResultString) const {
    337  MOZ_ASSERT(aValue.isFinite(), "aValue must be a valid non-Infinite number.");
    338 
    339  aResultString.Truncate();
    340 
    341  aValue = aValue.floor();
    342 
    343  // Based on ISO 8601 date.
    344  double year = JS::YearFromTime(aValue.toDouble());
    345  double month = JS::MonthFromTime(aValue.toDouble());
    346  double day = JS::DayFromTime(aValue.toDouble());
    347  // Adding 1 since day starts from 0.
    348  double dayInYear = JS::DayWithinYear(aValue.toDouble(), year) + 1;
    349 
    350  // Return if aValue is outside the valid JS date-time range.
    351  if (std::isnan(year) || std::isnan(month) || std::isnan(day) ||
    352      std::isnan(dayInYear)) {
    353    return false;
    354  }
    355 
    356  // DayOfWeek requires the year to be non-negative.
    357  if (year < 0) {
    358    return false;
    359  }
    360 
    361  // Adding 1 since month starts from 0.
    362  uint32_t isoWeekday = DayOfWeek(year, month + 1, day, true);
    363  // Target on Wednesday since ISO 8601 states that week 1 is the week
    364  // with the first Thursday of that year.
    365  uint32_t week = (dayInYear - isoWeekday + 10) / 7;
    366 
    367  if (week < 1) {
    368    year--;
    369    if (year < 1) {
    370      return false;
    371    }
    372    week = MaximumWeekInYear(year);
    373  } else if (week > MaximumWeekInYear(year)) {
    374    year++;
    375    if (year > kMaximumYear ||
    376        (year == kMaximumYear && week > kMaximumWeekInMaximumYear)) {
    377      return false;
    378    }
    379    week = 1;
    380  }
    381 
    382  aResultString.AppendPrintf("%04.0f-W%02d", year, week);
    383  return true;
    384 }
    385 
    386 // input type=month
    387 
    388 nsresult MonthInputType::GetBadInputMessage(nsAString& aMessage) {
    389  return nsContentUtils::GetMaybeLocalizedString(
    390      nsContentUtils::eDOM_PROPERTIES, "FormValidationInvalidMonth",
    391      mInputElement->OwnerDoc(), aMessage);
    392 }
    393 
    394 auto MonthInputType::ConvertStringToNumber(const nsAString& aValue) const
    395    -> StringToNumberResult {
    396  uint32_t year, month;
    397  if (!ParseMonth(aValue, &year, &month)) {
    398    return {};
    399  }
    400 
    401  if (year < kMinimumYear || year > kMaximumYear) {
    402    return {};
    403  }
    404 
    405  // Maximum valid month is 275760-09.
    406  if (year == kMaximumYear && month > kMaximumMonthInMaximumYear) {
    407    return {};
    408  }
    409 
    410  int32_t months = MonthsSinceJan1970(year, month);
    411  return {Decimal(int32_t(months))};
    412 }
    413 
    414 bool MonthInputType::ConvertNumberToString(Decimal aValue, Localized,
    415                                           nsAString& aResultString) const {
    416  MOZ_ASSERT(aValue.isFinite(), "aValue must be a valid non-Infinite number.");
    417 
    418  aResultString.Truncate();
    419 
    420  aValue = aValue.floor();
    421 
    422  double month = NS_floorModulo(aValue, Decimal(12)).toDouble();
    423  month = (month < 0 ? month + 12 : month);
    424 
    425  double year = 1970 + (aValue.toDouble() - month) / 12;
    426 
    427  // Maximum valid month is 275760-09.
    428  if (year < kMinimumYear || year > kMaximumYear) {
    429    return false;
    430  }
    431 
    432  if (year == kMaximumYear && month > 8) {
    433    return false;
    434  }
    435 
    436  aResultString.AppendPrintf("%04.0f-%02.0f", year, month + 1);
    437  return true;
    438 }
    439 
    440 // input type=datetime-local
    441 
    442 nsresult DateTimeLocalInputType::GetBadInputMessage(nsAString& aMessage) {
    443  return nsContentUtils::GetMaybeLocalizedString(
    444      nsContentUtils::eDOM_PROPERTIES, "FormValidationInvalidDateTime",
    445      mInputElement->OwnerDoc(), aMessage);
    446 }
    447 
    448 auto DateTimeLocalInputType::ConvertStringToNumber(
    449    const nsAString& aValue) const -> StringToNumberResult {
    450  uint32_t year, month, day, timeInMs;
    451  if (!ParseDateTimeLocal(aValue, &year, &month, &day, &timeInMs)) {
    452    return {};
    453  }
    454  JS::ClippedTime time =
    455      JS::TimeClip(JS::MakeDate(year, month - 1, day, timeInMs));
    456  if (!time.isValid()) {
    457    return {};
    458  }
    459  return {Decimal::fromDouble(time.toDouble())};
    460 }
    461 
    462 bool DateTimeLocalInputType::ConvertNumberToString(
    463    Decimal aValue, Localized, nsAString& aResultString) const {
    464  MOZ_ASSERT(aValue.isFinite(), "aValue must be a valid non-Infinite number.");
    465 
    466  aResultString.Truncate();
    467 
    468  aValue = aValue.floor();
    469 
    470  uint32_t timeValue =
    471      NS_floorModulo(aValue, Decimal::fromDouble(kMsPerDay)).toDouble();
    472 
    473  uint16_t milliseconds, seconds, minutes, hours;
    474  if (!GetTimeFromMs(timeValue, &hours, &minutes, &seconds, &milliseconds)) {
    475    return false;
    476  }
    477 
    478  double year = JS::YearFromTime(aValue.toDouble());
    479  double month = JS::MonthFromTime(aValue.toDouble());
    480  double day = JS::DayFromTime(aValue.toDouble());
    481 
    482  if (std::isnan(year) || std::isnan(month) || std::isnan(day)) {
    483    return false;
    484  }
    485 
    486  if (milliseconds != 0) {
    487    aResultString.AppendPrintf("%04.0f-%02.0f-%02.0fT%02d:%02d:%02d.%03d", year,
    488                               month + 1, day, hours, minutes, seconds,
    489                               milliseconds);
    490  } else if (seconds != 0) {
    491    aResultString.AppendPrintf("%04.0f-%02.0f-%02.0fT%02d:%02d:%02d", year,
    492                               month + 1, day, hours, minutes, seconds);
    493  } else {
    494    aResultString.AppendPrintf("%04.0f-%02.0f-%02.0fT%02d:%02d", year,
    495                               month + 1, day, hours, minutes);
    496  }
    497 
    498  return true;
    499 }
    500 
    501 }  // namespace mozilla::dom