Bug 853525 - Convert much of HTMLInputElement (step handling, validation, events, etc.) to use mozilla::Decimal (a Binary Coded Decimal type) to avoid many rounding issues when it has fractional step values. r=mounir

This commit is contained in:
Jonathan Watt 2013-05-06 00:23:18 +01:00
parent 50d06d14f4
commit 8df48d0c75
7 changed files with 209 additions and 185 deletions

View File

@ -580,7 +580,7 @@ HTMLRangeAccessible::GetMaximumValue(double* aMaximumValue)
if (rv != NS_OK_NO_ARIA_VALUE)
return rv;
*aMaximumValue = HTMLInputElement::FromContent(mContent)->GetMaximum();
*aMaximumValue = HTMLInputElement::FromContent(mContent)->GetMaximum().toDouble();
return NS_OK;
}
@ -592,7 +592,7 @@ HTMLRangeAccessible::GetMinimumValue(double* aMinimumValue)
if (rv != NS_OK_NO_ARIA_VALUE)
return rv;
*aMinimumValue = HTMLInputElement::FromContent(mContent)->GetMinimum();
*aMinimumValue = HTMLInputElement::FromContent(mContent)->GetMinimum().toDouble();
return NS_OK;
}
@ -604,7 +604,7 @@ HTMLRangeAccessible::GetMinimumIncrement(double* aMinimumIncrement)
if (rv != NS_OK_NO_ARIA_VALUE)
return rv;
*aMinimumIncrement = HTMLInputElement::FromContent(mContent)->GetStep();
*aMinimumIncrement = HTMLInputElement::FromContent(mContent)->GetStep().toDouble();
return NS_OK;
}
@ -615,7 +615,7 @@ HTMLRangeAccessible::GetCurrentValue(double* aCurrentValue)
if (rv != NS_OK_NO_ARIA_VALUE)
return rv;
*aCurrentValue = HTMLInputElement::FromContent(mContent)->GetValueAsDouble();
*aCurrentValue = HTMLInputElement::FromContent(mContent)->GetValueAsDecimal().toDouble();
return NS_OK;
}

View File

