Bug 1842027 - Make <input type=number> localization faster. r=masayuki

Using the fast unlocalized parser first exposes a subtle difference
between the ICU parser (which uses `double`), and Decimal::fromString
(which has a larger range).

Check for iee754 finiteness explicitly, matching our previous behavior,
and the expectation of this subtest:

  https://searchfox.org/mozilla-central/rev/a3852ea8db25c759bc8b108aeec870d66c95452c/testing/web-platform/tests/html/semantics/forms/the-input-element/number.html#33

That check matches this Blink code:

  https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:third_party/blink/renderer/core/html/parser/html_parser_idioms.cc;l=103;drc=0d1bbe8de137aaae7c956a33249eb840a0191627

This patch avoids a bunch of number->string->number conversions in the
common case where the value comes from the value attribute and thus
parses fine without localization shenanigans.

Differential Revision: https://phabricator.services.mozilla.com/D183254
This commit is contained in:
Emilio Cobos Álvarez
2023-07-12 15:28:07 +00:00
parent bd689353d0
commit 35a7e91f1f
10 changed files with 128 additions and 136 deletions

View File

@@ -1586,18 +1586,25 @@ Decimal HTMLInputElement::StringToDecimal(const nsAString& aValue) {
}
NS_LossyConvertUTF16toASCII asciiString(aValue);
std::string stdString(asciiString.get(), asciiString.Length());
return Decimal::fromString(stdString);
auto decimal = Decimal::fromString(stdString);
if (!decimal.isFinite()) {
return Decimal::nan();
}
// Numbers are considered finite IEEE 754 Double-precision floating point
// values, but decimal supports a bigger range.
static const Decimal maxDouble =
Decimal::fromDouble(std::numeric_limits<double>::max());
if (decimal < -maxDouble || decimal > maxDouble) {
return Decimal::nan();
}
return decimal;
}
Decimal HTMLInputElement::GetValueAsDecimal() const {
Decimal decimalValue;
nsAutoString stringValue;
GetNonFileValueInternal(stringValue);
return !mInputType->ConvertStringToNumber(stringValue, decimalValue)
? Decimal::nan()
: decimalValue;
Decimal result = mInputType->ConvertStringToNumber(stringValue).mResult;
return result.isFinite() ? result : Decimal::nan();
}
void HTMLInputElement::SetValue(const nsAString& aValue, CallerType aCallerType,
@@ -1872,8 +1879,8 @@ Decimal HTMLInputElement::GetMinimum() const {
nsAutoString minStr;
GetAttr(nsGkAtoms::min, minStr);
Decimal min;
return mInputType->ConvertStringToNumber(minStr, min) ? min : defaultMinimum;
Decimal min = mInputType->ConvertStringToNumber(minStr).mResult;
return min.isFinite() ? min : defaultMinimum;
}
Decimal HTMLInputElement::GetMaximum() const {
@@ -1892,8 +1899,8 @@ Decimal HTMLInputElement::GetMaximum() const {
nsAutoString maxStr;
GetAttr(nsGkAtoms::max, maxStr);
Decimal max;
return mInputType->ConvertStringToNumber(maxStr, max) ? max : defaultMaximum;
Decimal max = mInputType->ConvertStringToNumber(maxStr).mResult;
return max.isFinite() ? max : defaultMaximum;
}
Decimal HTMLInputElement::GetStepBase() const {
@@ -1901,22 +1908,23 @@ Decimal HTMLInputElement::GetStepBase() const {
mType == FormControlType::InputNumber ||
mType == FormControlType::InputRange,
"Check that kDefaultStepBase is correct for this new type");
Decimal stepBase;
// Do NOT use GetMinimum here - the spec says to use "the min content
// attribute", not "the minimum".
nsAutoString minStr;
if (GetAttr(nsGkAtoms::min, minStr) &&
mInputType->ConvertStringToNumber(minStr, stepBase)) {
return stepBase;
if (GetAttr(nsGkAtoms::min, minStr)) {
Decimal min = mInputType->ConvertStringToNumber(minStr).mResult;
if (min.isFinite()) {
return min;
}
}
// If @min is not a double, we should use @value.
nsAutoString valueStr;
if (GetAttr(nsGkAtoms::value, valueStr) &&
mInputType->ConvertStringToNumber(valueStr, stepBase)) {
return stepBase;
if (GetAttr(nsGkAtoms::value, valueStr)) {
Decimal value = mInputType->ConvertStringToNumber(valueStr).mResult;
if (value.isFinite()) {
return value;
}
}
if (mType == FormControlType::InputWeek) {
@@ -4595,44 +4603,42 @@ void HTMLInputElement::SanitizeValue(nsAString& aValue,
return;
}
Decimal value;
bool ok = mInputType->ConvertStringToNumber(aValue, value);
if (!ok) {
InputType::StringToNumberResult result =
mInputType->ConvertStringToNumber(aValue);
if (!result.mResult.isFinite()) {
aValue.Truncate();
return;
}
nsAutoString sanitizedValue;
if (aForGetter == ForValueGetter::Yes) {
// If the default non-localized algorithm parses the value, then we're
// done, don't un-localize it, to avoid precision loss, and to preserve
// scientific notation as well for example.
//
// FIXME(emilio, bug 1622808): Localization should ideally be more
// input-preserving.
if (StringToDecimal(aValue).isFinite()) {
if (!result.mLocalized) {
return;
}
// For the <input type=number> value getter, we return the unlocalized
// value if it doesn't parse as StringToDecimal, for compat with other
// browsers.
char buf[32];
DebugOnly<bool> ok = value.toString(buf, ArrayLength(buf));
sanitizedValue.AssignASCII(buf);
DebugOnly<bool> ok = result.mResult.toString(buf, ArrayLength(buf));
aValue.AssignASCII(buf);
MOZ_ASSERT(ok, "buf not big enough");
} else {
mInputType->ConvertNumberToString(value, sanitizedValue);
// Otherwise we localize as needed, but if both the localized and
// unlocalized version parse, we just use the unlocalized one, to
// preserve the input as much as possible.
// Otherwise this SanitizeValue call is for display. We localize as
// needed, but if both the localized and unlocalized version parse with
// the generic parser, we just use the unlocalized one, to preserve the
// input as much as possible.
//
// FIXME(emilio, bug 1622808): Localization should ideally be more
// input-preserving.
if (StringToDecimal(sanitizedValue).isFinite()) {
nsAutoString localizedValue;
mInputType->ConvertNumberToString(result.mResult, localizedValue);
if (StringToDecimal(localizedValue).isFinite()) {
return;
}
aValue.Assign(localizedValue);
}
aValue.Assign(sanitizedValue);
} break;
case FormControlType::InputRange: {
Decimal minimum = GetMinimum();
@@ -4645,9 +4651,8 @@ void HTMLInputElement::SanitizeValue(nsAString& aValue,
// parse out from aValue needs to be sanitized.
bool needSanitization = false;
Decimal value;
bool ok = mInputType->ConvertStringToNumber(aValue, value);
if (!ok) {
Decimal value = mInputType->ConvertStringToNumber(aValue).mResult;
if (!value.isFinite()) {
needSanitization = true;
// Set value to midway between minimum and maximum.
value = maximum <= minimum ? minimum
@@ -6756,13 +6761,16 @@ int32_t HTMLInputElement::GetWrapCols() {
int32_t HTMLInputElement::GetRows() { return DEFAULT_ROWS; }
void HTMLInputElement::GetDefaultValueFromContent(nsAString& aValue) {
if (GetEditorState()) {
GetDefaultValue(aValue);
// This is called by the frame to show the value.
// We have to sanitize it when needed.
if (mDoneCreating) {
SanitizeValue(aValue);
}
if (!GetEditorState()) {
return;
}
GetDefaultValue(aValue);
// This is called by the frame to show the value.
// We have to sanitize it when needed.
// FIXME: This is also used from GetNonFileValueInternal where we might not
// need to sanitize some of the time.
if (mDoneCreating) {
SanitizeValue(aValue);
}
}