diff options
Diffstat (limited to 'src/3rdparty/webkit/WebCore/html/HTMLInputElement.cpp')
-rw-r--r-- | src/3rdparty/webkit/WebCore/html/HTMLInputElement.cpp | 579 |
1 files changed, 265 insertions, 314 deletions
diff --git a/src/3rdparty/webkit/WebCore/html/HTMLInputElement.cpp b/src/3rdparty/webkit/WebCore/html/HTMLInputElement.cpp index dae0be5..dbc818b 100644 --- a/src/3rdparty/webkit/WebCore/html/HTMLInputElement.cpp +++ b/src/3rdparty/webkit/WebCore/html/HTMLInputElement.cpp @@ -26,9 +26,8 @@ #include "config.h" #include "HTMLInputElement.h" -#include "BeforeTextInsertedEvent.h" -#include "ChromeClient.h" #include "CSSPropertyNames.h" +#include "ChromeClient.h" #include "Document.h" #include "Editor.h" #include "Event.h" @@ -42,8 +41,10 @@ #include "HTMLFormElement.h" #include "HTMLImageLoader.h" #include "HTMLNames.h" +#include "ScriptEventListener.h" #include "KeyboardEvent.h" #include "LocalizedStrings.h" +#include "MappedAttribute.h" #include "MouseEvent.h" #include "Page.h" #include "RenderButton.h" @@ -53,10 +54,7 @@ #include "RenderText.h" #include "RenderTextControlSingleLine.h" #include "RenderTheme.h" -#include "SelectionController.h" -#include "TextBreakIterator.h" #include "TextEvent.h" -#include "TextIterator.h" #include <wtf/StdLibExtras.h> using namespace std; @@ -67,79 +65,23 @@ using namespace HTMLNames; const int maxSavedResults = 256; -// FIXME: According to HTML4, the length attribute's value can be arbitrarily -// large. However, due to http://bugs.webkit.org/show_bugs.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. -static const int cMaxLen = 524288; - -static int numGraphemeClusters(StringImpl* s) -{ - if (!s) - return 0; - - TextBreakIterator* it = characterBreakIterator(s->characters(), s->length()); - if (!it) - return 0; - int num = 0; - while (textBreakNext(it) != TextBreakDone) - ++num; - return num; -} - -static int numCharactersInGraphemeClusters(StringImpl* s, int numGraphemeClusters) -{ - if (!s) - return 0; - - TextBreakIterator* it = characterBreakIterator(s->characters(), s->length()); - if (!it) - return 0; - for (int i = 0; i < numGraphemeClusters; ++i) - if (textBreakNext(it) == TextBreakDone) - return s->length(); - return textBreakCurrent(it); -} - -static inline void notifyFormStateChanged(const HTMLInputElement* element) -{ - Frame* frame = element->document()->frame(); - if (!frame) - return; - frame->page()->chrome()->client()->formStateDidChange(element); -} - HTMLInputElement::HTMLInputElement(const QualifiedName& tagName, Document* doc, HTMLFormElement* f) : HTMLFormControlElementWithState(tagName, doc, f) + , m_xPos(0) + , m_yPos(0) + , m_maxResults(-1) + , m_type(TEXT) + , m_checked(false) + , m_defaultChecked(false) + , m_useDefaultChecked(true) + , m_indeterminate(false) + , m_haveType(false) + , m_activeSubmit(false) + , m_autocomplete(Uninitialized) + , m_autofilled(false) + , m_inited(false) { ASSERT(hasTagName(inputTag) || hasTagName(isindexTag)); - init(); -} - -void HTMLInputElement::init() -{ - m_type = TEXT; - m_maxLen = cMaxLen; - m_size = 20; - m_checked = false; - m_defaultChecked = false; - m_useDefaultChecked = true; - m_indeterminate = false; - - m_haveType = false; - m_activeSubmit = false; - m_autocomplete = Uninitialized; - m_inited = false; - m_autofilled = false; - m_placeholderShouldBeVisible = false; - - xPos = 0; - yPos = 0; - - cachedSelStart = -1; - cachedSelEnd = -1; - - m_maxResults = -1; } HTMLInputElement::~HTMLInputElement() @@ -154,9 +96,9 @@ HTMLInputElement::~HTMLInputElement() removeFromForm(); } -const AtomicString& HTMLInputElement::name() const +const AtomicString& HTMLInputElement::formControlName() const { - return m_name.isNull() ? emptyAtom : m_name; + return m_data.name(); } bool HTMLInputElement::autoComplete() const @@ -172,8 +114,7 @@ bool HTMLInputElement::autoComplete() const return true; } - -static inline HTMLFormElement::CheckedRadioButtons& checkedRadioButtons(const HTMLInputElement *element) +static inline CheckedRadioButtons& checkedRadioButtons(const HTMLInputElement *element) { if (HTMLFormElement* form = element->form()) return form->checkedRadioButtons(); @@ -192,9 +133,6 @@ bool HTMLInputElement::isKeyboardFocusable(KeyboardEvent* event) const return false; if (inputType() == RADIO) { - // Unnamed radio buttons are never focusable (matches WinIE). - if (name().isEmpty()) - return false; // Never allow keyboard tabbing to leave you in the same radio group. Always // skip any other elements in the group. @@ -222,23 +160,15 @@ bool HTMLInputElement::isMouseFocusable() const void HTMLInputElement::updateFocusAppearance(bool restorePreviousSelection) { - if (isTextField()) { - if (!restorePreviousSelection || cachedSelStart == -1) - select(); - else - // Restore the cached selection. - setSelectionRange(cachedSelStart, cachedSelEnd); - - if (document() && document()->frame()) - document()->frame()->revealSelection(); - } else + if (isTextField()) + InputElement::updateFocusAppearance(m_data, this, this, restorePreviousSelection); + else HTMLFormControlElementWithState::updateFocusAppearance(restorePreviousSelection); } void HTMLInputElement::aboutToUnload() { - if (isTextField() && focused() && document()->frame()) - document()->frame()->textFieldDidEndEditing(this); + InputElement::aboutToUnload(this, this); } bool HTMLInputElement::shouldUseInputMethod() const @@ -248,23 +178,17 @@ bool HTMLInputElement::shouldUseInputMethod() const void HTMLInputElement::dispatchFocusEvent() { - if (isTextField()) { - setAutofilled(false); - updatePlaceholderVisibility(); - if (inputType() == PASSWORD && document()->frame()) - document()->setUseSecureKeyboardEntryWhenActive(true); - } + InputElement::dispatchFocusEvent(m_data, this, this); + + if (isTextField()) + m_autofilled = false; + HTMLFormControlElementWithState::dispatchFocusEvent(); } void HTMLInputElement::dispatchBlurEvent() { - if (isTextField() && document()->frame()) { - updatePlaceholderVisibility(); - if (inputType() == PASSWORD) - document()->setUseSecureKeyboardEntryWhenActive(false); - document()->frame()->textFieldDidEndEditing(this); - } + InputElement::dispatchBlurEvent(m_data, this, this); HTMLFormControlElementWithState::dispatchBlurEvent(); } @@ -305,6 +229,14 @@ void HTMLInputElement::setInputType(const String& t) 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 newType = TEXT; @@ -334,14 +266,14 @@ void HTMLInputElement::setInputType(const String& t) bool isPasswordField = inputType() == PASSWORD; bool willRespectHeightAndWidth = respectHeightAndWidthAttrs(); - if (didStoreValue && !willStoreValue && !m_value.isNull()) { - setAttribute(valueAttr, m_value); - m_value = String(); + if (didStoreValue && !willStoreValue && !m_data.value().isNull()) { + setAttribute(valueAttr, m_data.value()); + m_data.setValue(String()); } if (!didStoreValue && willStoreValue) - m_value = constrainValue(getAttribute(valueAttr)); + m_data.setValue(constrainValue(getAttribute(valueAttr))); else - recheckValue(); + InputElement::updateValueIfNeeded(m_data, this); if (wasPasswordField && !isPasswordField) unregisterForActivationCallbackIfNeeded(); @@ -350,6 +282,7 @@ void HTMLInputElement::setInputType(const String& t) if (didRespectHeightAndWidth != willRespectHeightAndWidth) { NamedMappedAttrMap* map = mappedAttributes(); + ASSERT(map); if (Attribute* height = map->getAttributeItem(heightAttr)) attributeChanged(height, false); if (Attribute* width = map->getAttributeItem(widthAttr)) @@ -367,7 +300,7 @@ void HTMLInputElement::setInputType(const String& t) checkedRadioButtons(this).addButton(this); } - notifyFormStateChanged(this); + InputElement::notifyFormStateChanged(this); } m_haveType = true; @@ -375,7 +308,7 @@ void HTMLInputElement::setInputType(const String& t) m_imageLoader.clear(); } -const AtomicString& HTMLInputElement::type() const +const AtomicString& HTMLInputElement::formControlType() const { // needs to be lowercase according to DOM spec switch (inputType()) { @@ -387,6 +320,10 @@ const AtomicString& HTMLInputElement::type() const DEFINE_STATIC_LOCAL(const AtomicString, checkbox, ("checkbox")); return checkbox; } + case EMAIL: { + DEFINE_STATIC_LOCAL(const AtomicString, email, ("email")); + return email; + } case FILE: { DEFINE_STATIC_LOCAL(const AtomicString, file, ("file")); return file; @@ -401,6 +338,10 @@ const AtomicString& HTMLInputElement::type() const } 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; @@ -425,30 +366,42 @@ const AtomicString& HTMLInputElement::type() const 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; } -bool HTMLInputElement::saveState(String& result) const +bool HTMLInputElement::saveFormControlState(String& result) const { if (!autoComplete()) return false; switch (inputType()) { case BUTTON: + case EMAIL: case FILE: case HIDDEN: case IMAGE: case ISINDEX: + case NUMBER: case RANGE: case RESET: case SEARCH: case SUBMIT: + case TELEPHONE: case TEXT: + case URL: result = value(); return true; case CHECKBOX: @@ -462,20 +415,24 @@ bool HTMLInputElement::saveState(String& result) const return false; } -void HTMLInputElement::restoreState(const String& state) +void HTMLInputElement::restoreFormControlState(const String& state) { ASSERT(inputType() != PASSWORD); // should never save/restore password fields switch (inputType()) { case BUTTON: + case EMAIL: case FILE: case HIDDEN: case IMAGE: case ISINDEX: + case NUMBER: case RANGE: case RESET: case SEARCH: case SUBMIT: + case TELEPHONE: case TEXT: + case URL: setValue(state); break; case CHECKBOX: @@ -503,22 +460,22 @@ int HTMLInputElement::selectionStart() const { if (!isTextField()) return 0; - if (document()->focusedNode() != this && cachedSelStart != -1) - return cachedSelStart; + if (document()->focusedNode() != this && m_data.cachedSelectionStart() != -1) + return m_data.cachedSelectionStart(); if (!renderer()) return 0; - return static_cast<RenderTextControl*>(renderer())->selectionStart(); + return toRenderTextControl(renderer())->selectionStart(); } int HTMLInputElement::selectionEnd() const { if (!isTextField()) return 0; - if (document()->focusedNode() != this && cachedSelEnd != -1) - return cachedSelEnd; + if (document()->focusedNode() != this && m_data.cachedSelectionEnd() != -1) + return m_data.cachedSelectionEnd(); if (!renderer()) return 0; - return static_cast<RenderTextControl*>(renderer())->selectionEnd(); + return toRenderTextControl(renderer())->selectionEnd(); } void HTMLInputElement::setSelectionStart(int start) @@ -527,7 +484,7 @@ void HTMLInputElement::setSelectionStart(int start) return; if (!renderer()) return; - static_cast<RenderTextControl*>(renderer())->setSelectionStart(start); + toRenderTextControl(renderer())->setSelectionStart(start); } void HTMLInputElement::setSelectionEnd(int end) @@ -536,7 +493,7 @@ void HTMLInputElement::setSelectionEnd(int end) return; if (!renderer()) return; - static_cast<RenderTextControl*>(renderer())->setSelectionEnd(end); + toRenderTextControl(renderer())->setSelectionEnd(end); } void HTMLInputElement::select() @@ -545,16 +502,12 @@ void HTMLInputElement::select() return; if (!renderer()) return; - static_cast<RenderTextControl*>(renderer())->select(); + toRenderTextControl(renderer())->select(); } void HTMLInputElement::setSelectionRange(int start, int end) { - if (!isTextField()) - return; - if (!renderer()) - return; - static_cast<RenderTextControl*>(renderer())->setSelectionRange(start, end); + InputElement::updateSelectionRange(this, this, start, end); } void HTMLInputElement::accessKeyAction(bool sendToAnyElement) @@ -575,10 +528,14 @@ void HTMLInputElement::accessKeyAction(bool sendToAnyElement) case HIDDEN: // a no-op for this type break; + case EMAIL: case ISINDEX: + case NUMBER: case PASSWORD: case SEARCH: + case TELEPHONE: case TEXT: + case URL: // should never restore previous selection here focus(false); break; @@ -609,7 +566,7 @@ void HTMLInputElement::parseMappedAttribute(MappedAttribute *attr) { if (attr->name() == nameAttr) { checkedRadioButtons(this).removeButton(this); - m_name = attr->value(); + m_data.setName(attr->value()); checkedRadioButtons(this).addButton(this); } else if (attr->name() == autocompleteAttr) { if (equalIgnoringCase(attr->value(), "off")) { @@ -627,30 +584,22 @@ void HTMLInputElement::parseMappedAttribute(MappedAttribute *attr) setInputType(attr->value()); } else if (attr->name() == valueAttr) { // We only need to setChanged if the form is looking at the default value right now. - if (m_value.isNull()) - setChanged(); - setValueMatchesRenderer(false); + if (m_data.value().isNull()) + setNeedsStyleRecalc(); + setFormControlValueMatchesRenderer(false); } else if (attr->name() == checkedAttr) { m_defaultChecked = !attr->isNull(); if (m_useDefaultChecked) { setChecked(m_defaultChecked); m_useDefaultChecked = true; } - } else if (attr->name() == maxlengthAttr) { - int oldMaxLen = m_maxLen; - m_maxLen = !attr->isNull() ? attr->value().toInt() : cMaxLen; - if (m_maxLen <= 0 || m_maxLen > cMaxLen) - m_maxLen = cMaxLen; - if (oldMaxLen != m_maxLen) - recheckValue(); - setChanged(); - } else if (attr->name() == sizeAttr) { - m_size = !attr->isNull() ? attr->value().toInt() : 20; - if (renderer()) - renderer()->setNeedsLayoutAndPrefWidthsRecalc(); - } else if (attr->name() == altAttr) { + } else if (attr->name() == maxlengthAttr) + InputElement::parseMaxLengthAttribute(m_data, this, this, attr); + else if (attr->name() == sizeAttr) + InputElement::parseSizeAttribute(m_data, this, attr); + else if (attr->name() == altAttr) { if (renderer() && inputType() == IMAGE) - static_cast<RenderImage*>(renderer())->updateAltText(); + toRenderImage(renderer())->updateAltText(); } else if (attr->name() == srcAttr) { if (renderer() && inputType() == IMAGE) { if (!m_imageLoader) @@ -676,20 +625,20 @@ void HTMLInputElement::parseMappedAttribute(MappedAttribute *attr) if (respectHeightAndWidthAttrs()) addCSSLength(attr, CSSPropertyHeight, attr->value()); } else if (attr->name() == onfocusAttr) { - setInlineEventListenerForTypeAndAttribute(eventNames().focusEvent, attr); + setAttributeEventListener(eventNames().focusEvent, createAttributeEventListener(this, attr)); } else if (attr->name() == onblurAttr) { - setInlineEventListenerForTypeAndAttribute(eventNames().blurEvent, attr); + setAttributeEventListener(eventNames().blurEvent, createAttributeEventListener(this, attr)); } else if (attr->name() == onselectAttr) { - setInlineEventListenerForTypeAndAttribute(eventNames().selectEvent, attr); + setAttributeEventListener(eventNames().selectEvent, createAttributeEventListener(this, attr)); } else if (attr->name() == onchangeAttr) { - setInlineEventListenerForTypeAndAttribute(eventNames().changeEvent, attr); + setAttributeEventListener(eventNames().changeEvent, createAttributeEventListener(this, attr)); } else if (attr->name() == oninputAttr) { - setInlineEventListenerForTypeAndAttribute(eventNames().inputEvent, attr); + setAttributeEventListener(eventNames().inputEvent, createAttributeEventListener(this, attr)); } // Search field and slider attributes all just cause updateFromElement to be called through style // recalcing. else if (attr->name() == onsearchAttr) { - setInlineEventListenerForTypeAndAttribute(eventNames().searchEvent, 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; @@ -699,17 +648,17 @@ void HTMLInputElement::parseMappedAttribute(MappedAttribute *attr) detach(); attach(); } - setChanged(); + setNeedsStyleRecalc(); } else if (attr->name() == placeholderAttr) { if (isTextField()) - updatePlaceholderVisibility(true); + updatePlaceholderVisibility(); } else if (attr->name() == autosaveAttr || - attr->name() == incrementalAttr || - attr->name() == minAttr || - attr->name() == maxAttr || - attr->name() == multipleAttr || - attr->name() == precisionAttr) - setChanged(); + attr->name() == incrementalAttr || + attr->name() == minAttr || + attr->name() == maxAttr || + attr->name() == multipleAttr || + attr->name() == precisionAttr) + setNeedsStyleRecalc(); else HTMLFormControlElementWithState::parseMappedAttribute(attr); } @@ -719,16 +668,20 @@ bool HTMLInputElement::rendererIsNeeded(RenderStyle *style) switch (inputType()) { case BUTTON: case CHECKBOX: + 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; @@ -755,10 +708,14 @@ RenderObject *HTMLInputElement::createRenderer(RenderArena *arena, RenderStyle * return new (arena) RenderImage(this); case RANGE: return new (arena) RenderSlider(this); + case EMAIL: case ISINDEX: + case NUMBER: case PASSWORD: case SEARCH: + case TELEPHONE: case TEXT: + case URL: return new (arena) RenderTextControlSingleLine(this); } ASSERT(false); @@ -780,7 +737,7 @@ void HTMLInputElement::attach() m_imageLoader.set(new HTMLImageLoader(this)); m_imageLoader->updateFromElement(); if (renderer()) { - RenderImage* imageObj = static_cast<RenderImage*>(renderer()); + RenderImage* imageObj = toRenderImage(renderer()); imageObj->setCachedImage(m_imageLoader->image()); // If we have no image at all because we have no src attribute, set @@ -794,7 +751,7 @@ void HTMLInputElement::attach() void HTMLInputElement::detach() { HTMLFormControlElementWithState::detach(); - setValueMatchesRenderer(false); + setFormControlValueMatchesRenderer(false); } String HTMLInputElement::altText() const @@ -837,12 +794,16 @@ bool HTMLInputElement::appendFormData(FormDataList& encoding, bool multipart) return false; switch (inputType()) { + case EMAIL: case HIDDEN: case ISINDEX: + case NUMBER: case PASSWORD: case RANGE: case SEARCH: + case TELEPHONE: case TEXT: + case URL: // always successful encoding.appendData(name(), value()); return true; @@ -862,8 +823,8 @@ bool HTMLInputElement::appendFormData(FormDataList& encoding, bool multipart) case IMAGE: if (m_activeSubmit) { - encoding.appendData(name().isEmpty() ? "x" : (name() + ".x"), xPos); - encoding.appendData(name().isEmpty() ? "y" : (name() + ".y"), yPos); + encoding.appendData(name().isEmpty() ? "x" : (name() + ".x"), m_xPos); + encoding.appendData(name().isEmpty() ? "y" : (name() + ".y"), m_yPos); if (!name().isEmpty() && !value().isEmpty()) encoding.appendData(name(), value()); return true; @@ -917,7 +878,7 @@ void HTMLInputElement::setChecked(bool nowChecked, bool sendChangeEvent) m_useDefaultChecked = false; m_checked = nowChecked; - setChanged(); + setNeedsStyleRecalc(); checkedRadioButtons(this).addButton(this); @@ -930,7 +891,7 @@ void HTMLInputElement::setChecked(bool nowChecked, bool sendChangeEvent) // 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() && (inputType() != RADIO || nowChecked)) - onChange(); + dispatchFormControlChangeEvent(); } void HTMLInputElement::setIndeterminate(bool _indeterminate) @@ -941,20 +902,25 @@ void HTMLInputElement::setIndeterminate(bool _indeterminate) m_indeterminate = _indeterminate; - setChanged(); + setNeedsStyleRecalc(); if (renderer() && renderer()->style()->hasAppearance()) theme()->stateChanged(renderer(), CheckedState); } -void HTMLInputElement::copyNonAttributeProperties(const Element *source) +int HTMLInputElement::size() const +{ + return m_data.size(); +} + +void HTMLInputElement::copyNonAttributeProperties(const Element* source) { - const HTMLInputElement *sourceElem = static_cast<const HTMLInputElement *>(source); + const HTMLInputElement* sourceElement = static_cast<const HTMLInputElement*>(source); + + m_data.setValue(sourceElement->m_data.value()); + m_checked = sourceElement->m_checked; + m_indeterminate = sourceElement->m_indeterminate; - m_value = sourceElem->m_value; - m_checked = sourceElem->m_checked; - m_indeterminate = sourceElem->m_indeterminate; - HTMLFormControlElementWithState::copyNonAttributeProperties(source); } @@ -968,7 +934,7 @@ String HTMLInputElement::value() const return String(); } - String value = m_value; + String value = m_data.value(); if (value.isNull()) { value = constrainValue(getAttribute(valueAttr)); @@ -987,15 +953,19 @@ String HTMLInputElement::valueWithDefault() const switch (inputType()) { case BUTTON: case CHECKBOX: + case EMAIL: case FILE: case HIDDEN: case IMAGE: case ISINDEX: + case NUMBER: case PASSWORD: case RADIO: case RANGE: case SEARCH: + case TELEPHONE: case TEXT: + case URL: break; case RESET: v = resetButtonDefaultLabel(); @@ -1016,61 +986,54 @@ void HTMLInputElement::setValue(const String& value) if (inputType() == FILE && !value.isEmpty()) return; - if (isTextField()) - updatePlaceholderVisibility(); - - setValueMatchesRenderer(false); + setFormControlValueMatchesRenderer(false); if (storesValueSeparateFromAttribute()) { if (inputType() == FILE) m_fileList->clear(); else { - m_value = constrainValue(value); - if (isTextField() && inDocument()) - document()->updateRendering(); + m_data.setValue(constrainValue(value)); + if (isTextField()) { + InputElement::updatePlaceholderVisibility(m_data, this, this); + if (inDocument()) + document()->updateStyleIfNeeded(); + } } if (renderer()) renderer()->updateFromElement(); - setChanged(); + setNeedsStyleRecalc(); } else setAttribute(valueAttr, constrainValue(value)); if (isTextField()) { - unsigned max = m_value.length(); + unsigned max = m_data.value().length(); if (document()->focusedNode() == this) - setSelectionRange(max, max); - else { - cachedSelStart = max; - cachedSelEnd = max; - } + InputElement::updateSelectionRange(this, this, max, max); + else + cacheSelection(max, max); } - notifyFormStateChanged(this); + InputElement::notifyFormStateChanged(this); } -void HTMLInputElement::setValueFromRenderer(const String& value) +String HTMLInputElement::placeholder() const { - // Renderer and our event handler are responsible for constraining values. - ASSERT(value == constrainValue(value) || constrainValue(value).isEmpty()); - - // File upload controls will always use setFileListFromRenderer. - ASSERT (inputType() != FILE); - - if (isTextField()) - updatePlaceholderVisibility(); - - // 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 - if (value == "\n") - m_value = ""; - else - m_value = value; + return getAttribute(placeholderAttr).string(); +} - setValueMatchesRenderer(); +void HTMLInputElement::setPlaceholder(const String& value) +{ + setAttribute(placeholderAttr, value); +} - // Fire the "input" DOM event. - dispatchEventForType(eventNames().inputEvent, true, false); +bool HTMLInputElement::searchEventsShouldBeDispatched() const +{ + return hasAttribute(incrementalAttr); +} - notifyFormStateChanged(this); +void HTMLInputElement::setValueFromRenderer(const String& value) +{ + // File upload controls will always use setFileListFromRenderer. + ASSERT(inputType() != FILE); + InputElement::setValueFromRenderer(m_data, this, this, value); } void HTMLInputElement::setFileListFromRenderer(const Vector<String>& paths) @@ -1080,8 +1043,8 @@ void HTMLInputElement::setFileListFromRenderer(const Vector<String>& paths) for (int i = 0; i < size; i++) m_fileList->append(File::create(paths[i])); - setValueMatchesRenderer(); - notifyFormStateChanged(this); + setFormControlValueMatchesRenderer(true); + InputElement::notifyFormStateChanged(this); } bool HTMLInputElement::storesValueSeparateFromAttribute() const @@ -1095,12 +1058,16 @@ bool HTMLInputElement::storesValueSeparateFromAttribute() const case RESET: case SUBMIT: return false; + case EMAIL: case FILE: case ISINDEX: + case NUMBER: case PASSWORD: case RANGE: case SEARCH: + case TELEPHONE: case TEXT: + case URL: return true; } return false; @@ -1126,11 +1093,6 @@ void* HTMLInputElement::preDispatchEventHandler(Event *evt) } } else { // For radio buttons, store the current selected radio object. - if (name().isEmpty() || checked()) - return 0; // Match WinIE and don't allow unnamed radio buttons to be checked. - // Checked buttons just stay checked. - // FIXME: Need to learn to work without a form. - // We really want radio groups to end up in sane states, i.e., to have something checked. // Therefore if nothing is currently selected, we won't allow this action to be "undone", since // we want some object in the radio group to actually get selected. @@ -1166,8 +1128,7 @@ void HTMLInputElement::postDispatchEventHandler(Event *evt, void* data) // Make sure it is still a radio button and only do the restoration if it still // belongs to our group. - // Match WinIE and don't allow unnamed radio buttons to be checked. - if (input->form() == form() && input->inputType() == RADIO && !name().isEmpty() && input->name() == name()) { + 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); @@ -1183,6 +1144,9 @@ void HTMLInputElement::postDispatchEventHandler(Event *evt, void* data) void HTMLInputElement::defaultEventHandler(Event* evt) { + // FIXME: It would be better to refactor this for the different types of input element. + // Having them all in one giant function makes this hard to read, and almost all the handling is type-specific. + bool clickDefaultFormButton = false; if (isTextField() && evt->type() == eventNames().textInputEvent && evt->isTextEvent() && static_cast<TextEvent*>(evt)->data() == "\n") @@ -1194,30 +1158,40 @@ void HTMLInputElement::defaultEventHandler(Event* evt) // FIXME: We could just call offsetX() and offsetY() on the event, // but that's currently broken, so for now do the computation here. if (me->isSimulated() || !renderer()) { - xPos = 0; - yPos = 0; + m_xPos = 0; + m_yPos = 0; } else { // FIXME: This doesn't work correctly with transforms. + // FIXME: pageX/pageY need adjusting for pageZoomFactor(). Use actualPageLocation()? IntPoint absOffset = roundedIntPoint(renderer()->localToAbsolute()); - xPos = me->pageX() - absOffset.x(); - yPos = me->pageY() - absOffset.y(); + m_xPos = me->pageX() - absOffset.x(); + m_yPos = me->pageY() - absOffset.y(); } } - if (isTextField() && evt->type() == eventNames().keydownEvent && evt->isKeyboardEvent() && focused() && document()->frame() - && document()->frame()->doTextFieldCommandFromEvent(this, static_cast<KeyboardEvent*>(evt))) { + if (isTextField() + && evt->type() == eventNames().keydownEvent + && evt->isKeyboardEvent() + && focused() + && document()->frame() + && document()->frame()->doTextFieldCommandFromEvent(this, static_cast<KeyboardEvent*>(evt))) { evt->setDefaultHandled(); return; } - if (inputType() == RADIO && evt->isMouseEvent() - && evt->type() == eventNames().clickEvent && static_cast<MouseEvent*>(evt)->button() == LeftButton) { + if (inputType() == RADIO + && evt->isMouseEvent() + && evt->type() == eventNames().clickEvent + && static_cast<MouseEvent*>(evt)->button() == LeftButton) { evt->setDefaultHandled(); return; } - // Let the key handling done in EventTargetNode take precedence over the event handling here for editable text fields - if (!clickDefaultFormButton) { + // 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() && !clickDefaultFormButton + && (evt->type() == eventNames().keydownEvent || evt->type() == eventNames().keypressEvent); + if (callBaseClassEarly) { HTMLFormControlElementWithState::defaultEventHandler(evt); if (evt->defaultHandled()) return; @@ -1235,13 +1209,13 @@ void HTMLInputElement::defaultEventHandler(Event* evt) form()->reset(); else { m_activeSubmit = true; - // FIXME: Would be cleaner to get xPos and yPos out of the underlying mouse + // FIXME: Would be cleaner to get m_xPos and m_yPos out of the underlying mouse // event (if any) here instead of relying on the variables set above when // processing the click event. Even better, appendFormData could pass the - // event in, and then we could get rid of xPos and yPos altogether! + // event in, and then we could get rid of m_xPos and m_yPos altogether! if (!form()->prepareSubmit(evt)) { - xPos = 0; - yPos = 0; + m_xPos = 0; + m_yPos = 0; } m_activeSubmit = false; } @@ -1259,12 +1233,16 @@ void HTMLInputElement::defaultEventHandler(Event* evt) if (charCode == '\r') { switch (inputType()) { case CHECKBOX: + case EMAIL: case HIDDEN: case ISINDEX: + case NUMBER: case PASSWORD: case RANGE: case SEARCH: + case TELEPHONE: case TEXT: + case URL: // Simulate mouse click on the default form button for enter for these types of elements. clickDefaultFormButton = true; break; @@ -1316,7 +1294,8 @@ void HTMLInputElement::defaultEventHandler(Event* evt) case SUBMIT: case RADIO: setActive(true, true); - // No setDefaultHandled() - IE dispatches a keypress in this case. + // No setDefaultHandled(), because IE dispatches a keypress in this case + // and the caller will only dispatch a keypress if we don't call setDefaultHandled. return; default: break; @@ -1344,9 +1323,8 @@ void HTMLInputElement::defaultEventHandler(Event* evt) if (elt->form() != form()) break; if (n->hasTagName(inputTag)) { - // Match WinIE and don't allow unnamed radio buttons to be checked. HTMLInputElement* inputElt = static_cast<HTMLInputElement*>(n); - if (inputElt->inputType() == RADIO && !name().isEmpty() && inputElt->name() == name() && inputElt->isFocusable()) { + if (inputElt->inputType() == RADIO && inputElt->name() == name() && inputElt->isFocusable()) { inputElt->setChecked(true); document()->setFocusedNode(inputElt); inputElt->dispatchSimulatedClick(evt, false, false); @@ -1382,12 +1360,16 @@ void HTMLInputElement::defaultEventHandler(Event* evt) if (!checked()) clickElement = true; break; + case EMAIL: case HIDDEN: case ISINDEX: + case NUMBER: case PASSWORD: case RANGE: case SEARCH: + case TELEPHONE: case TEXT: + case URL: break; } } @@ -1407,12 +1389,12 @@ void HTMLInputElement::defaultEventHandler(Event* evt) } // Fire onChange for text fields. RenderObject* r = renderer(); - if (r && r->isTextField() && r->isEdited()) { - onChange(); + if (r && r->isTextField() && toRenderTextControl(r)->isEdited()) { + dispatchFormControlChangeEvent(); // Refetch the renderer since arbitrary JS code run during onchange can do anything, including destroying it. r = renderer(); - if (r) - r->setEdited(false); + if (r && r->isTextField()) + toRenderTextControl(r)->setEdited(false); } // Form may never have been present, or may have been destroyed by the change event. if (form()) @@ -1421,36 +1403,17 @@ void HTMLInputElement::defaultEventHandler(Event* evt) return; } - if (evt->isBeforeTextInsertedEvent()) { - // Make sure that the text to be inserted will not violate the maxLength. - int oldLen = numGraphemeClusters(value().impl()); - ASSERT(oldLen <= maxLength()); - int selectionLen = numGraphemeClusters(plainText(document()->frame()->selection()->selection().toRange().get()).impl()); - ASSERT(oldLen >= selectionLen); - int maxNewLen = maxLength() - (oldLen - selectionLen); - - // Truncate the inserted text to avoid violating the maxLength and other constraints. - BeforeTextInsertedEvent* textEvent = static_cast<BeforeTextInsertedEvent*>(evt); - textEvent->setText(constrainValue(textEvent->text(), maxNewLen)); - } + if (evt->isBeforeTextInsertedEvent()) + InputElement::handleBeforeTextInsertedEvent(m_data, this, document(), evt); if (isTextField() && renderer() && (evt->isMouseEvent() || evt->isDragEvent() || evt->isWheelEvent() || evt->type() == eventNames().blurEvent || evt->type() == eventNames().focusEvent)) static_cast<RenderTextControlSingleLine*>(renderer())->forwardEvent(evt); - if (inputType() == RANGE && renderer()) { - RenderSlider* slider = static_cast<RenderSlider*>(renderer()); - if (evt->isMouseEvent() && evt->type() == eventNames().mousedownEvent && static_cast<MouseEvent*>(evt)->button() == LeftButton) { - MouseEvent* mEvt = static_cast<MouseEvent*>(evt); - if (!slider->mouseEventIsInThumb(mEvt)) { - IntPoint eventOffset(mEvt->offsetX(), mEvt->offsetY()); - if (mEvt->target() != this) // Does this ever happen now? Was added for <video> controls - eventOffset = roundedIntPoint(renderer()->absoluteToLocal(FloatPoint(mEvt->pageX(), mEvt->pageY()), false, true)); - slider->setValueForPosition(slider->positionForOffset(eventOffset)); - } - } - if (evt->isMouseEvent() || evt->isDragEvent() || evt->isWheelEvent()) - slider->forwardEvent(evt); - } + if (inputType() == RANGE && renderer() && (evt->isMouseEvent() || evt->isDragEvent() || evt->isWheelEvent())) + static_cast<RenderSlider*>(renderer())->forwardEvent(evt); + + if (!callBaseClassEarly && !evt->defaultHandled()) + HTMLFormControlElementWithState::defaultEventHandler(evt); } bool HTMLInputElement::isURLAttribute(Attribute *attr) const @@ -1478,6 +1441,11 @@ void HTMLInputElement::setDefaultChecked(bool defaultChecked) setAttribute(checkedAttr, defaultChecked ? "" : 0); } +void HTMLInputElement::setDefaultName(const AtomicString& name) +{ + m_data.setName(name); +} + String HTMLInputElement::accept() const { return getAttribute(acceptAttr); @@ -1518,11 +1486,26 @@ void HTMLInputElement::setAlt(const String &value) setAttribute(altAttr, value); } +int HTMLInputElement::maxLength() const +{ + return m_data.maxLength(); +} + void HTMLInputElement::setMaxLength(int _maxLength) { setAttribute(maxlengthAttr, String::number(_maxLength)); } +bool HTMLInputElement::multiple() const +{ + return !getAttribute(multipleAttr).isNull(); +} + +void HTMLInputElement::setMultiple(bool multiple) +{ + setAttribute(multipleAttr, multiple ? "" : 0); +} + void HTMLInputElement::setSize(unsigned _size) { setAttribute(sizeAttr, String::number(_size)); @@ -1554,7 +1537,7 @@ void HTMLInputElement::setAutofilled(bool b) return; m_autofilled = b; - setChanged(); + setNeedsStyleRecalc(); } FileList* HTMLInputElement::files() @@ -1566,15 +1549,7 @@ FileList* HTMLInputElement::files() String HTMLInputElement::constrainValue(const String& proposedValue) const { - return constrainValue(proposedValue, m_maxLen); -} - -void HTMLInputElement::recheckValue() -{ - String oldValue = value(); - String newValue = constrainValue(oldValue); - if (newValue != oldValue) - setValue(newValue); + return InputElement::constrainValue(this, proposedValue, m_data.maxLength()); } bool HTMLInputElement::needsActivationCallback() @@ -1594,40 +1569,10 @@ void HTMLInputElement::unregisterForActivationCallbackIfNeeded() document()->unregisterForDocumentActivationCallbacks(this); } -void HTMLInputElement::updatePlaceholderVisibility(bool placeholderValueChanged) -{ - ASSERT(isTextField()); - - bool oldPlaceholderShouldBeVisible = m_placeholderShouldBeVisible; - - m_placeholderShouldBeVisible = value().isEmpty() - && document()->focusedNode() != this - && !getAttribute(placeholderAttr).isEmpty(); - - if ((oldPlaceholderShouldBeVisible != m_placeholderShouldBeVisible || placeholderValueChanged) && renderer()) - static_cast<RenderTextControlSingleLine*>(renderer())->updatePlaceholderVisibility(); -} - -String HTMLInputElement::constrainValue(const String& proposedValue, int maxLen) const +void HTMLInputElement::cacheSelection(int start, int end) { - String string = proposedValue; - if (isTextField()) { - string.replace("\r\n", " "); - string.replace('\r', ' '); - string.replace('\n', ' '); - StringImpl* s = string.impl(); - int newLen = numCharactersInGraphemeClusters(s, maxLen); - for (int i = 0; i < newLen; ++i) { - const UChar current = (*s)[i]; - if (current < ' ' && current != '\t') { - newLen = i; - break; - } - } - if (newLen < static_cast<int>(string.length())) - return string.substring(0, newLen); - } - return string; + m_data.setCachedSelectionStart(start); + m_data.setCachedSelectionEnd(end); } void HTMLInputElement::addSearchResult() @@ -1642,14 +1587,14 @@ void HTMLInputElement::onSearch() ASSERT(isSearchField()); if (renderer()) static_cast<RenderTextControlSingleLine*>(renderer())->stopSearchEventTimer(); - dispatchEventForType(eventNames().searchEvent, true, false); + dispatchEvent(eventNames().searchEvent, true, false); } -Selection HTMLInputElement::selection() const +VisibleSelection HTMLInputElement::selection() const { - if (!renderer() || !isTextField() || cachedSelStart == -1 || cachedSelEnd == -1) - return Selection(); - return static_cast<RenderTextControl*>(renderer())->selection(cachedSelStart, cachedSelEnd); + if (!renderer() || !isTextField() || m_data.cachedSelectionStart() == -1 || m_data.cachedSelectionEnd() == -1) + return VisibleSelection(); + return toRenderTextControl(renderer())->selection(m_data.cachedSelectionStart(), m_data.cachedSelectionEnd()); } void HTMLInputElement::documentDidBecomeActive() @@ -1689,4 +1634,10 @@ bool HTMLInputElement::willValidate() const return HTMLFormControlElementWithState::willValidate() && inputType() != HIDDEN && inputType() != BUTTON && inputType() != RESET; } + +bool HTMLInputElement::placeholderShouldBeVisible() const +{ + return m_data.placeholderShouldBeVisible(); +} + } // namespace |