summaryrefslogtreecommitdiffstats
path: root/Source/WebCore/html/HTMLInputElement.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/html/HTMLInputElement.cpp')
-rw-r--r--Source/WebCore/html/HTMLInputElement.cpp1565
1 files changed, 1565 insertions, 0 deletions
diff --git a/Source/WebCore/html/HTMLInputElement.cpp b/Source/WebCore/html/HTMLInputElement.cpp
new file mode 100644
index 0000000..14dd149
--- /dev/null
+++ b/Source/WebCore/html/HTMLInputElement.cpp
@@ -0,0 +1,1565 @@
+/*
+ * 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) 2010 Google Inc. 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 "config.h"
+#include "HTMLInputElement.h"
+
+#include "AXObjectCache.h"
+#include "Attribute.h"
+#include "BeforeTextInsertedEvent.h"
+#include "CSSPropertyNames.h"
+#include "Document.h"
+#include "EventNames.h"
+#include "ExceptionCode.h"
+#include "FileList.h"
+#include "HTMLCollection.h"
+#include "HTMLDataListElement.h"
+#include "HTMLFormElement.h"
+#include "HTMLNames.h"
+#include "HTMLOptionElement.h"
+#include "HTMLParserIdioms.h"
+#include "InputType.h"
+#include "KeyboardEvent.h"
+#include "LocalizedStrings.h"
+#include "MouseEvent.h"
+#include "PlatformMouseEvent.h"
+#include "RenderTextControlSingleLine.h"
+#include "RenderTheme.h"
+#include "RuntimeEnabledFeatures.h"
+#include "ScriptEventListener.h"
+#include "WheelEvent.h"
+#include <wtf/MathExtras.h>
+#include <wtf/StdLibExtras.h>
+
+#ifdef ANDROID_ACCEPT_CHANGES_TO_FOCUSED_TEXTFIELDS
+#include "PlatformBridge.h"
+#endif
+
+#if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS)
+#include "TouchEvent.h"
+#endif
+
+using namespace std;
+
+namespace WebCore {
+
+using namespace HTMLNames;
+
+const int maxSavedResults = 256;
+
+HTMLInputElement::HTMLInputElement(const QualifiedName& tagName, Document* document, HTMLFormElement* form, bool createdByParser)
+ : HTMLTextFormControlElement(tagName, document, form)
+ , m_maxResults(-1)
+ , m_isChecked(false)
+ , m_reflectsCheckedAttribute(true)
+ , m_isIndeterminate(false)
+ , m_hasType(false)
+ , m_isActivatedSubmit(false)
+ , m_autocomplete(Uninitialized)
+ , m_isAutofilled(false)
+ , m_stateRestored(false)
+ , m_parsingInProgress(createdByParser)
+ , m_inputType(InputType::createText(this))
+{
+ ASSERT(hasTagName(inputTag) || hasTagName(isindexTag));
+}
+
+PassRefPtr<HTMLInputElement> HTMLInputElement::create(const QualifiedName& tagName, Document* document, HTMLFormElement* form, bool createdByParser)
+{
+ return adoptRef(new HTMLInputElement(tagName, document, form, createdByParser));
+}
+
+HTMLInputElement::~HTMLInputElement()
+{
+ if (needsActivationCallback())
+ document()->unregisterForDocumentActivationCallbacks(this);
+
+ document()->checkedRadioButtons().removeButton(this);
+
+ // Need to remove this from the form while it is still an HTMLInputElement,
+ // so can't wait for the base class's destructor to do it.
+ removeFromForm();
+}
+
+const AtomicString& HTMLInputElement::formControlName() const
+{
+ return m_data.name();
+}
+
+bool HTMLInputElement::autoComplete() const
+{
+ if (m_autocomplete != Uninitialized)
+ return m_autocomplete == On;
+ return HTMLTextFormControlElement::autoComplete();
+}
+
+void HTMLInputElement::updateCheckedRadioButtons()
+{
+ if (attached() && checked())
+ checkedRadioButtons().addButton(this);
+
+ if (form()) {
+ const Vector<FormAssociatedElement*>& controls = form()->associatedElements();
+ for (unsigned i = 0; i < controls.size(); ++i) {
+ if (!controls[i]->isFormControlElement())
+ continue;
+ HTMLFormControlElement* control = static_cast<HTMLFormControlElement*>(controls[i]);
+ if (control->name() != name())
+ continue;
+ if (control->type() != type())
+ continue;
+ control->setNeedsValidityCheck();
+ }
+ } else {
+ // FIXME: Traversing the document is inefficient.
+ for (Node* node = document()->body(); node; node = node->traverseNextNode()) {
+ if (!node->isElementNode())
+ continue;
+ Element* element = static_cast<Element*>(node);
+ if (element->formControlName() != name())
+ continue;
+ if (element->formControlType() != type())
+ continue;
+ HTMLFormControlElement* control = static_cast<HTMLFormControlElement*>(element);
+ if (control->form())
+ continue;
+ control->setNeedsValidityCheck();
+ }
+ }
+
+ if (renderer() && renderer()->style()->hasAppearance())
+ renderer()->theme()->stateChanged(renderer(), CheckedState);
+}
+
+bool HTMLInputElement::isValidValue(const String& value) const
+{
+ if (!m_inputType->canSetStringValue()) {
+ ASSERT_NOT_REACHED();
+ return false;
+ }
+ return !m_inputType->typeMismatchFor(value)
+ && !stepMismatch(value)
+ && !rangeUnderflow(value)
+ && !rangeOverflow(value)
+ && !tooLong(value, IgnoreDirtyFlag)
+ && !patternMismatch(value)
+ && !valueMissing(value);
+}
+
+bool HTMLInputElement::typeMismatch() const
+{
+ return m_inputType->typeMismatch();
+}
+
+bool HTMLInputElement::valueMissing(const String& value) const
+{
+ if (!isRequiredFormControl() || readOnly() || disabled())
+ return false;
+ return m_inputType->valueMissing(value);
+}
+
+bool HTMLInputElement::patternMismatch(const String& value) const
+{
+ return m_inputType->patternMismatch(value);
+}
+
+bool HTMLInputElement::tooLong(const String& value, NeedsToCheckDirtyFlag check) const
+{
+ // We use isTextType() instead of supportsMaxLength() because of the
+ // 'virtual' overhead.
+ if (!isTextType())
+ return false;
+ int max = maxLength();
+ if (max < 0)
+ return false;
+ if (check == CheckDirtyFlag) {
+ // Return false for the default value even if it is longer than maxLength.
+ bool userEdited = !m_data.value().isNull();
+ if (!userEdited)
+ return false;
+ }
+ return numGraphemeClusters(value) > static_cast<unsigned>(max);
+}
+
+bool HTMLInputElement::rangeUnderflow(const String& value) const
+{
+ return m_inputType->rangeUnderflow(value);
+}
+
+bool HTMLInputElement::rangeOverflow(const String& value) const
+{
+ return m_inputType->rangeOverflow(value);
+}
+
+double HTMLInputElement::minimum() const
+{
+ return m_inputType->minimum();
+}
+
+double HTMLInputElement::maximum() const
+{
+ return m_inputType->maximum();
+}
+
+bool HTMLInputElement::stepMismatch(const String& value) const
+{
+ double step;
+ if (!getAllowedValueStep(&step))
+ return false;
+ return m_inputType->stepMismatch(value, step);
+}
+
+String HTMLInputElement::minimumString() const
+{
+ return m_inputType->serialize(minimum());
+}
+
+String HTMLInputElement::maximumString() const
+{
+ return m_inputType->serialize(maximum());
+}
+
+String HTMLInputElement::stepBaseString() const
+{
+ return m_inputType->serialize(m_inputType->stepBase());
+}
+
+String HTMLInputElement::stepString() const
+{
+ double step;
+ if (!getAllowedValueStep(&step)) {
+ // stepString() should be called only if stepMismatch() can be true.
+ ASSERT_NOT_REACHED();
+ return String();
+ }
+ return serializeForNumberType(step / m_inputType->stepScaleFactor());
+}
+
+String HTMLInputElement::typeMismatchText() const
+{
+ return m_inputType->typeMismatchText();
+}
+
+String HTMLInputElement::valueMissingText() const
+{
+ return m_inputType->valueMissingText();
+}
+
+bool HTMLInputElement::getAllowedValueStep(double* step) const
+{
+ return getAllowedValueStepWithDecimalPlaces(step, 0);
+}
+
+bool HTMLInputElement::getAllowedValueStepWithDecimalPlaces(double* step, unsigned* decimalPlaces) const
+{
+ ASSERT(step);
+ double defaultStep = m_inputType->defaultStep();
+ double stepScaleFactor = m_inputType->stepScaleFactor();
+ if (!isfinite(defaultStep) || !isfinite(stepScaleFactor))
+ return false;
+ const AtomicString& stepString = fastGetAttribute(stepAttr);
+ if (stepString.isEmpty()) {
+ *step = defaultStep * stepScaleFactor;
+ if (decimalPlaces)
+ *decimalPlaces = 0;
+ return true;
+ }
+ if (equalIgnoringCase(stepString, "any"))
+ return false;
+ double parsed;
+ if (!decimalPlaces) {
+ if (!parseToDoubleForNumberType(stepString, &parsed) || parsed <= 0.0) {
+ *step = defaultStep * stepScaleFactor;
+ return true;
+ }
+ } else {
+ if (!parseToDoubleForNumberTypeWithDecimalPlaces(stepString, &parsed, decimalPlaces) || parsed <= 0.0) {
+ *step = defaultStep * stepScaleFactor;
+ *decimalPlaces = 0;
+ return true;
+ }
+ }
+ // For date, month, week, the parsed value should be an integer for some types.
+ if (m_inputType->parsedStepValueShouldBeInteger())
+ parsed = max(round(parsed), 1.0);
+ double result = parsed * stepScaleFactor;
+ // For datetime, datetime-local, time, the result should be an integer.
+ if (m_inputType->scaledStepValueShouldBeInteger())
+ result = max(round(result), 1.0);
+ ASSERT(result > 0);
+ *step = result;
+ return true;
+}
+
+void HTMLInputElement::applyStep(double count, ExceptionCode& ec)
+{
+ double step;
+ unsigned stepDecimalPlaces, currentDecimalPlaces;
+ if (!getAllowedValueStepWithDecimalPlaces(&step, &stepDecimalPlaces)) {
+ ec = INVALID_STATE_ERR;
+ return;
+ }
+ const double nan = numeric_limits<double>::quiet_NaN();
+ double current = m_inputType->parseToDoubleWithDecimalPlaces(value(), nan, &currentDecimalPlaces);
+ if (!isfinite(current)) {
+ ec = INVALID_STATE_ERR;
+ return;
+ }
+ double newValue = current + step * count;
+ if (isinf(newValue)) {
+ ec = INVALID_STATE_ERR;
+ return;
+ }
+ double acceptableError = m_inputType->acceptableError(step);
+ if (newValue - m_inputType->minimum() < -acceptableError) {
+ ec = INVALID_STATE_ERR;
+ return;
+ }
+ if (newValue < m_inputType->minimum())
+ newValue = m_inputType->minimum();
+ unsigned baseDecimalPlaces;
+ double base = m_inputType->stepBaseWithDecimalPlaces(&baseDecimalPlaces);
+ baseDecimalPlaces = min(baseDecimalPlaces, 16u);
+ if (newValue < pow(10.0, 21.0)) {
+ if (stepMismatch(value())) {
+ double scale = pow(10.0, static_cast<double>(max(stepDecimalPlaces, currentDecimalPlaces)));
+ newValue = round(newValue * scale) / scale;
+ } else {
+ double scale = pow(10.0, static_cast<double>(max(stepDecimalPlaces, baseDecimalPlaces)));
+ newValue = round((base + round((newValue - base) / step) * step) * scale) / scale;
+ }
+ }
+ if (newValue - m_inputType->maximum() > acceptableError) {
+ ec = INVALID_STATE_ERR;
+ return;
+ }
+ if (newValue > m_inputType->maximum())
+ newValue = m_inputType->maximum();
+ setValueAsNumber(newValue, ec);
+
+ if (AXObjectCache::accessibilityEnabled())
+ document()->axObjectCache()->postNotification(renderer(), AXObjectCache::AXValueChanged, true);
+}
+
+void HTMLInputElement::stepUp(int n, ExceptionCode& ec)
+{
+ applyStep(n, ec);
+}
+
+void HTMLInputElement::stepDown(int n, ExceptionCode& ec)
+{
+ applyStep(-n, ec);
+}
+
+bool HTMLInputElement::isKeyboardFocusable(KeyboardEvent* event) const
+{
+ if (isTextField())
+ return HTMLFormControlElementWithState::isFocusable();
+ return HTMLFormControlElementWithState::isKeyboardFocusable(event) && m_inputType->isKeyboardFocusable();
+}
+
+bool HTMLInputElement::isMouseFocusable() const
+{
+ if (isTextField())
+ return HTMLFormControlElementWithState::isFocusable();
+ return HTMLFormControlElementWithState::isMouseFocusable();
+}
+
+void HTMLInputElement::updateFocusAppearance(bool restorePreviousSelection)
+{
+ if (isTextField())
+ InputElement::updateFocusAppearance(m_data, this, this, restorePreviousSelection);
+ else
+ HTMLFormControlElementWithState::updateFocusAppearance(restorePreviousSelection);
+}
+
+void HTMLInputElement::aboutToUnload()
+{
+ InputElement::aboutToUnload(this, this);
+}
+
+bool HTMLInputElement::shouldUseInputMethod() const
+{
+ return m_inputType->shouldUseInputMethod();
+}
+
+void HTMLInputElement::handleFocusEvent()
+{
+ InputElement::dispatchFocusEvent(this, this);
+}
+
+void HTMLInputElement::handleBlurEvent()
+{
+ m_inputType->handleBlurEvent();
+ InputElement::dispatchBlurEvent(this, this);
+}
+
+void HTMLInputElement::setType(const String& type)
+{
+ // FIXME: This should just call setAttribute. No reason to handle the empty string specially.
+ // We should write a test case to show that setting to the empty string does not remove the
+ // attribute in other browsers and then fix this. Note that setting to null *does* remove
+ // the attribute and setAttribute implements that.
+ if (type.isEmpty()) {
+ ExceptionCode ec;
+ removeAttribute(typeAttr, ec);
+ } else
+ setAttribute(typeAttr, type);
+}
+
+void HTMLInputElement::updateType()
+{
+ const AtomicString& typeString = fastGetAttribute(typeAttr);
+
+ OwnPtr<InputType> newType = InputType::create(this, typeString);
+#ifdef ANDROID_ACCEPT_CHANGES_TO_FOCUSED_TEXTFIELDS
+ if (newType->isPasswordField() && document()->focusedNode() == this)
+ PlatformBridge::updateTextfield(document()->view(), this, true, String());
+#endif
+
+ if (m_hasType && !newType->canChangeFromAnotherType()) {
+ // Set the attribute back to the old value.
+ // Useful in case we were called from inside parseMappedAttribute.
+ setAttribute(typeAttr, type());
+ return;
+ }
+
+ m_hasType = true;
+
+ if (m_inputType->formControlType() == newType->formControlType())
+ return;
+
+ checkedRadioButtons().removeButton(this);
+
+ bool wasAttached = attached();
+ if (wasAttached)
+ detach();
+
+ bool didStoreValue = m_inputType->storesValueSeparateFromAttribute();
+ bool neededActivationCallback = needsActivationCallback();
+ bool didRespectHeightAndWidth = m_inputType->shouldRespectHeightAndWidthAttributes();
+
+ m_inputType->destroyShadowSubtree();
+ m_inputType = newType.release();
+ m_inputType->createShadowSubtree();
+
+ setNeedsWillValidateCheck();
+
+ bool willStoreValue = m_inputType->storesValueSeparateFromAttribute();
+
+ if (didStoreValue && !willStoreValue && !m_data.value().isNull()) {
+ setAttribute(valueAttr, m_data.value());
+ m_data.setValue(String());
+ }
+ if (!didStoreValue && willStoreValue)
+ m_data.setValue(sanitizeValue(fastGetAttribute(valueAttr)));
+ else
+ InputElement::updateValueIfNeeded(m_data, this);
+
+ if (neededActivationCallback)
+ unregisterForActivationCallbackIfNeeded();
+ else
+ registerForActivationCallbackIfNeeded();
+
+ if (didRespectHeightAndWidth != m_inputType->shouldRespectHeightAndWidthAttributes()) {
+ NamedNodeMap* map = attributeMap();
+ ASSERT(map);
+ if (Attribute* height = map->getAttributeItem(heightAttr))
+ attributeChanged(height, false);
+ if (Attribute* width = map->getAttributeItem(widthAttr))
+ attributeChanged(width, false);
+ if (Attribute* align = map->getAttributeItem(alignAttr))
+ attributeChanged(align, false);
+ }
+
+ if (wasAttached) {
+ attach();
+ if (document()->focusedNode() == this)
+ updateFocusAppearance(true);
+ }
+
+ checkedRadioButtons().addButton(this);
+
+ setNeedsValidityCheck();
+ InputElement::notifyFormStateChanged(this);
+}
+
+const AtomicString& HTMLInputElement::formControlType() const
+{
+ return m_inputType->formControlType();
+}
+
+bool HTMLInputElement::saveFormControlState(String& result) const
+{
+ return m_inputType->saveFormControlState(result);
+}
+
+void HTMLInputElement::restoreFormControlState(const String& state)
+{
+ m_inputType->restoreFormControlState(state);
+ m_stateRestored = true;
+}
+
+bool HTMLInputElement::canStartSelection() const
+{
+ if (!isTextField())
+ return false;
+ return HTMLFormControlElementWithState::canStartSelection();
+}
+
+bool HTMLInputElement::canHaveSelection() const
+{
+ return isTextField();
+}
+
+void HTMLInputElement::accessKeyAction(bool sendToAnyElement)
+{
+ m_inputType->accessKeyAction(sendToAnyElement);
+}
+
+bool HTMLInputElement::mapToEntry(const QualifiedName& attrName, MappedAttributeEntry& result) const
+{
+ if (((attrName == heightAttr || attrName == widthAttr) && m_inputType->shouldRespectHeightAndWidthAttributes())
+ || attrName == vspaceAttr
+ || attrName == hspaceAttr) {
+ result = eUniversal;
+ return false;
+ }
+
+ if (attrName == alignAttr && m_inputType->shouldRespectAlignAttribute()) {
+ // Share with <img> since the alignment behavior is the same.
+ result = eReplaced;
+ return false;
+ }
+
+ return HTMLElement::mapToEntry(attrName, result);
+}
+
+void HTMLInputElement::parseMappedAttribute(Attribute* attr)
+{
+ if (attr->name() == nameAttr) {
+ checkedRadioButtons().removeButton(this);
+ m_data.setName(attr->value());
+ checkedRadioButtons().addButton(this);
+ HTMLFormControlElementWithState::parseMappedAttribute(attr);
+ } else if (attr->name() == autocompleteAttr) {
+ if (equalIgnoringCase(attr->value(), "off")) {
+ m_autocomplete = Off;
+ registerForActivationCallbackIfNeeded();
+ } else {
+ bool needsToUnregister = m_autocomplete == Off;
+
+ if (attr->isEmpty())
+ m_autocomplete = Uninitialized;
+ else
+ m_autocomplete = On;
+
+ if (needsToUnregister)
+ unregisterForActivationCallbackIfNeeded();
+ }
+ } else if (attr->name() == typeAttr) {
+ updateType();
+ } else if (attr->name() == valueAttr) {
+ // We only need to setChanged if the form is looking at the default value right now.
+ if (m_data.value().isNull())
+ setNeedsStyleRecalc();
+ setFormControlValueMatchesRenderer(false);
+ setNeedsValidityCheck();
+ } else if (attr->name() == checkedAttr) {
+ // Another radio button in the same group might be checked by state
+ // restore. We shouldn't call setChecked() even if this has the checked
+ // attribute. So, delay the setChecked() call until
+ // finishParsingChildren() is called if parsing is in progress.
+ if (!m_parsingInProgress && m_reflectsCheckedAttribute) {
+ setChecked(!attr->isNull());
+ m_reflectsCheckedAttribute = true;
+ }
+ } else if (attr->name() == maxlengthAttr) {
+ InputElement::parseMaxLengthAttribute(m_data, this, this, attr);
+ setNeedsValidityCheck();
+ } else if (attr->name() == sizeAttr)
+ InputElement::parseSizeAttribute(m_data, this, attr);
+ else if (attr->name() == altAttr)
+ m_inputType->altAttributeChanged();
+ else if (attr->name() == srcAttr)
+ m_inputType->srcAttributeChanged();
+ else if (attr->name() == usemapAttr || attr->name() == accesskeyAttr) {
+ // FIXME: ignore for the moment
+ } else if (attr->name() == vspaceAttr) {
+ addCSSLength(attr, CSSPropertyMarginTop, attr->value());
+ addCSSLength(attr, CSSPropertyMarginBottom, attr->value());
+ } else if (attr->name() == hspaceAttr) {
+ addCSSLength(attr, CSSPropertyMarginLeft, attr->value());
+ addCSSLength(attr, CSSPropertyMarginRight, attr->value());
+ } else if (attr->name() == alignAttr) {
+ if (m_inputType->shouldRespectAlignAttribute())
+ addHTMLAlignment(attr);
+ } else if (attr->name() == widthAttr) {
+ if (m_inputType->shouldRespectHeightAndWidthAttributes())
+ addCSSLength(attr, CSSPropertyWidth, attr->value());
+ } else if (attr->name() == heightAttr) {
+ if (m_inputType->shouldRespectHeightAndWidthAttributes())
+ addCSSLength(attr, CSSPropertyHeight, attr->value());
+ } else if (attr->name() == onsearchAttr) {
+ // Search field and slider attributes all just cause updateFromElement to be called through style recalcing.
+ setAttributeEventListener(eventNames().searchEvent, createAttributeEventListener(this, attr));
+ } else if (attr->name() == resultsAttr) {
+ int oldResults = m_maxResults;
+ m_maxResults = !attr->isNull() ? std::min(attr->value().toInt(), maxSavedResults) : -1;
+ // FIXME: Detaching just for maxResults change is not ideal. We should figure out the right
+ // time to relayout for this change.
+ if (m_maxResults != oldResults && (m_maxResults <= 0 || oldResults <= 0) && attached()) {
+ detach();
+ attach();
+ }
+ setNeedsStyleRecalc();
+ } else if (attr->name() == autosaveAttr || attr->name() == incrementalAttr)
+ setNeedsStyleRecalc();
+ else if (attr->name() == minAttr || attr->name() == maxAttr) {
+ m_inputType->minOrMaxAttributeChanged();
+ setNeedsValidityCheck();
+ } else if (attr->name() == multipleAttr || attr->name() == patternAttr || attr->name() == precisionAttr || attr->name() == stepAttr)
+ setNeedsValidityCheck();
+#if ENABLE(DATALIST)
+ else if (attr->name() == listAttr)
+ m_hasNonEmptyList = !attr->isEmpty();
+ // FIXME: we need to tell this change to a renderer if the attribute affects the appearance.
+#endif
+#if ENABLE(INPUT_SPEECH)
+ else if (attr->name() == webkitspeechAttr) {
+ if (renderer()) {
+ // This renderer and its children have quite different layouts and styles depending on
+ // whether the speech button is visible or not. So we reset the whole thing and recreate
+ // to get the right styles and layout.
+ detach();
+ attach();
+ }
+ setNeedsStyleRecalc();
+ } else if (attr->name() == onwebkitspeechchangeAttr)
+ setAttributeEventListener(eventNames().webkitspeechchangeEvent, createAttributeEventListener(this, attr));
+#endif
+ else
+ HTMLTextFormControlElement::parseMappedAttribute(attr);
+}
+
+void HTMLInputElement::finishParsingChildren()
+{
+ m_parsingInProgress = false;
+ HTMLFormControlElementWithState::finishParsingChildren();
+ if (!m_stateRestored) {
+ bool checked = hasAttribute(checkedAttr);
+ if (checked)
+ setChecked(checked);
+ m_reflectsCheckedAttribute = true;
+ }
+}
+
+bool HTMLInputElement::rendererIsNeeded(RenderStyle* style)
+{
+ return m_inputType->rendererIsNeeded() && HTMLFormControlElementWithState::rendererIsNeeded(style);
+}
+
+RenderObject* HTMLInputElement::createRenderer(RenderArena* arena, RenderStyle* style)
+{
+ return m_inputType->createRenderer(arena, style);
+}
+
+void HTMLInputElement::attach()
+{
+ if (!m_hasType)
+ updateType();
+
+ HTMLFormControlElementWithState::attach();
+
+ m_inputType->attach();
+
+ if (document()->focusedNode() == this)
+ document()->updateFocusAppearanceSoon(true /* restore selection */);
+}
+
+void HTMLInputElement::detach()
+{
+ HTMLFormControlElementWithState::detach();
+ setFormControlValueMatchesRenderer(false);
+}
+
+String HTMLInputElement::altText() const
+{
+ // http://www.w3.org/TR/1998/REC-html40-19980424/appendix/notes.html#altgen
+ // also heavily discussed by Hixie on bugzilla
+ // note this is intentionally different to HTMLImageElement::altText()
+ String alt = fastGetAttribute(altAttr);
+ // fall back to title attribute
+ if (alt.isNull())
+ alt = getAttribute(titleAttr);
+ if (alt.isNull())
+ alt = getAttribute(valueAttr);
+ if (alt.isEmpty())
+ alt = inputElementAltText();
+ return alt;
+}
+
+bool HTMLInputElement::isSuccessfulSubmitButton() const
+{
+ // HTML spec says that buttons must have names to be considered successful.
+ // However, other browsers do not impose this constraint. So we do not.
+ return !disabled() && m_inputType->canBeSuccessfulSubmitButton();
+}
+
+bool HTMLInputElement::isActivatedSubmit() const
+{
+ return m_isActivatedSubmit;
+}
+
+void HTMLInputElement::setActivatedSubmit(bool flag)
+{
+ m_isActivatedSubmit = flag;
+}
+
+bool HTMLInputElement::appendFormData(FormDataList& encoding, bool multipart)
+{
+ return m_inputType->isFormDataAppendable() && m_inputType->appendFormData(encoding, multipart);
+}
+
+void HTMLInputElement::reset()
+{
+ if (m_inputType->storesValueSeparateFromAttribute())
+ setValue(String());
+
+ setChecked(hasAttribute(checkedAttr));
+ m_reflectsCheckedAttribute = true;
+}
+
+bool HTMLInputElement::isTextField() const
+{
+ return m_inputType->isTextField();
+}
+
+bool HTMLInputElement::isTextType() const
+{
+ return m_inputType->isTextType();
+}
+
+void HTMLInputElement::setChecked(bool nowChecked, bool sendChangeEvent)
+{
+ if (checked() == nowChecked)
+ return;
+
+ checkedRadioButtons().removeButton(this);
+
+ m_reflectsCheckedAttribute = false;
+ m_isChecked = nowChecked;
+ setNeedsStyleRecalc();
+
+ updateCheckedRadioButtons();
+ setNeedsValidityCheck();
+
+ // Ideally we'd do this from the render tree (matching
+ // RenderTextView), but it's not possible to do it at the moment
+ // because of the way the code is structured.
+ if (renderer() && AXObjectCache::accessibilityEnabled())
+ renderer()->document()->axObjectCache()->postNotification(renderer(), AXObjectCache::AXCheckedStateChanged, true);
+
+ // Only send a change event for items in the document (avoid firing during
+ // parsing) and don't send a change event for a radio button that's getting
+ // unchecked to match other browsers. DOM is not a useful standard for this
+ // because it says only to fire change events at "lose focus" time, which is
+ // definitely wrong in practice for these types of elements.
+ if (sendChangeEvent && inDocument() && m_inputType->shouldSendChangeEventAfterCheckedChanged())
+ dispatchFormControlChangeEvent();
+}
+
+void HTMLInputElement::setIndeterminate(bool newValue)
+{
+ if (!m_inputType->isCheckable() || indeterminate() == newValue)
+ return;
+
+ m_isIndeterminate = newValue;
+
+ setNeedsStyleRecalc();
+
+ if (renderer() && renderer()->style()->hasAppearance())
+ renderer()->theme()->stateChanged(renderer(), CheckedState);
+}
+
+int HTMLInputElement::size() const
+{
+ return m_data.size();
+}
+
+void HTMLInputElement::copyNonAttributeProperties(const Element* source)
+{
+ const HTMLInputElement* sourceElement = static_cast<const HTMLInputElement*>(source);
+
+ m_data.setValue(sourceElement->m_data.value());
+ setChecked(sourceElement->m_isChecked);
+ m_reflectsCheckedAttribute = sourceElement->m_reflectsCheckedAttribute;
+ m_isIndeterminate = sourceElement->m_isIndeterminate;
+
+ HTMLFormControlElementWithState::copyNonAttributeProperties(source);
+}
+
+String HTMLInputElement::value() const
+{
+ String value;
+ if (m_inputType->getTypeSpecificValue(value))
+ return value;
+
+ value = m_data.value();
+ if (!value.isNull())
+ return value;
+
+ value = sanitizeValue(fastGetAttribute(valueAttr));
+ if (!value.isNull())
+ return value;
+
+ return m_inputType->fallbackValue();
+}
+
+String HTMLInputElement::valueWithDefault() const
+{
+ String value = this->value();
+ if (!value.isNull())
+ return value;
+
+ return m_inputType->defaultValue();
+}
+
+void HTMLInputElement::setValueForUser(const String& value)
+{
+ // Call setValue and make it send a change event.
+ setValue(value, true);
+}
+
+const String& HTMLInputElement::suggestedValue() const
+{
+ return m_data.suggestedValue();
+}
+
+void HTMLInputElement::setSuggestedValue(const String& value)
+{
+ if (!m_inputType->canSetSuggestedValue())
+ return;
+ setFormControlValueMatchesRenderer(false);
+ m_data.setSuggestedValue(sanitizeValue(value));
+ updatePlaceholderVisibility(false);
+ if (renderer())
+ renderer()->updateFromElement();
+ setNeedsStyleRecalc();
+}
+
+void HTMLInputElement::setValue(const String& value, bool sendChangeEvent)
+{
+ if (!m_inputType->canSetValue(value))
+ return;
+
+ setFormControlValueMatchesRenderer(false);
+ if (m_inputType->storesValueSeparateFromAttribute()) {
+ if (files())
+ files()->clear();
+ else {
+ m_data.setValue(sanitizeValue(value));
+ if (isTextField())
+ updatePlaceholderVisibility(false);
+ }
+ setNeedsStyleRecalc();
+ } else
+ setAttribute(valueAttr, sanitizeValue(value));
+
+ setNeedsValidityCheck();
+
+ if (isTextField()) {
+ unsigned max = m_data.value().length();
+#ifdef ANDROID_ACCEPT_CHANGES_TO_FOCUSED_TEXTFIELDS
+ // Make sure our UI side textfield changes to match the RenderTextControl
+ PlatformBridge::updateTextfield(document()->view(), this, false, value);
+#endif
+ if (document()->focusedNode() == this)
+ InputElement::updateSelectionRange(this, this, max, max);
+ else
+ cacheSelection(max, max);
+ m_data.setSuggestedValue(String());
+ }
+ m_inputType->valueChanged();
+
+ // Don't dispatch the change event when focused, it will be dispatched
+ // when the control loses focus.
+ if (sendChangeEvent && document()->focusedNode() != this)
+ dispatchFormControlChangeEvent();
+
+ InputElement::notifyFormStateChanged(this);
+}
+
+double HTMLInputElement::valueAsDate() const
+{
+ return m_inputType->valueAsDate();
+}
+
+void HTMLInputElement::setValueAsDate(double value, ExceptionCode& ec)
+{
+ m_inputType->setValueAsDate(value, ec);
+}
+
+double HTMLInputElement::valueAsNumber() const
+{
+ return m_inputType->valueAsNumber();
+}
+
+void HTMLInputElement::setValueAsNumber(double newValue, ExceptionCode& ec)
+{
+ if (!isfinite(newValue)) {
+ ec = NOT_SUPPORTED_ERR;
+ return;
+ }
+ m_inputType->setValueAsNumber(newValue, ec);
+}
+
+String HTMLInputElement::placeholder() const
+{
+ return fastGetAttribute(placeholderAttr).string();
+}
+
+void HTMLInputElement::setPlaceholder(const String& value)
+{
+ setAttribute(placeholderAttr, value);
+}
+
+bool HTMLInputElement::searchEventsShouldBeDispatched() const
+{
+ return hasAttribute(incrementalAttr);
+}
+
+void HTMLInputElement::setValueFromRenderer(const String& value)
+{
+ // File upload controls will always use setFileListFromRenderer.
+ ASSERT(!isFileUpload());
+
+ m_data.setSuggestedValue(String());
+ InputElement::setValueFromRenderer(m_data, this, this, value);
+ updatePlaceholderVisibility(false);
+ setNeedsValidityCheck();
+
+ // Clear autofill flag (and yellow background) on user edit.
+ setAutofilled(false);
+}
+
+void HTMLInputElement::setFileListFromRenderer(const Vector<String>& paths)
+{
+ m_inputType->setFileList(paths);
+
+ setFormControlValueMatchesRenderer(true);
+ InputElement::notifyFormStateChanged(this);
+ setNeedsValidityCheck();
+}
+
+void* HTMLInputElement::preDispatchEventHandler(Event* event)
+{
+ if (event->type() != eventNames().clickEvent)
+ return 0;
+ if (!event->isMouseEvent() || static_cast<MouseEvent*>(event)->button() != LeftButton)
+ return 0;
+ // FIXME: Check whether there are any cases where this actually ends up leaking.
+ return m_inputType->willDispatchClick().leakPtr();
+}
+
+void HTMLInputElement::postDispatchEventHandler(Event* event, void* dataFromPreDispatch)
+{
+ OwnPtr<ClickHandlingState> state = adoptPtr(static_cast<ClickHandlingState*>(dataFromPreDispatch));
+ if (!state)
+ return;
+ m_inputType->didDispatchClick(event, *state);
+}
+
+void HTMLInputElement::defaultEventHandler(Event* evt)
+{
+ if (evt->isMouseEvent() && evt->type() == eventNames().clickEvent && static_cast<MouseEvent*>(evt)->button() == LeftButton) {
+ m_inputType->handleClickEvent(static_cast<MouseEvent*>(evt));
+ if (evt->defaultHandled())
+ return;
+ }
+
+ if (evt->isKeyboardEvent() && evt->type() == eventNames().keydownEvent) {
+ m_inputType->handleKeydownEvent(static_cast<KeyboardEvent*>(evt));
+ if (evt->defaultHandled())
+ return;
+ }
+
+ // Call the base event handler before any of our own event handling for almost all events in text fields.
+ // Makes editing keyboard handling take precedence over the keydown and keypress handling in this function.
+ bool callBaseClassEarly = isTextField() && (evt->type() == eventNames().keydownEvent || evt->type() == eventNames().keypressEvent);
+ if (callBaseClassEarly) {
+ HTMLFormControlElementWithState::defaultEventHandler(evt);
+ if (evt->defaultHandled())
+ return;
+ }
+
+ // DOMActivate events cause the input to be "activated" - in the case of image and submit inputs, this means
+ // actually submitting the form. For reset inputs, the form is reset. These events are sent when the user clicks
+ // on the element, or presses enter while it is the active element. JavaScript code wishing to activate the element
+ // must dispatch a DOMActivate event - a click event will not do the job.
+ if (evt->type() == eventNames().DOMActivateEvent) {
+ m_inputType->handleDOMActivateEvent(evt);
+ if (evt->defaultHandled())
+ return;
+ }
+
+ // Use key press event here since sending simulated mouse events
+ // on key down blocks the proper sending of the key press event.
+ if (evt->isKeyboardEvent() && evt->type() == eventNames().keypressEvent) {
+ m_inputType->handleKeypressEvent(static_cast<KeyboardEvent*>(evt));
+ if (evt->defaultHandled())
+ return;
+ }
+
+ if (evt->isKeyboardEvent() && evt->type() == eventNames().keyupEvent) {
+ m_inputType->handleKeyupEvent(static_cast<KeyboardEvent*>(evt));
+ if (evt->defaultHandled())
+ return;
+ }
+
+ if (m_inputType->shouldSubmitImplicitly(evt)) {
+ if (isSearchField()) {
+ addSearchResult();
+ onSearch();
+ }
+ // Fire onChange for text fields.
+ RenderObject* r = renderer();
+ if (r && r->isTextField() && toRenderTextControl(r)->wasChangedSinceLastChangeEvent()) {
+ dispatchFormControlChangeEvent();
+ // Refetch the renderer since arbitrary JS code run during onchange can do anything, including destroying it.
+ r = renderer();
+ if (r && r->isTextField())
+ toRenderTextControl(r)->setChangedSinceLastChangeEvent(false);
+ }
+
+ RefPtr<HTMLFormElement> formForSubmission = m_inputType->formForSubmission();
+ // Form may never have been present, or may have been destroyed by code responding to the change event.
+ if (formForSubmission)
+ formForSubmission->submitImplicitly(evt, canTriggerImplicitSubmission());
+
+ evt->setDefaultHandled();
+ return;
+ }
+
+ if (evt->isBeforeTextInsertedEvent())
+ m_inputType->handleBeforeTextInsertedEvent(static_cast<BeforeTextInsertedEvent*>(evt));
+
+ if (evt->isWheelEvent()) {
+ m_inputType->handleWheelEvent(static_cast<WheelEvent*>(evt));
+ if (evt->defaultHandled())
+ return;
+ }
+
+ if (evt->isMouseEvent() && evt->type() == eventNames().mousedownEvent) {
+ m_inputType->handleMouseDownEvent(static_cast<MouseEvent*>(evt));
+ if (evt->defaultHandled())
+ return;
+ }
+
+#if PLATFORM(ANDROID) && ENABLE(TOUCH_EVENTS)
+ if (evt->isTouchEvent() && evt->type() == eventNames().touchstartEvent) {
+ m_inputType->handleTouchStartEvent(static_cast<TouchEvent*>(evt));
+ if (evt->defaultHandled())
+ return;
+ }
+#endif
+
+ m_inputType->forwardEvent(evt);
+
+ if (!callBaseClassEarly && !evt->defaultHandled())
+ HTMLFormControlElementWithState::defaultEventHandler(evt);
+}
+
+bool HTMLInputElement::isURLAttribute(Attribute *attr) const
+{
+ return (attr->name() == srcAttr || attr->name() == formactionAttr);
+}
+
+String HTMLInputElement::defaultValue() const
+{
+ return fastGetAttribute(valueAttr);
+}
+
+void HTMLInputElement::setDefaultValue(const String &value)
+{
+ setAttribute(valueAttr, value);
+}
+
+void HTMLInputElement::setDefaultName(const AtomicString& name)
+{
+ m_data.setName(name);
+}
+
+String HTMLInputElement::accept() const
+{
+ return fastGetAttribute(acceptAttr);
+}
+
+String HTMLInputElement::alt() const
+{
+ return fastGetAttribute(altAttr);
+}
+
+int HTMLInputElement::maxLength() const
+{
+ return m_data.maxLength();
+}
+
+void HTMLInputElement::setMaxLength(int maxLength, ExceptionCode& ec)
+{
+ if (maxLength < 0)
+ ec = INDEX_SIZE_ERR;
+ else
+ setAttribute(maxlengthAttr, String::number(maxLength));
+}
+
+bool HTMLInputElement::multiple() const
+{
+ return fastHasAttribute(multipleAttr);
+}
+
+void HTMLInputElement::setSize(unsigned size)
+{
+ setAttribute(sizeAttr, String::number(size));
+}
+
+KURL HTMLInputElement::src() const
+{
+ return document()->completeURL(fastGetAttribute(srcAttr));
+}
+
+void HTMLInputElement::setAutofilled(bool autofilled)
+{
+ if (autofilled == m_isAutofilled)
+ return;
+
+ m_isAutofilled = autofilled;
+ setNeedsStyleRecalc();
+}
+
+FileList* HTMLInputElement::files()
+{
+ return m_inputType->files();
+}
+
+String HTMLInputElement::visibleValue() const
+{
+ return m_inputType->visibleValue();
+}
+
+bool HTMLInputElement::isAcceptableValue(const String& proposedValue) const
+{
+ return m_inputType->isAcceptableValue(proposedValue);
+}
+
+String HTMLInputElement::sanitizeValue(const String& proposedValue) const
+{
+ return m_inputType->sanitizeValue(proposedValue);
+}
+
+bool HTMLInputElement::hasUnacceptableValue() const
+{
+ return m_inputType->hasUnacceptableValue();
+}
+
+bool HTMLInputElement::isInRange() const
+{
+ return m_inputType->supportsRangeLimitation() && !rangeUnderflow(value()) && !rangeOverflow(value());
+}
+
+bool HTMLInputElement::isOutOfRange() const
+{
+ return m_inputType->supportsRangeLimitation() && (rangeUnderflow(value()) || rangeOverflow(value()));
+}
+
+bool HTMLInputElement::needsActivationCallback()
+{
+ return m_autocomplete == Off || m_inputType->shouldResetOnDocumentActivation();
+}
+
+void HTMLInputElement::registerForActivationCallbackIfNeeded()
+{
+ if (needsActivationCallback())
+ document()->registerForDocumentActivationCallbacks(this);
+}
+
+void HTMLInputElement::unregisterForActivationCallbackIfNeeded()
+{
+ if (!needsActivationCallback())
+ document()->unregisterForDocumentActivationCallbacks(this);
+}
+
+bool HTMLInputElement::isRequiredFormControl() const
+{
+ return m_inputType->supportsRequired() && required();
+}
+
+void HTMLInputElement::cacheSelection(int start, int end)
+{
+ m_data.setCachedSelectionStart(start);
+ m_data.setCachedSelectionEnd(end);
+}
+
+void HTMLInputElement::addSearchResult()
+{
+ ASSERT(isSearchField());
+ if (renderer())
+ toRenderTextControlSingleLine(renderer())->addSearchResult();
+}
+
+void HTMLInputElement::onSearch()
+{
+ ASSERT(isSearchField());
+ if (renderer())
+ toRenderTextControlSingleLine(renderer())->stopSearchEventTimer();
+ dispatchEvent(Event::create(eventNames().searchEvent, true, false));
+}
+
+void HTMLInputElement::documentDidBecomeActive()
+{
+ ASSERT(needsActivationCallback());
+ reset();
+}
+
+void HTMLInputElement::willMoveToNewOwnerDocument()
+{
+ m_inputType->willMoveToNewOwnerDocument();
+
+ // Always unregister for cache callbacks when leaving a document, even if we would otherwise like to be registered
+ if (needsActivationCallback())
+ document()->unregisterForDocumentActivationCallbacks(this);
+
+ document()->checkedRadioButtons().removeButton(this);
+
+ HTMLFormControlElementWithState::willMoveToNewOwnerDocument();
+}
+
+void HTMLInputElement::didMoveToNewOwnerDocument()
+{
+ registerForActivationCallbackIfNeeded();
+
+ HTMLFormControlElementWithState::didMoveToNewOwnerDocument();
+}
+
+void HTMLInputElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const
+{
+ HTMLFormControlElementWithState::addSubresourceAttributeURLs(urls);
+
+ addSubresourceURL(urls, src());
+}
+
+bool HTMLInputElement::recalcWillValidate() const
+{
+ return m_inputType->supportsValidation() && HTMLFormControlElementWithState::recalcWillValidate();
+}
+
+#if ENABLE(DATALIST)
+
+HTMLElement* HTMLInputElement::list() const
+{
+ return dataList();
+}
+
+HTMLDataListElement* HTMLInputElement::dataList() const
+{
+ if (!m_hasNonEmptyList)
+ return 0;
+
+ if (!m_inputType->shouldRespectListAttribute())
+ return 0;
+
+ Element* element = document()->getElementById(fastGetAttribute(listAttr));
+ if (!element)
+ return 0;
+ if (!element->hasTagName(datalistTag))
+ return 0;
+
+ return static_cast<HTMLDataListElement*>(element);
+}
+
+HTMLOptionElement* HTMLInputElement::selectedOption() const
+{
+ String value = this->value();
+
+ // The empty string never matches to a datalist option because it
+ // doesn't represent a suggestion according to the standard.
+ if (value.isEmpty())
+ return 0;
+
+ HTMLDataListElement* sourceElement = dataList();
+ if (!sourceElement)
+ return 0;
+ RefPtr<HTMLCollection> options = sourceElement->options();
+ if (!options)
+ return 0;
+ unsigned length = options->length();
+ for (unsigned i = 0; i < length; ++i) {
+ HTMLOptionElement* option = static_cast<HTMLOptionElement*>(options->item(i));
+ if (!option->disabled() && value == option->value())
+ return option;
+ }
+ return 0;
+}
+
+#endif // ENABLE(DATALIST)
+
+void HTMLInputElement::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(hasSpinButton() || m_inputType->isRangeControl());
+ if (!hasSpinButton() && !m_inputType->isRangeControl())
+ return;
+ ASSERT(n);
+ if (!n)
+ return;
+
+ unsigned stepDecimalPlaces, baseDecimalPlaces;
+ double step, base;
+ // The value will be the default value after stepping for <input value=(empty/invalid) step="any" />
+ // 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 (equalIgnoringCase(fastGetAttribute(stepAttr), "any"))
+ step = 0;
+ else if (!getAllowedValueStepWithDecimalPlaces(&step, &stepDecimalPlaces))
+ return;
+ base = m_inputType->stepBaseWithDecimalPlaces(&baseDecimalPlaces);
+ baseDecimalPlaces = min(baseDecimalPlaces, 16u);
+
+ int sign;
+ if (step > 0)
+ sign = n;
+ else if (step < 0)
+ sign = -n;
+ else
+ sign = 0;
+
+ const double nan = numeric_limits<double>::quiet_NaN();
+ String currentStringValue = value();
+ double current = m_inputType->parseToDouble(currentStringValue, nan);
+ if (!isfinite(current)) {
+ ExceptionCode ec;
+ current = m_inputType->defaultValueForStepUp();
+ setValueAsNumber(current, ec);
+ }
+ if ((sign > 0 && current < m_inputType->minimum()) || (sign < 0 && current > m_inputType->maximum()))
+ setValue(m_inputType->serialize(sign > 0 ? m_inputType->minimum() : m_inputType->maximum()));
+ else {
+ ExceptionCode ec;
+ if (stepMismatch(currentStringValue)) {
+ ASSERT(step);
+ double newValue;
+ double scale = pow(10.0, static_cast<double>(max(stepDecimalPlaces, baseDecimalPlaces)));
+
+ if (sign < 0)
+ newValue = round((base + floor((current - base) / step) * step) * scale) / scale;
+ else if (sign > 0)
+ newValue = round((base + ceil((current - base) / step) * step) * scale) / scale;
+ else
+ newValue = current;
+
+ if (newValue < m_inputType->minimum())
+ newValue = m_inputType->minimum();
+ if (newValue > m_inputType->maximum())
+ newValue = m_inputType->maximum();
+
+ setValueAsNumber(newValue, ec);
+ current = newValue;
+ if (n > 1)
+ applyStep(n - 1, ec);
+ else if (n < -1)
+ applyStep(n + 1, ec);
+ } else
+ applyStep(n, ec);
+ }
+
+ if (currentStringValue != value()) {
+ if (renderer() && renderer()->isTextField())
+ toRenderTextControl(renderer())->setChangedSinceLastChangeEvent(true);
+ if (m_inputType->isRangeControl())
+ dispatchFormControlChangeEvent();
+ else
+ dispatchFormControlInputEvent();
+ }
+}
+
+#if ENABLE(WCSS)
+
+void HTMLInputElement::setWapInputFormat(String& mask)
+{
+ String validateMask = validateInputMask(m_data, mask);
+ if (!validateMask.isEmpty())
+ m_data.setInputFormatMask(validateMask);
+}
+
+#endif
+
+#if ENABLE(INPUT_SPEECH)
+
+bool HTMLInputElement::isSpeechEnabled() const
+{
+ // FIXME: Add support for RANGE, EMAIL, URL, COLOR and DATE/TIME input types.
+ return m_inputType->shouldRespectSpeechAttribute() && RuntimeEnabledFeatures::speechInputEnabled() && hasAttribute(webkitspeechAttr);
+}
+
+#endif
+
+bool HTMLInputElement::isTextButton() const
+{
+ return m_inputType->isTextButton();
+}
+
+bool HTMLInputElement::isRadioButton() const
+{
+ return m_inputType->isRadioButton();
+}
+
+bool HTMLInputElement::isSearchField() const
+{
+ return m_inputType->isSearchField();
+}
+
+bool HTMLInputElement::isInputTypeHidden() const
+{
+ return m_inputType->isHiddenType();
+}
+
+bool HTMLInputElement::isPasswordField() const
+{
+ return m_inputType->isPasswordField();
+}
+
+bool HTMLInputElement::isCheckbox() const
+{
+ return m_inputType->isCheckbox();
+}
+
+bool HTMLInputElement::isText() const
+{
+ return m_inputType->isTextType();
+}
+
+bool HTMLInputElement::isEmailField() const
+{
+ return m_inputType->isEmailField();
+}
+
+bool HTMLInputElement::isFileUpload() const
+{
+ return m_inputType->isFileUpload();
+}
+
+bool HTMLInputElement::isImageButton() const
+{
+ return m_inputType->isImageButton();
+}
+
+bool HTMLInputElement::isNumberField() const
+{
+ return m_inputType->isNumberField();
+}
+
+bool HTMLInputElement::isSubmitButton() const
+{
+ return m_inputType->isSubmitButton();
+}
+
+bool HTMLInputElement::isTelephoneField() const
+{
+ return m_inputType->isTelephoneField();
+}
+
+bool HTMLInputElement::isURLField() const
+{
+ return m_inputType->isURLField();
+}
+
+bool HTMLInputElement::isEnumeratable() const
+{
+ return m_inputType->isEnumeratable();
+}
+
+bool HTMLInputElement::isChecked() const
+{
+ return checked() && m_inputType->isCheckable();
+}
+
+bool HTMLInputElement::hasSpinButton() const
+{
+ return m_inputType->hasSpinButton();
+}
+
+bool HTMLInputElement::supportsPlaceholder() const
+{
+ return isTextType();
+}
+
+CheckedRadioButtons& HTMLInputElement::checkedRadioButtons() const
+{
+ if (HTMLFormElement* formElement = form())
+ return formElement->checkedRadioButtons();
+ return document()->checkedRadioButtons();
+}
+
+void HTMLInputElement::handleBeforeTextInsertedEvent(Event* event)
+{
+ InputElement::handleBeforeTextInsertedEvent(m_data, this, this, event);
+}
+
+} // namespace