bool InputType::rangeOverflow(const String& value) const { if (!isSteppable()) return false; const Decimal numericValue = parseToNumberOrNaN(value); if (!numericValue.isFinite()) return false; return numericValue > createStepRange(RejectAny).maximum(); }
bool InputType::isOutOfRange(const String& value) const { if (!isSteppable()) return false; const Decimal numericValue = parseToNumberOrNaN(value); if (!numericValue.isFinite()) return true; StepRange stepRange(createStepRange(RejectAny)); return numericValue < stepRange.minimum() || numericValue > stepRange.maximum(); }
String InputType::validationMessage() const { const String value = element().value(); // The order of the following checks is meaningful. e.g. We'd like to show the // badInput message even if the control has other validation errors. if (hasBadInput()) return badInputText(); if (valueMissing(value)) return valueMissingText(); if (typeMismatch()) return typeMismatchText(); if (patternMismatch(value)) return locale().queryString(WebLocalizedString::ValidationPatternMismatch); if (element().tooLong()) return locale().validationMessageTooLongText(value.length(), element().maxLength()); if (!isSteppable()) return emptyString(); const Decimal numericValue = parseToNumberOrNaN(value); if (!numericValue.isFinite()) return emptyString(); StepRange stepRange(createStepRange(RejectAny)); if (numericValue < stepRange.minimum()) return rangeUnderflowText(stepRange.minimum()); if (numericValue > stepRange.maximum()) return rangeOverflowText(stepRange.maximum()); if (stepRange.stepMismatch(numericValue)) { ASSERT(stepRange.hasStep()); Decimal candidate1 = stepRange.clampValue(numericValue); String localizedCandidate1 = localizeValue(serialize(candidate1)); Decimal candidate2 = candidate1 < numericValue ? candidate1 + stepRange.step() : candidate1 - stepRange.step(); if (!candidate2.isFinite() || candidate2 < stepRange.minimum() || candidate2 > stepRange.maximum()) return locale().queryString(WebLocalizedString::ValidationStepMismatchCloseToLimit, localizedCandidate1); String localizedCandidate2 = localizeValue(serialize(candidate2)); if (candidate1 < candidate2) return locale().queryString(WebLocalizedString::ValidationStepMismatch, localizedCandidate1, localizedCandidate2); return locale().queryString(WebLocalizedString::ValidationStepMismatch, localizedCandidate2, localizedCandidate1); } return emptyString(); }
bool StepRange::stepMismatch(const Decimal& valueForCheck) const { if (!m_hasStep) return false; if (!valueForCheck.isFinite()) return false; const Decimal value = (valueForCheck - m_stepBase).abs(); if (!value.isFinite()) return false; // Decimal's fractional part size is DBL_MAN_DIG-bit. If the current value // is greater than step*2^DBL_MANT_DIG, the following computation for // remainder makes no sense. DEFINE_STATIC_LOCAL(const Decimal, twoPowerOfDoubleMantissaBits, (Decimal::Positive, 0, UINT64_C(1) << DBL_MANT_DIG)); if (value / twoPowerOfDoubleMantissaBits > m_step) return false; // The computation follows HTML5 4.10.7.2.10 `The step attribute' : // ... that number subtracted from the step base is not an integral multiple // of the allowed value step, the element is suffering from a step mismatch. const Decimal remainder = (value - m_step * (value / m_step).round()).abs(); // Accepts errors in lower fractional part which IEEE 754 single-precision // can't represent. const Decimal computedAcceptableError = acceptableError(); return computedAcceptableError < remainder && remainder < (m_step - computedAcceptableError); }
bool InputType::isInRange(const String& value) const { if (!isSteppable()) return false; StepRange stepRange(createStepRange(RejectAny)); if (!stepRange.hasRangeLimitations()) return false; const Decimal numericValue = parseToNumberOrNaN(value); if (!numericValue.isFinite()) return true; return numericValue >= stepRange.minimum() && numericValue <= stepRange.maximum(); }
double nsRangeFrame::GetValueAsFractionOfRange() { MOZ_ASSERT(mContent->IsHTML(nsGkAtoms::input), "bad cast"); dom::HTMLInputElement* input = static_cast<dom::HTMLInputElement*>(mContent); MOZ_ASSERT(input->GetType() == NS_FORM_INPUT_RANGE); Decimal value = input->GetValueAsDecimal(); Decimal minimum = input->GetMinimum(); Decimal maximum = input->GetMaximum(); MOZ_ASSERT(value.isFinite() && minimum.isFinite() && maximum.isFinite(), "type=range should have a default maximum/minimum"); if (maximum <= minimum) { MOZ_ASSERT(value == minimum, "Unsanitized value"); return 0.0; } MOZ_ASSERT(value >= minimum && value <= maximum, "Unsanitized value"); return ((value - minimum) / (maximum - minimum)).toDouble(); }
bool InputType::isOutOfRange(const String& value) const { if (!isSteppable()) return false; // This function should return true if either validity.rangeUnderflow or // validity.rangeOverflow are true. // If the INPUT has no value, they are false. const Decimal numericValue = parseToNumberOrNaN(value); if (!numericValue.isFinite()) return false; StepRange stepRange(createStepRange(RejectAny)); return stepRange.hasRangeLimitations() && (numericValue < stepRange.minimum() || numericValue > stepRange.maximum()); }
bool InputType::isInRange(const String& value) const { if (!isSteppable()) return false; // This function should return true if both of validity.rangeUnderflow and // validity.rangeOverflow are false. // If the INPUT has no value, they are false. const Decimal numericValue = parseToNumberOrNaN(value); if (!numericValue.isFinite()) return true; StepRange stepRange(createStepRange(RejectAny)); return numericValue >= stepRange.minimum() && numericValue <= stepRange.maximum(); }
String InputType::validationMessage() const { const String value = element()->value(); // The order of the following checks is meaningful. e.g. We'd like to show the // valueMissing message even if the control has other validation errors. if (valueMissing(value)) return valueMissingText(); if (typeMismatch()) return typeMismatchText(); if (hasBadInput()) return badInputText(); if (patternMismatch(value)) return validationMessagePatternMismatchText(); if (element()->tooLong()) return validationMessageTooLongText(numGraphemeClusters(value), element()->maxLength()); if (!isSteppable()) return emptyString(); const Decimal numericValue = parseToNumberOrNaN(value); if (!numericValue.isFinite()) return emptyString(); StepRange stepRange(createStepRange(RejectAny)); if (numericValue < stepRange.minimum()) return validationMessageRangeUnderflowText(serialize(stepRange.minimum())); if (numericValue > stepRange.maximum()) return validationMessageRangeOverflowText(serialize(stepRange.maximum())); if (stepRange.stepMismatch(numericValue)) { const String stepString = stepRange.hasStep() ? serializeForNumberType(stepRange.step() / stepRange.stepScaleFactor()) : emptyString(); return validationMessageStepMismatchText(serialize(stepRange.stepBase()), stepString); } return emptyString(); }
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 StepRange::parseStep(AnyStepHandling anyStepHandling, const StepDescription& stepDescription, const String& stepString) { if (stepString.isEmpty()) return stepDescription.defaultValue(); if (equalIgnoringCase(stepString, "any")) { switch (anyStepHandling) { case RejectAny: return Decimal::nan(); case AnyIsDefaultStep: return stepDescription.defaultValue(); default: ASSERT_NOT_REACHED(); } } Decimal step = parseToDecimalForNumberType(stepString); if (!step.isFinite() || step <= 0) return stepDescription.defaultValue(); switch (stepDescription.stepValueShouldBe) { case StepValueShouldBeReal: step *= stepDescription.stepScaleFactor; break; case ParsedStepValueShouldBeInteger: // For date, month, and week, the parsed value should be an integer for some types. step = std::max(step.round(), Decimal(1)); step *= stepDescription.stepScaleFactor; break; case ScaledStepValueShouldBeInteger: // For datetime, datetime-local, time, the result should be an integer. step *= stepDescription.stepScaleFactor; step = std::max(step.round(), Decimal(1)); break; default: ASSERT_NOT_REACHED(); } ASSERT(step > 0); return step; }
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 nsNumberControlFrame::SetValueOfAnonTextControl(const nsAString& aValue) { if (mHandlingInputEvent) { // We have been called while our HTMLInputElement is processing a DOM // 'input' event targeted at our anonymous text control. Our // HTMLInputElement has taken the value of our anon text control and // called SetValueInternal on itself to keep its own value in sync. As a // result SetValueInternal has called us. In this one case we do not want // to update our anon text control, especially since aValue will be the // sanitized value, and only the internal value should be sanitized (not // the value shown to the user, and certainly we shouldn't change it as // they type). return; } // Init to aValue so that we set aValue as the value of our text control if // aValue isn't a valid number (in which case the HTMLInputElement's validity // state will be set to invalid) or if aValue can't be localized: nsAutoString localizedValue(aValue); #ifdef ENABLE_INTL_API // Try and localize the value we will set: Decimal val = HTMLInputElement::StringToDecimal(aValue); if (val.isFinite()) { ICUUtils::LanguageTagIterForContent langTagIter(mContent); ICUUtils::LocalizeNumber(val.toDouble(), langTagIter, localizedValue); } #endif // We need to update the value of our anonymous text control here. Note that // this must be its value, and not its 'value' attribute (the default value), // since the default value is ignored once a user types into the text // control. HTMLInputElement::FromContent(mTextField)->SetValue(localizedValue); }
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); } }
Decimal nsRangeFrame::GetValueAtEventPoint(WidgetGUIEvent* aEvent) { MOZ_ASSERT(aEvent->eventStructType == NS_MOUSE_EVENT || aEvent->eventStructType == NS_TOUCH_EVENT, "Unexpected event type - aEvent->refPoint may be meaningless"); MOZ_ASSERT(mContent->IsHTML(nsGkAtoms::input), "bad cast"); dom::HTMLInputElement* input = static_cast<dom::HTMLInputElement*>(mContent); MOZ_ASSERT(input->GetType() == NS_FORM_INPUT_RANGE); 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; } Decimal range = maximum - minimum; LayoutDeviceIntPoint absPoint; if (aEvent->eventStructType == NS_TOUCH_EVENT) { MOZ_ASSERT(aEvent->AsTouchEvent()->touches.Length() == 1, "Unexpected number of touches"); absPoint = LayoutDeviceIntPoint::FromUntyped( aEvent->AsTouchEvent()->touches[0]->mRefPoint); } else { absPoint = aEvent->refPoint; } nsPoint point = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, LayoutDeviceIntPoint::ToUntyped(absPoint), this); if (point == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)) { // We don't want to change the current value for this error state. return static_cast<dom::HTMLInputElement*>(mContent)->GetValueAsDecimal(); } nsRect rangeContentRect = GetContentRectRelativeToSelf(); nsSize thumbSize; if (IsThemed()) { // We need to get the size of the thumb from the theme. nsPresContext *presContext = PresContext(); bool notUsedCanOverride; nsIntSize size; presContext->GetTheme()-> GetMinimumWidgetSize(presContext, this, NS_THEME_RANGE_THUMB, &size, ¬UsedCanOverride); thumbSize.width = presContext->DevPixelsToAppUnits(size.width); thumbSize.height = presContext->DevPixelsToAppUnits(size.height); MOZ_ASSERT(thumbSize.width > 0 && thumbSize.height > 0); } else { nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame(); if (thumbFrame) { // diplay:none? thumbSize = thumbFrame->GetSize(); } } Decimal fraction; if (IsHorizontal()) { nscoord traversableDistance = rangeContentRect.width - thumbSize.width; if (traversableDistance <= 0) { return minimum; } nscoord posAtStart = rangeContentRect.x + thumbSize.width/2; nscoord posAtEnd = posAtStart + traversableDistance; nscoord posOfPoint = mozilla::clamped(point.x, posAtStart, posAtEnd); fraction = Decimal(posOfPoint - posAtStart) / Decimal(traversableDistance); if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) { fraction = Decimal(1) - fraction; } } else { nscoord traversableDistance = rangeContentRect.height - thumbSize.height; if (traversableDistance <= 0) { return minimum; } nscoord posAtStart = rangeContentRect.y + thumbSize.height/2; nscoord posAtEnd = posAtStart + traversableDistance; 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 = Decimal(1) - Decimal(posOfPoint - posAtStart) / Decimal(traversableDistance); } MOZ_ASSERT(fraction >= Decimal(0) && fraction <= Decimal(1)); return minimum + fraction * range; }
void InputType::stepUpFromRenderer(int n) { // The only difference from stepUp()/stepDown() is the extra treatment // of the current value before applying the step: // // 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 // // 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; EventQueueScope scope; const Decimal step = stepRange.step(); int sign; if (step > 0) sign = n; else if (step < 0) sign = -n; else sign = 0; Decimal current = parseToNumberOrNaN(element().value()); if (!current.isFinite()) { 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, DispatchNoEvent, IGNORE_EXCEPTION); } if ((sign > 0 && current < stepRange.minimum()) || (sign < 0 && current > stepRange.maximum())) { setValueAsDecimal(sign > 0 ? stepRange.minimum() : stepRange.maximum(), DispatchChangeEvent, IGNORE_EXCEPTION); return; } applyStep(current, n, AnyIsDefaultStep, DispatchChangeEvent, IGNORE_EXCEPTION); }
double BaseDateAndTimeInputType::valueAsDouble() const { const Decimal value = parseToNumber(element()->value(), Decimal::nan()); return value.isFinite() ? value.toDouble() : DateComponents::invalidMilliseconds(); }
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()); }
void SliderThumbElement::setPositionFromPoint(const LayoutPoint& point) { HTMLInputElement* input(hostInput()); Element* trackElement = input->userAgentShadowRoot()->getElementById( ShadowElementNames::sliderTrack()); if (!input->layoutObject() || !layoutBox() || !trackElement->layoutBox()) return; LayoutPoint offset = LayoutPoint( input->layoutObject()->absoluteToLocal(FloatPoint(point), UseTransforms)); bool isVertical = hasVerticalAppearance(input); bool isLeftToRightDirection = layoutBox()->style()->isLeftToRightDirection(); LayoutUnit trackSize; LayoutUnit position; LayoutUnit currentPosition; // We need to calculate currentPosition from absolute points becaue the // layoutObject for this node is usually on a layer and layoutBox()->x() and // y() are unusable. // FIXME: This should probably respect transforms. LayoutPoint absoluteThumbOrigin = layoutBox()->absoluteBoundingBoxRectIgnoringTransforms().location(); LayoutPoint absoluteSliderContentOrigin = LayoutPoint(input->layoutObject()->localToAbsolute()); IntRect trackBoundingBox = trackElement->layoutObject()->absoluteBoundingBoxRectIgnoringTransforms(); IntRect inputBoundingBox = input->layoutObject()->absoluteBoundingBoxRectIgnoringTransforms(); if (isVertical) { trackSize = trackElement->layoutBox()->contentHeight() - layoutBox()->size().height(); position = offset.y() - layoutBox()->size().height() / 2 - trackBoundingBox.y() + inputBoundingBox.y() - layoutBox()->marginBottom(); currentPosition = absoluteThumbOrigin.y() - absoluteSliderContentOrigin.y(); } else { trackSize = trackElement->layoutBox()->contentWidth() - layoutBox()->size().width(); position = offset.x() - layoutBox()->size().width() / 2 - trackBoundingBox.x() + inputBoundingBox.x(); position -= isLeftToRightDirection ? layoutBox()->marginLeft() : layoutBox()->marginRight(); currentPosition = absoluteThumbOrigin.x() - absoluteSliderContentOrigin.x(); } position = std::min(position, trackSize).clampNegativeToZero(); const Decimal ratio = Decimal::fromDouble(static_cast<double>(position) / trackSize); const Decimal fraction = isVertical || !isLeftToRightDirection ? Decimal(1) - ratio : ratio; StepRange stepRange(input->createStepRange(RejectAny)); Decimal value = stepRange.clampValue(stepRange.valueFromProportion(fraction)); Decimal closest = input->findClosestTickMarkValue(value); if (closest.isFinite()) { double closestFraction = stepRange.proportionFromValue(closest).toDouble(); double closestRatio = isVertical || !isLeftToRightDirection ? 1.0 - closestFraction : closestFraction; LayoutUnit closestPosition(trackSize * closestRatio); const LayoutUnit snappingThreshold(5); if ((closestPosition - position).abs() <= snappingThreshold) value = closest; } String valueString = serializeForNumberType(value); if (valueString == input->value()) return; // FIXME: This is no longer being set from renderer. Consider updating the // method name. input->setValueFromRenderer(valueString); if (layoutObject()) layoutObject()->setNeedsLayoutAndFullPaintInvalidation( LayoutInvalidationReason::SliderValueChanged); }
String NumberInputType::serialize(const Decimal& value) const { if (!value.isFinite()) return String(); return serializeForNumberType(value); }
void RangeInputType::handleKeydownEvent(KeyboardEvent* event) { if (element().isDisabledOrReadOnly()) return; const String& key = event->key(); const Decimal current = parseToNumberOrNaN(element().value()); DCHECK(current.isFinite()); StepRange stepRange(createStepRange(RejectAny)); // FIXME: We can't use stepUp() for the step value "any". So, we increase // or decrease the value by 1/100 of the value range. Is it reasonable? const Decimal step = equalIgnoringCase(element().fastGetAttribute(stepAttr), "any") ? (stepRange.maximum() - stepRange.minimum()) / 100 : stepRange.step(); const Decimal bigStep = std::max((stepRange.maximum() - stepRange.minimum()) / 10, step); TextDirection dir = LTR; bool isVertical = false; if (element().layoutObject()) { dir = computedTextDirection(); ControlPart part = element().layoutObject()->style()->appearance(); isVertical = part == SliderVerticalPart; } Decimal newValue; if (key == "ArrowUp") newValue = current + step; else if (key == "ArrowDown") newValue = current - step; else if (key == "ArrowLeft") newValue = (isVertical || dir == RTL) ? current + step : current - step; else if (key == "ArrowRight") newValue = (isVertical || dir == RTL) ? current - step : current + step; else if (key == "PageUp") newValue = current + bigStep; else if (key == "PageDown") newValue = current - bigStep; else if (key == "Home") newValue = isVertical ? stepRange.maximum() : stepRange.minimum(); else if (key == "End") newValue = isVertical ? stepRange.minimum() : stepRange.maximum(); else return; // Did not match any key binding. newValue = stepRange.clampValue(newValue); if (newValue != current) { EventQueueScope scope; TextFieldEventBehavior eventBehavior = DispatchInputAndChangeEvent; setValueAsDecimal(newValue, eventBehavior, IGNORE_EXCEPTION); if (AXObjectCache* cache = element().document().existingAXObjectCache()) cache->handleValueChanged(&element()); } event->setDefaultHandled(); }