diff options
Diffstat (limited to 'WebCore/html/HTMLInputElement.cpp')
-rw-r--r-- | WebCore/html/HTMLInputElement.cpp | 337 |
1 files changed, 237 insertions, 100 deletions
diff --git a/WebCore/html/HTMLInputElement.cpp b/WebCore/html/HTMLInputElement.cpp index 59e4e2f..5050237 100644 --- a/WebCore/html/HTMLInputElement.cpp +++ b/WebCore/html/HTMLInputElement.cpp @@ -34,14 +34,17 @@ #include "Event.h" #include "EventHandler.h" #include "EventNames.h" +#include "ExceptionCode.h" #include "File.h" #include "FileList.h" #include "FocusController.h" #include "FormDataList.h" #include "Frame.h" +#include "HTMLDataListElement.h" #include "HTMLFormElement.h" #include "HTMLImageLoader.h" #include "HTMLNames.h" +#include "HTMLOptionElement.h" #include "ScriptEventListener.h" #include "KeyboardEvent.h" #include "LocalizedStrings.h" @@ -60,6 +63,7 @@ #ifdef ANDROID_ACCEPT_CHANGES_TO_FOCUSED_TEXTFIELDS #include "WebViewCore.h" #endif +#include <wtf/MathExtras.h> #include <wtf/StdLibExtras.h> using namespace std; @@ -71,7 +75,7 @@ using namespace HTMLNames; const int maxSavedResults = 256; HTMLInputElement::HTMLInputElement(const QualifiedName& tagName, Document* doc, HTMLFormElement* f) - : HTMLFormControlElementWithState(tagName, doc, f) + : HTMLTextFormControlElement(tagName, doc, f) , m_xPos(0) , m_yPos(0) , m_maxResults(-1) @@ -138,6 +142,8 @@ bool HTMLInputElement::valueMissing() const return !checked(); case RADIO: return !document()->checkedRadioButtons().checkedButtonForGroup(name()); + case COLOR: + return false; case HIDDEN: case RANGE: case SUBMIT: @@ -166,6 +172,7 @@ bool HTMLInputElement::patternMismatch() const case BUTTON: case RANGE: case NUMBER: + case COLOR: return false; case TEXT: case SEARCH: @@ -192,6 +199,98 @@ bool HTMLInputElement::patternMismatch() const return false; } +bool HTMLInputElement::tooLong() const +{ + switch (inputType()) { + case EMAIL: + case PASSWORD: + case SEARCH: + case TELEPHONE: + case TEXT: + case URL: { + int max = maxLength(); + if (max < 0) + return false; + // Return false for the default value even if it is longer than maxLength. + bool userEdited = !m_data.value().isNull(); + if (!userEdited) + return false; + return value().length() > static_cast<unsigned>(max); + } + case BUTTON: + case CHECKBOX: + case COLOR: + case FILE: + case HIDDEN: + case IMAGE: + case ISINDEX: + case NUMBER: + case RADIO: + case RANGE: + case RESET: + case SUBMIT: + return false; + } + ASSERT_NOT_REACHED(); + return false; +} + +bool HTMLInputElement::rangeUnderflow() const +{ + if (inputType() == NUMBER) { + double min = 0.0; + double doubleValue = 0.0; + if (formStringToDouble(getAttribute(minAttr), &min) && formStringToDouble(value(), &doubleValue)) + return doubleValue < min; + } else if (inputType() == RANGE) { + double doubleValue; + if (formStringToDouble(value(), &doubleValue)) + return doubleValue < rangeMinimum(); + } + return false; +} + +bool HTMLInputElement::rangeOverflow() const +{ + if (inputType() == NUMBER) { + double max = 0.0; + double doubleValue = 0.0; + if (formStringToDouble(getAttribute(maxAttr), &max) && formStringToDouble(value(), &doubleValue)) + return doubleValue > max; + } else if (inputType() == RANGE) { + double doubleValue; + if (formStringToDouble(value(), &doubleValue)) + return doubleValue > rangeMaximum(); + } + return false; +} + +double HTMLInputElement::rangeMinimum() const +{ + ASSERT(inputType() == RANGE); + // The range type's "default minimum" is 0. + double min = 0.0; + formStringToDouble(getAttribute(minAttr), &min); + return min; +} + +double HTMLInputElement::rangeMaximum() const +{ + ASSERT(inputType() == RANGE); + // The range type's "default maximum" is 100. + static const double defaultMaximum = 100.0; + double max = defaultMaximum; + formStringToDouble(getAttribute(maxAttr), &max); + const double min = rangeMinimum(); + + if (max < min) { + // A remedy for the inconsistent min/max values. + // Sets the maxmimum to the default (100.0) or the minimum value. + max = min < defaultMaximum ? defaultMaximum : min; + } + return max; +} + static inline CheckedRadioButtons& checkedRadioButtons(const HTMLInputElement *element) { if (HTMLFormElement* form = element->form()) @@ -254,20 +353,17 @@ bool HTMLInputElement::shouldUseInputMethod() const return m_type == TEXT || m_type == SEARCH || m_type == ISINDEX; } -void HTMLInputElement::dispatchFocusEvent() +void HTMLInputElement::handleFocusEvent() { - InputElement::dispatchFocusEvent(m_data, this, this); + InputElement::dispatchFocusEvent(this, this); if (isTextField()) m_autofilled = false; - - HTMLFormControlElementWithState::dispatchFocusEvent(); } -void HTMLInputElement::dispatchBlurEvent() +void HTMLInputElement::handleBlurEvent() { - InputElement::dispatchBlurEvent(m_data, this, this); - HTMLFormControlElementWithState::dispatchBlurEvent(); + InputElement::dispatchBlurEvent(this, this); } void HTMLInputElement::setType(const String& t) @@ -325,6 +421,8 @@ void HTMLInputElement::setInputType(const String& t) newType = TELEPHONE; else if (equalIgnoringCase(t, "url")) newType = URL; + else if (equalIgnoringCase(t, "color")) + newType = COLOR; else newType = TEXT; @@ -359,7 +457,7 @@ void HTMLInputElement::setInputType(const String& t) m_data.setValue(String()); } if (!didStoreValue && willStoreValue) - m_data.setValue(constrainValue(getAttribute(valueAttr))); + m_data.setValue(sanitizeValue(getAttribute(valueAttr))); else InputElement::updateValueIfNeeded(m_data, this); @@ -389,6 +487,7 @@ void HTMLInputElement::setInputType(const String& t) } InputElement::notifyFormStateChanged(this); + updateValidity(); } m_haveType = true; @@ -408,6 +507,10 @@ const AtomicString& HTMLInputElement::formControlType() const DEFINE_STATIC_LOCAL(const AtomicString, checkbox, ("checkbox")); return checkbox; } + case COLOR: { + DEFINE_STATIC_LOCAL(const AtomicString, color, ("color")); + return color; + } case EMAIL: { DEFINE_STATIC_LOCAL(const AtomicString, email, ("email")); return email; @@ -477,6 +580,7 @@ bool HTMLInputElement::saveFormControlState(String& result) const switch (inputType()) { case BUTTON: + case COLOR: case EMAIL: case FILE: case HIDDEN: @@ -508,6 +612,7 @@ void HTMLInputElement::restoreFormControlState(const String& state) ASSERT(inputType() != PASSWORD); // should never save/restore password fields switch (inputType()) { case BUTTON: + case COLOR: case EMAIL: case FILE: case HIDDEN: @@ -544,63 +649,6 @@ bool HTMLInputElement::canHaveSelection() const return isTextField(); } -int HTMLInputElement::selectionStart() const -{ - if (!isTextField()) - return 0; - if (document()->focusedNode() != this && m_data.cachedSelectionStart() != -1) - return m_data.cachedSelectionStart(); - if (!renderer()) - return 0; - return toRenderTextControl(renderer())->selectionStart(); -} - -int HTMLInputElement::selectionEnd() const -{ - if (!isTextField()) - return 0; - if (document()->focusedNode() != this && m_data.cachedSelectionEnd() != -1) - return m_data.cachedSelectionEnd(); - if (!renderer()) - return 0; - return toRenderTextControl(renderer())->selectionEnd(); -} - -static bool isTextFieldWithRenderer(HTMLInputElement* element) -{ - if (!element->isTextField()) - return false; - - element->document()->updateLayoutIgnorePendingStylesheets(); - if (!element->renderer()) - return false; - - return true; -} - -void HTMLInputElement::setSelectionStart(int start) -{ - if (isTextFieldWithRenderer(this)) - toRenderTextControl(renderer())->setSelectionStart(start); -} - -void HTMLInputElement::setSelectionEnd(int end) -{ - if (isTextFieldWithRenderer(this)) - toRenderTextControl(renderer())->setSelectionEnd(end); -} - -void HTMLInputElement::select() -{ - if (isTextFieldWithRenderer(this)) - toRenderTextControl(renderer())->select(); -} - -void HTMLInputElement::setSelectionRange(int start, int end) -{ - InputElement::updateSelectionRange(this, this, start, end); -} - void HTMLInputElement::accessKeyAction(bool sendToAnyElement) { switch (inputType()) { @@ -619,6 +667,7 @@ void HTMLInputElement::accessKeyAction(bool sendToAnyElement) case HIDDEN: // a no-op for this type break; + case COLOR: case EMAIL: case ISINDEX: case NUMBER: @@ -659,6 +708,7 @@ void HTMLInputElement::parseMappedAttribute(MappedAttribute *attr) checkedRadioButtons(this).removeButton(this); m_data.setName(attr->value()); checkedRadioButtons(this).addButton(this); + HTMLFormControlElementWithState::parseMappedAttribute(attr); } else if (attr->name() == autocompleteAttr) { if (equalIgnoringCase(attr->value(), "off")) { m_autocomplete = Off; @@ -678,12 +728,14 @@ void HTMLInputElement::parseMappedAttribute(MappedAttribute *attr) if (m_data.value().isNull()) setNeedsStyleRecalc(); setFormControlValueMatchesRenderer(false); + updateValidity(); } else if (attr->name() == checkedAttr) { m_defaultChecked = !attr->isNull(); if (m_useDefaultChecked) { setChecked(m_defaultChecked); m_useDefaultChecked = true; } + updateValidity(); } else if (attr->name() == maxlengthAttr) InputElement::parseMaxLengthAttribute(m_data, this, this, attr); else if (attr->name() == sizeAttr) @@ -715,14 +767,6 @@ void HTMLInputElement::parseMappedAttribute(MappedAttribute *attr) } else if (attr->name() == heightAttr) { if (respectHeightAndWidthAttrs()) addCSSLength(attr, CSSPropertyHeight, attr->value()); - } else if (attr->name() == onfocusAttr) { - setAttributeEventListener(eventNames().focusEvent, createAttributeEventListener(this, attr)); - } else if (attr->name() == onblurAttr) { - setAttributeEventListener(eventNames().blurEvent, createAttributeEventListener(this, attr)); - } else if (attr->name() == onselectAttr) { - setAttributeEventListener(eventNames().selectEvent, createAttributeEventListener(this, attr)); - } else if (attr->name() == onchangeAttr) { - setAttributeEventListener(eventNames().changeEvent, createAttributeEventListener(this, attr)); } // Search field and slider attributes all just cause updateFromElement to be called through style // recalcing. @@ -730,7 +774,7 @@ void HTMLInputElement::parseMappedAttribute(MappedAttribute *attr) setAttributeEventListener(eventNames().searchEvent, createAttributeEventListener(this, attr)); } else if (attr->name() == resultsAttr) { int oldResults = m_maxResults; - m_maxResults = !attr->isNull() ? min(attr->value().toInt(), maxSavedResults) : -1; + m_maxResults = !attr->isNull() ? std::min(attr->value().toInt(), maxSavedResults) : -1; // FIXME: Detaching just for maxResults change is not ideal. We should figure out the right // time to relayout for this change. if (m_maxResults != oldResults && (m_maxResults <= 0 || oldResults <= 0) && attached()) { @@ -738,9 +782,6 @@ void HTMLInputElement::parseMappedAttribute(MappedAttribute *attr) attach(); } setNeedsStyleRecalc(); - } else if (attr->name() == placeholderAttr) { - if (isTextField()) - updatePlaceholderVisibility(); } else if (attr->name() == autosaveAttr || attr->name() == incrementalAttr || attr->name() == minAttr || @@ -748,8 +789,15 @@ void HTMLInputElement::parseMappedAttribute(MappedAttribute *attr) attr->name() == multipleAttr || attr->name() == precisionAttr) setNeedsStyleRecalc(); + else if (attr->name() == patternAttr) + updateValidity(); +#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 else - HTMLFormControlElementWithState::parseMappedAttribute(attr); + HTMLTextFormControlElement::parseMappedAttribute(attr); } bool HTMLInputElement::rendererIsNeeded(RenderStyle *style) @@ -757,6 +805,7 @@ bool HTMLInputElement::rendererIsNeeded(RenderStyle *style) switch (inputType()) { case BUTTON: case CHECKBOX: + case COLOR: case EMAIL: case FILE: case IMAGE: @@ -797,6 +846,7 @@ RenderObject *HTMLInputElement::createRenderer(RenderArena *arena, RenderStyle * return new (arena) RenderImage(this); case RANGE: return new (arena) RenderSlider(this); + case COLOR: case EMAIL: case ISINDEX: case NUMBER: @@ -805,7 +855,7 @@ RenderObject *HTMLInputElement::createRenderer(RenderArena *arena, RenderStyle * case TELEPHONE: case TEXT: case URL: - return new (arena) RenderTextControlSingleLine(this); + return new (arena) RenderTextControlSingleLine(this, placeholderShouldBeVisible()); } ASSERT(false); return 0; @@ -883,6 +933,7 @@ bool HTMLInputElement::appendFormData(FormDataList& encoding, bool multipart) return false; switch (inputType()) { + case COLOR: case EMAIL: case HIDDEN: case ISINDEX: @@ -990,7 +1041,7 @@ void HTMLInputElement::setChecked(bool nowChecked, bool sendChangeEvent) // RenderTextView), but it's not possible to do it at the moment // because of the way the code is structured. if (renderer() && AXObjectCache::accessibilityEnabled()) - renderer()->document()->axObjectCache()->postNotification(renderer(), "AXCheckedStateChanged", true); + renderer()->document()->axObjectCache()->postNotification(renderer(), AXObjectCache::AXCheckedStateChanged, true); // Only send a change event for items in the document (avoid firing during // parsing) and don't send a change event for a radio button that's getting @@ -1043,7 +1094,7 @@ String HTMLInputElement::value() const String value = m_data.value(); if (value.isNull()) { - value = constrainValue(getAttribute(valueAttr)); + value = sanitizeValue(getAttribute(valueAttr)); // If no attribute exists, then just use "on" or "" based off the checked() state of the control. if (value.isNull() && (inputType() == CHECKBOX || inputType() == RADIO)) @@ -1060,6 +1111,7 @@ String HTMLInputElement::valueWithDefault() const switch (inputType()) { case BUTTON: case CHECKBOX: + case COLOR: case EMAIL: case FILE: case HIDDEN: @@ -1098,9 +1150,9 @@ void HTMLInputElement::setValue(const String& value) if (inputType() == FILE) m_fileList->clear(); else { - m_data.setValue(constrainValue(value)); + m_data.setValue(sanitizeValue(value)); if (isTextField()) { - InputElement::updatePlaceholderVisibility(m_data, this, this); + updatePlaceholderVisibility(false); if (inDocument()) document()->updateStyleIfNeeded(); } @@ -1109,7 +1161,7 @@ void HTMLInputElement::setValue(const String& value) renderer()->updateFromElement(); setNeedsStyleRecalc(); } else - setAttribute(valueAttr, constrainValue(value)); + setAttribute(valueAttr, sanitizeValue(value)); if (isTextField()) { unsigned max = m_data.value().length(); @@ -1123,6 +1175,7 @@ void HTMLInputElement::setValue(const String& value) cacheSelection(max, max); } InputElement::notifyFormStateChanged(this); + updateValidity(); } String HTMLInputElement::placeholder() const @@ -1144,7 +1197,9 @@ void HTMLInputElement::setValueFromRenderer(const String& value) { // File upload controls will always use setFileListFromRenderer. ASSERT(inputType() != FILE); + updatePlaceholderVisibility(false); InputElement::setValueFromRenderer(m_data, this, this, value); + updateValidity(); } void HTMLInputElement::setFileListFromRenderer(const Vector<String>& paths) @@ -1156,6 +1211,7 @@ void HTMLInputElement::setFileListFromRenderer(const Vector<String>& paths) setFormControlValueMatchesRenderer(true); InputElement::notifyFormStateChanged(this); + updateValidity(); } bool HTMLInputElement::storesValueSeparateFromAttribute() const @@ -1169,6 +1225,7 @@ bool HTMLInputElement::storesValueSeparateFromAttribute() const case RESET: case SUBMIT: return false; + case COLOR: case EMAIL: case FILE: case ISINDEX: @@ -1344,6 +1401,7 @@ void HTMLInputElement::defaultEventHandler(Event* evt) if (charCode == '\r') { switch (inputType()) { case CHECKBOX: + case COLOR: case EMAIL: case HIDDEN: case ISINDEX: @@ -1472,6 +1530,7 @@ void HTMLInputElement::defaultEventHandler(Event* evt) if (!checked()) clickElement = true; break; + case COLOR: case EMAIL: case HIDDEN: case ISINDEX: @@ -1516,7 +1575,7 @@ void HTMLInputElement::defaultEventHandler(Event* evt) } if (evt->isBeforeTextInsertedEvent()) - InputElement::handleBeforeTextInsertedEvent(m_data, this, document(), evt); + InputElement::handleBeforeTextInsertedEvent(m_data, this, this, evt); if (isTextField() && renderer() && (evt->isMouseEvent() || evt->isDragEvent() || evt->isWheelEvent() || evt->type() == eventNames().blurEvent || evt->type() == eventNames().focusEvent)) toRenderTextControlSingleLine(renderer())->forwardEvent(evt); @@ -1603,9 +1662,12 @@ int HTMLInputElement::maxLength() const return m_data.maxLength(); } -void HTMLInputElement::setMaxLength(int _maxLength) +void HTMLInputElement::setMaxLength(int maxLength, ExceptionCode& ec) { - setAttribute(maxlengthAttr, String::number(_maxLength)); + if (maxLength < 0) + ec = INDEX_SIZE_ERR; + else + setAttribute(maxlengthAttr, String::number(maxLength)); } bool HTMLInputElement::multiple() const @@ -1659,9 +1721,11 @@ FileList* HTMLInputElement::files() return m_fileList.get(); } -String HTMLInputElement::constrainValue(const String& proposedValue) const +String HTMLInputElement::sanitizeValue(const String& proposedValue) const { - return InputElement::constrainValue(this, proposedValue, m_data.maxLength()); + if (isTextField()) + return InputElement::sanitizeValue(this, proposedValue); + return proposedValue; } bool HTMLInputElement::needsActivationCallback() @@ -1704,6 +1768,7 @@ bool HTMLInputElement::isRequiredFormControl() const case IMAGE: case RESET: case BUTTON: + case COLOR: case ISINDEX: return false; } @@ -1730,14 +1795,7 @@ void HTMLInputElement::onSearch() ASSERT(isSearchField()); if (renderer()) toRenderTextControlSingleLine(renderer())->stopSearchEventTimer(); - dispatchEvent(eventNames().searchEvent, true, false); -} - -VisibleSelection HTMLInputElement::selection() const -{ - if (!renderer() || !isTextField() || m_data.cachedSelectionStart() == -1 || m_data.cachedSelectionEnd() == -1) - return VisibleSelection(); - return toRenderTextControl(renderer())->selection(m_data.cachedSelectionStart(), m_data.cachedSelectionEnd()); + dispatchEvent(Event::create(eventNames().searchEvent, true, false)); } void HTMLInputElement::documentDidBecomeActive() @@ -1778,9 +1836,88 @@ bool HTMLInputElement::willValidate() const inputType() != BUTTON && inputType() != RESET; } -bool HTMLInputElement::placeholderShouldBeVisible() const +bool HTMLInputElement::formStringToDouble(const String& src, double* out) { - return m_data.placeholderShouldBeVisible(); + // See HTML5 2.4.4.3 `Real numbers.' + + if (src.isEmpty()) + return false; + // String::toDouble() accepts leading + \t \n \v \f \r and SPACE, which are invalid in HTML5. + // So, check the first character. + if (src[0] != '-' && (src[0] < '0' || src[0] > '9')) + return false; + + bool valid = false; + double value = src.toDouble(&valid); + if (!valid) + return false; + // NaN and Infinity are not valid numbers according to the standard. + if (isnan(value) || isinf(value)) + return false; + if (out) + *out = value; + return true; +} + +#if ENABLE(DATALIST) +HTMLElement* HTMLInputElement::list() const +{ + return dataList(); +} + +HTMLDataListElement* HTMLInputElement::dataList() const +{ + if (!m_hasNonEmptyList) + return 0; + + switch (inputType()) { + case TEXT: + case SEARCH: + case URL: + case TELEPHONE: + case EMAIL: + case NUMBER: + case RANGE: + case COLOR: { + Element* element = document()->getElementById(getAttribute(listAttr)); + if (element && element->hasTagName(datalistTag)) + return static_cast<HTMLDataListElement*>(element); + break; + } + case HIDDEN: + case PASSWORD: + case CHECKBOX: + case RADIO: + case FILE: + case SUBMIT: + case IMAGE: + case RESET: + case BUTTON: + case ISINDEX: + break; + } + return 0; +} + +HTMLOptionElement* HTMLInputElement::selectedOption() const +{ + String currentValue = value(); + // The empty value never matches to a datalist option because it + // doesn't represent a suggestion according to the standard. + if (currentValue.isEmpty()) + return 0; + + HTMLDataListElement* sourceElement = dataList(); + if (!sourceElement) + return 0; + RefPtr<HTMLCollection> options = sourceElement->options(); + for (unsigned i = 0; options && i < options->length(); ++i) { + HTMLOptionElement* option = static_cast<HTMLOptionElement*>(options->item(i)); + if (!option->disabled() && currentValue == option->value()) + return option; + } + return 0; } +#endif // ENABLE(DATALIST) } // namespace |