blob: 781299f4f4f9a55897da9c65a72816c769acb84d [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 "third_party/blink/renderer/core/html/forms/input_type.h"
#include <limits>
#include <memory>
#include <utility>
#include "third_party/blink/public/strings/grit/blink_strings.h"
#include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
#include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h"
#include "third_party/blink/renderer/core/dom/node_computed_style.h"
#include "third_party/blink/renderer/core/events/keyboard_event.h"
#include "third_party/blink/renderer/core/fileapi/file_list.h"
#include "third_party/blink/renderer/core/html/forms/button_input_type.h"
#include "third_party/blink/renderer/core/html/forms/checkbox_input_type.h"
#include "third_party/blink/renderer/core/html/forms/color_chooser.h"
#include "third_party/blink/renderer/core/html/forms/color_input_type.h"
#include "third_party/blink/renderer/core/html/forms/date_input_type.h"
#include "third_party/blink/renderer/core/html/forms/date_time_local_input_type.h"
#include "third_party/blink/renderer/core/html/forms/email_input_type.h"
#include "third_party/blink/renderer/core/html/forms/file_input_type.h"
#include "third_party/blink/renderer/core/html/forms/form_data.h"
#include "third_party/blink/renderer/core/html/forms/hidden_input_type.h"
#include "third_party/blink/renderer/core/html/forms/html_form_element.h"
#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
#include "third_party/blink/renderer/core/html/forms/image_input_type.h"
#include "third_party/blink/renderer/core/html/forms/month_input_type.h"
#include "third_party/blink/renderer/core/html/forms/number_input_type.h"
#include "third_party/blink/renderer/core/html/forms/password_input_type.h"
#include "third_party/blink/renderer/core/html/forms/radio_input_type.h"
#include "third_party/blink/renderer/core/html/forms/range_input_type.h"
#include "third_party/blink/renderer/core/html/forms/reset_input_type.h"
#include "third_party/blink/renderer/core/html/forms/search_input_type.h"
#include "third_party/blink/renderer/core/html/forms/submit_input_type.h"
#include "third_party/blink/renderer/core/html/forms/telephone_input_type.h"
#include "third_party/blink/renderer/core/html/forms/text_input_type.h"
#include "third_party/blink/renderer/core/html/forms/time_input_type.h"
#include "third_party/blink/renderer/core/html/forms/url_input_type.h"
#include "third_party/blink/renderer/core/html/forms/week_input_type.h"
#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
#include "third_party/blink/renderer/core/input_type_names.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/layout/layout_theme.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/json/json_values.h"
#include "third_party/blink/renderer/platform/text/platform_locale.h"
#include "third_party/blink/renderer/platform/text/text_break_iterator.h"
namespace blink {
// Listed once to avoid any discrepancy between InputType::Create and
// InputType::NormalizeTypeName.
//
// No need to register "text" because it is the default type.
#define INPUT_TYPES(INPUT_TYPE) \
INPUT_TYPE(kButton, ButtonInputType) \
INPUT_TYPE(kCheckbox, CheckboxInputType) \
INPUT_TYPE(kColor, ColorInputType) \
INPUT_TYPE(kDate, DateInputType) \
INPUT_TYPE(kDatetimeLocal, DateTimeLocalInputType) \
INPUT_TYPE(kEmail, EmailInputType) \
INPUT_TYPE(kFile, FileInputType) \
INPUT_TYPE(kHidden, HiddenInputType) \
INPUT_TYPE(kImage, ImageInputType) \
INPUT_TYPE(kMonth, MonthInputType) \
INPUT_TYPE(kNumber, NumberInputType) \
INPUT_TYPE(kPassword, PasswordInputType) \
INPUT_TYPE(kRadio, RadioInputType) \
INPUT_TYPE(kRange, RangeInputType) \
INPUT_TYPE(kReset, ResetInputType) \
INPUT_TYPE(kSearch, SearchInputType) \
INPUT_TYPE(kSubmit, SubmitInputType) \
INPUT_TYPE(kTel, TelephoneInputType) \
INPUT_TYPE(kTime, TimeInputType) \
INPUT_TYPE(kUrl, URLInputType) \
INPUT_TYPE(kWeek, WeekInputType)
InputType* InputType::Create(HTMLInputElement& element,
const AtomicString& type_name) {
if (type_name.IsEmpty())
return MakeGarbageCollected<TextInputType>(element);
#define INPUT_TYPE_FACTORY(input_type, class_name) \
if (type_name == input_type_names::input_type) \
return MakeGarbageCollected<class_name>(element);
INPUT_TYPES(INPUT_TYPE_FACTORY)
#undef INPUT_TYPE_FACTORY
return MakeGarbageCollected<TextInputType>(element);
}
const AtomicString& InputType::NormalizeTypeName(
const AtomicString& type_name) {
if (type_name.IsEmpty())
return input_type_names::kText;
AtomicString type_name_lower = type_name.LowerASCII();
#define NORMALIZE_INPUT_TYPE(input_type, class_name) \
if (type_name_lower == input_type_names::input_type) \
return input_type_names::input_type;
INPUT_TYPES(NORMALIZE_INPUT_TYPE)
#undef NORMALIZE_INPUT_TYPE
return input_type_names::kText;
}
InputType::~InputType() = default;
void InputType::Trace(Visitor* visitor) const {
visitor->Trace(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 !GetElement().GetName().IsEmpty();
}
void InputType::AppendToFormData(FormData& form_data) const {
form_data.AppendFromElement(GetElement().GetName(), GetElement().value());
}
String InputType::ResultForDialogSubmit() const {
return GetElement().FastGetAttribute(html_names::kValueAttr);
}
double InputType::ValueAsDate() const {
return DateComponents::InvalidMilliseconds();
}
void InputType::SetValueAsDate(const base::Optional<base::Time>&,
ExceptionState& exception_state) const {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"This input element does not support Date values.");
}
double InputType::ValueAsDouble() const {
return std::numeric_limits<double>::quiet_NaN();
}
void InputType::SetValueAsDouble(double double_value,
TextFieldEventBehavior event_behavior,
ExceptionState& exception_state) const {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"This input element does not support Number values.");
}
void InputType::SetValueAsDecimal(const Decimal& new_value,
TextFieldEventBehavior event_behavior,
ExceptionState&) const {
GetElement().setValue(Serialize(new_value), event_behavior);
}
void InputType::ReadingChecked() const {}
void InputType::WillUpdateCheckedness(bool) {}
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&,
TextControlElement::NeedsToCheckDirtyFlag) const {
return false;
}
bool InputType::TooShort(const String&,
TextControlElement::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 numeric_value = ParseToNumberOrNaN(value);
if (!numeric_value.IsFinite())
return false;
StepRange step_range = CreateStepRange(kRejectAny);
if (step_range.HasReversedRange()) {
// With a reversed range, any value outside of the midnight-crossing valid
// range is considered underflow and overflow.
return numeric_value > step_range.Maximum() &&
numeric_value < step_range.Minimum();
} else {
return numeric_value < step_range.Minimum();
}
}
bool InputType::RangeOverflow(const String& value) const {
if (!IsSteppable())
return false;
const Decimal numeric_value = ParseToNumberOrNaN(value);
if (!numeric_value.IsFinite())
return false;
StepRange step_range = CreateStepRange(kRejectAny);
if (step_range.HasReversedRange()) {
// With a reversed range, any value outside of the midnight-crossing valid
// range is considered underflow and overflow.
return numeric_value > step_range.Maximum() &&
numeric_value < step_range.Minimum();
} else {
return numeric_value > step_range.Maximum();
}
}
Decimal InputType::DefaultValueForStepUp() const {
return 0;
}
double InputType::Minimum() const {
return CreateStepRange(kRejectAny).Minimum().ToDouble();
}
double InputType::Maximum() const {
return CreateStepRange(kRejectAny).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 numeric_value = ParseToNumberOrNaN(value);
if (!numeric_value.IsFinite())
return true;
StepRange step_range(CreateStepRange(kRejectAny));
return step_range.HasRangeLimitations() &&
numeric_value >= step_range.Minimum() &&
numeric_value <= step_range.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 numeric_value = ParseToNumberOrNaN(value);
if (!numeric_value.IsFinite())
return false;
StepRange step_range(CreateStepRange(kRejectAny));
return step_range.HasRangeLimitations() &&
(numeric_value < step_range.Minimum() ||
numeric_value > step_range.Maximum());
}
void InputType::InRangeChanged() const {
if (IsSteppable()) {
GetElement().PseudoStateChanged(CSSSelector::kPseudoInRange);
GetElement().PseudoStateChanged(CSSSelector::kPseudoOutOfRange);
}
}
bool InputType::StepMismatch(const String& value) const {
if (!IsSteppable())
return false;
const Decimal numeric_value = ParseToNumberOrNaN(value);
if (!numeric_value.IsFinite())
return false;
return CreateStepRange(kRejectAny).StepMismatch(numeric_value);
}
String InputType::BadInputText() const {
NOTREACHED();
return GetLocale().QueryString(IDS_FORM_VALIDATION_TYPE_MISMATCH);
}
String InputType::ValueNotEqualText(const Decimal& value) const {
NOTREACHED();
return String();
}
String InputType::RangeOverflowText(const Decimal&) const {
NOTREACHED();
return String();
}
String InputType::RangeUnderflowText(const Decimal&) const {
NOTREACHED();
return String();
}
String InputType::ReversedRangeOutOfRangeText(const Decimal&,
const Decimal&) const {
NOTREACHED();
return String();
}
String InputType::RangeInvalidText(const Decimal&, const Decimal&) const {
NOTREACHED();
return String();
}
String InputType::TypeMismatchText() const {
return GetLocale().QueryString(IDS_FORM_VALIDATION_TYPE_MISMATCH);
}
String InputType::ValueMissingText() const {
return GetLocale().QueryString(IDS_FORM_VALIDATION_VALUE_MISSING);
}
std::pair<String, String> InputType::ValidationMessage(
const InputTypeView& input_type_view) const {
const String value = GetElement().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 (input_type_view.HasBadInput())
return std::make_pair(BadInputText(), g_empty_string);
if (ValueMissing(value))
return std::make_pair(ValueMissingText(), g_empty_string);
if (TypeMismatch())
return std::make_pair(TypeMismatchText(), g_empty_string);
if (PatternMismatch(value)) {
// https://html.spec.whatwg.org/C/#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(
GetLocale().QueryString(IDS_FORM_VALIDATION_PATTERN_MISMATCH),
GetElement().FastGetAttribute(html_names::kTitleAttr).GetString());
}
if (GetElement().TooLong()) {
return std::make_pair(GetLocale().ValidationMessageTooLongText(
value.length(), GetElement().maxLength()),
g_empty_string);
}
if (GetElement().TooShort()) {
return std::make_pair(GetLocale().ValidationMessageTooShortText(
value.length(), GetElement().minLength()),
g_empty_string);
}
if (!IsSteppable())
return std::make_pair(g_empty_string, g_empty_string);
const Decimal numeric_value = ParseToNumberOrNaN(value);
if (!numeric_value.IsFinite())
return std::make_pair(g_empty_string, g_empty_string);
StepRange step_range(CreateStepRange(kRejectAny));
if (step_range.Minimum() > step_range.Maximum() &&
!step_range.HasReversedRange()) {
return std::make_pair(
RangeInvalidText(step_range.Minimum(), step_range.Maximum()),
g_empty_string);
}
if (step_range.HasReversedRange() && numeric_value < step_range.Minimum() &&
numeric_value > step_range.Maximum()) {
return std::make_pair(
ReversedRangeOutOfRangeText(step_range.Minimum(), step_range.Maximum()),
g_empty_string);
}
if (numeric_value != step_range.Minimum() &&
step_range.Minimum() == step_range.Maximum()) {
return std::make_pair(ValueNotEqualText(step_range.Minimum()),
g_empty_string);
}
if (numeric_value < step_range.Minimum())
return std::make_pair(RangeUnderflowText(step_range.Minimum()),
g_empty_string);
if (numeric_value > step_range.Maximum())
return std::make_pair(RangeOverflowText(step_range.Maximum()),
g_empty_string);
if (step_range.StepMismatch(numeric_value)) {
DCHECK(step_range.HasStep());
Decimal candidate1 = step_range.ClampValue(numeric_value);
String localized_candidate1 = LocalizeValue(Serialize(candidate1));
Decimal candidate2 = candidate1 < numeric_value
? candidate1 + step_range.Step()
: candidate1 - step_range.Step();
if (!candidate2.IsFinite() || candidate2 < step_range.Minimum() ||
candidate2 > step_range.Maximum()) {
return std::make_pair(
GetLocale().QueryString(
IDS_FORM_VALIDATION_STEP_MISMATCH_CLOSE_TO_LIMIT,
localized_candidate1),
g_empty_string);
}
String localized_candidate2 = LocalizeValue(Serialize(candidate2));
if (candidate1 < candidate2) {
return std::make_pair(
GetLocale().QueryString(IDS_FORM_VALIDATION_STEP_MISMATCH,
localized_candidate1, localized_candidate2),
g_empty_string);
}
return std::make_pair(
GetLocale().QueryString(IDS_FORM_VALIDATION_STEP_MISMATCH,
localized_candidate2, localized_candidate1),
g_empty_string);
}
return std::make_pair(g_empty_string, g_empty_string);
}
Decimal InputType::ParseToNumber(const String&,
const Decimal& default_value) const {
NOTREACHED();
return default_value;
}
Decimal InputType::ParseToNumberOrNaN(const String& string) const {
return ParseToNumber(string, Decimal::Nan());
}
String InputType::Serialize(const Decimal&) const {
NOTREACHED();
return String();
}
ChromeClient* InputType::GetChromeClient() const {
if (Page* page = GetElement().GetDocument().GetPage())
return &page->GetChromeClient();
return nullptr;
}
Locale& InputType::GetLocale() const {
return GetElement().GetLocale();
}
bool InputType::CanSetStringValue() const {
return true;
}
bool InputType::IsKeyboardFocusable() const {
return GetElement().IsFocusable();
}
bool InputType::MayTriggerVirtualKeyboard() const {
return false;
}
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;
}
bool InputType::SetFiles(FileList*) {
return false;
}
void InputType::SetFilesAndDispatchEvents(FileList*) {}
void InputType::SetFilesFromPaths(const Vector<String>& paths) {}
String InputType::ValueInFilenameValueMode() const {
NOTREACHED();
return String();
}
String InputType::DefaultLabel() const {
return String();
}
bool InputType::CanSetSuggestedValue() {
return false;
}
bool InputType::ShouldSendChangeEventAfterCheckedChanged() {
return true;
}
void InputType::DispatchSearchEvent() {}
void InputType::SetValue(const String& sanitized_value,
bool value_changed,
TextFieldEventBehavior event_behavior,
TextControlSetValueSelection) {
// This setValue() implementation is used only for ValueMode::kValue except
// TextFieldInputType. That is to say, type=color, type=range, and temporal
// input types.
DCHECK_EQ(GetValueMode(), ValueMode::kValue);
if (event_behavior == TextFieldEventBehavior::kDispatchNoEvent)
GetElement().SetNonAttributeValue(sanitized_value);
else
GetElement().SetNonAttributeValueByUserEdit(sanitized_value);
if (!value_changed)
return;
switch (event_behavior) {
case TextFieldEventBehavior::kDispatchChangeEvent:
GetElement().DispatchFormControlChangeEvent();
break;
case TextFieldEventBehavior::kDispatchInputEvent:
GetElement().DispatchInputEvent();
break;
case TextFieldEventBehavior::kDispatchInputAndChangeEvent:
GetElement().DispatchInputEvent();
GetElement().DispatchFormControlChangeEvent();
break;
case TextFieldEventBehavior::kDispatchNoEvent:
break;
}
}
bool InputType::CanSetValue(const String&) {
return true;
}
String InputType::LocalizeValue(const String& proposed_value) const {
return proposed_value;
}
String InputType::VisibleValue() const {
return GetElement().value();
}
String InputType::SanitizeValue(const String& proposed_value) const {
return proposed_value;
}
String InputType::SanitizeUserInputValue(const String& proposed_value) const {
return SanitizeValue(proposed_value);
}
void InputType::WarnIfValueIsInvalidAndElementIsVisible(
const String& value) const {
// Don't warn if the value is set in Modernizr.
const ComputedStyle* style = GetElement().GetComputedStyle();
if (style && style->Visibility() != EVisibility::kHidden)
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 -1;
}
int InputType::MinLength() const {
return 0;
}
bool InputType::SupportsPlaceholder() const {
return false;
}
bool InputType::SupportsReadOnly() const {
return false;
}
String InputType::DefaultToolTip(const InputTypeView& input_type_view) const {
if (GetElement().Form() && GetElement().Form()->NoValidate())
return String();
return ValidationMessage(input_type_view).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();
}
void InputType::CopyNonAttributeProperties(const HTMLInputElement&) {}
void InputType::OnAttachWithLayoutObject() {}
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::GetColorChooserClient() {
return nullptr;
}
void InputType::ApplyStep(const Decimal& current,
double count,
AnyStepHandling any_step_handling,
TextFieldEventBehavior event_behavior,
ExceptionState& exception_state) {
// https://html.spec.whatwg.org/C/#dom-input-stepup
StepRange step_range(CreateStepRange(any_step_handling));
// 2. If the element has no allowed value step, then throw an
// InvalidStateError exception, and abort these steps.
if (!step_range.HasStep()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"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 (step_range.Minimum() > step_range.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 aligned_maximum = step_range.StepSnappedMaximum();
if (!aligned_maximum.IsFinite())
return;
Decimal base = step_range.StepBase();
Decimal step = step_range.Step();
EventQueueScope scope;
Decimal new_value = current;
const AtomicString& step_string =
GetElement().FastGetAttribute(html_names::kStepAttr);
if (!EqualIgnoringASCIICase(step_string, "any") &&
step_range.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) {
new_value = base + ((new_value - base) / step).Floor() * step;
++count;
} else if (count > 0) {
new_value = base + ((new_value - base) / step).Ceil() * step;
--count;
}
}
new_value = new_value + step_range.Step() * Decimal::FromDouble(count);
if (!EqualIgnoringASCIICase(step_string, "any"))
new_value = step_range.AlignValueForStep(current, new_value);
// 8. 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.
if (new_value < step_range.Minimum()) {
const Decimal aligned_minimum =
base + ((step_range.Minimum() - base) / step).Ceil() * step;
DCHECK_GE(aligned_minimum, step_range.Minimum());
new_value = aligned_minimum;
}
// 9. 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 (new_value > step_range.Maximum())
new_value = aligned_maximum;
// 10. If either the method invoked was the stepDown() method and value is
// greater than valueBeforeStepping, or the method invoked was the stepUp()
// method and value is less than valueBeforeStepping, then return.
if ((count < 0 && current < new_value) || (count > 0 && current > new_value))
return;
// 11. 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.
// 12. Set the value of the element to value as string.
SetValueAsDecimal(new_value, event_behavior, exception_state);
if (AXObjectCache* cache = GetElement().GetDocument().ExistingAXObjectCache())
cache->HandleValueChanged(&GetElement());
}
bool InputType::GetAllowedValueStep(Decimal* step) const {
StepRange step_range(CreateStepRange(kRejectAny));
*step = step_range.Step();
return step_range.HasStep();
}
StepRange InputType::CreateStepRange(AnyStepHandling) const {
NOTREACHED();
return StepRange();
}
void InputType::StepUp(double n, ExceptionState& exception_state) {
if (!IsSteppable()) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"This form element is not steppable.");
return;
}
const Decimal current = ParseToNumber(GetElement().value(), 0);
ApplyStep(current, n, kRejectAny, TextFieldEventBehavior::kDispatchNoEvent,
exception_state);
}
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 smaller 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 step_range(CreateStepRange(kAnyIsDefaultStep));
// 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 (!step_range.HasStep())
return;
EventQueueScope scope;
const Decimal step = step_range.Step();
int sign;
if (step > 0)
sign = n;
else if (step < 0)
sign = -n;
else
sign = 0;
Decimal current = ParseToNumberOrNaN(GetElement().value());
if (!current.IsFinite()) {
current = DefaultValueForStepUp();
const Decimal next_diff = step * n;
if (current < step_range.Minimum() - next_diff)
current = step_range.Minimum() - next_diff;
if (current > step_range.Maximum() - next_diff)
current = step_range.Maximum() - next_diff;
SetValueAsDecimal(current, TextFieldEventBehavior::kDispatchNoEvent,
IGNORE_EXCEPTION_FOR_TESTING);
}
if ((sign > 0 && current < step_range.Minimum()) ||
(sign < 0 && current > step_range.Maximum())) {
SetValueAsDecimal(sign > 0 ? step_range.Minimum() : step_range.Maximum(),
TextFieldEventBehavior::kDispatchChangeEvent,
IGNORE_EXCEPTION_FOR_TESTING);
return;
}
if ((sign > 0 && current >= step_range.Maximum()) ||
(sign < 0 && current <= step_range.Minimum()))
return;
ApplyStep(current, n, kAnyIsDefaultStep,
TextFieldEventBehavior::kDispatchChangeEvent,
IGNORE_EXCEPTION_FOR_TESTING);
}
void InputType::CountUsageIfVisible(WebFeature feature) const {
if (const ComputedStyle* style = GetElement().GetComputedStyle()) {
if (style->Visibility() != EVisibility::kHidden)
UseCounter::Count(GetElement().GetDocument(), feature);
}
}
Decimal InputType::FindStepBase(const Decimal& default_value) const {
Decimal step_base = ParseToNumber(
GetElement().FastGetAttribute(html_names::kMinAttr), Decimal::Nan());
if (!step_base.IsFinite()) {
step_base = ParseToNumber(
GetElement().FastGetAttribute(html_names::kValueAttr), default_value);
}
return step_base;
}
StepRange InputType::CreateReversibleStepRange(
AnyStepHandling any_step_handling,
const Decimal& step_base_default,
const Decimal& minimum_default,
const Decimal& maximum_default,
const StepRange::StepDescription& step_description) const {
return CreateStepRange(any_step_handling, step_base_default, minimum_default,
maximum_default, step_description,
/*supports_reversed_range=*/true);
}
StepRange InputType::CreateStepRange(
AnyStepHandling any_step_handling,
const Decimal& step_base_default,
const Decimal& minimum_default,
const Decimal& maximum_default,
const StepRange::StepDescription& step_description) const {
return CreateStepRange(any_step_handling, step_base_default, minimum_default,
maximum_default, step_description,
/*supports_reversed_range=*/false);
}
StepRange InputType::CreateStepRange(
AnyStepHandling any_step_handling,
const Decimal& step_base_default,
const Decimal& minimum_default,
const Decimal& maximum_default,
const StepRange::StepDescription& step_description,
bool supports_reversed_range) const {
bool has_range_limitations = false;
const Decimal step_base = FindStepBase(step_base_default);
Decimal minimum =
ParseToNumberOrNaN(GetElement().FastGetAttribute(html_names::kMinAttr));
if (minimum.IsFinite())
has_range_limitations = true;
else
minimum = minimum_default;
Decimal maximum =
ParseToNumberOrNaN(GetElement().FastGetAttribute(html_names::kMaxAttr));
if (maximum.IsFinite())
has_range_limitations = true;
else
maximum = maximum_default;
const Decimal step = StepRange::ParseStep(
any_step_handling, step_description,
GetElement().FastGetAttribute(html_names::kStepAttr));
bool has_reversed_range =
has_range_limitations && supports_reversed_range && maximum < minimum;
return StepRange(step_base, minimum, maximum, has_range_limitations,
has_reversed_range, step, step_description);
}
void InputType::AddWarningToConsole(const char* message_format,
const String& value) const {
GetElement().GetDocument().AddConsoleMessage(
MakeGarbageCollected<ConsoleMessage>(
mojom::ConsoleMessageSource::kRendering,
mojom::ConsoleMessageLevel::kWarning,
String::Format(message_format,
JSONValue::QuoteString(value).Utf8().c_str())));
}
} // namespace blink