String serializeForNumberType(const Decimal& number) { if (number.isZero()) { // Decimal::toString appends exponent, e.g. "0e-18" return number.isNegative() ? "-0" : "0"; } return number.toString(); }
DateTimeNumericFieldElement::Step DateTimeEditBuilder::createStep(double msPerFieldUnit, double msPerFieldSize) const { const Decimal msPerFieldUnitDecimal(static_cast<int>(msPerFieldUnit)); const Decimal msPerFieldSizeDecimal(static_cast<int>(msPerFieldSize)); Decimal stepMilliseconds = stepRange().step(); ASSERT(!msPerFieldUnitDecimal.isZero()); ASSERT(!msPerFieldSizeDecimal.isZero()); ASSERT(!stepMilliseconds.isZero()); DateTimeNumericFieldElement::Step step(1, 0); if (stepMilliseconds.remainder(msPerFieldSizeDecimal).isZero()) stepMilliseconds = msPerFieldSizeDecimal; if (msPerFieldSizeDecimal.remainder(stepMilliseconds).isZero() && stepMilliseconds.remainder(msPerFieldUnitDecimal).isZero()) { step.step = static_cast<int>((stepMilliseconds / msPerFieldUnitDecimal).toDouble()); step.stepBase = static_cast<int>((stepRange().stepBase() / msPerFieldUnitDecimal).floor().remainder(msPerFieldSizeDecimal / msPerFieldUnitDecimal).toDouble()); } return step; }
Decimal parseToDecimalForNumberType(const String& string, const Decimal& fallbackValue) { // http://www.whatwg.org/specs/web-apps/current-work/#floating-point-numbers and parseToDoubleForNumberType // String::toDouble() accepts leading + and whitespace characters, which are not valid here. const UChar firstCharacter = string[0]; if (firstCharacter != '-' && firstCharacter != '.' && !isASCIIDigit(firstCharacter)) return fallbackValue; const Decimal value = Decimal::fromString(string); if (!value.isFinite()) return fallbackValue; // Numbers are considered finite IEEE 754 Double-precision floating point values. const Decimal doubleMax = Decimal::fromDouble(std::numeric_limits<double>::max()); if (value < -doubleMax || value > doubleMax) return fallbackValue; // We return +0 for -0 case. return value.isZero() ? Decimal(0) : value; }
Decimal parseToDecimalForNumberType(const String& string, const Decimal& fallbackValue) { // See HTML5 2.5.4.3 `Real numbers.' and parseToDoubleForNumberType // String::toDouble() accepts leading + and whitespace characters, which are not valid here. const UChar firstCharacter = string[0]; if (firstCharacter != '-' && firstCharacter != '.' && !isASCIIDigit(firstCharacter)) return fallbackValue; const Decimal value = Decimal::fromString(string); if (!value.isFinite()) return fallbackValue; // Numbers are considered finite IEEE 754 single-precision floating point values. // See HTML5 2.5.4.3 `Real numbers.' // FIXME: We should use numeric_limits<double>::max for number input type. const Decimal floatMax = Decimal::fromDouble(std::numeric_limits<float>::max()); if (value < -floatMax || value > floatMax) return fallbackValue; // We return +0 for -0 case. return value.isZero() ? Decimal(0) : value; }
void InputType::stepUpFromRenderer(int n) { // The differences from stepUp()/stepDown(): // // Difference 1: the current value // If the current value is not a number, including empty, the current value is assumed as 0. // * If 0 is in-range, and matches to step value // - The value should be the +step if n > 0 // - The value should be the -step if n < 0 // If -step or +step is out of range, new value should be 0. // * If 0 is smaller than the minimum value // - The value should be the minimum value for any n // * If 0 is larger than the maximum value // - The value should be the maximum value for any n // * If 0 is in-range, but not matched to step value // - The value should be the larger matched value nearest to 0 if n > 0 // e.g. <input type=number min=-100 step=3> -> 2 // - The value should be the smaler matched value nearest to 0 if n < 0 // e.g. <input type=number min=-100 step=3> -> -1 // As for date/datetime-local/month/time/week types, the current value is assumed as "the current local date/time". // As for datetime type, the current value is assumed as "the current date/time in UTC". // If the current value is smaller than the minimum value: // - The value should be the minimum value if n > 0 // - Nothing should happen if n < 0 // If the current value is larger than the maximum value: // - The value should be the maximum value if n < 0 // - Nothing should happen if n > 0 // // Difference 2: clamping steps // If the current value is not matched to step value: // - The value should be the larger matched value nearest to 0 if n > 0 // e.g. <input type=number value=3 min=-100 step=3> -> 5 // - The value should be the smaler matched value nearest to 0 if n < 0 // e.g. <input type=number value=3 min=-100 step=3> -> 2 // // n is assumed as -n if step < 0. ASSERT(isSteppable()); if (!isSteppable()) return; ASSERT(n); if (!n) return; StepRange stepRange(createStepRange(AnyIsDefaultStep)); // FIXME: Not any changes after stepping, even if it is an invalid value, may be better. // (e.g. Stepping-up for <input type="number" value="foo" step="any" /> => "foo") if (!stepRange.hasStep()) return; const Decimal step = stepRange.step(); int sign; if (step > 0) sign = n; else if (step < 0) sign = -n; else sign = 0; String currentStringValue = element()->value(); Decimal current = parseToNumberOrNaN(currentStringValue); if (!current.isFinite()) { ExceptionCode ec; current = defaultValueForStepUp(); const Decimal nextDiff = step * n; if (current < stepRange.minimum() - nextDiff) current = stepRange.minimum() - nextDiff; if (current > stepRange.maximum() - nextDiff) current = stepRange.maximum() - nextDiff; setValueAsDecimal(current, DispatchInputAndChangeEvent, ec); } if ((sign > 0 && current < stepRange.minimum()) || (sign < 0 && current > stepRange.maximum())) { ExceptionCode ec; setValueAsDecimal(sign > 0 ? stepRange.minimum() : stepRange.maximum(), DispatchInputAndChangeEvent, ec); } else { ExceptionCode ec; if (stepMismatch(element()->value())) { ASSERT(!step.isZero()); const Decimal base = stepRange.stepBase(); Decimal newValue; if (sign < 0) newValue = base + ((current - base) / step).floor() * step; else if (sign > 0) newValue = base + ((current - base) / step).ceiling() * step; else newValue = current; if (newValue < stepRange.minimum()) newValue = stepRange.minimum(); if (newValue > stepRange.maximum()) newValue = stepRange.maximum(); setValueAsDecimal(newValue, n == 1 || n == -1 ? DispatchInputAndChangeEvent : DispatchNoEvent, ec); if (n > 1) applyStep(n - 1, AnyIsDefaultStep, DispatchInputAndChangeEvent, ec); else if (n < -1) applyStep(n + 1, AnyIsDefaultStep, DispatchInputAndChangeEvent, ec); } else applyStep(n, AnyIsDefaultStep, DispatchInputAndChangeEvent, ec); } }
void InputType::applyStep(const Decimal& current, int count, AnyStepHandling anyStepHandling, TextFieldEventBehavior eventBehavior, ExceptionState& exceptionState) { StepRange stepRange(createStepRange(anyStepHandling)); if (!stepRange.hasStep()) { exceptionState.throwDOMException(InvalidStateError, "This form element does not have an allowed value step."); return; } EventQueueScope scope; const Decimal step = stepRange.step(); const AtomicString& stepString = element().fastGetAttribute(stepAttr); if (!equalIgnoringCase(stepString, "any") && stepRange.stepMismatch(current)) { // Snap-to-step / clamping steps // If the current value is not matched to step value: // - The value should be the larger matched value nearest to 0 if count > 0 // e.g. <input type=number value=3 min=-100 step=3> -> 5 // - The value should be the smaller matched value nearest to 0 if count < 0 // e.g. <input type=number value=3 min=-100 step=3> -> 2 // ASSERT(!step.isZero()); Decimal newValue; const Decimal base = stepRange.stepBase(); if (count < 0) newValue = base + ((current - base) / step).floor() * step; else if (count > 0) newValue = base + ((current - base) / step).ceiling() * step; else newValue = current; if (newValue < stepRange.minimum()) newValue = stepRange.minimum(); if (newValue > stepRange.maximum()) newValue = stepRange.maximum(); setValueAsDecimal(newValue, count == 1 || count == -1 ? DispatchChangeEvent : DispatchNoEvent, IGNORE_EXCEPTION); if (count > 1) { applyStep(newValue, count - 1, AnyIsDefaultStep, DispatchChangeEvent, IGNORE_EXCEPTION); return; } if (count < -1) { applyStep(newValue, count + 1, AnyIsDefaultStep, DispatchChangeEvent, IGNORE_EXCEPTION); return; } } else { Decimal newValue = current + stepRange.step() * count; if (!equalIgnoringCase(stepString, "any")) newValue = stepRange.alignValueForStep(current, newValue); if (newValue > stepRange.maximum()) newValue = newValue - stepRange.step(); else if (newValue < stepRange.minimum()) newValue = newValue + stepRange.step(); setValueAsDecimal(newValue, eventBehavior, exceptionState); } if (AXObjectCache* cache = element().document().existingAXObjectCache()) cache->postNotification(&element(), AXObjectCache::AXValueChanged, true); }
void InputType::applyStep(const Decimal& current, int count, AnyStepHandling anyStepHandling, TextFieldEventBehavior eventBehavior, ExceptionState& exceptionState) { // https://html.spec.whatwg.org/multipage/forms.html#dom-input-stepup StepRange stepRange(createStepRange(anyStepHandling)); // 2. If the element has no allowed value step, then throw an // InvalidStateError exception, and abort these steps. if (!stepRange.hasStep()) { exceptionState.throwDOMException(InvalidStateError, "This form element does not have an allowed value step."); return; } // 3. If the element has a minimum and a maximum and the minimum is greater // than the maximum, then abort these steps. if (stepRange.minimum() > stepRange.maximum()) return; // 4. If the element has a minimum and a maximum and there is no value // greater than or equal to the element's minimum and less than or equal to // the element's maximum that, when subtracted from the step base, is an // integral multiple of the allowed value step, then abort these steps. Decimal alignedMaximum = stepRange.stepSnappedMaximum(); if (!alignedMaximum.isFinite()) return; Decimal base = stepRange.stepBase(); Decimal step = stepRange.step(); EventQueueScope scope; Decimal newValue = current; const AtomicString& stepString = element().fastGetAttribute(stepAttr); if (!equalIgnoringCase(stepString, "any") && stepRange.stepMismatch(current)) { // Snap-to-step / clamping steps // If the current value is not matched to step value: // - The value should be the larger matched value nearest to 0 if count > 0 // e.g. <input type=number value=3 min=-100 step=3> -> 5 // - The value should be the smaller matched value nearest to 0 if count < 0 // e.g. <input type=number value=3 min=-100 step=3> -> 2 // DCHECK(!step.isZero()); if (count < 0) { newValue = base + ((newValue - base) / step).floor() * step; ++count; } else if (count > 0) { newValue = base + ((newValue - base) / step).ceil() * step; --count; } } newValue = newValue + stepRange.step() * count; if (!equalIgnoringCase(stepString, "any")) newValue = stepRange.alignValueForStep(current, newValue); // 7. If the element has a minimum, and value is less than that minimum, // then set value to the smallest value that, when subtracted from the step // base, is an integral multiple of the allowed value step, and that is more // than or equal to minimum. // 8. If the element has a maximum, and value is greater than that maximum, // then set value to the largest value that, when subtracted from the step // base, is an integral multiple of the allowed value step, and that is less // than or equal to maximum. if (newValue > stepRange.maximum()) { newValue = alignedMaximum; } else if (newValue < stepRange.minimum()) { const Decimal alignedMinimum = base + ((stepRange.minimum() - base) / step).ceil() * step; DCHECK_GE(alignedMinimum, stepRange.minimum()); newValue = alignedMinimum; } // 9. Let value as string be the result of running the algorithm to convert // a number to a string, as defined for the input element's type attribute's // current state, on value. // 10. Set the value of the element to value as string. setValueAsDecimal(newValue, eventBehavior, exceptionState); if (AXObjectCache* cache = element().document().existingAXObjectCache()) cache->handleValueChanged(&element()); }