std::pair<String, String> InputType::validationMessage(const InputTypeView& inputTypeView) 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 (inputTypeView.hasBadInput())
        return std::make_pair(badInputText(), emptyString());

    if (valueMissing(value))
        return std::make_pair(valueMissingText(), emptyString());

    if (typeMismatch())
        return std::make_pair(typeMismatchText(), emptyString());

    if (patternMismatch(value)) {
        // https://html.spec.whatwg.org/multipage/forms.html#attr-input-pattern
        //   When an input element has a pattern attribute specified, authors
        //   should include a title attribute to give a description of the
        //   pattern. User agents may use the contents of this attribute, if it
        //   is present, when informing the user that the pattern is not matched
        return std::make_pair(locale().queryString(WebLocalizedString::ValidationPatternMismatch), element().fastGetAttribute(titleAttr).getString());
    }

    if (element().tooLong())
        return std::make_pair(locale().validationMessageTooLongText(value.length(), element().maxLength()), emptyString());

    if (element().tooShort())
        return std::make_pair(locale().validationMessageTooShortText(value.length(), element().minLength()), emptyString());

    if (!isSteppable())
        return std::make_pair(emptyString(), emptyString());

    const Decimal numericValue = parseToNumberOrNaN(value);
    if (!numericValue.isFinite())
        return std::make_pair(emptyString(), emptyString());

    StepRange stepRange(createStepRange(RejectAny));

    if (numericValue < stepRange.minimum())
        return std::make_pair(rangeUnderflowText(stepRange.minimum()), emptyString());

    if (numericValue > stepRange.maximum())
        return std::make_pair(rangeOverflowText(stepRange.maximum()), emptyString());

    if (stepRange.stepMismatch(numericValue)) {
        DCHECK(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 std::make_pair(locale().queryString(WebLocalizedString::ValidationStepMismatchCloseToLimit, localizedCandidate1), emptyString());
        String localizedCandidate2 = localizeValue(serialize(candidate2));
        if (candidate1 < candidate2)
            return std::make_pair(locale().queryString(WebLocalizedString::ValidationStepMismatch, localizedCandidate1, localizedCandidate2), emptyString());
        return std::make_pair(locale().queryString(WebLocalizedString::ValidationStepMismatch, localizedCandidate2, localizedCandidate1), emptyString());
    }

    return std::make_pair(emptyString(), emptyString());
}
void SliderThumbElement::setPositionFromPoint(const LayoutPoint& absolutePoint)
{
    RefPtr<HTMLInputElement> input = hostInput();
    if (!input || !input->renderer() || !renderBox())
        return;

    HTMLElement* trackElement = input->sliderTrackElement();
    if (!trackElement->renderBox())
        return;

    // Do all the tracking math relative to the input's renderer's box.
    RenderBox& inputRenderer = downcast<RenderBox>(*input->renderer());
    RenderBox& trackRenderer = *trackElement->renderBox();

    bool isVertical = hasVerticalAppearance(input.get());
    bool isLeftToRightDirection = renderBox()->style().isLeftToRightDirection();
    
    LayoutPoint offset(inputRenderer.absoluteToLocal(absolutePoint, UseTransforms));
    FloatRect trackBoundingBox = trackRenderer.localToContainerQuad(FloatRect(0, 0, trackRenderer.width(), trackRenderer.height()), &inputRenderer).enclosingBoundingBox();

    LayoutUnit trackLength;
    LayoutUnit position;
    if (isVertical) {
        trackLength = trackRenderer.contentHeight() - renderBox()->height();
        position = offset.y() - renderBox()->height() / 2 - trackBoundingBox.y() - renderBox()->marginBottom();
    } else {
        trackLength = trackRenderer.contentWidth() - renderBox()->width();
        position = offset.x() - renderBox()->width() / 2 - trackBoundingBox.x();
        position -= isLeftToRightDirection ? renderBox()->marginLeft() : renderBox()->marginRight();
    }

    position = std::max<LayoutUnit>(0, std::min(position, trackLength));
    const Decimal ratio = Decimal::fromDouble(static_cast<double>(position) / trackLength);
    const Decimal fraction = isVertical || !isLeftToRightDirection ? Decimal(1) - ratio : ratio;
    StepRange stepRange(input->createStepRange(RejectAny));
    Decimal value = stepRange.clampValue(stepRange.valueFromProportion(fraction));

#if ENABLE(DATALIST_ELEMENT)
    const LayoutUnit snappingThreshold = renderer()->theme().sliderTickSnappingThreshold();
    if (snappingThreshold > 0) {
        Decimal closest = input->findClosestTickMarkValue(value);
        if (closest.isFinite()) {
            double closestFraction = stepRange.proportionFromValue(closest).toDouble();
            double closestRatio = isVertical || !isLeftToRightDirection ? 1.0 - closestFraction : closestFraction;
            LayoutUnit closestPosition = trackLength * closestRatio;
            if ((closestPosition - position).abs() <= snappingThreshold)
                value = closest;
        }
    }
#endif

    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 (renderer())
        renderer()->setNeedsLayout();
}
void RangeInputType::handleKeydownEvent(KeyboardEvent* event)
{
    if (element()->isDisabledOrReadOnly())
        return;

    const String& key = event->keyIdentifier();

    const Decimal current = parseToNumberOrNaN(element()->value());
    ASSERT(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 = max((stepRange.maximum() - stepRange.minimum()) / 10, step);

    bool isVertical = false;
    if (element()->renderer()) {
        ControlPart part = element()->renderer()->style()->appearance();
        isVertical = part == SliderVerticalPart || part == MediaVolumeSliderPart;
    }

    Decimal newValue;
    if (key == "Up")
        newValue = current + step;
    else if (key == "Down")
        newValue = current - step;
    else if (key == "Left")
        newValue = isVertical ? current + step : current - step;
    else if (key == "Right")
        newValue = isVertical ? 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;
        ExceptionCode ec;
        TextFieldEventBehavior eventBehavior = DispatchChangeEvent;
        setValueAsDecimal(newValue, eventBehavior, ec);

        if (AXObjectCache::accessibilityEnabled())
            element()->document()->axObjectCache()->postNotification(element(), AXObjectCache::AXValueChanged, true);
        element()->dispatchFormControlChangeEvent();
    }

    event->setDefaultHandled();
}
void SliderThumbElement::setPositionFromPoint(const LayoutPoint& point)
{
    RefPtrWillBeRawPtr<HTMLInputElement> input(hostInput());
    Element* trackElement = input->closedShadowRoot()->getElementById(ShadowElementNames::sliderTrack());

    if (!input->layoutObject() || !layoutBox() || !trackElement->layoutBox())
        return;

    LayoutPoint offset = roundedLayoutPoint(input->layoutObject()->absoluteToLocal(FloatPoint(point), UseTransforms));
    bool isVertical = hasVerticalAppearance(input.get());
    bool isLeftToRightDirection = layoutBox()->style()->isLeftToRightDirection();
    LayoutUnit trackSize;
    LayoutUnit position;
    LayoutUnit currentPosition;
    // We need to calculate currentPosition from absolute points becaue the
    // renderer 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 = roundedLayoutPoint(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::max<LayoutUnit>(0, std::min(position, trackSize));
    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);
}
Exemple #5
0
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();
}
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();
}
Exemple #8
0
void InputType::applyStep(int count, AnyStepHandling anyStepHandling, TextFieldEventBehavior eventBehavior, ExceptionCode& ec)
{
    StepRange stepRange(createStepRange(anyStepHandling));
    if (!stepRange.hasStep()) {
        ec = INVALID_STATE_ERR;
        return;
    }

    const Decimal current = parseToNumberOrNaN(element().value());
    if (!current.isFinite()) {
        ec = INVALID_STATE_ERR;
        return;
    }
    Decimal newValue = current + stepRange.step() * count;
    if (!newValue.isFinite()) {
        ec = INVALID_STATE_ERR;
        return;
    }

    const Decimal acceptableErrorValue = stepRange.acceptableError();
    if (newValue - stepRange.minimum() < -acceptableErrorValue) {
        ec = INVALID_STATE_ERR;
        return;
    }
    if (newValue < stepRange.minimum())
        newValue = stepRange.minimum();

    const AtomicString& stepString = element().fastGetAttribute(stepAttr);
    if (!equalIgnoringCase(stepString, "any"))
        newValue = stepRange.alignValueForStep(current, newValue);

    if (newValue - stepRange.maximum() > acceptableErrorValue) {
        ec = INVALID_STATE_ERR;
        return;
    }
    if (newValue > stepRange.maximum())
        newValue = stepRange.maximum();

    setValueAsDecimal(newValue, eventBehavior, ec);

    if (AXObjectCache* cache = element().document().existingAXObjectCache())
        cache->postNotification(&element(), AXObjectCache::AXValueChanged);
}
Exemple #9
0
String InputType::validationMessage() 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 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();
}
Exemple #10
0
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;

    EventQueueScope scope;
    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()) {
        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(), DispatchInputAndChangeEvent, IGNORE_EXCEPTION);
    else {
        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, IGNORE_EXCEPTION);
            if (n > 1)
                applyStep(n - 1, AnyIsDefaultStep, DispatchInputAndChangeEvent, IGNORE_EXCEPTION);
            else if (n < -1)
                applyStep(n + 1, AnyIsDefaultStep, DispatchInputAndChangeEvent, IGNORE_EXCEPTION);
        } else
            applyStep(n, AnyIsDefaultStep, DispatchInputAndChangeEvent, IGNORE_EXCEPTION);
    }
}
Exemple #11
0
bool InputType::getAllowedValueStep(Decimal* step) const
{
    StepRange stepRange(createStepRange(RejectAny));
    *step = stepRange.step();
    return stepRange.hasStep();
}
String RangeInputType::sanitizeValue(const String& proposedValue) const
{
    StepRange stepRange(createStepRange(RejectAny));
    const Decimal proposedNumericValue = parseToNumber(proposedValue, stepRange.defaultValue());
    return serializeForNumberType(stepRange.clampValue(proposedNumericValue));
}
void InputType::stepUpFromLayoutObject(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.

    DCHECK(isSteppable());
    if (!isSteppable())
        return;
    DCHECK(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;
    }
    if ((sign > 0 && current >= stepRange.maximum()) || (sign < 0 && current <= stepRange.minimum()))
        return;
    applyStep(current, n, AnyIsDefaultStep, DispatchChangeEvent, IGNORE_EXCEPTION);
}
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());
}
Exemple #15
0
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();
}