summaryrefslogtreecommitdiff
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.cpp1845
1 files changed, 1845 insertions, 0 deletions
diff --git a/Source/WebCore/html/HTMLInputElement.cpp b/Source/WebCore/html/HTMLInputElement.cpp
new file mode 100644
index 000000000..46d1049bd
--- /dev/null
+++ b/Source/WebCore/html/HTMLInputElement.cpp
@@ -0,0 +1,1845 @@
+/*
+ * 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.
+ * Copyright (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
+ *
+ * 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 "BeforeTextInsertedEvent.h"
+#include "CSSPropertyNames.h"
+#include "CSSValueKeywords.h"
+#include "Document.h"
+#include "EventNames.h"
+#include "ExceptionCode.h"
+#include "FileList.h"
+#include "Frame.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 "NumberInputType.h"
+#include "RenderTextControlSingleLine.h"
+#include "RenderTheme.h"
+#include "SearchInputType.h"
+#include "ScriptEventListener.h"
+#include "WheelEvent.h"
+#include <wtf/MathExtras.h>
+#include <wtf/StdLibExtras.h>
+
+#if ENABLE(INPUT_COLOR)
+#include "ColorInputType.h"
+#endif
+
+#if ENABLE(INPUT_SPEECH)
+#include "RuntimeEnabledFeatures.h"
+#endif
+
+using namespace std;
+
+namespace WebCore {
+
+using namespace HTMLNames;
+
+// FIXME: According to HTML4, the length attribute's value can be arbitrarily
+// large. However, due to https://bugs.webkit.org/show_bug.cgi?id=14536 things
+// get rather sluggish when a text field has a larger number of characters than
+// this, even when just clicking in the text field.
+const int HTMLInputElement::maximumLength = 524288;
+const int defaultSize = 20;
+const int maxSavedResults = 256;
+
+HTMLInputElement::HTMLInputElement(const QualifiedName& tagName, Document* document, HTMLFormElement* form, bool createdByParser)
+ : HTMLTextFormControlElement(tagName, document, form)
+ , m_size(defaultSize)
+ , m_maxLength(maximumLength)
+ , 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_wasModifiedByUser(false)
+ , m_canReceiveDroppedFiles(false)
+ , m_inputType(InputType::createText(this))
+{
+ ASSERT(hasTagName(inputTag) || hasTagName(isindexTag));
+}
+
+PassRefPtr<HTMLInputElement> HTMLInputElement::create(const QualifiedName& tagName, Document* document, HTMLFormElement* form, bool createdByParser)
+{
+ RefPtr<HTMLInputElement> inputElement = adoptRef(new HTMLInputElement(tagName, document, form, createdByParser));
+ inputElement->createShadowSubtree();
+ return inputElement.release();
+}
+
+void HTMLInputElement::createShadowSubtree()
+{
+ m_inputType->createShadowSubtree();
+}
+
+HTMLInputElement::~HTMLInputElement()
+{
+ if (needsSuspensionCallback())
+ document()->unregisterForPageCacheSuspensionCallbacks(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_name.isNull() ? emptyAtom : m_name;
+}
+
+HTMLElement* HTMLInputElement::containerElement() const
+{
+ return m_inputType->containerElement();
+}
+
+HTMLElement* HTMLInputElement::innerTextElement() const
+{
+ return m_inputType->innerTextElement();
+}
+
+HTMLElement* HTMLInputElement::innerBlockElement() const
+{
+ return m_inputType->innerBlockElement();
+}
+
+HTMLElement* HTMLInputElement::innerSpinButtonElement() const
+{
+ return m_inputType->innerSpinButtonElement();
+}
+
+HTMLElement* HTMLInputElement::resultsButtonElement() const
+{
+ return m_inputType->resultsButtonElement();
+}
+
+HTMLElement* HTMLInputElement::cancelButtonElement() const
+{
+ return m_inputType->cancelButtonElement();
+}
+
+#if ENABLE(INPUT_SPEECH)
+HTMLElement* HTMLInputElement::speechButtonElement() const
+{
+ return m_inputType->speechButtonElement();
+}
+#endif
+
+HTMLElement* HTMLInputElement::placeholderElement() const
+{
+ return m_inputType->placeholderElement();
+}
+
+bool HTMLInputElement::shouldAutocomplete() const
+{
+ if (m_autocomplete != Uninitialized)
+ return m_autocomplete == On;
+ return HTMLTextFormControlElement::shouldAutocomplete();
+}
+
+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 {
+ typedef Document::FormElementListHashSet::const_iterator Iterator;
+ Iterator end = document()->formElements()->end();
+ for (Iterator it = document()->formElements()->begin(); it != end; ++it) {
+ HTMLFormControlElementWithState* control = *it;
+ if (control->formControlName() != name())
+ continue;
+ if (control->formControlType() != type())
+ continue;
+ if (control->form())
+ continue;
+ control->setNeedsValidityCheck();
+ }
+ }
+}
+
+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 or a value set by a script even if
+ // it is longer than maxLength.
+ if (!hasDirtyValue() || !m_wasModifiedByUser)
+ 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(RejectAny, step, 0);
+}
+
+bool HTMLInputElement::getAllowedValueStepWithDecimalPlaces(AnyStepHandling anyStepHandling, 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")) {
+ switch (anyStepHandling) {
+ case RejectAny:
+ return false;
+ case AnyIsDefaultStep:
+ *step = defaultStep * stepScaleFactor;
+ if (decimalPlaces)
+ *decimalPlaces = 0;
+ return true;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+ }
+
+ 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, AnyStepHandling anyStepHandling, bool sendChangeEvent, ExceptionCode& ec)
+{
+ double step;
+ unsigned stepDecimalPlaces, currentDecimalPlaces;
+ if (!getAllowedValueStepWithDecimalPlaces(anyStepHandling, &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();
+
+ const AtomicString& stepString = fastGetAttribute(stepAttr);
+ if (!equalIgnoringCase(stepString, "any"))
+ newValue = alignValueForStep(newValue, step, currentDecimalPlaces, stepDecimalPlaces);
+
+ if (newValue - m_inputType->maximum() > acceptableError) {
+ ec = INVALID_STATE_ERR;
+ return;
+ }
+ if (newValue > m_inputType->maximum())
+ newValue = m_inputType->maximum();
+
+ setValueAsNumber(newValue, ec, sendChangeEvent);
+
+ if (AXObjectCache::accessibilityEnabled())
+ document()->axObjectCache()->postNotification(renderer(), AXObjectCache::AXValueChanged, true);
+}
+
+double HTMLInputElement::alignValueForStep(double newValue, double step, unsigned currentDecimalPlaces, unsigned stepDecimalPlaces)
+{
+ if (newValue >= pow(10.0, 21.0))
+ return newValue;
+
+ unsigned baseDecimalPlaces;
+ double base = m_inputType->stepBaseWithDecimalPlaces(&baseDecimalPlaces);
+ baseDecimalPlaces = min(baseDecimalPlaces, 16u);
+ 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;
+ }
+
+ return newValue;
+}
+
+void HTMLInputElement::stepUp(int n, ExceptionCode& ec)
+{
+ bool sendChangeEvent = false;
+ applyStep(n, RejectAny, sendChangeEvent, ec);
+}
+
+void HTMLInputElement::stepDown(int n, ExceptionCode& ec)
+{
+ bool sendChangeEvent = false;
+ applyStep(-n, RejectAny, sendChangeEvent, ec);
+}
+
+bool HTMLInputElement::isKeyboardFocusable(KeyboardEvent* event) const
+{
+ if (isTextField())
+ return HTMLTextFormControlElement::isFocusable();
+ return HTMLTextFormControlElement::isKeyboardFocusable(event) && m_inputType->isKeyboardFocusable();
+}
+
+bool HTMLInputElement::isMouseFocusable() const
+{
+ if (isTextField())
+ return HTMLTextFormControlElement::isFocusable();
+ return HTMLTextFormControlElement::isMouseFocusable();
+}
+
+void HTMLInputElement::updateFocusAppearance(bool restorePreviousSelection)
+{
+ if (isTextField()) {
+ if (!restorePreviousSelection || !hasCachedSelection())
+ select();
+ else
+ restoreCachedSelection();
+ if (document()->frame())
+ document()->frame()->selection()->revealSelection();
+ } else
+ HTMLTextFormControlElement::updateFocusAppearance(restorePreviousSelection);
+}
+
+void HTMLInputElement::aboutToUnload()
+{
+ if (!isTextField() || !focused())
+ return;
+
+ Frame* frame = document()->frame();
+ if (!frame)
+ return;
+
+ frame->editor()->textFieldDidEndEditing(this);
+}
+
+bool HTMLInputElement::shouldUseInputMethod()
+{
+ return m_inputType->shouldUseInputMethod();
+}
+
+void HTMLInputElement::handleFocusEvent()
+{
+ if (!isTextField())
+ return;
+ if (isPasswordField() && document()->frame())
+ document()->setUseSecureKeyboardEntryWhenActive(true);
+}
+
+void HTMLInputElement::handleBlurEvent()
+{
+ m_inputType->handleBlurEvent();
+ if (!isTextField())
+ return;
+ Frame* frame = document()->frame();
+ if (!frame)
+ return;
+ if (isPasswordField())
+ document()->setUseSecureKeyboardEntryWhenActive(false);
+ frame->editor()->textFieldDidEndEditing(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())
+ removeAttribute(typeAttr);
+ else
+ setAttribute(typeAttr, type);
+}
+
+void HTMLInputElement::updateType()
+{
+ OwnPtr<InputType> newType = InputType::create(this, fastGetAttribute(typeAttr));
+ bool hadType = m_hasType;
+ m_hasType = true;
+ if (m_inputType->formControlType() == newType->formControlType())
+ return;
+
+ if (hadType && !newType->canChangeFromAnotherType()) {
+ // Set the attribute back to the old value.
+ // Useful in case we were called from inside parseMappedAttribute.
+ setAttribute(typeAttr, type());
+ return;
+ }
+
+ checkedRadioButtons().removeButton(this);
+
+ bool wasAttached = attached();
+ if (wasAttached)
+ detach();
+
+ bool didStoreValue = m_inputType->storesValueSeparateFromAttribute();
+ bool neededSuspensionCallback = needsSuspensionCallback();
+ bool didRespectHeightAndWidth = m_inputType->shouldRespectHeightAndWidthAttributes();
+
+ m_inputType->destroyShadowSubtree();
+ m_inputType = newType.release();
+ m_inputType->createShadowSubtree();
+
+ setNeedsWillValidateCheck();
+
+ bool willStoreValue = m_inputType->storesValueSeparateFromAttribute();
+
+ if (didStoreValue && !willStoreValue && hasDirtyValue()) {
+ setAttribute(valueAttr, m_valueIfDirty);
+ m_valueIfDirty = String();
+ }
+ if (!didStoreValue && willStoreValue)
+ m_valueIfDirty = sanitizeValue(fastGetAttribute(valueAttr));
+ else
+ updateValueIfNeeded();
+
+ setFormControlValueMatchesRenderer(false);
+ updateInnerTextValue();
+
+ m_wasModifiedByUser = false;
+
+ if (neededSuspensionCallback)
+ unregisterForSuspensionCallbackIfNeeded();
+ else
+ registerForSuspensionCallbackIfNeeded();
+
+ 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);
+ }
+
+ setChangedSinceLastFormControlChangeEvent(false);
+
+ checkedRadioButtons().addButton(this);
+
+ setNeedsValidityCheck();
+ notifyFormStateChanged();
+}
+
+void HTMLInputElement::updateInnerTextValue()
+{
+ if (!isTextField())
+ return;
+
+ if (!suggestedValue().isNull()) {
+ setInnerTextValue(suggestedValue());
+ updatePlaceholderVisibility(false);
+ } else if (!formControlValueMatchesRenderer()) {
+ // Update the renderer value if the formControlValueMatchesRenderer() flag is false.
+ // It protects an unacceptable renderer value from being overwritten with the DOM value.
+ setInnerTextValue(visibleValue());
+ updatePlaceholderVisibility(false);
+ }
+}
+
+void HTMLInputElement::subtreeHasChanged()
+{
+ ASSERT(isTextField());
+ ASSERT(renderer());
+ RenderTextControlSingleLine* renderTextControl = toRenderTextControlSingleLine(renderer());
+
+ bool wasChanged = wasChangedSinceLastFormControlChangeEvent();
+ setChangedSinceLastFormControlChangeEvent(true);
+
+ // We don't need to call sanitizeUserInputValue() function here because
+ // HTMLInputElement::handleBeforeTextInsertedEvent() has already called
+ // sanitizeUserInputValue().
+ // sanitizeValue() is needed because IME input doesn't dispatch BeforeTextInsertedEvent.
+ String value = innerTextValue();
+ if (isAcceptableValue(value))
+ setValueFromRenderer(sanitizeValue(convertFromVisibleValue(value)));
+ updatePlaceholderVisibility(false);
+ // Recalc for :invalid and hasUnacceptableValue() change.
+ setNeedsStyleRecalc();
+
+ if (cancelButtonElement())
+ renderTextControl->updateCancelButtonVisibility();
+
+ // If the incremental attribute is set, then dispatch the search event
+ if (searchEventsShouldBeDispatched() && isSearchField() && m_inputType)
+ static_cast<SearchInputType*>(m_inputType.get())->startSearchEventTimer();
+
+ if (!wasChanged && focused()) {
+ if (Frame* frame = document()->frame())
+ frame->editor()->textFieldDidBeginEditing(this);
+ }
+
+ if (focused()) {
+ if (Frame* frame = document()->frame())
+ frame->editor()->textDidChangeInTextField(this);
+ }
+ // When typing in an input field, childrenChanged is not called, so we need to force the directionality check.
+ if (isTextField())
+ calculateAndAdjustDirectionality();
+}
+
+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 HTMLTextFormControlElement::canStartSelection();
+}
+
+bool HTMLInputElement::canHaveSelection() const
+{
+ return isTextField();
+}
+
+void HTMLInputElement::accessKeyAction(bool sendMouseEvents)
+{
+ m_inputType->accessKeyAction(sendMouseEvents);
+}
+
+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_name = attr->value();
+ checkedRadioButtons().addButton(this);
+ HTMLTextFormControlElement::parseMappedAttribute(attr);
+ } else if (attr->name() == autocompleteAttr) {
+ if (equalIgnoringCase(attr->value(), "off")) {
+ m_autocomplete = Off;
+ registerForSuspensionCallbackIfNeeded();
+ } else {
+ bool needsToUnregister = m_autocomplete == Off;
+
+ if (attr->isEmpty())
+ m_autocomplete = Uninitialized;
+ else
+ m_autocomplete = On;
+
+ if (needsToUnregister)
+ unregisterForSuspensionCallbackIfNeeded();
+ }
+ } 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 (!hasDirtyValue()) {
+ updatePlaceholderVisibility(false);
+ 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)
+ parseMaxLengthAttribute(attr);
+ else if (attr->name() == sizeAttr) {
+ int oldSize = m_size;
+ int value = attr->value().toInt();
+ m_size = value > 0 ? value : defaultSize;
+ if (m_size != oldSize && renderer())
+ renderer()->setNeedsLayoutAndPrefWidthsRecalc();
+ } 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() == borderAttr && isImageButton()) {
+ applyBorderAttribute(attr);
+ } 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))
+ reattachIfAttached();
+ 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) {
+ m_inputType->multipleAttributeChanged();
+ setNeedsValidityCheck();
+ } else if (attr->name() == stepAttr) {
+ m_inputType->stepAttributeChanged();
+ setNeedsValidityCheck();
+ } else if (attr->name() == patternAttr || attr->name() == precisionAttr)
+ setNeedsValidityCheck();
+ else if (attr->name() == disabledAttr) {
+ m_inputType->disabledAttributeChanged();
+ HTMLTextFormControlElement::parseMappedAttribute(attr);
+ } else if (attr->name() == readonlyAttr) {
+ m_inputType->readonlyAttributeChanged();
+ HTMLTextFormControlElement::parseMappedAttribute(attr);
+ }
+#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();
+ m_inputType->destroyShadowSubtree();
+ m_inputType->createShadowSubtree();
+ attach();
+ } else {
+ m_inputType->destroyShadowSubtree();
+ m_inputType->createShadowSubtree();
+ }
+ setFormControlValueMatchesRenderer(false);
+ setNeedsStyleRecalc();
+ } else if (attr->name() == onwebkitspeechchangeAttr)
+ setAttributeEventListener(eventNames().webkitspeechchangeEvent, createAttributeEventListener(this, attr));
+#endif
+ else
+ HTMLTextFormControlElement::parseMappedAttribute(attr);
+ updateInnerTextValue();
+}
+
+void HTMLInputElement::finishParsingChildren()
+{
+ m_parsingInProgress = false;
+ HTMLTextFormControlElement::finishParsingChildren();
+ if (!m_stateRestored) {
+ bool checked = hasAttribute(checkedAttr);
+ if (checked)
+ setChecked(checked);
+ m_reflectsCheckedAttribute = true;
+ }
+}
+
+bool HTMLInputElement::rendererIsNeeded(const NodeRenderingContext& context)
+{
+ return m_inputType->rendererIsNeeded() && HTMLTextFormControlElement::rendererIsNeeded(context);
+}
+
+RenderObject* HTMLInputElement::createRenderer(RenderArena* arena, RenderStyle* style)
+{
+ return m_inputType->createRenderer(arena, style);
+}
+
+void HTMLInputElement::attach()
+{
+ suspendPostAttachCallbacks();
+
+ if (!m_hasType)
+ updateType();
+
+ HTMLTextFormControlElement::attach();
+
+ m_inputType->attach();
+
+ if (document()->focusedNode() == this)
+ document()->updateFocusAppearanceSoon(true /* restore selection */);
+
+ resumePostAttachCallbacks();
+}
+
+void HTMLInputElement::detach()
+{
+ HTMLTextFormControlElement::detach();
+ setFormControlValueMatchesRenderer(false);
+ m_inputType->detach();
+}
+
+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());
+
+ setAutofilled(false);
+ 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();
+ if (isRadioButton())
+ updateCheckedRadioButtons();
+ if (renderer() && renderer()->style()->hasAppearance())
+ renderer()->theme()->stateChanged(renderer(), CheckedState);
+ 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()->checkedStateChanged(renderer());
+
+ // 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()) {
+ setTextAsOfLastFormControlChangeEvent(String());
+ 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_size;
+}
+
+bool HTMLInputElement::sizeShouldIncludeDecoration(int& preferredSize) const
+{
+ return m_inputType->sizeShouldIncludeDecoration(defaultSize, preferredSize);
+}
+
+void HTMLInputElement::copyNonAttributeProperties(const Element* source)
+{
+ const HTMLInputElement* sourceElement = static_cast<const HTMLInputElement*>(source);
+
+ m_valueIfDirty = sourceElement->m_valueIfDirty;
+ m_wasModifiedByUser = false;
+ setChecked(sourceElement->m_isChecked);
+ m_reflectsCheckedAttribute = sourceElement->m_reflectsCheckedAttribute;
+ m_isIndeterminate = sourceElement->m_isIndeterminate;
+
+ HTMLTextFormControlElement::copyNonAttributeProperties(source);
+
+ setFormControlValueMatchesRenderer(false);
+ updateInnerTextValue();
+}
+
+String HTMLInputElement::value() const
+{
+ String value;
+ if (m_inputType->getTypeSpecificValue(value))
+ return value;
+
+ value = m_valueIfDirty;
+ 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_suggestedValue;
+}
+
+void HTMLInputElement::setSuggestedValue(const String& value)
+{
+ if (!m_inputType->canSetSuggestedValue())
+ return;
+ setFormControlValueMatchesRenderer(false);
+ m_suggestedValue = sanitizeValue(value);
+ setNeedsStyleRecalc();
+ updateInnerTextValue();
+}
+
+void HTMLInputElement::setValue(const String& value, bool sendChangeEvent)
+{
+ if (!m_inputType->canSetValue(value))
+ return;
+
+ RefPtr<HTMLInputElement> protector(this);
+ String sanitizedValue = sanitizeValue(value);
+ bool valueChanged = sanitizedValue != this->value();
+
+ setLastChangeWasNotUserEdit();
+ setFormControlValueMatchesRenderer(false);
+ m_suggestedValue = String(); // Prevent TextFieldInputType::setValue from using the suggested value.
+ m_inputType->setValue(sanitizedValue, valueChanged, sendChangeEvent);
+
+ if (!valueChanged)
+ return;
+
+ if (sendChangeEvent)
+ m_inputType->dispatchChangeEventInResponseToSetValue();
+
+ // FIXME: Why do we do this when !sendChangeEvent?
+ if (isTextField() && (!focused() || !sendChangeEvent))
+ setTextAsOfLastFormControlChangeEvent(value);
+
+ notifyFormStateChanged();
+}
+
+void HTMLInputElement::setValueInternal(const String& sanitizedValue, bool sendChangeEvent)
+{
+ m_valueIfDirty = sanitizedValue;
+ m_wasModifiedByUser = sendChangeEvent;
+ setNeedsValidityCheck();
+}
+
+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, bool sendChangeEvent)
+{
+ if (!isfinite(newValue)) {
+ ec = NOT_SUPPORTED_ERR;
+ return;
+ }
+ m_inputType->setValueAsNumber(newValue, sendChangeEvent, 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 never use this.
+ ASSERT(!isFileUpload());
+
+ m_suggestedValue = String();
+
+ // Renderer and our event handler are responsible for sanitizing values.
+ ASSERT(value == sanitizeValue(value) || sanitizeValue(value).isEmpty());
+
+ // Workaround for bug where trailing \n is included in the result of textContent.
+ // The assert macro above may also be simplified to: value == constrainValue(value)
+ // http://bugs.webkit.org/show_bug.cgi?id=9661
+ m_valueIfDirty = value == "\n" ? String("") : value;
+
+ setFormControlValueMatchesRenderer(true);
+ m_wasModifiedByUser = true;
+
+ // Input event is fired by the Node::defaultEventHandler for editable controls.
+ if (!isTextField())
+ dispatchInputEvent();
+ notifyFormStateChanged();
+
+ setNeedsValidityCheck();
+
+ // Clear autofill flag (and yellow background) on user edit.
+ setAutofilled(false);
+}
+
+void* HTMLInputElement::preDispatchEventHandler(Event* event)
+{
+ if (event->type() == eventNames().textInputEvent && m_inputType->shouldSubmitImplicitly(event)) {
+ event->stopPropagation();
+ return 0;
+ }
+ 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) {
+ HTMLTextFormControlElement::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();
+ }
+ // Form submission finishes editing, just as loss of focus does.
+ // If there was a change, send the event now.
+ if (wasChangedSinceLastFormControlChangeEvent())
+ dispatchFormControlChangeEvent();
+
+ 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->hasInterface(eventNames().interfaceForWheelEvent)) {
+ 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;
+ }
+
+ m_inputType->forwardEvent(evt);
+
+ if (!callBaseClassEarly && !evt->defaultHandled())
+ HTMLTextFormControlElement::defaultEventHandler(evt);
+}
+
+bool HTMLInputElement::isURLAttribute(Attribute *attr) const
+{
+ return attr->name() == srcAttr || attr->name() == formactionAttr || HTMLTextFormControlElement::isURLAttribute(attr);
+}
+
+String HTMLInputElement::defaultValue() const
+{
+ return fastGetAttribute(valueAttr);
+}
+
+void HTMLInputElement::setDefaultValue(const String &value)
+{
+ setAttribute(valueAttr, value);
+}
+
+void HTMLInputElement::setDefaultName(const AtomicString& name)
+{
+ m_name = name;
+}
+
+static inline bool isRFC2616TokenCharacter(UChar ch)
+{
+ return isASCII(ch) && ch > ' ' && ch != '"' && ch != '(' && ch != ')' && ch != ',' && ch != '/' && (ch < ':' || ch > '@') && (ch < '[' || ch > ']') && ch != '{' && ch != '}' && ch != 0x7f;
+}
+
+static inline bool isValidMIMEType(const String& type)
+{
+ size_t slashPosition = type.find('/');
+ if (slashPosition == notFound || !slashPosition || slashPosition == type.length() - 1)
+ return false;
+ for (size_t i = 0; i < type.length(); ++i) {
+ if (!isRFC2616TokenCharacter(type[i]) && i != slashPosition)
+ return false;
+ }
+ return true;
+}
+
+Vector<String> HTMLInputElement::acceptMIMETypes()
+{
+ Vector<String> mimeTypes;
+
+ String acceptString = accept();
+ if (acceptString.isEmpty())
+ return mimeTypes;
+
+ Vector<String> splitTypes;
+ acceptString.split(',', false, splitTypes);
+ for (size_t i = 0; i < splitTypes.size(); ++i) {
+ String trimmedMimeType = stripLeadingAndTrailingHTMLSpaces(splitTypes[i]);
+ if (trimmedMimeType.isEmpty())
+ continue;
+ if (!isValidMIMEType(trimmedMimeType))
+ continue;
+ mimeTypes.append(trimmedMimeType.lower());
+ }
+
+ return mimeTypes;
+}
+
+String HTMLInputElement::accept() const
+{
+ return fastGetAttribute(acceptAttr);
+}
+
+String HTMLInputElement::alt() const
+{
+ return fastGetAttribute(altAttr);
+}
+
+int HTMLInputElement::maxLength() const
+{
+ return m_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();
+}
+
+void HTMLInputElement::receiveDroppedFiles(const Vector<String>& filenames)
+{
+ m_inputType->receiveDroppedFiles(filenames);
+}
+
+Icon* HTMLInputElement::icon() const
+{
+ return m_inputType->icon();
+}
+
+bool HTMLInputElement::canReceiveDroppedFiles() const
+{
+ return m_canReceiveDroppedFiles;
+}
+
+void HTMLInputElement::setCanReceiveDroppedFiles(bool canReceiveDroppedFiles)
+{
+ if (m_canReceiveDroppedFiles == canReceiveDroppedFiles)
+ return;
+ m_canReceiveDroppedFiles = canReceiveDroppedFiles;
+ renderer()->updateFromElement();
+}
+
+String HTMLInputElement::visibleValue() const
+{
+ return m_inputType->visibleValue();
+}
+
+String HTMLInputElement::convertFromVisibleValue(const String& visibleValue) const
+{
+ return m_inputType->convertFromVisibleValue(visibleValue);
+}
+
+bool HTMLInputElement::isAcceptableValue(const String& proposedValue) const
+{
+ return m_inputType->isAcceptableValue(proposedValue);
+}
+
+String HTMLInputElement::sanitizeValue(const String& proposedValue) const
+{
+ if (proposedValue.isNull())
+ return proposedValue;
+ 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::needsSuspensionCallback()
+{
+ return m_autocomplete == Off || m_inputType->shouldResetOnDocumentActivation();
+}
+
+void HTMLInputElement::registerForSuspensionCallbackIfNeeded()
+{
+ if (needsSuspensionCallback())
+ document()->registerForPageCacheSuspensionCallbacks(this);
+}
+
+void HTMLInputElement::unregisterForSuspensionCallbackIfNeeded()
+{
+ if (!needsSuspensionCallback())
+ document()->unregisterForPageCacheSuspensionCallbacks(this);
+}
+
+bool HTMLInputElement::isRequiredFormControl() const
+{
+ return m_inputType->supportsRequired() && required();
+}
+
+void HTMLInputElement::addSearchResult()
+{
+ ASSERT(isSearchField());
+ if (renderer())
+ toRenderTextControlSingleLine(renderer())->addSearchResult();
+}
+
+void HTMLInputElement::onSearch()
+{
+ ASSERT(isSearchField());
+ if (m_inputType)
+ static_cast<SearchInputType*>(m_inputType.get())->stopSearchEventTimer();
+ dispatchEvent(Event::create(eventNames().searchEvent, true, false));
+}
+
+void HTMLInputElement::documentDidResumeFromPageCache()
+{
+ ASSERT(needsSuspensionCallback());
+ reset();
+}
+
+void HTMLInputElement::didMoveToNewDocument(Document* oldDocument)
+{
+ m_inputType->willMoveToNewOwnerDocument();
+ bool needsSuspensionCallback = this->needsSuspensionCallback();
+ if (oldDocument) {
+ // Always unregister for cache callbacks when leaving a document, even if we would otherwise like to be registered
+ if (needsSuspensionCallback)
+ oldDocument->unregisterForPageCacheSuspensionCallbacks(this);
+ oldDocument->checkedRadioButtons().removeButton(this);
+ }
+
+ if (needsSuspensionCallback)
+ document()->registerForPageCacheSuspensionCallbacks(this);
+
+ HTMLTextFormControlElement::didMoveToNewDocument(oldDocument);
+}
+
+void HTMLInputElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const
+{
+ HTMLTextFormControlElement::addSubresourceAttributeURLs(urls);
+
+ addSubresourceURL(urls, src());
+}
+
+bool HTMLInputElement::recalcWillValidate() const
+{
+ return m_inputType->supportsValidation() && HTMLTextFormControlElement::recalcWillValidate();
+}
+
+#if ENABLE(INPUT_COLOR)
+void HTMLInputElement::selectColorInColorChooser(const Color& color)
+{
+ if (!m_inputType->isColorControl())
+ return;
+ static_cast<ColorInputType*>(m_inputType.get())->didChooseColor(color);
+}
+#endif
+
+#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 = treeScope()->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)
+
+bool HTMLInputElement::isSteppable() const
+{
+ return m_inputType->isSteppable();
+}
+
+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(isSteppable());
+ if (!isSteppable())
+ return;
+ ASSERT(n);
+ if (!n)
+ return;
+
+ unsigned stepDecimalPlaces, baseDecimalPlaces;
+ double step, base;
+ // 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 (!getAllowedValueStepWithDecimalPlaces(AnyIsDefaultStep, &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);
+ const bool sendChangeEvent = true;
+ if (!isfinite(current)) {
+ ExceptionCode ec;
+ current = m_inputType->defaultValueForStepUp();
+ double nextDiff = step * n;
+ if (current < m_inputType->minimum() - nextDiff)
+ current = m_inputType->minimum() - nextDiff;
+ if (current > m_inputType->maximum() - nextDiff)
+ current = m_inputType->maximum() - nextDiff;
+ setValueAsNumber(current, ec, sendChangeEvent);
+ }
+ 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()), sendChangeEvent);
+ else {
+ ExceptionCode ec;
+ if (stepMismatch(value())) {
+ 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, n == 1 || n == -1);
+ current = newValue;
+ if (n > 1)
+ applyStep(n - 1, AnyIsDefaultStep, sendChangeEvent, ec);
+ else if (n < -1)
+ applyStep(n + 1, AnyIsDefaultStep, sendChangeEvent, ec);
+ } else
+ applyStep(n, AnyIsDefaultStep, sendChangeEvent, ec);
+ }
+}
+
+#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::isRangeControl() const
+{
+ return m_inputType->isRangeControl();
+}
+
+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::shouldAppearChecked() const
+{
+ return checked() && m_inputType->isCheckable();
+}
+
+bool HTMLInputElement::supportsPlaceholder() const
+{
+ return m_inputType->supportsPlaceholder();
+}
+
+void HTMLInputElement::updatePlaceholderText()
+{
+ return m_inputType->updatePlaceholderText();
+}
+
+CheckedRadioButtons& HTMLInputElement::checkedRadioButtons() const
+{
+ if (HTMLFormElement* formElement = form())
+ return formElement->checkedRadioButtons();
+ return document()->checkedRadioButtons();
+}
+
+void HTMLInputElement::parseMaxLengthAttribute(Attribute* attribute)
+{
+ int maxLength;
+ if (!parseHTMLInteger(attribute->value(), maxLength))
+ maxLength = maximumLength;
+ if (maxLength < 0 || maxLength > maximumLength)
+ maxLength = maximumLength;
+ int oldMaxLength = m_maxLength;
+ m_maxLength = maxLength;
+ if (oldMaxLength != maxLength)
+ updateValueIfNeeded();
+ setNeedsStyleRecalc();
+ setNeedsValidityCheck();
+}
+
+void HTMLInputElement::updateValueIfNeeded()
+{
+ String newValue = sanitizeValue(m_valueIfDirty);
+ ASSERT(!m_valueIfDirty.isNull() || newValue.isNull());
+ if (newValue != m_valueIfDirty)
+ setValue(newValue);
+}
+
+String HTMLInputElement::defaultToolTip() const
+{
+ return m_inputType->defaultToolTip();
+}
+
+} // namespace