Example #1
0
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();
}
Example #2
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();
}
Example #3
0
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();
}
Example #4
0
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);
}
Example #5
0
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();
}
Example #6
0
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();
}
Example #7
0
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();
}
Example #9
0
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();
}
Example #10
0
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;
}
Example #11
0
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;
}
Example #12
0
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);
}
Example #14
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;

    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);
    }
}
Example #15
0
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,
                           &notUsedCanOverride);
    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;
}
Example #16
0
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);
}
Example #17
0
double BaseDateAndTimeInputType::valueAsDouble() const
{
    const Decimal value = parseToNumber(element()->value(), Decimal::nan());
    return value.isFinite() ? value.toDouble() : DateComponents::invalidMilliseconds();
}
Example #18
0
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());
}
Example #19
0
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);
}
Example #20
0
String NumberInputType::serialize(const Decimal& value) const
{
    if (!value.isFinite())
        return String();
    return serializeForNumberType(value);
}
Example #21
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();
}