diff options
Diffstat (limited to 'src/3rdparty/webkit/WebCore/html/HTMLInputElement.cpp')
-rw-r--r-- | src/3rdparty/webkit/WebCore/html/HTMLInputElement.cpp | 1431 |
1 files changed, 1142 insertions, 289 deletions
diff --git a/src/3rdparty/webkit/WebCore/html/HTMLInputElement.cpp b/src/3rdparty/webkit/WebCore/html/HTMLInputElement.cpp index 652bc40..6a214ab 100644 --- a/src/3rdparty/webkit/WebCore/html/HTMLInputElement.cpp +++ b/src/3rdparty/webkit/WebCore/html/HTMLInputElement.cpp @@ -2,7 +2,7 @@ * 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 Apple Inc. All rights reserved. + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. * (C) 2006 Alexey Proskuryakov (ap@nypop.com) * Copyright (C) 2007 Samuel Weinig (sam@webkit.org) * @@ -29,6 +29,7 @@ #include "AXObjectCache.h" #include "CSSPropertyNames.h" #include "ChromeClient.h" +#include "DateComponents.h" #include "Document.h" #include "Editor.h" #include "Event.h" @@ -59,9 +60,13 @@ #include "RenderText.h" #include "RenderTextControlSingleLine.h" #include "RenderTheme.h" +#include "StepRange.h" +#include "StringHash.h" #include "TextEvent.h" +#include <wtf/HashMap.h> #include <wtf/MathExtras.h> #include <wtf/StdLibExtras.h> +#include <wtf/dtoa.h> using namespace std; @@ -71,6 +76,45 @@ using namespace HTMLNames; const int maxSavedResults = 256; +// Constant values for getAllowedValueStep(). +static const double dateDefaultStep = 1.0; +static const double dateStepScaleFactor = 86400000.0; +static const double dateTimeDefaultStep = 60.0; +static const double dateTimeStepScaleFactor = 1000.0; +static const double monthDefaultStep = 1.0; +static const double monthStepScaleFactor = 1.0; +static const double numberDefaultStep = 1.0; +static const double numberStepScaleFactor = 1.0; +static const double timeDefaultStep = 60.0; +static const double timeStepScaleFactor = 1000.0; +static const double weekDefaultStep = 1.0; +static const double weekStepScaleFactor = 604800000.0; + +// Constant values for minimum(). +static const double dateDefaultMinimum = -12219292800000.0; // This means 1582-10-15T00:00Z. +static const double dateTimeDefaultMinimum = -12219292800000.0; // ditto. +static const double monthDefaultMinimum = (1582.0 - 1970) * 12 + 10 - 1; // 1582-10 +static const double numberDefaultMinimum = -DBL_MAX; +static const double rangeDefaultMinimum = 0.0; +static const double timeDefaultMinimum = 0.0; // 00:00:00.000 +static const double weekDefaultMinimum = -12212380800000.0; // 1583-01-03, the first Monday of 1583. + +// Constant values for maximum(). +static const double dateDefaultMaximum = DBL_MAX; +static const double dateTimeDefaultMaximum = DBL_MAX; +// DateComponents::m_year can't represent a year greater than INT_MAX. +static const double monthDefaultMaximum = (INT_MAX - 1970) * 12.0 + 12 - 1; +static const double numberDefaultMaximum = DBL_MAX; +static const double rangeDefaultMaximum = 100.0; +static const double timeDefaultMaximum = 86399999.0; // 23:59:59.999 +static const double weekDefaultMaximum = DBL_MAX; + +static const double defaultStepBase = 0.0; +static const double weekDefaultStepBase = -259200000.0; // The first day of 1970-W01. + +static const double msecPerMinute = 60 * 1000; +static const double msecPerSecond = 1000; + HTMLInputElement::HTMLInputElement(const QualifiedName& tagName, Document* doc, HTMLFormElement* f) : HTMLTextFormControlElement(tagName, doc, f) , m_xPos(0) @@ -120,34 +164,47 @@ bool HTMLInputElement::autoComplete() const return true; } +static inline CheckedRadioButtons& checkedRadioButtons(const HTMLInputElement* element) +{ + if (HTMLFormElement* form = element->form()) + return form->checkedRadioButtons(); + return element->document()->checkedRadioButtons(); +} + bool HTMLInputElement::valueMissing() const { if (!isRequiredFormControl() || readOnly() || disabled()) return false; switch (inputType()) { - case TEXT: - case SEARCH: - case URL: - case TELEPHONE: + case DATE: + case DATETIME: + case DATETIMELOCAL: case EMAIL: - case PASSWORD: - case NUMBER: case FILE: + case MONTH: + case NUMBER: + case PASSWORD: + case SEARCH: + case TELEPHONE: + case TEXT: + case TIME: + case URL: + case WEEK: return value().isEmpty(); case CHECKBOX: return !checked(); case RADIO: - return !document()->checkedRadioButtons().checkedButtonForGroup(name()); + return !checkedRadioButtons(this).checkedButtonForGroup(name()); case COLOR: return false; + case BUTTON: case HIDDEN: - case RANGE: - case SUBMIT: case IMAGE: - case RESET: - case BUTTON: case ISINDEX: + case RANGE: + case RESET: + case SUBMIT: break; } @@ -158,25 +215,31 @@ bool HTMLInputElement::valueMissing() const bool HTMLInputElement::patternMismatch() const { switch (inputType()) { - case ISINDEX: + case BUTTON: case CHECKBOX: - case RADIO: - case SUBMIT: - case RESET: + case COLOR: + case DATE: + case DATETIME: + case DATETIMELOCAL: case FILE: case HIDDEN: case IMAGE: - case BUTTON: - case RANGE: + case ISINDEX: + case MONTH: case NUMBER: - case COLOR: + case RADIO: + case RANGE: + case RESET: + case SUBMIT: + case TIME: + case WEEK: return false; - case TEXT: - case SEARCH: - case URL: - case TELEPHONE: case EMAIL: case PASSWORD: + case SEARCH: + case TELEPHONE: + case TEXT: + case URL: const AtomicString& pattern = getAttribute(patternAttr); String value = this->value(); @@ -212,20 +275,26 @@ bool HTMLInputElement::tooLong() const bool userEdited = !m_data.value().isNull(); if (!userEdited) return false; - return value().length() > static_cast<unsigned>(max); + return value().numGraphemeClusters() > static_cast<unsigned>(max); } case BUTTON: case CHECKBOX: case COLOR: + case DATE: + case DATETIME: + case DATETIMELOCAL: case FILE: case HIDDEN: case IMAGE: case ISINDEX: + case MONTH: case NUMBER: case RADIO: case RANGE: case RESET: case SUBMIT: + case TIME: + case WEEK: return false; } ASSERT_NOT_REACHED(); @@ -234,66 +303,390 @@ bool HTMLInputElement::tooLong() const 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(); + const double nan = numeric_limits<double>::quiet_NaN(); + switch (inputType()) { + case DATE: + case DATETIME: + case DATETIMELOCAL: + case MONTH: + case NUMBER: + case TIME: + case WEEK: { + double doubleValue = parseToDouble(value(), nan); + return isfinite(doubleValue) && doubleValue < minimum(); + } + case RANGE: // Guaranteed by sanitization. + ASSERT(parseToDouble(value(), nan) >= minimum()); + case BUTTON: + case CHECKBOX: + case COLOR: + case EMAIL: + case FILE: + case HIDDEN: + case IMAGE: + case ISINDEX: + case PASSWORD: + case RADIO: + case RESET: + case SEARCH: + case SUBMIT: + case TELEPHONE: + case TEXT: + case URL: + break; } 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) { + const double nan = numeric_limits<double>::quiet_NaN(); + switch (inputType()) { + case DATE: + case DATETIME: + case DATETIMELOCAL: + case MONTH: + case NUMBER: + case TIME: + case WEEK: { + double doubleValue = parseToDouble(value(), nan); + return isfinite(doubleValue) && doubleValue > maximum(); + } + case RANGE: // Guaranteed by sanitization. + ASSERT(parseToDouble(value(), nan) <= maximum()); + case BUTTON: + case CHECKBOX: + case COLOR: + case EMAIL: + case FILE: + case HIDDEN: + case IMAGE: + case ISINDEX: + case PASSWORD: + case RADIO: + case RESET: + case SEARCH: + case SUBMIT: + case TELEPHONE: + case TEXT: + case URL: + break; + } + return false; +} + +double HTMLInputElement::minimum() const +{ + switch (inputType()) { + case DATE: + return parseToDouble(getAttribute(minAttr), dateDefaultMinimum); + case DATETIME: + case DATETIMELOCAL: + return parseToDouble(getAttribute(minAttr), dateTimeDefaultMinimum); + case MONTH: + return parseToDouble(getAttribute(minAttr), monthDefaultMinimum); + case NUMBER: + return parseToDouble(getAttribute(minAttr), numberDefaultMinimum); + case RANGE: + return parseToDouble(getAttribute(minAttr), rangeDefaultMinimum); + case TIME: + return parseToDouble(getAttribute(minAttr), timeDefaultMinimum); + case WEEK: + return parseToDouble(getAttribute(minAttr), weekDefaultMinimum); + case BUTTON: + case CHECKBOX: + case COLOR: + case EMAIL: + case FILE: + case HIDDEN: + case IMAGE: + case ISINDEX: + case PASSWORD: + case RADIO: + case RESET: + case SEARCH: + case SUBMIT: + case TELEPHONE: + case TEXT: + case URL: + break; + } + ASSERT_NOT_REACHED(); + return 0; +} + +double HTMLInputElement::maximum() const +{ + switch (inputType()) { + case DATE: + return parseToDouble(getAttribute(maxAttr), dateDefaultMaximum); + case DATETIME: + case DATETIMELOCAL: + return parseToDouble(getAttribute(maxAttr), dateTimeDefaultMaximum); + case MONTH: + return parseToDouble(getAttribute(maxAttr), monthDefaultMaximum); + case NUMBER: + return parseToDouble(getAttribute(maxAttr), numberDefaultMaximum); + case RANGE: { + double max = parseToDouble(getAttribute(maxAttr), rangeDefaultMaximum); + // A remedy for the inconsistent min/max values for RANGE. + // Sets the maximum to the default or the minimum value. + double min = minimum(); + if (max < min) + max = std::max(min, rangeDefaultMaximum); + return max; + } + case TIME: + return parseToDouble(getAttribute(maxAttr), timeDefaultMaximum); + case WEEK: + return parseToDouble(getAttribute(maxAttr), weekDefaultMaximum); + case BUTTON: + case CHECKBOX: + case COLOR: + case EMAIL: + case FILE: + case HIDDEN: + case IMAGE: + case ISINDEX: + case PASSWORD: + case RADIO: + case RESET: + case SEARCH: + case SUBMIT: + case TELEPHONE: + case TEXT: + case URL: + break; + } + ASSERT_NOT_REACHED(); + return 0; +} + +double HTMLInputElement::stepBase() const +{ + switch (inputType()) { + case RANGE: + return minimum(); + case DATE: + case DATETIME: + case DATETIMELOCAL: + case MONTH: + case NUMBER: + case TIME: + return parseToDouble(getAttribute(minAttr), defaultStepBase); + case WEEK: + return parseToDouble(getAttribute(minAttr), weekDefaultStepBase); + case BUTTON: + case CHECKBOX: + case COLOR: + case EMAIL: + case FILE: + case HIDDEN: + case IMAGE: + case ISINDEX: + case PASSWORD: + case RADIO: + case RESET: + case SEARCH: + case SUBMIT: + case TELEPHONE: + case TEXT: + case URL: + break; + } + ASSERT_NOT_REACHED(); + return 0.0; +} + +bool HTMLInputElement::stepMismatch() const +{ + double step; + if (!getAllowedValueStep(&step)) + return false; + switch (inputType()) { + case RANGE: + // stepMismatch doesn't occur for RANGE. RenderSlider guarantees the + // value matches to step on user input, and sanitation takes care + // of the general case. + return false; + case NUMBER: { double doubleValue; - if (formStringToDouble(value(), &doubleValue)) - return doubleValue > rangeMaximum(); + if (!parseToDoubleForNumberType(value(), &doubleValue)) + return false; + doubleValue = fabs(doubleValue - stepBase()); + if (isinf(doubleValue)) + return false; + // double's fractional part size is DBL_MAN_DIG-bit. If the current + // value is greater than step*2^DBL_MANT_DIG, the following fmod() makes + // no sense. + if (doubleValue / pow(2.0, DBL_MANT_DIG) > step) + return false; + double remainder = fmod(doubleValue, step); + // Accepts errors in lower 7-bit. + double acceptableError = step / pow(2.0, DBL_MANT_DIG - 7); + return acceptableError < remainder && remainder < (step - acceptableError); + } + case DATE: + case DATETIME: + case DATETIMELOCAL: + case MONTH: + case TIME: + case WEEK: { + const double nan = numeric_limits<double>::quiet_NaN(); + double doubleValue = parseToDouble(value(), nan); + doubleValue = fabs(doubleValue - stepBase()); + if (!isfinite(doubleValue)) + return false; + ASSERT(round(doubleValue) == doubleValue); + ASSERT(round(step) == step); + return fmod(doubleValue, step); } + case BUTTON: + case CHECKBOX: + case COLOR: + case EMAIL: + case FILE: + case HIDDEN: + case IMAGE: + case ISINDEX: + case PASSWORD: + case RADIO: + case RESET: + case SEARCH: + case SUBMIT: + case TELEPHONE: + case TEXT: + case URL: + break; + } + // Non-supported types should be rejected by getAllowedValueStep(). + ASSERT_NOT_REACHED(); return false; } -double HTMLInputElement::rangeMinimum() const +bool HTMLInputElement::getStepParameters(double* defaultStep, double* stepScaleFactor) const { - ASSERT(inputType() == RANGE); - // The range type's "default minimum" is 0. - double min = 0.0; - formStringToDouble(getAttribute(minAttr), &min); - return min; + ASSERT(defaultStep); + ASSERT(stepScaleFactor); + switch (inputType()) { + case NUMBER: + case RANGE: + *defaultStep = numberDefaultStep; + *stepScaleFactor = numberStepScaleFactor; + return true; + case DATE: + *defaultStep = dateDefaultStep; + *stepScaleFactor = dateStepScaleFactor; + return true; + case DATETIME: + case DATETIMELOCAL: + *defaultStep = dateTimeDefaultStep; + *stepScaleFactor = dateTimeStepScaleFactor; + return true; + case MONTH: + *defaultStep = monthDefaultStep; + *stepScaleFactor = monthStepScaleFactor; + return true; + case TIME: + *defaultStep = timeDefaultStep; + *stepScaleFactor = timeStepScaleFactor; + return true; + case WEEK: + *defaultStep = weekDefaultStep; + *stepScaleFactor = weekStepScaleFactor; + return true; + case BUTTON: + case CHECKBOX: + case COLOR: + case EMAIL: + case FILE: + case HIDDEN: + case IMAGE: + case ISINDEX: + case PASSWORD: + case RADIO: + case RESET: + case SEARCH: + case SUBMIT: + case TELEPHONE: + case TEXT: + case URL: + return false; + } + ASSERT_NOT_REACHED(); + return false; } -double HTMLInputElement::rangeMaximum() const +bool HTMLInputElement::getAllowedValueStep(double* step) 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(); + ASSERT(step); + double defaultStep; + double stepScaleFactor; + if (!getStepParameters(&defaultStep, &stepScaleFactor)) + return false; + const AtomicString& stepString = getAttribute(stepAttr); + if (stepString.isEmpty()) { + *step = defaultStep * stepScaleFactor; + return true; + } + if (equalIgnoringCase(stepString, "any")) + return false; + double parsed; + if (!parseToDoubleForNumberType(stepString, &parsed) || parsed <= 0.0) { + *step = defaultStep * stepScaleFactor; + return true; + } + // For DATE, MONTH, WEEK, the parsed value should be an integer. + if (inputType() == DATE || inputType() == MONTH || inputType() == WEEK) + parsed = max(round(parsed), 1.0); + double result = parsed * stepScaleFactor; + // For DATETIME, DATETIMELOCAL, TIME, the result should be an integer. + if (inputType() == DATETIME || inputType() == DATETIMELOCAL || inputType() == TIME) + result = max(round(result), 1.0); + ASSERT(result > 0); + *step = result; + return true; +} - 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; +void HTMLInputElement::applyStep(double count, ExceptionCode& ec) +{ + double step; + if (!getAllowedValueStep(&step)) { + ec = INVALID_STATE_ERR; + return; + } + const double nan = numeric_limits<double>::quiet_NaN(); + double current = parseToDouble(value(), nan); + if (!isfinite(current)) { + ec = INVALID_STATE_ERR; + return; + } + double newValue = current + step * count; + if (isinf(newValue)) { + ec = INVALID_STATE_ERR; + return; + } + if (newValue < minimum()) { + ec = INVALID_STATE_ERR; + return; } - return max; + double base = stepBase(); + newValue = base + round((newValue - base) / step) * step; + if (newValue > maximum()) { + ec = INVALID_STATE_ERR; + return; + } + setValueAsNumber(newValue, ec); } -static inline CheckedRadioButtons& checkedRadioButtons(const HTMLInputElement *element) +void HTMLInputElement::stepUp(int n, ExceptionCode& ec) { - if (HTMLFormElement* form = element->form()) - return form->checkedRadioButtons(); - - return element->document()->checkedRadioButtons(); + applyStep(n, ec); +} + +void HTMLInputElement::stepDown(int n, ExceptionCode& ec) +{ + applyStep(-n, ec); } bool HTMLInputElement::isKeyboardFocusable(KeyboardEvent* event) const @@ -372,46 +765,41 @@ void HTMLInputElement::setType(const String& t) setAttribute(typeAttr, t); } +typedef HashMap<String, HTMLInputElement::InputType, CaseFoldingHash> InputTypeMap; +static const InputTypeMap* createTypeMap() +{ + InputTypeMap* map = new InputTypeMap; + map->add("button", HTMLInputElement::BUTTON); + map->add("checkbox", HTMLInputElement::CHECKBOX); + map->add("color", HTMLInputElement::COLOR); + map->add("date", HTMLInputElement::DATE); + map->add("datetime", HTMLInputElement::DATETIME); + map->add("datetime-local", HTMLInputElement::DATETIMELOCAL); + map->add("email", HTMLInputElement::EMAIL); + map->add("file", HTMLInputElement::FILE); + map->add("hidden", HTMLInputElement::HIDDEN); + map->add("image", HTMLInputElement::IMAGE); + map->add("khtml_isindex", HTMLInputElement::ISINDEX); + map->add("month", HTMLInputElement::MONTH); + map->add("number", HTMLInputElement::NUMBER); + map->add("password", HTMLInputElement::PASSWORD); + map->add("radio", HTMLInputElement::RADIO); + map->add("range", HTMLInputElement::RANGE); + map->add("reset", HTMLInputElement::RESET); + map->add("search", HTMLInputElement::SEARCH); + map->add("submit", HTMLInputElement::SUBMIT); + map->add("tel", HTMLInputElement::TELEPHONE); + map->add("time", HTMLInputElement::TIME); + map->add("url", HTMLInputElement::URL); + map->add("week", HTMLInputElement::WEEK); + // No need to register "text" because it is the default type. + return map; +} + void HTMLInputElement::setInputType(const String& t) { - InputType newType; - - if (equalIgnoringCase(t, "password")) - newType = PASSWORD; - else if (equalIgnoringCase(t, "checkbox")) - newType = CHECKBOX; - else if (equalIgnoringCase(t, "radio")) - newType = RADIO; - else if (equalIgnoringCase(t, "submit")) - newType = SUBMIT; - else if (equalIgnoringCase(t, "reset")) - newType = RESET; - else if (equalIgnoringCase(t, "file")) - newType = FILE; - else if (equalIgnoringCase(t, "hidden")) - newType = HIDDEN; - else if (equalIgnoringCase(t, "image")) - newType = IMAGE; - else if (equalIgnoringCase(t, "button")) - newType = BUTTON; - else if (equalIgnoringCase(t, "khtml_isindex")) - newType = ISINDEX; - else if (equalIgnoringCase(t, "search")) - newType = SEARCH; - else if (equalIgnoringCase(t, "range")) - newType = RANGE; - else if (equalIgnoringCase(t, "email")) - newType = EMAIL; - else if (equalIgnoringCase(t, "number")) - newType = NUMBER; - else if (equalIgnoringCase(t, "tel")) - newType = TELEPHONE; - else if (equalIgnoringCase(t, "url")) - newType = URL; - else if (equalIgnoringCase(t, "color")) - newType = COLOR; - else - newType = TEXT; + static const InputTypeMap* typeMap = createTypeMap(); + InputType newType = t.isNull() ? TEXT : typeMap->get(t); // IMPORTANT: Don't allow the type to be changed to FILE after the first // type change, otherwise a JavaScript programmer would be able to set a text @@ -473,8 +861,9 @@ void HTMLInputElement::setInputType(const String& t) checkedRadioButtons(this).addButton(this); } + setNeedsWillValidateCheck(); + setNeedsValidityCheck(); InputElement::notifyFormStateChanged(this); - updateValidity(); } m_haveType = true; @@ -482,82 +871,42 @@ void HTMLInputElement::setInputType(const String& t) m_imageLoader.clear(); } +static const AtomicString* createFormControlTypes() +{ + AtomicString* types = new AtomicString[HTMLInputElement::numberOfTypes]; + // The values must be lowercased because they will be the return values of + // input.type and it must be lowercase according to DOM Level 2. + types[HTMLInputElement::BUTTON] = "button"; + types[HTMLInputElement::CHECKBOX] = "checkbox"; + types[HTMLInputElement::COLOR] = "color"; + types[HTMLInputElement::DATE] = "date"; + types[HTMLInputElement::DATETIME] = "datetime"; + types[HTMLInputElement::DATETIMELOCAL] = "datetime-local"; + types[HTMLInputElement::EMAIL] = "email"; + types[HTMLInputElement::FILE] = "file"; + types[HTMLInputElement::HIDDEN] = "hidden"; + types[HTMLInputElement::IMAGE] = "image"; + types[HTMLInputElement::ISINDEX] = emptyAtom; + types[HTMLInputElement::MONTH] = "month"; + types[HTMLInputElement::NUMBER] = "number"; + types[HTMLInputElement::PASSWORD] = "password"; + types[HTMLInputElement::RADIO] = "radio"; + types[HTMLInputElement::RANGE] = "range"; + types[HTMLInputElement::RESET] = "reset"; + types[HTMLInputElement::SEARCH] = "search"; + types[HTMLInputElement::SUBMIT] = "submit"; + types[HTMLInputElement::TELEPHONE] = "tel"; + types[HTMLInputElement::TEXT] = "text"; + types[HTMLInputElement::TIME] = "time"; + types[HTMLInputElement::URL] = "url"; + types[HTMLInputElement::WEEK] = "week"; + return types; +} + const AtomicString& HTMLInputElement::formControlType() const { - // needs to be lowercase according to DOM spec - switch (inputType()) { - case BUTTON: { - DEFINE_STATIC_LOCAL(const AtomicString, button, ("button")); - return button; - } - case CHECKBOX: { - 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; - } - case FILE: { - DEFINE_STATIC_LOCAL(const AtomicString, file, ("file")); - return file; - } - case HIDDEN: { - DEFINE_STATIC_LOCAL(const AtomicString, hidden, ("hidden")); - return hidden; - } - case IMAGE: { - DEFINE_STATIC_LOCAL(const AtomicString, image, ("image")); - return image; - } - case ISINDEX: - return emptyAtom; - case NUMBER: { - DEFINE_STATIC_LOCAL(const AtomicString, number, ("number")); - return number; - } - case PASSWORD: { - DEFINE_STATIC_LOCAL(const AtomicString, password, ("password")); - return password; - } - case RADIO: { - DEFINE_STATIC_LOCAL(const AtomicString, radio, ("radio")); - return radio; - } - case RANGE: { - DEFINE_STATIC_LOCAL(const AtomicString, range, ("range")); - return range; - } - case RESET: { - DEFINE_STATIC_LOCAL(const AtomicString, reset, ("reset")); - return reset; - } - case SEARCH: { - DEFINE_STATIC_LOCAL(const AtomicString, search, ("search")); - return search; - } - case SUBMIT: { - DEFINE_STATIC_LOCAL(const AtomicString, submit, ("submit")); - return submit; - } - case TELEPHONE: { - DEFINE_STATIC_LOCAL(const AtomicString, telephone, ("tel")); - return telephone; - } - case TEXT: { - DEFINE_STATIC_LOCAL(const AtomicString, text, ("text")); - return text; - } - case URL: { - DEFINE_STATIC_LOCAL(const AtomicString, url, ("url")); - return url; - } - } - return emptyAtom; + static const AtomicString* formControlTypes = createFormControlTypes(); + return formControlTypes[inputType()]; } bool HTMLInputElement::saveFormControlState(String& result) const @@ -568,11 +917,15 @@ bool HTMLInputElement::saveFormControlState(String& result) const switch (inputType()) { case BUTTON: case COLOR: + case DATE: + case DATETIME: + case DATETIMELOCAL: case EMAIL: case FILE: case HIDDEN: case IMAGE: case ISINDEX: + case MONTH: case NUMBER: case RANGE: case RESET: @@ -580,7 +933,9 @@ bool HTMLInputElement::saveFormControlState(String& result) const case SUBMIT: case TELEPHONE: case TEXT: + case TIME: case URL: + case WEEK: result = value(); return true; case CHECKBOX: @@ -600,11 +955,15 @@ void HTMLInputElement::restoreFormControlState(const String& state) switch (inputType()) { case BUTTON: case COLOR: + case DATE: + case DATETIME: + case DATETIMELOCAL: case EMAIL: case FILE: case HIDDEN: case IMAGE: case ISINDEX: + case MONTH: case NUMBER: case RANGE: case RESET: @@ -612,7 +971,9 @@ void HTMLInputElement::restoreFormControlState(const String& state) case SUBMIT: case TELEPHONE: case TEXT: + case TIME: case URL: + case WEEK: setValue(state); break; case CHECKBOX: @@ -655,14 +1016,20 @@ void HTMLInputElement::accessKeyAction(bool sendToAnyElement) // a no-op for this type break; case COLOR: + case DATE: + case DATETIME: + case DATETIMELOCAL: case EMAIL: case ISINDEX: + case MONTH: case NUMBER: case PASSWORD: case SEARCH: case TELEPHONE: case TEXT: + case TIME: case URL: + case WEEK: // should never restore previous selection here focus(false); break; @@ -718,17 +1085,18 @@ void HTMLInputElement::parseMappedAttribute(MappedAttribute *attr) if (m_data.value().isNull()) setNeedsStyleRecalc(); setFormControlValueMatchesRenderer(false); - updateValidity(); + setNeedsValidityCheck(); } else if (attr->name() == checkedAttr) { m_defaultChecked = !attr->isNull(); if (m_useDefaultChecked) { setChecked(m_defaultChecked); m_useDefaultChecked = true; } - updateValidity(); - } else if (attr->name() == maxlengthAttr) + setNeedsValidityCheck(); + } else if (attr->name() == maxlengthAttr) { InputElement::parseMaxLengthAttribute(m_data, this, this, attr); - else if (attr->name() == sizeAttr) + setNeedsValidityCheck(); + } else if (attr->name() == sizeAttr) InputElement::parseSizeAttribute(m_data, this, attr); else if (attr->name() == altAttr) { if (renderer() && inputType() == IMAGE) @@ -772,15 +1140,22 @@ void HTMLInputElement::parseMappedAttribute(MappedAttribute *attr) attach(); } setNeedsStyleRecalc(); - } else if (attr->name() == autosaveAttr || - attr->name() == incrementalAttr || - attr->name() == minAttr || - attr->name() == maxAttr || - attr->name() == multipleAttr || - attr->name() == precisionAttr) + } else if (attr->name() == autosaveAttr + || attr->name() == incrementalAttr) setNeedsStyleRecalc(); - else if (attr->name() == patternAttr) - updateValidity(); + else if (attr->name() == minAttr + || attr->name() == maxAttr) { + if (inputType() == RANGE) { + // Sanitize the value. + setValue(value()); + setNeedsStyleRecalc(); + } + setNeedsValidityCheck(); + } else if (attr->name() == multipleAttr + || attr->name() == patternAttr + || attr->name() == precisionAttr + || attr->name() == stepAttr) + setNeedsValidityCheck(); #if ENABLE(DATALIST) else if (attr->name() == listAttr) m_hasNonEmptyList = !attr->isEmpty(); @@ -792,30 +1167,9 @@ void HTMLInputElement::parseMappedAttribute(MappedAttribute *attr) bool HTMLInputElement::rendererIsNeeded(RenderStyle *style) { - switch (inputType()) { - case BUTTON: - case CHECKBOX: - case COLOR: - case EMAIL: - case FILE: - case IMAGE: - case ISINDEX: - case NUMBER: - case PASSWORD: - case RADIO: - case RANGE: - case RESET: - case SEARCH: - case SUBMIT: - case TELEPHONE: - case TEXT: - case URL: - return HTMLFormControlElementWithState::rendererIsNeeded(style); - case HIDDEN: - return false; - } - ASSERT(false); - return false; + if (inputType() == HIDDEN) + return false; + return HTMLFormControlElementWithState::rendererIsNeeded(style); } RenderObject *HTMLInputElement::createRenderer(RenderArena *arena, RenderStyle *style) @@ -837,14 +1191,20 @@ RenderObject *HTMLInputElement::createRenderer(RenderArena *arena, RenderStyle * case RANGE: return new (arena) RenderSlider(this); case COLOR: + case DATE: + case DATETIME: + case DATETIMELOCAL: case EMAIL: case ISINDEX: + case MONTH: case NUMBER: case PASSWORD: case SEARCH: case TELEPHONE: case TEXT: + case TIME: case URL: + case WEEK: return new (arena) RenderTextControlSingleLine(this, placeholderShouldBeVisible()); } ASSERT(false); @@ -875,6 +1235,9 @@ void HTMLInputElement::attach() imageObj->setImageSizeForAltText(); } } + + if (document()->focusedNode() == this) + document()->updateFocusAppearanceSoon(true /* restore selection */); } void HTMLInputElement::detach() @@ -924,16 +1287,22 @@ bool HTMLInputElement::appendFormData(FormDataList& encoding, bool multipart) switch (inputType()) { case COLOR: + case DATE: + case DATETIME: + case DATETIMELOCAL: case EMAIL: case HIDDEN: case ISINDEX: + case MONTH: case NUMBER: case PASSWORD: case RANGE: case SEARCH: case TELEPHONE: case TEXT: + case TIME: case URL: + case WEEK: // always successful encoding.appendData(name(), value()); return true; @@ -1011,6 +1380,40 @@ void HTMLInputElement::reset() m_useDefaultChecked = true; } +bool HTMLInputElement::isTextField() const +{ + switch (inputType()) { + case COLOR: + case DATE: + case DATETIME: + case DATETIMELOCAL: + case EMAIL: + case ISINDEX: + case MONTH: + case NUMBER: + case PASSWORD: + case SEARCH: + case TELEPHONE: + case TEXT: + case TIME: + case URL: + case WEEK: + return true; + case BUTTON: + case CHECKBOX: + case FILE: + case HIDDEN: + case IMAGE: + case RADIO: + case RANGE: + case RESET: + case SUBMIT: + return false; + } + ASSERT_NOT_REACHED(); + return false; +} + void HTMLInputElement::setChecked(bool nowChecked, bool sendChangeEvent) { if (checked() == nowChecked) @@ -1044,8 +1447,8 @@ void HTMLInputElement::setChecked(bool nowChecked, bool sendChangeEvent) void HTMLInputElement::setIndeterminate(bool _indeterminate) { - // Only checkboxes honor indeterminate. - if (inputType() != CHECKBOX || indeterminate() == _indeterminate) + // Only checkboxes and radio buttons honor indeterminate. + if (!allowsIndeterminate() || indeterminate() == _indeterminate) return; m_indeterminate = _indeterminate; @@ -1066,7 +1469,9 @@ void HTMLInputElement::copyNonAttributeProperties(const Element* source) const HTMLInputElement* sourceElement = static_cast<const HTMLInputElement*>(source); m_data.setValue(sourceElement->m_data.value()); - m_checked = sourceElement->m_checked; + setChecked(sourceElement->m_checked); + m_defaultChecked = sourceElement->m_defaultChecked; + m_useDefaultChecked = sourceElement->m_useDefaultChecked; m_indeterminate = sourceElement->m_indeterminate; HTMLFormControlElementWithState::copyNonAttributeProperties(source); @@ -1085,10 +1490,16 @@ String HTMLInputElement::value() const String value = m_data.value(); if (value.isNull()) { 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)) - return checked() ? "on" : ""; + + // If no attribute exists, extra handling may be necessary. + // For Checkbox Types just use "on" or "" based off the checked() state of the control. + // For a Range Input use the calculated default value. + if (value.isNull()) { + if (inputType() == CHECKBOX || inputType() == RADIO) + return checked() ? "on" : ""; + else if (inputType() == RANGE) + return serializeForNumberType(StepRange(this).defaultValue()); + } } return value; @@ -1102,11 +1513,15 @@ String HTMLInputElement::valueWithDefault() const case BUTTON: case CHECKBOX: case COLOR: + case DATE: + case DATETIME: + case DATETIMELOCAL: case EMAIL: case FILE: case HIDDEN: case IMAGE: case ISINDEX: + case MONTH: case NUMBER: case PASSWORD: case RADIO: @@ -1114,7 +1529,9 @@ String HTMLInputElement::valueWithDefault() const case SEARCH: case TELEPHONE: case TEXT: + case TIME: case URL: + case WEEK: break; case RESET: v = resetButtonDefaultLabel(); @@ -1127,7 +1544,30 @@ String HTMLInputElement::valueWithDefault() const return v; } -void HTMLInputElement::setValue(const String& value) +void HTMLInputElement::setValueForUser(const String& value) +{ + // Call setValue and make it send a change event. + setValue(value, true); +} + +const String& HTMLInputElement::suggestedValue() const +{ + return m_data.suggestedValue(); +} + +void HTMLInputElement::setSuggestedValue(const String& value) +{ + if (inputType() != TEXT) + return; + setFormControlValueMatchesRenderer(false); + m_data.setSuggestedValue(sanitizeValue(value)); + updatePlaceholderVisibility(false); + if (renderer()) + renderer()->updateFromElement(); + setNeedsStyleRecalc(); +} + +void HTMLInputElement::setValue(const String& value, bool sendChangeEvent) { // For security reasons, we don't allow setting the filename, but we do allow clearing it. // The HTML5 spec (as of the 10/24/08 working draft) says that the value attribute isn't applicable to the file upload control @@ -1137,10 +1577,14 @@ void HTMLInputElement::setValue(const String& value) setFormControlValueMatchesRenderer(false); if (storesValueSeparateFromAttribute()) { - if (inputType() == FILE) + if (inputType() == FILE) { m_fileList->clear(); - else { + setNeedsValidityCheck(); + } else { m_data.setValue(sanitizeValue(value)); + // setNeedsValidityCheck() needs to be called after updating the value, + // before style recalc. + setNeedsValidityCheck(); if (isTextField()) { updatePlaceholderVisibility(false); if (inDocument()) @@ -1150,18 +1594,336 @@ void HTMLInputElement::setValue(const String& value) if (renderer()) renderer()->updateFromElement(); setNeedsStyleRecalc(); - } else + } else { setAttribute(valueAttr, sanitizeValue(value)); - + setNeedsValidityCheck(); + } + if (isTextField()) { unsigned max = m_data.value().length(); if (document()->focusedNode() == this) InputElement::updateSelectionRange(this, this, max, max); else cacheSelection(max, max); + m_data.setSuggestedValue(String()); } + + // Don't dispatch the change event when focused, it will be dispatched + // when the control loses focus. + if (sendChangeEvent && document()->focusedNode() != this) + dispatchFormControlChangeEvent(); + InputElement::notifyFormStateChanged(this); - updateValidity(); +} + +double HTMLInputElement::parseToDouble(const String& src, double defaultValue) const +{ + switch (inputType()) { + case DATE: + case DATETIME: + case DATETIMELOCAL: + case TIME: + case WEEK: { + DateComponents date; + if (!parseToDateComponents(inputType(), src, &date)) + return defaultValue; + double msec = date.millisecondsSinceEpoch(); + ASSERT(isfinite(msec)); + return msec; + } + case MONTH: { + DateComponents date; + if (!parseToDateComponents(inputType(), src, &date)) + return defaultValue; + double months = date.monthsSinceEpoch(); + ASSERT(isfinite(months)); + return months; + } + case NUMBER: + case RANGE: { + double numberValue; + if (!parseToDoubleForNumberType(src, &numberValue)) + return defaultValue; + ASSERT(isfinite(numberValue)); + return numberValue; + } + + case BUTTON: + case CHECKBOX: + case COLOR: + case EMAIL: + case FILE: + case HIDDEN: + case IMAGE: + case ISINDEX: + case PASSWORD: + case RADIO: + case RESET: + case SEARCH: + case SUBMIT: + case TELEPHONE: + case TEXT: + case URL: + return defaultValue; + } + ASSERT_NOT_REACHED(); + return defaultValue; +} + +double HTMLInputElement::valueAsDate() const +{ + switch (inputType()) { + case DATE: + case DATETIME: + case TIME: + case WEEK: + return parseToDouble(value(), DateComponents::invalidMilliseconds()); + case MONTH: { + DateComponents date; + if (!parseToDateComponents(inputType(), value(), &date)) + return DateComponents::invalidMilliseconds(); + double msec = date.millisecondsSinceEpoch(); + ASSERT(isfinite(msec)); + return msec; + } + + case BUTTON: + case CHECKBOX: + case COLOR: + case DATETIMELOCAL: // valueAsDate doesn't work for the DATETIMELOCAL type according to the standard. + case EMAIL: + case FILE: + case HIDDEN: + case IMAGE: + case ISINDEX: + case NUMBER: + case PASSWORD: + case RADIO: + case RANGE: + case RESET: + case SEARCH: + case SUBMIT: + case TELEPHONE: + case TEXT: + case URL: + return DateComponents::invalidMilliseconds(); + } + ASSERT_NOT_REACHED(); + return DateComponents::invalidMilliseconds(); +} + +void HTMLInputElement::setValueAsDate(double value, ExceptionCode& ec) +{ + switch (inputType()) { + case DATE: + case DATETIME: + case TIME: + case WEEK: + setValue(serializeForDateTimeTypes(value)); + return; + case MONTH: { + DateComponents date; + if (!date.setMillisecondsSinceEpochForMonth(value)) { + setValue(String()); + return; + } + setValue(date.toString()); + return; + } + case BUTTON: + case CHECKBOX: + case COLOR: + case DATETIMELOCAL: // valueAsDate doesn't work for the DATETIMELOCAL type according to the standard. + case EMAIL: + case FILE: + case HIDDEN: + case IMAGE: + case ISINDEX: + case NUMBER: + case PASSWORD: + case RADIO: + case RANGE: + case RESET: + case SEARCH: + case SUBMIT: + case TELEPHONE: + case TEXT: + case URL: + ec = INVALID_STATE_ERR; + return; + } + ASSERT_NOT_REACHED(); +} + +double HTMLInputElement::valueAsNumber() const +{ + const double nan = numeric_limits<double>::quiet_NaN(); + switch (inputType()) { + case DATE: + case DATETIME: + case DATETIMELOCAL: + case MONTH: + case NUMBER: + case RANGE: + case TIME: + case WEEK: + return parseToDouble(value(), nan); + + case BUTTON: + case CHECKBOX: + case COLOR: + case EMAIL: + case FILE: + case HIDDEN: + case IMAGE: + case ISINDEX: + case PASSWORD: + case RADIO: + case RESET: + case SEARCH: + case SUBMIT: + case TELEPHONE: + case TEXT: + case URL: + return nan; + } + ASSERT_NOT_REACHED(); + return nan; +} + +void HTMLInputElement::setValueAsNumber(double newValue, ExceptionCode& ec) +{ + if (!isfinite(newValue)) { + ec = NOT_SUPPORTED_ERR; + return; + } + switch (inputType()) { + case DATE: + case DATETIME: + case DATETIMELOCAL: + case MONTH: + case NUMBER: + case RANGE: + case TIME: + case WEEK: + setValue(serialize(newValue)); + return; + + case BUTTON: + case CHECKBOX: + case COLOR: + case EMAIL: + case FILE: + case HIDDEN: + case IMAGE: + case ISINDEX: + case PASSWORD: + case RADIO: + case RESET: + case SEARCH: + case SUBMIT: + case TELEPHONE: + case TEXT: + case URL: + ec = INVALID_STATE_ERR; + return; + } + ASSERT_NOT_REACHED(); +} + +String HTMLInputElement::serializeForDateTimeTypes(double value) const +{ + bool success = false; + DateComponents date; + switch (inputType()) { + case DATE: + success = date.setMillisecondsSinceEpochForDate(value); + break; + case DATETIME: + success = date.setMillisecondsSinceEpochForDateTime(value); + break; + case DATETIMELOCAL: + success = date.setMillisecondsSinceEpochForDateTimeLocal(value); + break; + case MONTH: + success = date.setMonthsSinceEpoch(value); + break; + case TIME: + success = date.setMillisecondsSinceMidnight(value); + break; + case WEEK: + success = date.setMillisecondsSinceEpochForWeek(value); + break; + case NUMBER: + case RANGE: + case BUTTON: + case CHECKBOX: + case COLOR: + case EMAIL: + case FILE: + case HIDDEN: + case IMAGE: + case ISINDEX: + case PASSWORD: + case RADIO: + case RESET: + case SEARCH: + case SUBMIT: + case TELEPHONE: + case TEXT: + case URL: + ASSERT_NOT_REACHED(); + return String(); + } + if (!success) + return String(); + + double step; + if (!getAllowedValueStep(&step)) + return date.toString(); + if (!fmod(step, msecPerMinute)) + return date.toString(DateComponents::None); + if (!fmod(step, msecPerSecond)) + return date.toString(DateComponents::Second); + return date.toString(DateComponents::Millisecond); +} + +String HTMLInputElement::serialize(double value) const +{ + if (!isfinite(value)) + return String(); + switch (inputType()) { + case DATE: + case DATETIME: + case DATETIMELOCAL: + case MONTH: + case TIME: + case WEEK: + return serializeForDateTimeTypes(value); + case NUMBER: + case RANGE: + return serializeForNumberType(value); + + case BUTTON: + case CHECKBOX: + case COLOR: + case EMAIL: + case FILE: + case HIDDEN: + case IMAGE: + case ISINDEX: + case PASSWORD: + case RADIO: + case RESET: + case SEARCH: + case SUBMIT: + case TELEPHONE: + case TEXT: + case URL: + break; + } + ASSERT_NOT_REACHED(); + return String(); } String HTMLInputElement::placeholder() const @@ -1183,9 +1945,10 @@ void HTMLInputElement::setValueFromRenderer(const String& value) { // File upload controls will always use setFileListFromRenderer. ASSERT(inputType() != FILE); + m_data.setSuggestedValue(String()); updatePlaceholderVisibility(false); InputElement::setValueFromRenderer(m_data, this, this, value); - updateValidity(); + setNeedsValidityCheck(); } void HTMLInputElement::setFileListFromRenderer(const Vector<String>& paths) @@ -1197,7 +1960,7 @@ void HTMLInputElement::setFileListFromRenderer(const Vector<String>& paths) setFormControlValueMatchesRenderer(true); InputElement::notifyFormStateChanged(this); - updateValidity(); + setNeedsValidityCheck(); } bool HTMLInputElement::storesValueSeparateFromAttribute() const @@ -1212,21 +1975,37 @@ bool HTMLInputElement::storesValueSeparateFromAttribute() const case SUBMIT: return false; case COLOR: + case DATE: + case DATETIME: + case DATETIMELOCAL: case EMAIL: case FILE: case ISINDEX: + case MONTH: case NUMBER: case PASSWORD: case RANGE: case SEARCH: case TELEPHONE: case TEXT: + case TIME: case URL: + case WEEK: return true; } return false; } +struct EventHandlingState { + RefPtr<HTMLInputElement> m_currRadio; + bool m_indeterminate; + bool m_checked; + + EventHandlingState(bool indeterminate, bool checked) + : m_indeterminate(indeterminate) + , m_checked(checked) { } +}; + void* HTMLInputElement::preDispatchEventHandler(Event *evt) { // preventDefault or "return false" are used to reverse the automatic checking/selection we do here. @@ -1234,17 +2013,14 @@ void* HTMLInputElement::preDispatchEventHandler(Event *evt) void* result = 0; if ((inputType() == CHECKBOX || inputType() == RADIO) && evt->isMouseEvent() && evt->type() == eventNames().clickEvent && static_cast<MouseEvent*>(evt)->button() == LeftButton) { + + EventHandlingState* state = new EventHandlingState(indeterminate(), checked()); + if (inputType() == CHECKBOX) { - // As a way to store the state, we return 0 if we were unchecked, 1 if we were checked, and 2 for - // indeterminate. - if (indeterminate()) { - result = (void*)0x2; + if (indeterminate()) setIndeterminate(false); - } else { - if (checked()) - result = (void*)0x1; + else setChecked(!checked(), true); - } } else { // For radio buttons, store the current selected radio object. // We really want radio groups to end up in sane states, i.e., to have something checked. @@ -1254,11 +2030,13 @@ void* HTMLInputElement::preDispatchEventHandler(Event *evt) if (currRadio) { // We have a radio button selected that is not us. Cache it in our result field and ref it so // that it can't be destroyed. - currRadio->ref(); - result = currRadio; + state->m_currRadio = currRadio; } + if (indeterminate()) + setIndeterminate(false); setChecked(true, true); } + result = state; } return result; } @@ -1267,28 +2045,30 @@ void HTMLInputElement::postDispatchEventHandler(Event *evt, void* data) { if ((inputType() == CHECKBOX || inputType() == RADIO) && evt->isMouseEvent() && evt->type() == eventNames().clickEvent && static_cast<MouseEvent*>(evt)->button() == LeftButton) { - if (inputType() == CHECKBOX) { - // Reverse the checking we did in preDispatch. - if (evt->defaultPrevented() || evt->defaultHandled()) { - if (data == (void*)0x2) - setIndeterminate(true); - else - setChecked(data); - } - } else if (data) { - HTMLInputElement* input = static_cast<HTMLInputElement*>(data); - if (evt->defaultPrevented() || evt->defaultHandled()) { - // Restore the original selected radio button if possible. - // Make sure it is still a radio button and only do the restoration if it still - // belongs to our group. - - if (input->form() == form() && input->inputType() == RADIO && input->name() == name()) { - // Ok, the old radio button is still in our form and in our group and is still a - // radio button, so it's safe to restore selection to it. - input->setChecked(true); + + if (EventHandlingState* state = reinterpret_cast<EventHandlingState*>(data)) { + if (inputType() == CHECKBOX) { + // Reverse the checking we did in preDispatch. + if (evt->defaultPrevented() || evt->defaultHandled()) { + setIndeterminate(state->m_indeterminate); + setChecked(state->m_checked); + } + } else { + HTMLInputElement* input = state->m_currRadio.get(); + if (evt->defaultPrevented() || evt->defaultHandled()) { + // Restore the original selected radio button if possible. + // Make sure it is still a radio button and only do the restoration if it still + // belongs to our group. + + if (input && input->form() == form() && input->inputType() == RADIO && input->name() == name()) { + // Ok, the old radio button is still in our form and in our group and is still a + // radio button, so it's safe to restore selection to it. + input->setChecked(true); + } + setIndeterminate(state->m_indeterminate); } } - input->deref(); + delete state; } // Left clicks on radio buttons and check boxes already performed default actions in preDispatchEventHandler(). @@ -1388,16 +2168,22 @@ void HTMLInputElement::defaultEventHandler(Event* evt) switch (inputType()) { case CHECKBOX: case COLOR: + case DATE: + case DATETIME: + case DATETIMELOCAL: case EMAIL: case HIDDEN: case ISINDEX: + case MONTH: case NUMBER: case PASSWORD: case RANGE: case SEARCH: case TELEPHONE: case TEXT: + case TIME: case URL: + case WEEK: // Simulate mouse click on the default form button for enter for these types of elements. clickDefaultFormButton = true; break; @@ -1516,16 +2302,22 @@ void HTMLInputElement::defaultEventHandler(Event* evt) clickElement = true; break; case COLOR: + case DATE: + case DATETIME: + case DATETIMELOCAL: case EMAIL: case HIDDEN: case ISINDEX: + case MONTH: case NUMBER: case PASSWORD: case RANGE: case SEARCH: case TELEPHONE: case TEXT: + case TIME: case URL: + case WEEK: break; } } @@ -1545,12 +2337,12 @@ void HTMLInputElement::defaultEventHandler(Event* evt) } // Fire onChange for text fields. RenderObject* r = renderer(); - if (r && r->isTextField() && toRenderTextControl(r)->isEdited()) { + if (r && r->isTextField() && toRenderTextControl(r)->wasChangedSinceLastChangeEvent()) { dispatchFormControlChangeEvent(); // Refetch the renderer since arbitrary JS code run during onchange can do anything, including destroying it. r = renderer(); if (r && r->isTextField()) - toRenderTextControl(r)->setEdited(false); + toRenderTextControl(r)->setChangedSinceLastChangeEvent(false); } RefPtr<HTMLFormElement> formForSubmission = form(); @@ -1730,6 +2522,13 @@ String HTMLInputElement::sanitizeValue(const String& proposedValue) const { if (isTextField()) return InputElement::sanitizeValue(this, proposedValue); + + // If the proposedValue is null than this is a reset scenario and we + // want the range input's value attribute to take priority over the + // calculated default (middle) value. + if (inputType() == RANGE && !proposedValue.isNull()) + return serializeForNumberType(StepRange(this).clampValue(proposedValue)); + return proposedValue; } @@ -1756,25 +2555,31 @@ bool HTMLInputElement::isRequiredFormControl() const return false; switch (inputType()) { - case TEXT: - case SEARCH: - case URL: - case TELEPHONE: + case CHECKBOX: + case DATE: + case DATETIME: + case DATETIMELOCAL: case EMAIL: - case PASSWORD: + case FILE: + case MONTH: case NUMBER: - case CHECKBOX: + case PASSWORD: case RADIO: - case FILE: + case SEARCH: + case TELEPHONE: + case TEXT: + case TIME: + case URL: + case WEEK: return true; - case HIDDEN: - case RANGE: - case SUBMIT: - case IMAGE: - case RESET: case BUTTON: case COLOR: + case HIDDEN: + case IMAGE: case ISINDEX: + case RANGE: + case RESET: + case SUBMIT: return false; } @@ -1834,14 +2639,23 @@ void HTMLInputElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) cons addSubresourceURL(urls, src()); } -bool HTMLInputElement::willValidate() const +bool HTMLInputElement::recalcWillValidate() const { - // FIXME: This shall check for new WF2 input types too - return HTMLFormControlElementWithState::willValidate() && inputType() != HIDDEN && - inputType() != BUTTON && inputType() != RESET; + return HTMLFormControlElementWithState::recalcWillValidate() + && inputType() != HIDDEN && inputType() != BUTTON && inputType() != RESET; } -bool HTMLInputElement::formStringToDouble(const String& src, double* out) +String HTMLInputElement::serializeForNumberType(double number) +{ + // According to HTML5, "the best representation of the number n as a floating + // point number" is a string produced by applying ToString() to n. + DtoaBuffer buffer; + unsigned length; + doubleToStringInJavaScriptFormat(number, buffer, &length); + return String(buffer, length); +} + +bool HTMLInputElement::parseToDoubleForNumberType(const String& src, double* out) { // See HTML5 2.4.4.3 `Real numbers.' @@ -1857,13 +2671,46 @@ bool HTMLInputElement::formStringToDouble(const String& src, double* out) if (!valid) return false; // NaN and Infinity are not valid numbers according to the standard. - if (isnan(value) || isinf(value)) + if (!isfinite(value)) return false; + // -0 -> 0 + if (!value) + value = 0; if (out) *out = value; return true; } +bool HTMLInputElement::parseToDateComponents(InputType type, const String& formString, DateComponents* out) +{ + if (formString.isEmpty()) + return false; + DateComponents ignoredResult; + if (!out) + out = &ignoredResult; + const UChar* characters = formString.characters(); + unsigned length = formString.length(); + unsigned end; + + switch (type) { + case DATE: + return out->parseDate(characters, length, 0, end) && end == length; + case DATETIME: + return out->parseDateTime(characters, length, 0, end) && end == length; + case DATETIMELOCAL: + return out->parseDateTimeLocal(characters, length, 0, end) && end == length; + case MONTH: + return out->parseMonth(characters, length, 0, end) && end == length; + case WEEK: + return out->parseWeek(characters, length, 0, end) && end == length; + case TIME: + return out->parseTime(characters, length, 0, end) && end == length; + default: + ASSERT_NOT_REACHED(); + return false; + } +} + #if ENABLE(DATALIST) HTMLElement* HTMLInputElement::list() const { @@ -1876,29 +2723,35 @@ HTMLDataListElement* HTMLInputElement::dataList() const return 0; switch (inputType()) { - case TEXT: - case SEARCH: - case URL: - case TELEPHONE: + case COLOR: + case DATE: + case DATETIME: + case DATETIMELOCAL: case EMAIL: + case MONTH: case NUMBER: case RANGE: - case COLOR: { + case SEARCH: + case TELEPHONE: + case TEXT: + case TIME: + case URL: + case WEEK: { Element* element = document()->getElementById(getAttribute(listAttr)); if (element && element->hasTagName(datalistTag)) return static_cast<HTMLDataListElement*>(element); break; } - case HIDDEN: - case PASSWORD: + case BUTTON: case CHECKBOX: - case RADIO: case FILE: - case SUBMIT: + case HIDDEN: case IMAGE: - case RESET: - case BUTTON: case ISINDEX: + case PASSWORD: + case RADIO: + case RESET: + case SUBMIT: break; } return 0; |