blob: 2ffac571c91546d8d0053ccce2bcd4f0bccecaa4 [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2001 Dirk Mueller (mueller@kde.org)
* Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved.
* (C) 2006 Alexey Proskuryakov (ap@nypop.com)
* Copyright (C) 2007 Samuel Weinig (sam@webkit.org)
* Copyright (C) 2009, 2010, 2011, 2012 Google Inc. All rights reserved.
* Copyright (C) 2012 Samsung Electronics. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "core/html/forms/InputType.h"
#include "bindings/core/v8/ExceptionMessages.h"
#include "bindings/core/v8/ExceptionState.h"
#include "core/InputTypeNames.h"
#include "core/dom/AXObjectCache.h"
#include "core/dom/ExceptionCode.h"
#include "core/dom/NodeComputedStyle.h"
#include "core/events/KeyboardEvent.h"
#include "core/events/ScopedEventQueue.h"
#include "core/fileapi/FileList.h"
#include "core/frame/FrameHost.h"
#include "core/html/FormData.h"
#include "core/html/HTMLFormElement.h"
#include "core/html/HTMLInputElement.h"
#include "core/html/HTMLShadowElement.h"
#include "core/html/forms/ButtonInputType.h"
#include "core/html/forms/CheckboxInputType.h"
#include "core/html/forms/ColorChooser.h"
#include "core/html/forms/ColorInputType.h"
#include "core/html/forms/DateInputType.h"
#include "core/html/forms/DateTimeLocalInputType.h"
#include "core/html/forms/EmailInputType.h"
#include "core/html/forms/FileInputType.h"
#include "core/html/forms/HiddenInputType.h"
#include "core/html/forms/ImageInputType.h"
#include "core/html/forms/MonthInputType.h"
#include "core/html/forms/NumberInputType.h"
#include "core/html/forms/PasswordInputType.h"
#include "core/html/forms/RadioInputType.h"
#include "core/html/forms/RangeInputType.h"
#include "core/html/forms/ResetInputType.h"
#include "core/html/forms/SearchInputType.h"
#include "core/html/forms/SubmitInputType.h"
#include "core/html/forms/TelephoneInputType.h"
#include "core/html/forms/TextInputType.h"
#include "core/html/forms/TimeInputType.h"
#include "core/html/forms/URLInputType.h"
#include "core/html/forms/WeekInputType.h"
#include "core/html/parser/HTMLParserIdioms.h"
#include "core/inspector/ConsoleMessage.h"
#include "core/layout/LayoutTheme.h"
#include "platform/JSONValues.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "platform/text/PlatformLocale.h"
#include "platform/text/TextBreakIterator.h"
#include "wtf/PtrUtil.h"
#include <memory>
namespace blink {
using blink::WebLocalizedString;
using namespace HTMLNames;
using InputTypeFactoryFunction = InputType* (*)(HTMLInputElement&);
using InputTypeFactoryMap = HashMap<AtomicString, InputTypeFactoryFunction, CaseFoldingHash>;
static std::unique_ptr<InputTypeFactoryMap> createInputTypeFactoryMap()
{
std::unique_ptr<InputTypeFactoryMap> map = wrapUnique(new InputTypeFactoryMap);
map->add(InputTypeNames::button, ButtonInputType::create);
map->add(InputTypeNames::checkbox, CheckboxInputType::create);
map->add(InputTypeNames::color, ColorInputType::create);
map->add(InputTypeNames::date, DateInputType::create);
map->add(InputTypeNames::datetime_local, DateTimeLocalInputType::create);
map->add(InputTypeNames::email, EmailInputType::create);
map->add(InputTypeNames::file, FileInputType::create);
map->add(InputTypeNames::hidden, HiddenInputType::create);
map->add(InputTypeNames::image, ImageInputType::create);
map->add(InputTypeNames::month, MonthInputType::create);
map->add(InputTypeNames::number, NumberInputType::create);
map->add(InputTypeNames::password, PasswordInputType::create);
map->add(InputTypeNames::radio, RadioInputType::create);
map->add(InputTypeNames::range, RangeInputType::create);
map->add(InputTypeNames::reset, ResetInputType::create);
map->add(InputTypeNames::search, SearchInputType::create);
map->add(InputTypeNames::submit, SubmitInputType::create);
map->add(InputTypeNames::tel, TelephoneInputType::create);
map->add(InputTypeNames::time, TimeInputType::create);
map->add(InputTypeNames::url, URLInputType::create);
map->add(InputTypeNames::week, WeekInputType::create);
// No need to register "text" because it is the default type.
return map;
}
static const InputTypeFactoryMap* factoryMap()
{
static const InputTypeFactoryMap* factoryMap = createInputTypeFactoryMap().release();
return factoryMap;
}
InputType* InputType::create(HTMLInputElement& element, const AtomicString& typeName)
{
InputTypeFactoryFunction factory = typeName.isEmpty() ? 0 : factoryMap()->get(typeName);
if (!factory)
factory = TextInputType::create;
return factory(element);
}
InputType* InputType::createText(HTMLInputElement& element)
{
return TextInputType::create(element);
}
const AtomicString& InputType::normalizeTypeName(const AtomicString& typeName)
{
if (typeName.isEmpty())
return InputTypeNames::text;
InputTypeFactoryMap::const_iterator it = factoryMap()->find(typeName);
return it == factoryMap()->end() ? InputTypeNames::text : it->key;
}
InputType::~InputType()
{
}
DEFINE_TRACE(InputType)
{
visitor->trace(m_element);
}
bool InputType::isTextField() const
{
return false;
}
bool InputType::shouldSaveAndRestoreFormControlState() const
{
return true;
}
bool InputType::isFormDataAppendable() const
{
// There is no form data unless there's a name for non-image types.
return !element().name().isEmpty();
}
void InputType::appendToFormData(FormData& formData) const
{
formData.append(element().name(), element().value());
}
String InputType::resultForDialogSubmit() const
{
return element().fastGetAttribute(valueAttr);
}
double InputType::valueAsDate() const
{
return DateComponents::invalidMilliseconds();
}
void InputType::setValueAsDate(double, ExceptionState& exceptionState) const
{
exceptionState.throwDOMException(InvalidStateError, "This input element does not support Date values.");
}
double InputType::valueAsDouble() const
{
return std::numeric_limits<double>::quiet_NaN();
}
void InputType::setValueAsDouble(double doubleValue, TextFieldEventBehavior eventBehavior, ExceptionState& exceptionState) const
{
exceptionState.throwDOMException(InvalidStateError, "This input element does not support Number values.");
}
void InputType::setValueAsDecimal(const Decimal& newValue, TextFieldEventBehavior eventBehavior, ExceptionState&) const
{
element().setValue(serialize(newValue), eventBehavior);
}
void InputType::readingChecked() const
{
}
bool InputType::supportsValidation() const
{
return true;
}
bool InputType::typeMismatchFor(const String&) const
{
return false;
}
bool InputType::typeMismatch() const
{
return false;
}
bool InputType::supportsRequired() const
{
// Almost all validatable types support @required.
return supportsValidation();
}
bool InputType::valueMissing(const String&) const
{
return false;
}
bool InputType::tooLong(const String&, HTMLTextFormControlElement::NeedsToCheckDirtyFlag) const
{
return false;
}
bool InputType::tooShort(const String&, HTMLTextFormControlElement::NeedsToCheckDirtyFlag) const
{
return false;
}
bool InputType::patternMismatch(const String&) const
{
return false;
}
bool InputType::rangeUnderflow(const String& value) const
{
if (!isSteppable())
return false;
const Decimal numericValue = parseToNumberOrNaN(value);
if (!numericValue.isFinite())
return false;
return numericValue < createStepRange(RejectAny).minimum();
}
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();
}
Decimal InputType::defaultValueForStepUp() const
{
return 0;
}
double InputType::minimum() const
{
return createStepRange(RejectAny).minimum().toDouble();
}
double InputType::maximum() const
{
return createStepRange(RejectAny).maximum().toDouble();
}
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 stepRange.hasRangeLimitations() && 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::stepMismatch(const String& value) const
{
if (!isSteppable())
return false;
const Decimal numericValue = parseToNumberOrNaN(value);
if (!numericValue.isFinite())
return false;
return createStepRange(RejectAny).stepMismatch(numericValue);
}
String InputType::badInputText() const
{
NOTREACHED();
return locale().queryString(WebLocalizedString::ValidationTypeMismatch);
}
String InputType::rangeOverflowText(const Decimal&) const
{
NOTREACHED();
return String();
}
String InputType::rangeUnderflowText(const Decimal&) const
{
NOTREACHED();
return String();
}
String InputType::typeMismatchText() const
{
return locale().queryString(WebLocalizedString::ValidationTypeMismatch);
}
String InputType::valueMissingText() const
{
return locale().queryString(WebLocalizedString::ValidationValueMissing);
}
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());
}
Decimal InputType::parseToNumber(const String&, const Decimal& defaultValue) const
{
NOTREACHED();
return defaultValue;
}
Decimal InputType::parseToNumberOrNaN(const String& string) const
{
return parseToNumber(string, Decimal::nan());
}
String InputType::serialize(const Decimal&) const
{
NOTREACHED();
return String();
}
ChromeClient* InputType::chromeClient() const
{
if (FrameHost* host = element().document().frameHost())
return &host->chromeClient();
return nullptr;
}
Locale& InputType::locale() const
{
return element().locale();
}
bool InputType::canSetStringValue() const
{
return true;
}
bool InputType::isKeyboardFocusable() const
{
return element().isFocusable();
}
bool InputType::shouldShowFocusRingOnMouseFocus() const
{
return false;
}
void InputType::enableSecureTextInput()
{
}
void InputType::disableSecureTextInput()
{
}
void InputType::countUsage()
{
}
bool InputType::shouldRespectAlignAttribute()
{
return false;
}
void InputType::sanitizeValueInResponseToMinOrMaxAttributeChange()
{
}
bool InputType::canBeSuccessfulSubmitButton()
{
return false;
}
bool InputType::matchesDefaultPseudoClass()
{
return false;
}
bool InputType::layoutObjectIsNeeded()
{
return true;
}
FileList* InputType::files()
{
return nullptr;
}
void InputType::setFiles(FileList*)
{
}
void InputType::setFilesFromPaths(const Vector<String>& paths)
{
}
bool InputType::getTypeSpecificValue(String&)
{
return false;
}
String InputType::fallbackValue() const
{
return String();
}
String InputType::defaultValue() const
{
return String();
}
bool InputType::canSetSuggestedValue()
{
return false;
}
bool InputType::shouldSendChangeEventAfterCheckedChanged()
{
return true;
}
bool InputType::storesValueSeparateFromAttribute()
{
return true;
}
bool InputType::shouldDispatchFormControlChangeEvent(String& oldValue, String& newValue)
{
return !equalIgnoringNullity(oldValue, newValue);
}
void InputType::dispatchSearchEvent()
{
}
void InputType::setValue(const String& sanitizedValue, bool valueChanged, TextFieldEventBehavior eventBehavior)
{
element().setValueInternal(sanitizedValue, eventBehavior);
if (!valueChanged)
return;
switch (eventBehavior) {
case DispatchChangeEvent:
element().dispatchFormControlChangeEvent();
break;
case DispatchInputAndChangeEvent:
element().dispatchFormControlInputEvent();
element().dispatchFormControlChangeEvent();
break;
case DispatchNoEvent:
break;
}
}
bool InputType::canSetValue(const String&)
{
return true;
}
String InputType::localizeValue(const String& proposedValue) const
{
return proposedValue;
}
String InputType::visibleValue() const
{
return element().value();
}
String InputType::sanitizeValue(const String& proposedValue) const
{
return proposedValue;
}
String InputType::sanitizeUserInputValue(const String& proposedValue) const
{
return sanitizeValue(proposedValue);
}
void InputType::warnIfValueIsInvalidAndElementIsVisible(const String& value) const
{
// Don't warn if the value is set in Modernizr.
const ComputedStyle* style = element().computedStyle();
if (style && style->visibility() != EVisibility::Hidden)
warnIfValueIsInvalid(value);
}
void InputType::warnIfValueIsInvalid(const String&) const
{
}
bool InputType::receiveDroppedFiles(const DragData*)
{
NOTREACHED();
return false;
}
String InputType::droppedFileSystemId()
{
NOTREACHED();
return String();
}
bool InputType::shouldRespectListAttribute()
{
return false;
}
bool InputType::isTextButton() const
{
return false;
}
bool InputType::isInteractiveContent() const
{
return true;
}
bool InputType::isEnumeratable()
{
return true;
}
bool InputType::isCheckable()
{
return false;
}
bool InputType::isSteppable() const
{
return false;
}
bool InputType::shouldRespectHeightAndWidthAttributes()
{
return false;
}
int InputType::maxLength() const
{
return HTMLInputElement::maximumLength;
}
int InputType::minLength() const
{
return 0;
}
bool InputType::supportsPlaceholder() const
{
return false;
}
bool InputType::supportsReadOnly() const
{
return false;
}
String InputType::defaultToolTip(const InputTypeView& inputTypeView) const
{
if (element().form() && element().form()->noValidate())
return String();
return validationMessage(inputTypeView).first;
}
Decimal InputType::findClosestTickMarkValue(const Decimal&)
{
NOTREACHED();
return Decimal::nan();
}
bool InputType::hasLegalLinkAttribute(const QualifiedName&) const
{
return false;
}
const QualifiedName& InputType::subResourceAttributeName() const
{
return QualifiedName::null();
}
bool InputType::supportsAutocapitalize() const
{
return false;
}
const AtomicString& InputType::defaultAutocapitalize() const
{
DEFINE_STATIC_LOCAL(const AtomicString, none, ("none"));
return none;
}
bool InputType::shouldAppearIndeterminate() const
{
return false;
}
bool InputType::supportsInputModeAttribute() const
{
return false;
}
bool InputType::supportsSelectionAPI() const
{
return false;
}
unsigned InputType::height() const
{
return 0;
}
unsigned InputType::width() const
{
return 0;
}
ColorChooserClient* InputType::colorChooserClient()
{
return nullptr;
}
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());
}
bool InputType::getAllowedValueStep(Decimal* step) const
{
StepRange stepRange(createStepRange(RejectAny));
*step = stepRange.step();
return stepRange.hasStep();
}
StepRange InputType::createStepRange(AnyStepHandling) const
{
NOTREACHED();
return StepRange();
}
void InputType::stepUp(int n, ExceptionState& exceptionState)
{
if (!isSteppable()) {
exceptionState.throwDOMException(InvalidStateError, "This form element is not steppable.");
return;
}
const Decimal current = parseToNumber(element().value(), 0);
applyStep(current, n, RejectAny, DispatchNoEvent, exceptionState);
}
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::countUsageIfVisible(UseCounter::Feature feature) const
{
if (const ComputedStyle* style = element().computedStyle()) {
if (style->visibility() != EVisibility::Hidden)
UseCounter::count(element().document(), feature);
}
}
Decimal InputType::findStepBase(const Decimal& defaultValue) const
{
Decimal stepBase = parseToNumber(element().fastGetAttribute(minAttr), Decimal::nan());
if (!stepBase.isFinite())
stepBase = parseToNumber(element().fastGetAttribute(valueAttr), defaultValue);
return stepBase;
}
StepRange InputType::createStepRange(AnyStepHandling anyStepHandling, const Decimal& stepBaseDefault, const Decimal& minimumDefault, const Decimal& maximumDefault, const StepRange::StepDescription& stepDescription) const
{
bool hasRangeLimitations = false;
const Decimal stepBase = findStepBase(stepBaseDefault);
Decimal minimum = parseToNumberOrNaN(element().fastGetAttribute(minAttr));
if (minimum.isFinite())
hasRangeLimitations = true;
else
minimum = minimumDefault;
Decimal maximum = parseToNumberOrNaN(element().fastGetAttribute(maxAttr));
if (maximum.isFinite())
hasRangeLimitations = true;
else
maximum = maximumDefault;
const Decimal step = StepRange::parseStep(anyStepHandling, stepDescription, element().fastGetAttribute(stepAttr));
return StepRange(stepBase, minimum, maximum, hasRangeLimitations, step, stepDescription);
}
void InputType::addWarningToConsole(const char* messageFormat, const String& value) const
{
element().document().addConsoleMessage(ConsoleMessage::create(RenderingMessageSource, WarningMessageLevel,
String::format(messageFormat, JSONValue::quoteString(value).utf8().data())));
}
} // namespace blink