summaryrefslogtreecommitdiffstats
path: root/WebCore/html/HTMLInputElement.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'WebCore/html/HTMLInputElement.cpp')
-rw-r--r--WebCore/html/HTMLInputElement.cpp337
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