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/NumericInputTypes.h"
8 
9 #include "mozilla/TextControlState.h"
10 #include "mozilla/dom/HTMLInputElement.h"
11 #include "ICUUtils.h"
12 
13 using namespace mozilla;
14 using namespace mozilla::dom;
15 
IsRangeOverflow() const16 bool NumericInputTypeBase::IsRangeOverflow() const {
17   Decimal maximum = mInputElement->GetMaximum();
18   if (maximum.isNaN()) {
19     return false;
20   }
21 
22   Decimal value = mInputElement->GetValueAsDecimal();
23   if (value.isNaN()) {
24     return false;
25   }
26 
27   return value > maximum;
28 }
29 
IsRangeUnderflow() const30 bool NumericInputTypeBase::IsRangeUnderflow() const {
31   Decimal minimum = mInputElement->GetMinimum();
32   if (minimum.isNaN()) {
33     return false;
34   }
35 
36   Decimal value = mInputElement->GetValueAsDecimal();
37   if (value.isNaN()) {
38     return false;
39   }
40 
41   return value < minimum;
42 }
43 
HasStepMismatch(bool aUseZeroIfValueNaN) const44 bool NumericInputTypeBase::HasStepMismatch(bool aUseZeroIfValueNaN) const {
45   Decimal value = mInputElement->GetValueAsDecimal();
46   if (value.isNaN()) {
47     if (aUseZeroIfValueNaN) {
48       value = Decimal(0);
49     } else {
50       // The element can't suffer from step mismatch if it's value isn't a
51       // number.
52       return false;
53     }
54   }
55 
56   Decimal step = mInputElement->GetStep();
57   if (step == kStepAny) {
58     return false;
59   }
60 
61   // Value has to be an integral multiple of step.
62   return NS_floorModulo(value - GetStepBase(), step) != Decimal(0);
63 }
64 
GetRangeOverflowMessage(nsAString & aMessage)65 nsresult NumericInputTypeBase::GetRangeOverflowMessage(nsAString& aMessage) {
66   // We want to show the value as parsed when it's a number
67   Decimal maximum = mInputElement->GetMaximum();
68   MOZ_ASSERT(!maximum.isNaN());
69 
70   nsAutoString maxStr;
71   char buf[32];
72   DebugOnly<bool> ok = maximum.toString(buf, ArrayLength(buf));
73   maxStr.AssignASCII(buf);
74   MOZ_ASSERT(ok, "buf not big enough");
75 
76   return nsContentUtils::FormatMaybeLocalizedString(
77       aMessage, nsContentUtils::eDOM_PROPERTIES,
78       "FormValidationNumberRangeOverflow", mInputElement->OwnerDoc(), maxStr);
79 }
80 
GetRangeUnderflowMessage(nsAString & aMessage)81 nsresult NumericInputTypeBase::GetRangeUnderflowMessage(nsAString& aMessage) {
82   Decimal minimum = mInputElement->GetMinimum();
83   MOZ_ASSERT(!minimum.isNaN());
84 
85   nsAutoString minStr;
86   char buf[32];
87   DebugOnly<bool> ok = minimum.toString(buf, ArrayLength(buf));
88   minStr.AssignASCII(buf);
89   MOZ_ASSERT(ok, "buf not big enough");
90 
91   return nsContentUtils::FormatMaybeLocalizedString(
92       aMessage, nsContentUtils::eDOM_PROPERTIES,
93       "FormValidationNumberRangeUnderflow", mInputElement->OwnerDoc(), minStr);
94 }
95 
ConvertStringToNumber(nsAString & aValue,Decimal & aResultValue) const96 bool NumericInputTypeBase::ConvertStringToNumber(nsAString& aValue,
97                                                  Decimal& aResultValue) const {
98   aResultValue = HTMLInputElement::StringToDecimal(aValue);
99   return aResultValue.isFinite();
100 }
101 
ConvertNumberToString(Decimal aValue,nsAString & aResultString) const102 bool NumericInputTypeBase::ConvertNumberToString(
103     Decimal aValue, nsAString& aResultString) const {
104   MOZ_ASSERT(aValue.isFinite(), "aValue must be a valid non-Infinite number.");
105 
106   aResultString.Truncate();
107 
108   char buf[32];
109   bool ok = aValue.toString(buf, ArrayLength(buf));
110   aResultString.AssignASCII(buf);
111   MOZ_ASSERT(ok, "buf not big enough");
112 
113   return ok;
114 }
115 
116 /* input type=number */
117 
IsValueMissing() const118 bool NumberInputType::IsValueMissing() const {
119   if (!mInputElement->IsRequired()) {
120     return false;
121   }
122 
123   if (!IsMutable()) {
124     return false;
125   }
126 
127   return IsValueEmpty();
128 }
129 
HasBadInput() const130 bool NumberInputType::HasBadInput() const {
131   nsAutoString value;
132   GetNonFileValueInternal(value);
133   return !value.IsEmpty() && mInputElement->GetValueAsDecimal().isNaN();
134 }
135 
ConvertStringToNumber(nsAString & aValue,Decimal & aResultValue) const136 bool NumberInputType::ConvertStringToNumber(nsAString& aValue,
137                                             Decimal& aResultValue) const {
138   ICUUtils::LanguageTagIterForContent langTagIter(mInputElement);
139   aResultValue =
140       Decimal::fromDouble(ICUUtils::ParseNumber(aValue, langTagIter));
141   if (aResultValue.isFinite()) {
142     return true;
143   }
144   return NumericInputTypeBase::ConvertStringToNumber(aValue, aResultValue);
145 }
146 
ConvertNumberToString(Decimal aValue,nsAString & aResultString) const147 bool NumberInputType::ConvertNumberToString(Decimal aValue,
148                                             nsAString& aResultString) const {
149   MOZ_ASSERT(aValue.isFinite(), "aValue must be a valid non-Infinite number.");
150 
151   aResultString.Truncate();
152   ICUUtils::LanguageTagIterForContent langTagIter(mInputElement);
153   ICUUtils::LocalizeNumber(aValue.toDouble(), langTagIter, aResultString);
154   return true;
155 }
156 
GetValueMissingMessage(nsAString & aMessage)157 nsresult NumberInputType::GetValueMissingMessage(nsAString& aMessage) {
158   return nsContentUtils::GetMaybeLocalizedString(
159       nsContentUtils::eDOM_PROPERTIES, "FormValidationBadInputNumber",
160       mInputElement->OwnerDoc(), aMessage);
161 }
162 
GetBadInputMessage(nsAString & aMessage)163 nsresult NumberInputType::GetBadInputMessage(nsAString& aMessage) {
164   return nsContentUtils::GetMaybeLocalizedString(
165       nsContentUtils::eDOM_PROPERTIES, "FormValidationBadInputNumber",
166       mInputElement->OwnerDoc(), aMessage);
167 }
168 
IsMutable() const169 bool NumberInputType::IsMutable() const {
170   return !mInputElement->IsDisabled() &&
171          !mInputElement->HasAttr(kNameSpaceID_None, nsGkAtoms::readonly);
172 }
173 
174 /* input type=range */
MinMaxStepAttrChanged()175 nsresult RangeInputType::MinMaxStepAttrChanged() {
176   // The value may need to change when @min/max/step changes since the value may
177   // have been invalid and can now change to a valid value, or vice versa. For
178   // example, consider: <input type=range value=-1 max=1 step=3>. The valid
179   // range is 0 to 1 while the nearest valid steps are -1 and 2 (the max value
180   // having prevented there being a valid step in range). Changing @max to/from
181   // 1 and a number greater than on equal to 3 should change whether we have a
182   // step mismatch or not.
183   // The value may also need to change between a value that results in a step
184   // mismatch and a value that results in overflow. For example, if @max in the
185   // example above were to change from 1 to -1.
186   nsAutoString value;
187   GetNonFileValueInternal(value);
188   return SetValueInternal(value,
189                           TextControlState::ValueSetterOption::ByInternalAPI);
190 }
191