diff options
Diffstat (limited to 'Source/WebCore/html/HTMLInputElement.cpp')
-rw-r--r-- | Source/WebCore/html/HTMLInputElement.cpp | 1845 |
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, ¤tDecimalPlaces); + 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 |