@ -105,6 +105,14 @@ NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Input)
static NS_DEFINE_CID(kXULControllersCID, NS_XULCONTROLLERS_CID);
// This must come outside of any namespace, or else it won't overload with the
// double based version in nsMathUtils.h
inline NS_HIDDEN_(mozilla::Decimal)
NS_floorModulo(mozilla::Decimal x, mozilla::Decimal y)
{
return (x - y * (x / y).floor());
}
namespace mozilla {
namespace dom {
@ -183,13 +191,13 @@ static const nsAttrValue::EnumTable kInputInputmodeTable[] = {
// Default inputmode value is "auto".
static const nsAttrValue::EnumTable* kInputDefaultInputmode = &kInputInputmodeTable[0];
const double HTMLInputElement::kStepScaleFactorDate = 86400000;
const double HTMLInputElement::kStepScaleFactorNumberRange = 1;
const double HTMLInputElement::kStepScaleFactorTime = 1000;
const double HTMLInputElement::kDefaultStepBase = 0;
const double HTMLInputElement::kDefaultStep = 1;
const double HTMLInputElement::kDefaultStepTime = 60;
const double HTMLInputElement::kStepAny = 0;
const Decimal HTMLInputElement::kStepScaleFactorDate = 86400000;
const Decimal HTMLInputElement::kStepScaleFactorNumberRange = 1;
const Decimal HTMLInputElement::kStepScaleFactorTime = 1000;
const Decimal HTMLInputElement::kDefaultStepBase = 0;
const Decimal HTMLInputElement::kDefaultStep = 1;
const Decimal HTMLInputElement::kDefaultStepTime = 60;
const Decimal HTMLInputElement::kStepAny = 0;
#define NS_INPUT_ELEMENT_STATE_IID \
{ /* dc3b3d14-23e2-4479-b513-7b369343e3a0 */ \
@ -1144,9 +1152,19 @@ HTMLInputElement::IsValueEmpty() const
return value.IsEmpty();
}
static Decimal StringToDecimal(nsAString& aValue)
{
if (!IsASCII(aValue)) {
return Decimal::nan();
}
NS_LossyConvertUTF16toASCII asciiString(aValue);
std::string stdString = asciiString.get();
return Decimal::fromString(stdString);
}
bool
HTMLInputElement::ConvertStringToNumber(nsAString& aValue,
double& aResultValue) const
Decimal& aResultValue) const
{
MOZ_ASSERT(DoesValueAsNumberApply(),
"ConvertStringToNumber only applies if .valueAsNumber applies");
@ -1155,12 +1173,10 @@ HTMLInputElement::ConvertStringToNumber(nsAString& aValue,
case NS_FORM_INPUT_NUMBER:
case NS_FORM_INPUT_RANGE:
{
nsresult ec;
aResultValue = PromiseFlatString(aValue).ToDouble(&ec);
if (NS_FAILED(ec) || !MOZ_DOUBLE_IS_FINITE(aResultValue)) {
aResultValue = StringToDecimal(aValue);
if (!aResultValue.isFinite()) {
return false;
}
return true;
}
case NS_FORM_INPUT_DATE:
@ -1175,7 +1191,7 @@ HTMLInputElement::ConvertStringToNumber(nsAString& aValue,
return false;
}
aResultValue = date;
aResultValue = Decimal::fromDouble(date);
return true;
}
case NS_FORM_INPUT_TIME:
@ -1184,7 +1200,7 @@ HTMLInputElement::ConvertStringToNumber(nsAString& aValue,
return false;
}
aResultValue = static_cast<double>(milliseconds);
aResultValue = int32_t(milliseconds);
return true;
default:
MOZ_ASSERT(false, "Unrecognized input type");
@ -1192,16 +1208,16 @@ HTMLInputElement::ConvertStringToNumber(nsAString& aValue,
}
}
double
HTMLInputElement::GetValueAsDouble() const
Decimal
HTMLInputElement::GetValueAsDecimal() const
{
double doubleValue;
Decimal decimalValue;
nsAutoString stringValue;
GetValueInternal(stringValue);
return !ConvertStringToNumber(stringValue, doubleValue) ? MOZ_DOUBLE_NaN()
: doubleValue;
return !ConvertStringToNumber(stringValue, decimalValue) ? Decimal::nan()
: decimalValue;
}
void
@ -1301,11 +1317,11 @@ HTMLInputElement::GetList(nsIDOMHTMLElement** aValue)
}
void
HTMLInputElement::SetValue(double aValue)
HTMLInputElement::SetValue(Decimal aValue)
{
MOZ_ASSERT(!MOZ_DOUBLE_IS_INFINITE(aValue), "aValue must not be Infinity!");
MOZ_ASSERT(!aValue.isInfinity(), "aValue must not be Infinity!");
if (MOZ_DOUBLE_IS_NaN(aValue)) {
if (aValue.isNaN()) {
SetValue(EmptyString());
return;
}
@ -1316,12 +1332,12 @@ HTMLInputElement::SetValue(double aValue)
}
bool
HTMLInputElement::ConvertNumberToString(double aValue,
HTMLInputElement::ConvertNumberToString(Decimal aValue,
nsAString& aResultString) const
{
MOZ_ASSERT(DoesValueAsNumberApply(),
"ConvertNumberToString is only implemented for types implementing .valueAsNumber");
MOZ_ASSERT(!MOZ_DOUBLE_IS_NaN(aValue) && !MOZ_DOUBLE_IS_INFINITE(aValue),
MOZ_ASSERT(aValue.isFinite(),
"aValue must be a valid non-Infinite number.");
aResultString.Truncate();
@ -1329,16 +1345,21 @@ HTMLInputElement::ConvertNumberToString(double aValue,
switch (mType) {
case NS_FORM_INPUT_NUMBER:
case NS_FORM_INPUT_RANGE:
aResultString.AppendFloat(aValue);
return true;
{
char buf[32];
bool ok = aValue.toString(buf, ArrayLength(buf));
aResultString.AssignASCII(buf);
MOZ_ASSERT(ok, "buf not big enough");
return ok;
}
case NS_FORM_INPUT_DATE:
{
// The specs (and our JS APIs) require |aValue| to be truncated.
aValue = floor(aValue);
aValue = aValue.floor();
double year = JS::YearFromTime(aValue);
double month = JS::MonthFromTime(aValue);
double day = JS::DayFromTime(aValue);
double year = JS::YearFromTime(aValue.toDouble());
double month = JS::MonthFromTime(aValue.toDouble());
double day = JS::DayFromTime(aValue.toDouble());
if (MOZ_DOUBLE_IS_NaN(year) ||
MOZ_DOUBLE_IS_NaN(month) ||
@ -1356,7 +1377,7 @@ HTMLInputElement::ConvertNumberToString(double aValue,
// 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(floor(aValue), 86400000);
uint32_t value = NS_floorModulo(aValue.floor(), 86400000).toDouble();
uint16_t milliseconds = value % 1000;
value /= 1000;
@ -1438,7 +1459,7 @@ HTMLInputElement::SetValueAsDate(Nullable<Date> aDate, ErrorResult& aRv)
return;
}
SetValue(aDate.Value().TimeStamp());
SetValue(Decimal::fromDouble(aDate.Value().TimeStamp()));
}
NS_IMETHODIMP
@ -1463,7 +1484,7 @@ HTMLInputElement::SetValueAsNumber(double aValueAsNumber, ErrorResult& aRv)
return;
}
SetValue(aValueAsNumber);
SetValue(Decimal::fromDouble(aValueAsNumber));
}
NS_IMETHODIMP
@ -1474,15 +1495,15 @@ HTMLInputElement::SetValueAsNumber(double aValueAsNumber)
return rv.ErrorCode();
}
double
Decimal
HTMLInputElement::GetMinimum() const
{
MOZ_ASSERT(DoesValueAsNumberApply(),
"GetMinAsDouble() should only be used for types that allow .valueAsNumber");
"GetMinimum() should only be used for types that allow .valueAsNumber");
// Only type=range has a default minimum
double defaultMinimum =
mType == NS_FORM_INPUT_RANGE ? 0.0 : MOZ_DOUBLE_NaN();
Decimal defaultMinimum =
mType == NS_FORM_INPUT_RANGE ? 0 : Decimal::nan();
if (!HasAttr(kNameSpaceID_None, nsGkAtoms::min)) {
return defaultMinimum;
@ -1491,19 +1512,19 @@ HTMLInputElement::GetMinimum() const
nsAutoString minStr;
GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr);
double min;
Decimal min;
return ConvertStringToNumber(minStr, min) ? min : defaultMinimum;
}
double
Decimal
HTMLInputElement::GetMaximum() const
{
MOZ_ASSERT(DoesValueAsNumberApply(),
"GetMaxAsDouble() should only be used for types that allow .valueAsNumber");
"GetMaximum() should only be used for types that allow .valueAsNumber");
// Only type=range has a default maximum
double defaultMaximum =
mType == NS_FORM_INPUT_RANGE ? 100.0 : MOZ_DOUBLE_NaN();
Decimal defaultMaximum =
mType == NS_FORM_INPUT_RANGE ? 100 : Decimal::nan();
if (!HasAttr(kNameSpaceID_None, nsGkAtoms::max)) {
return defaultMaximum;
@ -1512,11 +1533,11 @@ HTMLInputElement::GetMaximum() const
nsAutoString maxStr;
GetAttr(kNameSpaceID_None, nsGkAtoms::max, maxStr);
double max;
Decimal max;
return ConvertStringToNumber(maxStr, max) ? max : defaultMaximum;
}
double
Decimal
HTMLInputElement::GetStepBase() const
{
MOZ_ASSERT(mType == NS_FORM_INPUT_NUMBER ||
@ -1525,7 +1546,7 @@ HTMLInputElement::GetStepBase() const
mType == NS_FORM_INPUT_RANGE,
"Check that kDefaultStepBase is correct for this new type");
double stepBase;
Decimal stepBase;
// Do NOT use GetMinimum here - the spec says to use "the min content
// attribute", not "the minimum".
@ -1552,20 +1573,20 @@ HTMLInputElement::ApplyStep(int32_t aStep)
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
double step = GetStep();
Decimal step = GetStep();
if (step == kStepAny) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
double value = GetValueAsDouble();
if (MOZ_DOUBLE_IS_NaN(value)) {
Decimal value = GetValueAsDecimal();
if (value.isNaN()) {
return NS_OK;
}
double minimum = GetMinimum();
Decimal minimum = GetMinimum();
double maximum = GetMaximum();
if (!MOZ_DOUBLE_IS_NaN(maximum)) {
Decimal maximum = GetMaximum();
if (!maximum.isNaN()) {
// "max - (max - stepBase) % step" is the nearest valid value to max.
maximum = maximum - NS_floorModulo(maximum - GetStepBase(), step);
}
@ -1589,15 +1610,16 @@ HTMLInputElement::ApplyStep(int32_t aStep)
}
}
value += aStep * step;
value += step * aStep;
// For date inputs, the value can hold a string that is not a day. We do not
// want to round it, as it might result in a step mismatch. Instead we want to
// clamp to the next valid value.
if (mType == NS_FORM_INPUT_DATE &&
NS_floorModulo(value - GetStepBase(), GetStepScaleFactor()) != 0) {
double validStep = EuclidLCM<uint64_t>(static_cast<uint64_t>(step),
static_cast<uint64_t>(GetStepScaleFactor()));
MOZ_ASSERT(GetStep() > 0);
Decimal validStep = EuclidLCM<Decimal>(GetStep().floor(),
GetStepScaleFactor().floor());
if (aStep > 0) {
value -= NS_floorModulo(value - GetStepBase(), validStep);
value += validStep;
@ -1610,12 +1632,12 @@ HTMLInputElement::ApplyStep(int32_t aStep)
// minimum unless stepUp() moves us higher than minimum.
if (GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW) && aStep > 0 &&
value <= minimum) {
MOZ_ASSERT(!MOZ_DOUBLE_IS_NaN(minimum), "Can't be NaN if we are here");
MOZ_ASSERT(!minimum.isNaN(), "Can't be NaN if we are here");
value = minimum;
// Same goes for stepDown() and maximum.
} else if (GetValidityState(VALIDITY_STATE_RANGE_OVERFLOW) && aStep < 0 &&
value >= maximum) {
MOZ_ASSERT(!MOZ_DOUBLE_IS_NaN(maximum), "Can't be NaN if we are here");
MOZ_ASSERT(!maximum.isNaN(), "Can't be NaN if we are here");
value = maximum;
// If we go down, we want to clamp on min.
} else if (aStep < 0 && minimum == minimum) {
@ -2587,7 +2609,7 @@ void
HTMLInputElement::StartRangeThumbDrag(nsGUIEvent* aEvent)
{
mIsDraggingRange = true;
mRangeThumbDragStartValue = GetValueAsDouble();
mRangeThumbDragStartValue = GetValueAsDecimal();
nsIPresShell::SetCapturingContent(this, CAPTURE_IGNOREALLOWED |
CAPTURE_RETARGETTOELEMENT);
nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
@ -2639,9 +2661,9 @@ HTMLInputElement::CancelRangeThumbDrag(bool aIsForUserEvent)
}
void
HTMLInputElement::SetValueOfRangeForUserEvent(double aValue)
HTMLInputElement::SetValueOfRangeForUserEvent(Decimal aValue)
{
MOZ_ASSERT(MOZ_DOUBLE_IS_FINITE(aValue));
MOZ_ASSERT(aValue.isFinite());
nsAutoString val;
ConvertNumberToString(aValue, val);
@ -2980,19 +3002,17 @@ HTMLInputElement::PostHandleEvent(nsEventChainPostVisitor& aVisitor)
keyEvent->keyCode == NS_VK_PAGE_DOWN ||
keyEvent->keyCode == NS_VK_HOME ||
keyEvent->keyCode == NS_VK_END)) {
double minimum = GetMinimum();
double maximum = GetMaximum();
MOZ_ASSERT(MOZ_DOUBLE_IS_FINITE(minimum) &&
MOZ_DOUBLE_IS_FINITE(maximum));
Decimal minimum = GetMinimum();
Decimal maximum = GetMaximum();
MOZ_ASSERT(minimum.isFinite() && maximum.isFinite());
if (minimum < maximum) { // else the value is locked to the minimum
double value = GetValueAsDouble();
double step = GetStep();
Decimal value = GetValueAsDecimal();
Decimal step = GetStep();
if (step == kStepAny) {
step = GetDefaultStep();
}
MOZ_ASSERT(MOZ_DOUBLE_IS_FINITE(value) &&
MOZ_DOUBLE_IS_FINITE(step));
double newValue;
MOZ_ASSERT(value.isFinite() && step.isFinite());
Decimal newValue;
switch (keyEvent->keyCode) {
case NS_VK_LEFT:
newValue = value + (IsLTR(this) ? -step : step);
@ -3017,10 +3037,10 @@ HTMLInputElement::PostHandleEvent(nsEventChainPostVisitor& aVisitor)
case NS_VK_PAGE_UP:
// For PgUp/PgDn we jump 10% of the total range, unless step
// requires us to jump more.
newValue = value + std::max(step, 0.1 * (maximum - minimum));
newValue = value + std::max(step, (maximum - minimum) / 10);
break;
case NS_VK_PAGE_DOWN:
newValue = value - std::max(step, 0.1 * (maximum - minimum));
newValue = value - std::max(step, (maximum - minimum) / 10);
break;
}
SetValueOfRangeForUserEvent(newValue);
@ -3430,10 +3450,9 @@ HTMLInputElement::SanitizeValue(nsAString& aValue)
break;
case NS_FORM_INPUT_RANGE:
{
double minimum = GetMinimum();
double maximum = GetMaximum();
MOZ_ASSERT(MOZ_DOUBLE_IS_FINITE(minimum) &&
MOZ_DOUBLE_IS_FINITE(maximum),
Decimal minimum = GetMinimum();
Decimal maximum = GetMaximum();
MOZ_ASSERT(minimum.isFinite() && maximum.isFinite(),
"type=range should have a default maximum/minimum");
// We use this to avoid modifying the string unnecessarily, since that
@ -3441,12 +3460,12 @@ HTMLInputElement::SanitizeValue(nsAString& aValue)
// parse out from aValue needs to be sanitized.
bool needSanitization = false;
double value;
Decimal value;
bool ok = ConvertStringToNumber(aValue, value);
if (!ok) {
needSanitization = true;
// Set value to midway between minimum and maximum.
value = maximum <= minimum ? minimum : minimum + (maximum - minimum)/2.0;
value = maximum <= minimum ? minimum : minimum + (maximum - minimum)/2;
} else if (value < minimum || maximum < minimum) {
needSanitization = true;
value = minimum;
@ -3455,13 +3474,13 @@ HTMLInputElement::SanitizeValue(nsAString& aValue)
value = maximum;
}
double step = GetStep();
Decimal step = GetStep();
if (step != kStepAny) {
double stepBase = GetStepBase();
Decimal stepBase = GetStepBase();
// There could be rounding issues below when dealing with fractional
// numbers, but let's ignore that until ECMAScript supplies us with a
// decimal number type.
double deltaToStep = NS_floorModulo(value - stepBase, step);
Decimal deltaToStep = NS_floorModulo(value - stepBase, step);
if (deltaToStep != 0) {
// "suffering from a step mismatch"
// Round the element's value to the nearest number for which the
@ -3470,9 +3489,9 @@ HTMLInputElement::SanitizeValue(nsAString& aValue)
// less than the minimum, which is less than or equal to the
// maximum, if there is a number that matches these constraints:
MOZ_ASSERT(deltaToStep > 0, "stepBelow/stepAbove will be wrong");
double stepBelow = value - deltaToStep;
double stepAbove = value - deltaToStep + step;
double halfStep = step / 2;
Decimal stepBelow = value - deltaToStep;
Decimal stepAbove = value - deltaToStep + step;
Decimal halfStep = step / 2;
bool stepAboveIsClosest = (stepAbove - value) <= halfStep;
bool stepAboveInRange = stepAbove >= minimum &&
stepAbove <= maximum;
@ -3490,8 +3509,10 @@ HTMLInputElement::SanitizeValue(nsAString& aValue)
}
if (needSanitization) {
aValue.Truncate();
aValue.AppendFloat(value);
char buf[32];
DebugOnly<bool> ok = value.toString(buf, ArrayLength(buf));
aValue.AssignASCII(buf);
MOZ_ASSERT(ok, "buf not big enough");
}
}
break;
@ -4976,7 +4997,7 @@ HTMLInputElement::DoesMinMaxApply() const
}
}
double
Decimal
HTMLInputElement::GetStep() const
{
MOZ_ASSERT(DoesStepApply(), "GetStep() can only be called if @step applies");
@ -4993,9 +5014,8 @@ HTMLInputElement::GetStep() const
return kStepAny;
}
nsresult ec;
double step = stepStr.ToDouble(&ec);
if (NS_FAILED(ec) || !MOZ_DOUBLE_IS_FINITE(step) || step <= 0) {
Decimal step = StringToDecimal(stepStr);
if (!step.isFinite() || step <= 0) {
step = GetDefaultStep();
}
@ -5140,13 +5160,13 @@ HTMLInputElement::IsRangeOverflow() const
return false;
}
double maximum = GetMaximum();
if (MOZ_DOUBLE_IS_NaN(maximum)) {
Decimal maximum = GetMaximum();
if (maximum.isNaN()) {
return false;
}
double value = GetValueAsDouble();
if (MOZ_DOUBLE_IS_NaN(value)) {
Decimal value = GetValueAsDecimal();
if (value.isNaN()) {
return false;
}
@ -5160,13 +5180,13 @@ HTMLInputElement::IsRangeUnderflow() const
return false;
}
double minimum = GetMinimum();
if (MOZ_DOUBLE_IS_NaN(minimum)) {
Decimal minimum = GetMinimum();
if (minimum.isNaN()) {
return false;
}
double value = GetValueAsDouble();
if (MOZ_DOUBLE_IS_NaN(value)) {
Decimal value = GetValueAsDecimal();
if (value.isNaN()) {
return false;
}
@ -5180,13 +5200,13 @@ HTMLInputElement::HasStepMismatch() const
return false;
}
double value = GetValueAsDouble();
if (MOZ_DOUBLE_IS_NaN(value)) {
Decimal value = GetValueAsDecimal();
if (value.isNaN()) {
// The element can't suffer from step mismatch if it's value isn't a number.
return false;
}
double step = GetStep();
Decimal step = GetStep();
if (step == kStepAny) {
return false;
}
@ -5197,7 +5217,7 @@ HTMLInputElement::HasStepMismatch() const
// an integer (millisecond precision), we can get rid of the precision
// loss by rounding step. This will however lead to erroneous results
// when step was intented to have a precision superior to a millisecond.
step = NS_round(step);
step = step.round();
}
// Value has to be an integral multiple of step.
@ -5432,10 +5452,13 @@ HTMLInputElement::GetValidationMessage(nsAString& aValidationMessage,
if (mType == NS_FORM_INPUT_NUMBER ||
mType == NS_FORM_INPUT_RANGE) {
//We want to show the value as parsed when it's a number
double maximum = GetMaximum();
MOZ_ASSERT(!MOZ_DOUBLE_IS_NaN(maximum));
Decimal maximum = GetMaximum();
MOZ_ASSERT(!maximum.isNaN());
maxStr.AppendFloat(maximum);
char buf[32];
DebugOnly<bool> ok = maximum.toString(buf, ArrayLength(buf));
maxStr.AssignASCII(buf);
MOZ_ASSERT(ok, "buf not big enough");
} else if (mType == NS_FORM_INPUT_DATE || mType == NS_FORM_INPUT_TIME) {
GetAttr(kNameSpaceID_None, nsGkAtoms::max, maxStr);
} else {
@ -5456,10 +5479,13 @@ HTMLInputElement::GetValidationMessage(nsAString& aValidationMessage,
nsAutoString minStr;
if (mType == NS_FORM_INPUT_NUMBER ||
mType == NS_FORM_INPUT_RANGE) {
double minimum = GetMinimum();
MOZ_ASSERT(!MOZ_DOUBLE_IS_NaN(minimum));
Decimal minimum = GetMinimum();
MOZ_ASSERT(!minimum.isNaN());
minStr.AppendFloat(minimum);
char buf[32];
DebugOnly<bool> ok = minimum.toString(buf, ArrayLength(buf));
minStr.AssignASCII(buf);
MOZ_ASSERT(ok, "buf not big enough");
} else if (mType == NS_FORM_INPUT_DATE || mType == NS_FORM_INPUT_TIME) {
GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr);
} else {
@ -5477,11 +5503,11 @@ HTMLInputElement::GetValidationMessage(nsAString& aValidationMessage,
{
nsXPIDLString message;
double value = GetValueAsDouble();
MOZ_ASSERT(!MOZ_DOUBLE_IS_NaN(value));
Decimal value = GetValueAsDecimal();
MOZ_ASSERT(!value.isNaN());
double step = GetStep();
MOZ_ASSERT(step != kStepAny);
Decimal step = GetStep();
MOZ_ASSERT(step != kStepAny && step > 0);
// In case this is a date and the step is not an integer, we don't want to
// display the dates corresponding to the truncated timestamps of valueLow
@ -5489,18 +5515,18 @@ HTMLInputElement::GetValidationMessage(nsAString& aValidationMessage,
// Instead we want the timestamps to correspond to a rounded day. That is,
// we want a multiple of the step scale factor (1 day) as well as of step.
if (mType == NS_FORM_INPUT_DATE) {
step = EuclidLCM<uint64_t>(static_cast<uint64_t>(step),
static_cast<uint64_t>(GetStepScaleFactor()));
step = EuclidLCM<Decimal>(step.floor(),
GetStepScaleFactor().floor());
}
double stepBase = GetStepBase();
Decimal stepBase = GetStepBase();
double valueLow = value - NS_floorModulo(value - stepBase, step);
double valueHigh = value + step - NS_floorModulo(value - stepBase, step);
Decimal valueLow = value - NS_floorModulo(value - stepBase, step);
Decimal valueHigh = value + step - NS_floorModulo(value - stepBase, step);
double maximum = GetMaximum();
Decimal maximum = GetMaximum();
if (MOZ_DOUBLE_IS_NaN(maximum) || valueHigh <= maximum) {
if (maximum.isNaN() || valueHigh <= maximum) {
nsAutoString valueLowStr, valueHighStr;
ConvertNumberToString(valueLow, valueLowStr);
ConvertNumberToString(valueHigh, valueHighStr);
@ -5940,7 +5966,7 @@ HTMLInputElement::GetFilterFromAccept()
return filter;
}
double
Decimal
HTMLInputElement::GetStepScaleFactor() const
{
MOZ_ASSERT(DoesStepApply());
@ -5955,11 +5981,11 @@ HTMLInputElement::GetStepScaleFactor() const
return kStepScaleFactorTime;
default:
MOZ_ASSERT(false, "Unrecognized input type");
return MOZ_DOUBLE_NaN();
return Decimal::nan();
}
}
double
Decimal
HTMLInputElement::GetDefaultStep() const
{
MOZ_ASSERT(DoesStepApply());
@ -5973,7 +5999,7 @@ HTMLInputElement::GetDefaultStep() const
return kDefaultStepTime;
default:
MOZ_ASSERT(false, "Unrecognized input type");
return MOZ_DOUBLE_NaN();
return Decimal::nan();
}
}
@ -6008,14 +6034,14 @@ HTMLInputElement::UpdateHasRange()
return;
}
double minimum = GetMinimum();
if (!MOZ_DOUBLE_IS_NaN(minimum)) {
Decimal minimum = GetMinimum();
if (!minimum.isNaN()) {
mHasRange = true;
return;
}
double maximum = GetMaximum();
if (!MOZ_DOUBLE_IS_NaN(maximum)) {
Decimal maximum = GetMaximum();
if (!maximum.isNaN()) {
mHasRange = true;
return;
}

View File

@ -20,6 +20,7 @@
#include "nsIFile.h"
#include "nsIFilePicker.h"
#include "nsIContentPrefService2.h"
#include "mozilla/Decimal.h"
class nsDOMFileList;
class nsIFilePicker;
@ -154,7 +155,7 @@ public:
void StartRangeThumbDrag(nsGUIEvent* aEvent);
void FinishRangeThumbDrag(nsGUIEvent* aEvent = nullptr);
void CancelRangeThumbDrag(bool aIsForUserEvent = true);
void SetValueOfRangeForUserEvent(double aValue);
void SetValueOfRangeForUserEvent(Decimal aValue);
virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
nsIContent* aBindingParent,
@ -314,12 +315,12 @@ public:
void FireChangeEventIfNeeded();
/**
* Returns the input element's value as a double-precision float.
* Returns the input element's value as a Decimal.
* Returns NaN if the current element's value is not a floating point number.
*
* @return the input element's value as a double-precision float.
* @return the input element's value as a Decimal.
*/
double GetValueAsDouble() const;
Decimal GetValueAsDecimal() const;
/**
* Returns the input's "minimum" (as defined by the HTML5 spec) as a double.
@ -329,7 +330,7 @@ public:
*
* NOTE: Only call this if you know DoesMinMaxApply() returns true.
*/
double GetMinimum() const;
Decimal GetMinimum() const;
/**
* Returns the input's "maximum" (as defined by the HTML5 spec) as a double.
@ -339,7 +340,7 @@ public:
*
* NOTE:Only call this if you know DoesMinMaxApply() returns true.
*/
double GetMaximum() const;
Decimal GetMaximum() const;
// WebIDL
@ -579,7 +580,7 @@ public:
double ValueAsNumber() const
{
return DoesValueAsNumberApply() ? GetValueAsDouble()
return DoesValueAsNumberApply() ? GetValueAsDecimal().toDouble()
: MOZ_DOUBLE_NaN();
}
@ -608,7 +609,7 @@ public:
*
* @return the current step value.
*/
double GetStep() const;
Decimal GetStep() const;
void GetValidationMessage(nsAString& aValidationMessage, ErrorResult& aRv);
@ -947,28 +948,28 @@ protected:
nsIRadioGroupContainer* GetRadioGroupContainer() const;
/**
* Convert a string to a number in a type specific way,
* 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.
* @param aResultValue the timestamp as a double.
* @param aResultValue the number as a Decimal.
* @result whether the parsing was successful.
*/
bool ConvertStringToNumber(nsAString& aValue, double& aResultValue) const;
bool ConvertStringToNumber(nsAString& aValue, Decimal& aResultValue) const;
/**
* Convert a double to a string in a type specific way, ie convert a timestamp
* 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 double to be converted
* @param aResultString [out] the string representing the double
* @param aValue the Decimal to be converted
* @param aResultString [out] the string representing the Decimal
* @return whether the function succeded, 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.
*/
bool ConvertNumberToString(double aValue, nsAString& aResultString) const;
bool ConvertNumberToString(Decimal aValue, nsAString& aResultString) const;
/**
* Parse a date string of the form yyyy-mm-dd
@ -1017,11 +1018,11 @@ protected:
static bool ParseTime(const nsAString& aValue, uint32_t* aResult);
/**
* Sets the value of the element to the string representation of the double.
* Sets the value of the element to the string representation of the Decimal.
*
* @param aValue The double that will be used to set the value.
* @param aValue The Decimal that will be used to set the value.
*/
void SetValue(double aValue);
void SetValue(Decimal aValue);
/**
* Update the HAS_RANGE bit field value.
@ -1033,7 +1034,7 @@ protected:
* See:
* http://www.whatwg.org/specs/web-apps/current-work/multipage/common-input-element-attributes.html#concept-input-step-scale
*/
double GetStepScaleFactor() const;
Decimal GetStepScaleFactor() const;
/**
* Return the base used to compute if a value matches step.
@ -1041,13 +1042,13 @@ protected:
*
* @return The step base.
*/
double GetStepBase() const;
Decimal GetStepBase() const;
/**
* Returns the default step for the current type.
* @return the default step for the current type.
*/
double GetDefaultStep() const;
Decimal GetDefaultStep() const;
/**
* Apply a step change from stepUp or stepDown by multiplying aStep by the
@ -1123,22 +1124,22 @@ protected:
* the drag started. Used to reset the input to its old value if the drag is
* canceled.
*/
double mRangeThumbDragStartValue;
Decimal mRangeThumbDragStartValue;
// Step scale factor values, for input types that have one.
static const double kStepScaleFactorDate;
static const double kStepScaleFactorNumberRange;
static const double kStepScaleFactorTime;
static const Decimal kStepScaleFactorDate;
static const Decimal kStepScaleFactorNumberRange;
static const Decimal kStepScaleFactorTime;
// Default step base value when a type do not have specific one.
static const double kDefaultStepBase;
static const Decimal kDefaultStepBase;
// Default step used when there is no specified step.
static const double kDefaultStep;
static const double kDefaultStepTime;
static const Decimal kDefaultStep;
static const Decimal kDefaultStepTime;
// Float value returned by GetStep() when the step attribute is set to 'any'.
static const double kStepAny;
static const Decimal kStepAny;
/**
* The type of this input (<input type=...>) as an integer.

View File

@ -681,8 +681,7 @@ for (var test of data) {
{ step: '0.001', value: '15:05:05', min: '00:00', result: true },
{ step: '0.000001', value: '15:05:05', min: '00:00', result: true },
// This value has conversion to double issues.
{ step: '0.0000001', value: '15:05:05', min: '00:00', result: true,
todo: true },
{ step: '0.0000001', value: '15:05:05', min: '00:00', result: true },
// Some random values.
{ step: '100', value: '15:06:40', min: '00:00', result: true },
{ step: '100', value: '15:05:05.010', min: '00:00', result: false,

View File

@ -108,7 +108,7 @@ function checkNumberGet()
["42", 42],
["-42", -42], // should work for negative values
["42.1234", 42.1234],
["123.12345678912345", 123.12345678912345], // double precision
["123.123456789123", 123.123456789123], // double precision
["1e2", 100], // e should be usable
["2e1", 20],
["1e-1", 0.1], // value after e can be negative
@ -196,7 +196,7 @@ function checkRangeGet()
["42", 42],
["-42", -42], // should work for negative values
["42.1234", 42.1234],
["123.12345678912345", 123.12345678912345], // double precision
["123.123456789123", 123.123456789123], // double precision
["1e2", 100], // e should be usable
["2e1", 20],
["1e-1", 0.1], // value after e can be negative

View File

@ -369,13 +369,11 @@ nsRangeFrame::GetValueAsFractionOfRange()
MOZ_ASSERT(input->GetType() == NS_FORM_INPUT_RANGE);
double value = input->GetValueAsDouble();
double minimum = input->GetMinimum();
double maximum = input->GetMaximum();
Decimal value = input->GetValueAsDecimal();
Decimal minimum = input->GetMinimum();
Decimal maximum = input->GetMaximum();
MOZ_ASSERT(MOZ_DOUBLE_IS_FINITE(value) &&
MOZ_DOUBLE_IS_FINITE(minimum) &&
MOZ_DOUBLE_IS_FINITE(maximum),
MOZ_ASSERT(value.isFinite() && minimum.isFinite() && maximum.isFinite(),
"type=range should have a default maximum/minimum");
if (maximum <= minimum) {
@ -385,10 +383,10 @@ nsRangeFrame::GetValueAsFractionOfRange()
MOZ_ASSERT(value >= minimum && value <= maximum, "Unsanitized value");
return (value - minimum) / (maximum - minimum);
return ((value - minimum) / (maximum - minimum)).toDouble();
}
double
Decimal
nsRangeFrame::GetValueAtEventPoint(nsGUIEvent* aEvent)
{
MOZ_ASSERT(aEvent->eventStructType == NS_MOUSE_EVENT ||
@ -400,15 +398,14 @@ nsRangeFrame::GetValueAtEventPoint(nsGUIEvent* aEvent)
MOZ_ASSERT(input->GetType() == NS_FORM_INPUT_RANGE);
double minimum = input->GetMinimum();
double maximum = input->GetMaximum();
MOZ_ASSERT(MOZ_DOUBLE_IS_FINITE(minimum) &&
MOZ_DOUBLE_IS_FINITE(maximum),
Decimal minimum = input->GetMinimum();
Decimal maximum = input->GetMaximum();
MOZ_ASSERT(minimum.isFinite() && maximum.isFinite(),
"type=range should have a default maximum/minimum");
if (maximum <= minimum) {
return minimum;
}
double range = maximum - minimum;
Decimal range = maximum - minimum;
nsIntPoint absPoint;
if (aEvent->eventStructType == NS_TOUCH_EVENT) {
@ -423,7 +420,7 @@ nsRangeFrame::GetValueAtEventPoint(nsGUIEvent* aEvent)
if (point == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)) {
// We don't want to change the current value for this error state.
return GetValue();
return static_cast<dom::HTMLInputElement*>(mContent)->GetValueAsDecimal();
}
nsRect rangeContentRect = GetContentRectRelativeToSelf();
@ -449,7 +446,7 @@ nsRangeFrame::GetValueAtEventPoint(nsGUIEvent* aEvent)
}
}
double fraction;
Decimal fraction;
if (IsHorizontal()) {
nscoord traversableDistance = rangeContentRect.width - thumbSize.width;
if (traversableDistance <= 0) {
@ -458,9 +455,9 @@ nsRangeFrame::GetValueAtEventPoint(nsGUIEvent* aEvent)
nscoord posAtStart = rangeContentRect.x + thumbSize.width/2;
nscoord posAtEnd = posAtStart + traversableDistance;
nscoord posOfPoint = mozilla::clamped(point.x, posAtStart, posAtEnd);
fraction = (posOfPoint - posAtStart) / double(traversableDistance);
fraction = Decimal(posOfPoint - posAtStart) / traversableDistance;
if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
fraction = 1.0 - fraction;
fraction = Decimal(1) - fraction;
}
} else {
nscoord traversableDistance = rangeContentRect.height - thumbSize.height;
@ -472,10 +469,10 @@ nsRangeFrame::GetValueAtEventPoint(nsGUIEvent* aEvent)
nscoord posOfPoint = mozilla::clamped(point.y, posAtStart, posAtEnd);
// For a vertical range, the top (posAtStart) is the highest value, so we
// subtract the fraction from 1.0 to get that polarity correct.
fraction = 1.0 - (posOfPoint - posAtStart) / double(traversableDistance);
fraction = Decimal(1) - Decimal(posOfPoint - posAtStart) / traversableDistance;
}
MOZ_ASSERT(fraction >= 0.0 && fraction <= 1.0);
MOZ_ASSERT(fraction >= 0 && fraction <= 1);
return minimum + fraction * range;
}
@ -723,19 +720,19 @@ nsRangeFrame::IsHorizontal(const nsSize *aFrameSizeOverride) const
double
nsRangeFrame::GetMin() const
{
return static_cast<dom::HTMLInputElement*>(mContent)->GetMinimum();
return static_cast<dom::HTMLInputElement*>(mContent)->GetMinimum().toDouble();
}
double
nsRangeFrame::GetMax() const
{
return static_cast<dom::HTMLInputElement*>(mContent)->GetMaximum();
return static_cast<dom::HTMLInputElement*>(mContent)->GetMaximum().toDouble();
}
double
nsRangeFrame::GetValue() const
{
return static_cast<dom::HTMLInputElement*>(mContent)->GetValueAsDouble();
return static_cast<dom::HTMLInputElement*>(mContent)->GetValueAsDecimal().toDouble();
}
nsIAtom*

View File

@ -7,6 +7,7 @@
#define nsRangeFrame_h___
#include "mozilla/Attributes.h"
#include "mozilla/Decimal.h"
#include "nsContainerFrame.h"
#include "nsIAnonymousContentCreator.h"
#include "nsCOMPtr.h"
@ -110,7 +111,7 @@ public:
*/
bool ShouldUseNativeStyle() const;
double GetValueAtEventPoint(nsGUIEvent* aEvent);
mozilla::Decimal GetValueAtEventPoint(nsGUIEvent* aEvent);
/**
* Helper that's used when the value of the range changes to reposition the