2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * (C) 2001 Dirk Mueller (mueller@kde.org)
5 * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved.
6 * (C) 2006 Alexey Proskuryakov (ap@nypop.com)
7 * Copyright (C) 2007 Samuel Weinig (sam@webkit.org)
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Library General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Library General Public License for more details.
19 * You should have received a copy of the GNU Library General Public License
20 * along with this library; see the file COPYING.LIB. If not, write to
21 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 * Boston, MA 02110-1301, USA.
27 #include "HTMLInputElement.h"
29 #include "BeforeTextInsertedEvent.h"
30 #include "CSSPropertyNames.h"
34 #include "EventHandler.h"
35 #include "EventNames.h"
38 #include "FocusController.h"
39 #include "FormDataList.h"
41 #include "HTMLFormElement.h"
42 #include "HTMLImageLoader.h"
43 #include "HTMLNames.h"
44 #include "KeyboardEvent.h"
45 #include "LocalizedStrings.h"
46 #include "MouseEvent.h"
48 #include "RenderButton.h"
49 #include "RenderFileUploadControl.h"
50 #include "RenderImage.h"
51 #include "RenderSlider.h"
52 #include "RenderText.h"
53 #include "RenderTextControl.h"
54 #include "RenderTheme.h"
55 #include "SelectionController.h"
56 #include "TextBreakIterator.h"
57 #include "TextEvent.h"
58 #include "TextIterator.h"
64 using namespace EventNames;
65 using namespace HTMLNames;
67 const int maxSavedResults = 256;
69 // FIXME: According to HTML4, the length attribute's value can be arbitrarily
70 // large. However, due to http://bugs.webkit.org/show_bugs.cgi?id=14536 things
71 // get rather sluggish when a text field has a larger number of characters than
72 // this, even when just clicking in the text field.
73 static const int cMaxLen = 524288;
75 static int numGraphemeClusters(StringImpl* s)
80 TextBreakIterator* it = characterBreakIterator(s->characters(), s->length());
84 while (textBreakNext(it) != TextBreakDone)
89 static int numCharactersInGraphemeClusters(StringImpl* s, int numGraphemeClusters)
94 TextBreakIterator* it = characterBreakIterator(s->characters(), s->length());
97 for (int i = 0; i < numGraphemeClusters; ++i)
98 if (textBreakNext(it) == TextBreakDone)
100 return textBreakCurrent(it);
103 HTMLInputElement::HTMLInputElement(Document* doc, HTMLFormElement* f)
104 : HTMLFormControlElementWithState(inputTag, doc, f)
109 HTMLInputElement::HTMLInputElement(const QualifiedName& tagName, Document* doc, HTMLFormElement* f)
110 : HTMLFormControlElementWithState(tagName, doc, f)
115 void HTMLInputElement::init()
121 m_defaultChecked = false;
122 m_useDefaultChecked = true;
123 m_indeterminate = false;
126 m_activeSubmit = false;
127 m_autocomplete = Uninitialized;
129 m_autofilled = false;
140 HTMLInputElement::~HTMLInputElement()
142 if (inputType() == PASSWORD)
143 document()->unregisterForCacheCallbacks(this);
145 document()->checkedRadioButtons().removeButton(this);
147 // Need to remove this from the form while it is still an HTMLInputElement,
148 // so can't wait for the base class's destructor to do it.
152 const AtomicString& HTMLInputElement::name() const
154 return m_name.isNull() ? emptyAtom : m_name;
157 bool HTMLInputElement::autoComplete() const
159 if (m_autocomplete != Uninitialized)
160 return m_autocomplete == On;
162 // Assuming we're still in a Form, respect the Form's setting
163 if (HTMLFormElement* form = this->form())
164 return form->autoComplete();
166 // The default is true
171 static inline HTMLFormElement::CheckedRadioButtons& checkedRadioButtons(const HTMLInputElement *element)
173 if (HTMLFormElement* form = element->form())
174 return form->checkedRadioButtons();
176 return element->document()->checkedRadioButtons();
179 bool HTMLInputElement::isKeyboardFocusable(KeyboardEvent* event) const
181 // If text fields can be focused, then they should always be keyboard focusable
183 return HTMLFormControlElementWithState::isFocusable();
185 // If the base class says we can't be focused, then we can stop now.
186 if (!HTMLFormControlElementWithState::isKeyboardFocusable(event))
189 if (inputType() == RADIO) {
190 // Unnamed radio buttons are never focusable (matches WinIE).
191 if (name().isEmpty())
194 // Never allow keyboard tabbing to leave you in the same radio group. Always
195 // skip any other elements in the group.
196 Node* currentFocusedNode = document()->focusedNode();
197 if (currentFocusedNode && currentFocusedNode->hasTagName(inputTag)) {
198 HTMLInputElement* focusedInput = static_cast<HTMLInputElement*>(currentFocusedNode);
199 if (focusedInput->inputType() == RADIO && focusedInput->form() == form() &&
200 focusedInput->name() == name())
204 // Allow keyboard focus if we're checked or if nothing in the group is checked.
205 return checked() || !checkedRadioButtons(this).checkedButtonForGroup(name());
211 bool HTMLInputElement::isMouseFocusable() const
214 return HTMLFormControlElementWithState::isFocusable();
215 return HTMLFormControlElementWithState::isMouseFocusable();
218 void HTMLInputElement::updateFocusAppearance(bool restorePreviousSelection)
221 if (!restorePreviousSelection || cachedSelStart == -1)
224 // Restore the cached selection.
225 setSelectionRange(cachedSelStart, cachedSelEnd);
227 if (document() && document()->frame())
228 document()->frame()->revealSelection();
230 HTMLFormControlElementWithState::updateFocusAppearance(restorePreviousSelection);
233 void HTMLInputElement::aboutToUnload()
235 if (isTextField() && focused() && document()->frame())
236 document()->frame()->textFieldDidEndEditing(this);
239 bool HTMLInputElement::shouldUseInputMethod() const
241 return m_type == TEXT || m_type == SEARCH || m_type == ISINDEX;
244 void HTMLInputElement::dispatchFocusEvent()
247 setAutofilled(false);
248 if (inputType() == PASSWORD && document()->frame())
249 document()->setUseSecureKeyboardEntryWhenActive(true);
251 HTMLFormControlElementWithState::dispatchFocusEvent();
254 void HTMLInputElement::dispatchBlurEvent()
256 if (isTextField() && document()->frame()) {
257 if (inputType() == PASSWORD)
258 document()->setUseSecureKeyboardEntryWhenActive(false);
259 document()->frame()->textFieldDidEndEditing(this);
261 HTMLFormControlElementWithState::dispatchBlurEvent();
264 void HTMLInputElement::setType(const String& t)
268 removeAttribute(typeAttr, exccode);
270 setAttribute(typeAttr, t);
273 void HTMLInputElement::setInputType(const String& t)
277 if (equalIgnoringCase(t, "password"))
279 else if (equalIgnoringCase(t, "checkbox"))
281 else if (equalIgnoringCase(t, "radio"))
283 else if (equalIgnoringCase(t, "submit"))
285 else if (equalIgnoringCase(t, "reset"))
287 else if (equalIgnoringCase(t, "file"))
289 else if (equalIgnoringCase(t, "hidden"))
291 else if (equalIgnoringCase(t, "image"))
293 else if (equalIgnoringCase(t, "button"))
295 else if (equalIgnoringCase(t, "khtml_isindex"))
297 else if (equalIgnoringCase(t, "search"))
299 else if (equalIgnoringCase(t, "range"))
304 // IMPORTANT: Don't allow the type to be changed to FILE after the first
305 // type change, otherwise a JavaScript programmer would be able to set a text
306 // field's value to something like /etc/passwd and then change it to a file field.
307 if (inputType() != newType) {
308 if (newType == FILE && m_haveType)
309 // Set the attribute back to the old value.
310 // Useful in case we were called from inside parseMappedAttribute.
311 setAttribute(typeAttr, type());
313 checkedRadioButtons(this).removeButton(this);
315 if (newType == FILE && !m_fileList)
316 m_fileList = FileList::create();
318 bool wasAttached = attached();
322 bool didStoreValue = storesValueSeparateFromAttribute();
323 bool wasPasswordField = inputType() == PASSWORD;
324 bool didRespectHeightAndWidth = respectHeightAndWidthAttrs();
326 bool willStoreValue = storesValueSeparateFromAttribute();
327 bool isPasswordField = inputType() == PASSWORD;
328 bool willRespectHeightAndWidth = respectHeightAndWidthAttrs();
330 if (didStoreValue && !willStoreValue && !m_value.isNull()) {
331 setAttribute(valueAttr, m_value);
334 if (!didStoreValue && willStoreValue)
335 m_value = constrainValue(getAttribute(valueAttr));
339 if (wasPasswordField && !isPasswordField)
340 document()->unregisterForCacheCallbacks(this);
341 else if (!wasPasswordField && isPasswordField)
342 document()->registerForCacheCallbacks(this);
344 if (didRespectHeightAndWidth != willRespectHeightAndWidth) {
345 NamedMappedAttrMap* map = mappedAttributes();
346 if (MappedAttribute* height = map->getAttributeItem(heightAttr))
347 attributeChanged(height, false);
348 if (MappedAttribute* width = map->getAttributeItem(widthAttr))
349 attributeChanged(width, false);
350 if (MappedAttribute* align = map->getAttributeItem(alignAttr))
351 attributeChanged(align, false);
356 if (document()->focusedNode() == this)
357 updateFocusAppearance(true);
360 checkedRadioButtons(this).addButton(this);
365 if (inputType() != IMAGE && m_imageLoader)
366 m_imageLoader.clear();
369 const AtomicString& HTMLInputElement::type() const
371 // needs to be lowercase according to DOM spec
372 switch (inputType()) {
374 static const AtomicString button("button");
378 static const AtomicString checkbox("checkbox");
382 static const AtomicString file("file");
386 static const AtomicString hidden("hidden");
390 static const AtomicString image("image");
396 static const AtomicString password("password");
400 static const AtomicString radio("radio");
404 static const AtomicString range("range");
408 static const AtomicString reset("reset");
412 static const AtomicString search("search");
416 static const AtomicString submit("submit");
420 static const AtomicString text("text");
427 bool HTMLInputElement::saveState(String& result) const
429 switch (inputType()) {
444 result = checked() ? "on" : "off";
449 ASSERT_NOT_REACHED();
453 void HTMLInputElement::restoreState(const String& state)
455 ASSERT(inputType() != PASSWORD); // should never save/restore password fields
456 switch (inputType()) {
471 setChecked(state == "on");
478 bool HTMLInputElement::canStartSelection() const
482 return HTMLFormControlElementWithState::canStartSelection();
485 bool HTMLInputElement::canHaveSelection() const
487 return isTextField();
490 int HTMLInputElement::selectionStart() const
494 if (document()->focusedNode() != this && cachedSelStart != -1)
495 return cachedSelStart;
498 return static_cast<RenderTextControl*>(renderer())->selectionStart();
501 int HTMLInputElement::selectionEnd() const
505 if (document()->focusedNode() != this && cachedSelEnd != -1)
509 return static_cast<RenderTextControl*>(renderer())->selectionEnd();
512 void HTMLInputElement::setSelectionStart(int start)
518 static_cast<RenderTextControl*>(renderer())->setSelectionStart(start);
521 void HTMLInputElement::setSelectionEnd(int end)
527 static_cast<RenderTextControl*>(renderer())->setSelectionEnd(end);
530 void HTMLInputElement::select()
536 static_cast<RenderTextControl*>(renderer())->select();
539 void HTMLInputElement::setSelectionRange(int start, int end)
545 static_cast<RenderTextControl*>(renderer())->setSelectionRange(start, end);
548 void HTMLInputElement::accessKeyAction(bool sendToAnyElement)
550 switch (inputType()) {
560 // send the mouse button events iff the caller specified sendToAnyElement
561 dispatchSimulatedClick(0, sendToAnyElement);
564 // a no-op for this type
570 // should never restore previous selection here
576 bool HTMLInputElement::mapToEntry(const QualifiedName& attrName, MappedAttributeEntry& result) const
578 if (((attrName == heightAttr || attrName == widthAttr) && respectHeightAndWidthAttrs()) ||
579 attrName == vspaceAttr ||
580 attrName == hspaceAttr) {
585 if (attrName == alignAttr) {
586 if (inputType() == IMAGE) {
587 // Share with <img> since the alignment behavior is the same.
593 return HTMLElement::mapToEntry(attrName, result);
596 void HTMLInputElement::parseMappedAttribute(MappedAttribute *attr)
598 if (attr->name() == nameAttr) {
599 checkedRadioButtons(this).removeButton(this);
600 m_name = attr->value();
601 checkedRadioButtons(this).addButton(this);
602 } else if (attr->name() == autocompleteAttr) {
603 if (equalIgnoringCase(attr->value(), "off"))
604 m_autocomplete = Off;
605 else if (attr->isEmpty())
606 m_autocomplete = Uninitialized;
609 } else if (attr->name() == typeAttr) {
610 setInputType(attr->value());
611 } else if (attr->name() == valueAttr) {
612 // We only need to setChanged if the form is looking at the default value right now.
613 if (m_value.isNull())
615 setValueMatchesRenderer(false);
616 } else if (attr->name() == checkedAttr) {
617 m_defaultChecked = !attr->isNull();
618 if (m_useDefaultChecked) {
619 setChecked(m_defaultChecked);
620 m_useDefaultChecked = true;
622 } else if (attr->name() == maxlengthAttr) {
623 int oldMaxLen = m_maxLen;
624 m_maxLen = !attr->isNull() ? attr->value().toInt() : cMaxLen;
625 if (m_maxLen <= 0 || m_maxLen > cMaxLen)
627 if (oldMaxLen != m_maxLen)
630 } else if (attr->name() == sizeAttr) {
631 m_size = !attr->isNull() ? attr->value().toInt() : 20;
632 } else if (attr->name() == altAttr) {
633 if (renderer() && inputType() == IMAGE)
634 static_cast<RenderImage*>(renderer())->updateAltText();
635 } else if (attr->name() == srcAttr) {
636 if (renderer() && inputType() == IMAGE) {
638 m_imageLoader.set(new HTMLImageLoader(this));
639 m_imageLoader->updateFromElement();
641 } else if (attr->name() == usemapAttr ||
642 attr->name() == accesskeyAttr) {
643 // FIXME: ignore for the moment
644 } else if (attr->name() == vspaceAttr) {
645 addCSSLength(attr, CSSPropertyMarginTop, attr->value());
646 addCSSLength(attr, CSSPropertyMarginBottom, attr->value());
647 } else if (attr->name() == hspaceAttr) {
648 addCSSLength(attr, CSSPropertyMarginLeft, attr->value());
649 addCSSLength(attr, CSSPropertyMarginRight, attr->value());
650 } else if (attr->name() == alignAttr) {
651 if (inputType() == IMAGE)
652 addHTMLAlignment(attr);
653 } else if (attr->name() == widthAttr) {
654 if (respectHeightAndWidthAttrs())
655 addCSSLength(attr, CSSPropertyWidth, attr->value());
656 } else if (attr->name() == heightAttr) {
657 if (respectHeightAndWidthAttrs())
658 addCSSLength(attr, CSSPropertyHeight, attr->value());
659 } else if (attr->name() == onfocusAttr) {
660 setHTMLEventListener(focusEvent, attr);
661 } else if (attr->name() == onblurAttr) {
662 setHTMLEventListener(blurEvent, attr);
663 } else if (attr->name() == onselectAttr) {
664 setHTMLEventListener(selectEvent, attr);
665 } else if (attr->name() == onchangeAttr) {
666 setHTMLEventListener(changeEvent, attr);
667 } else if (attr->name() == oninputAttr) {
668 setHTMLEventListener(inputEvent, attr);
670 // Search field and slider attributes all just cause updateFromElement to be called through style
672 else if (attr->name() == onsearchAttr) {
673 setHTMLEventListener(searchEvent, attr);
674 } else if (attr->name() == resultsAttr) {
675 int oldResults = m_maxResults;
676 m_maxResults = !attr->isNull() ? min(attr->value().toInt(), maxSavedResults) : -1;
677 // FIXME: Detaching just for maxResults change is not ideal. We should figure out the right
678 // time to relayout for this change.
679 if (m_maxResults != oldResults && (m_maxResults <= 0 || oldResults <= 0) && attached()) {
684 } else if (attr->name() == autosaveAttr ||
685 attr->name() == incrementalAttr ||
686 attr->name() == placeholderAttr ||
687 attr->name() == minAttr ||
688 attr->name() == maxAttr ||
689 attr->name() == precisionAttr) {
692 HTMLFormControlElementWithState::parseMappedAttribute(attr);
695 bool HTMLInputElement::rendererIsNeeded(RenderStyle *style)
697 switch (inputType()) {
710 return HTMLFormControlElementWithState::rendererIsNeeded(style);
718 RenderObject *HTMLInputElement::createRenderer(RenderArena *arena, RenderStyle *style)
720 switch (inputType()) {
724 return new (arena) RenderButton(this);
727 return RenderObject::createObject(this, style);
729 return new (arena) RenderFileUploadControl(this);
733 return new (arena) RenderImage(this);
735 return new (arena) RenderSlider(this);
740 return new (arena) RenderTextControl(this, false);
746 void HTMLInputElement::attach()
750 setInputType(getAttribute(typeAttr));
754 HTMLFormControlElementWithState::attach();
756 if (inputType() == IMAGE) {
758 m_imageLoader.set(new HTMLImageLoader(this));
759 m_imageLoader->updateFromElement();
761 RenderImage* imageObj = static_cast<RenderImage*>(renderer());
762 imageObj->setCachedImage(m_imageLoader->image());
764 // If we have no image at all because we have no src attribute, set
765 // image height and width for the alt text instead.
766 if (!m_imageLoader->image() && !imageObj->cachedImage())
767 imageObj->setImageSizeForAltText();
772 void HTMLInputElement::detach()
774 HTMLFormControlElementWithState::detach();
775 setValueMatchesRenderer(false);
778 String HTMLInputElement::altText() const
780 // http://www.w3.org/TR/1998/REC-html40-19980424/appendix/notes.html#altgen
781 // also heavily discussed by Hixie on bugzilla
782 // note this is intentionally different to HTMLImageElement::altText()
783 String alt = getAttribute(altAttr);
784 // fall back to title attribute
786 alt = getAttribute(titleAttr);
788 alt = getAttribute(valueAttr);
790 alt = inputElementAltText();
794 bool HTMLInputElement::isSuccessfulSubmitButton() const
796 // HTML spec says that buttons must have names to be considered successful.
797 // However, other browsers do not impose this constraint. So we do likewise.
798 return !disabled() && (inputType() == IMAGE || inputType() == SUBMIT);
801 bool HTMLInputElement::isActivatedSubmit() const
803 return m_activeSubmit;
806 void HTMLInputElement::setActivatedSubmit(bool flag)
808 m_activeSubmit = flag;
811 bool HTMLInputElement::appendFormData(FormDataList& encoding, bool multipart)
813 // image generates its own names, but for other types there is no form data unless there's a name
814 if (name().isEmpty() && inputType() != IMAGE)
817 switch (inputType()) {
825 encoding.appendData(name(), value());
831 encoding.appendData(name(), value());
838 // these types of buttons are never successful
842 if (m_activeSubmit) {
843 encoding.appendData(name().isEmpty() ? "x" : (name() + ".x"), xPos);
844 encoding.appendData(name().isEmpty() ? "y" : (name() + ".y"), yPos);
845 if (!name().isEmpty() && !value().isEmpty())
846 encoding.appendData(name(), value());
852 if (m_activeSubmit) {
853 String enc_str = valueWithDefault();
854 encoding.appendData(name(), enc_str);
860 // Can't submit file on GET.
864 // If no filename at all is entered, return successful but empty.
865 // Null would be more logical, but Netscape posts an empty file. Argh.
866 if (m_fileList->isEmpty()) {
867 encoding.appendData(name(), String(""));
871 encoding.appendFile(name(), m_fileList->item(0)->path());
877 void HTMLInputElement::reset()
879 if (storesValueSeparateFromAttribute())
882 setChecked(m_defaultChecked);
883 m_useDefaultChecked = true;
886 void HTMLInputElement::setChecked(bool nowChecked, bool sendChangeEvent)
888 if (checked() == nowChecked)
891 checkedRadioButtons(this).removeButton(this);
893 m_useDefaultChecked = false;
894 m_checked = nowChecked;
897 checkedRadioButtons(this).addButton(this);
899 if (renderer() && renderer()->style()->hasAppearance())
900 theme()->stateChanged(renderer(), CheckedState);
902 // Only send a change event for items in the document (avoid firing during
903 // parsing) and don't send a change event for a radio button that's getting
904 // unchecked to match other browsers. DOM is not a useful standard for this
905 // because it says only to fire change events at "lose focus" time, which is
906 // definitely wrong in practice for these types of elements.
907 if (sendChangeEvent && inDocument() && (inputType() != RADIO || nowChecked))
911 void HTMLInputElement::setIndeterminate(bool _indeterminate)
913 // Only checkboxes honor indeterminate.
914 if (inputType() != CHECKBOX || indeterminate() == _indeterminate)
917 m_indeterminate = _indeterminate;
921 if (renderer() && renderer()->style()->hasAppearance())
922 theme()->stateChanged(renderer(), CheckedState);
925 void HTMLInputElement::copyNonAttributeProperties(const Element *source)
927 const HTMLInputElement *sourceElem = static_cast<const HTMLInputElement *>(source);
929 m_value = sourceElem->m_value;
930 m_checked = sourceElem->m_checked;
931 m_indeterminate = sourceElem->m_indeterminate;
933 HTMLFormControlElementWithState::copyNonAttributeProperties(source);
936 String HTMLInputElement::value() const
938 if (inputType() == FILE) {
939 if (!m_fileList->isEmpty())
940 return m_fileList->item(0)->fileName();
944 String value = m_value;
945 if (value.isNull()) {
946 value = constrainValue(getAttribute(valueAttr));
948 // If no attribute exists, then just use "on" or "" based off the checked() state of the control.
949 if (value.isNull() && (inputType() == CHECKBOX || inputType() == RADIO))
950 return checked() ? "on" : "";
956 String HTMLInputElement::valueWithDefault() const
960 switch (inputType()) {
974 v = resetButtonDefaultLabel();
977 v = submitButtonDefaultLabel();
984 void HTMLInputElement::setValue(const String& value)
986 // For security reasons, we don't allow setting the filename, but we do allow clearing it.
987 if (inputType() == FILE && !value.isEmpty())
990 setValueMatchesRenderer(false);
991 if (storesValueSeparateFromAttribute()) {
992 if (inputType() == FILE)
995 m_value = constrainValue(value);
996 if (isTextField() && inDocument())
997 document()->updateRendering();
1000 renderer()->updateFromElement();
1003 setAttribute(valueAttr, constrainValue(value));
1005 if (isTextField()) {
1006 unsigned max = m_value.length();
1007 if (document()->focusedNode() == this)
1008 setSelectionRange(max, max);
1010 cachedSelStart = max;
1016 void HTMLInputElement::setValueFromRenderer(const String& value)
1018 // Renderer and our event handler are responsible for constraining values.
1019 ASSERT(value == constrainValue(value) || constrainValue(value).isEmpty());
1021 if (inputType() == FILE) {
1022 m_fileList->clear();
1023 m_fileList->append(File::create(value));
1025 // Workaround for bug where trailing \n is included in the result of textContent.
1026 // The assert macro above may also be simplified to: value == constrainValue(value)
1027 // http://bugs.webkit.org/show_bug.cgi?id=9661
1034 setValueMatchesRenderer();
1036 // Fire the "input" DOM event.
1037 dispatchHTMLEvent(inputEvent, true, false);
1040 bool HTMLInputElement::storesValueSeparateFromAttribute() const
1042 switch (inputType()) {
1062 void* HTMLInputElement::preDispatchEventHandler(Event *evt)
1064 // preventDefault or "return false" are used to reverse the automatic checking/selection we do here.
1065 // This result gives us enough info to perform the "undo" in postDispatch of the action we take here.
1067 if ((inputType() == CHECKBOX || inputType() == RADIO) && evt->isMouseEvent()
1068 && evt->type() == clickEvent && static_cast<MouseEvent*>(evt)->button() == LeftButton) {
1069 if (inputType() == CHECKBOX) {
1070 // As a way to store the state, we return 0 if we were unchecked, 1 if we were checked, and 2 for
1072 if (indeterminate()) {
1073 result = (void*)0x2;
1074 setIndeterminate(false);
1077 result = (void*)0x1;
1078 setChecked(!checked(), true);
1081 // For radio buttons, store the current selected radio object.
1082 if (name().isEmpty() || checked())
1083 return 0; // Match WinIE and don't allow unnamed radio buttons to be checked.
1084 // Checked buttons just stay checked.
1085 // FIXME: Need to learn to work without a form.
1087 // We really want radio groups to end up in sane states, i.e., to have something checked.
1088 // Therefore if nothing is currently selected, we won't allow this action to be "undone", since
1089 // we want some object in the radio group to actually get selected.
1090 HTMLInputElement* currRadio = checkedRadioButtons(this).checkedButtonForGroup(name());
1092 // We have a radio button selected that is not us. Cache it in our result field and ref it so
1093 // that it can't be destroyed.
1097 setChecked(true, true);
1103 void HTMLInputElement::postDispatchEventHandler(Event *evt, void* data)
1105 if ((inputType() == CHECKBOX || inputType() == RADIO) && evt->isMouseEvent()
1106 && evt->type() == clickEvent && static_cast<MouseEvent*>(evt)->button() == LeftButton) {
1107 if (inputType() == CHECKBOX) {
1108 // Reverse the checking we did in preDispatch.
1109 if (evt->defaultPrevented() || evt->defaultHandled()) {
1110 if (data == (void*)0x2)
1111 setIndeterminate(true);
1116 HTMLInputElement* input = static_cast<HTMLInputElement*>(data);
1117 if (evt->defaultPrevented() || evt->defaultHandled()) {
1118 // Restore the original selected radio button if possible.
1119 // Make sure it is still a radio button and only do the restoration if it still
1120 // belongs to our group.
1122 // Match WinIE and don't allow unnamed radio buttons to be checked.
1123 if (input->form() == form() && input->inputType() == RADIO && !name().isEmpty() && input->name() == name()) {
1124 // Ok, the old radio button is still in our form and in our group and is still a
1125 // radio button, so it's safe to restore selection to it.
1126 input->setChecked(true);
1132 // Left clicks on radio buttons and check boxes already performed default actions in preDispatchEventHandler().
1133 evt->setDefaultHandled();
1137 void HTMLInputElement::defaultEventHandler(Event* evt)
1139 bool clickDefaultFormButton = false;
1141 if (isTextField() && evt->type() == textInputEvent && evt->isTextEvent() && static_cast<TextEvent*>(evt)->data() == "\n")
1142 clickDefaultFormButton = true;
1144 if (inputType() == IMAGE && evt->isMouseEvent() && evt->type() == clickEvent) {
1145 // record the mouse position for when we get the DOMActivate event
1146 MouseEvent* me = static_cast<MouseEvent*>(evt);
1147 // FIXME: We could just call offsetX() and offsetY() on the event,
1148 // but that's currently broken, so for now do the computation here.
1149 if (me->isSimulated() || !renderer()) {
1153 int offsetX, offsetY;
1154 renderer()->absolutePosition(offsetX, offsetY);
1155 xPos = me->pageX() - offsetX;
1156 // FIXME: Why is yPos a short?
1157 yPos = me->pageY() - offsetY;
1161 if (isTextField() && evt->type() == keydownEvent && evt->isKeyboardEvent() && focused() && document()->frame()
1162 && document()->frame()->doTextFieldCommandFromEvent(this, static_cast<KeyboardEvent*>(evt))) {
1163 evt->setDefaultHandled();
1167 if (inputType() == RADIO && evt->isMouseEvent()
1168 && evt->type() == clickEvent && static_cast<MouseEvent*>(evt)->button() == LeftButton) {
1169 evt->setDefaultHandled();
1173 // Let the key handling done in EventTargetNode take precedence over the event handling here for editable text fields
1174 if (!clickDefaultFormButton) {
1175 HTMLFormControlElementWithState::defaultEventHandler(evt);
1176 if (evt->defaultHandled())
1180 // DOMActivate events cause the input to be "activated" - in the case of image and submit inputs, this means
1181 // actually submitting the form. For reset inputs, the form is reset. These events are sent when the user clicks
1182 // on the element, or presses enter while it is the active element. JavaScript code wishing to activate the element
1183 // must dispatch a DOMActivate event - a click event will not do the job.
1184 if (evt->type() == DOMActivateEvent && !disabled()) {
1185 if (inputType() == IMAGE || inputType() == SUBMIT || inputType() == RESET) {
1188 if (inputType() == RESET)
1191 m_activeSubmit = true;
1192 // FIXME: Would be cleaner to get xPos and yPos out of the underlying mouse
1193 // event (if any) here instead of relying on the variables set above when
1194 // processing the click event. Even better, appendFormData could pass the
1195 // event in, and then we could get rid of xPos and yPos altogether!
1196 if (!form()->prepareSubmit(evt)) {
1200 m_activeSubmit = false;
1202 } else if (inputType() == FILE && renderer())
1203 static_cast<RenderFileUploadControl*>(renderer())->click();
1206 // Use key press event here since sending simulated mouse events
1207 // on key down blocks the proper sending of the key press event.
1208 if (evt->type() == keypressEvent && evt->isKeyboardEvent()) {
1209 bool clickElement = false;
1211 int charCode = static_cast<KeyboardEvent*>(evt)->charCode();
1213 if (charCode == '\r') {
1214 switch (inputType()) {
1222 // Simulate mouse click on the default form button for enter for these types of elements.
1223 clickDefaultFormButton = true;
1230 // Simulate mouse click for enter for these types of elements.
1231 clickElement = true;
1234 break; // Don't do anything for enter on a radio button.
1236 } else if (charCode == ' ') {
1237 switch (inputType()) {
1245 // Prevent scrolling down the page.
1246 evt->setDefaultHandled();
1254 dispatchSimulatedClick(evt);
1255 evt->setDefaultHandled();
1260 if (evt->type() == keydownEvent && evt->isKeyboardEvent()) {
1261 String key = static_cast<KeyboardEvent*>(evt)->keyIdentifier();
1263 if (key == "U+0020") {
1264 switch (inputType()) {
1272 setActive(true, true);
1273 // No setDefaultHandled() - IE dispatches a keypress in this case.
1280 if (inputType() == RADIO && (key == "Up" || key == "Down" || key == "Left" || key == "Right")) {
1281 // Left and up mean "previous radio button".
1282 // Right and down mean "next radio button".
1283 // Tested in WinIE, and even for RTL, left still means previous radio button (and so moves
1284 // to the right). Seems strange, but we'll match it.
1285 bool forward = (key == "Down" || key == "Right");
1287 // We can only stay within the form's children if the form hasn't been demoted to a leaf because
1288 // of malformed HTML.
1290 while ((n = (forward ? n->traverseNextNode() : n->traversePreviousNode()))) {
1291 // Once we encounter a form element, we know we're through.
1292 if (n->hasTagName(formTag))
1295 // Look for more radio buttons.
1296 if (n->hasTagName(inputTag)) {
1297 HTMLInputElement* elt = static_cast<HTMLInputElement*>(n);
1298 if (elt->form() != form())
1300 if (n->hasTagName(inputTag)) {
1301 // Match WinIE and don't allow unnamed radio buttons to be checked.
1302 HTMLInputElement* inputElt = static_cast<HTMLInputElement*>(n);
1303 if (inputElt->inputType() == RADIO && !name().isEmpty() && inputElt->name() == name() && inputElt->isFocusable()) {
1304 inputElt->setChecked(true);
1305 document()->setFocusedNode(inputElt);
1306 inputElt->dispatchSimulatedClick(evt, false, false);
1307 evt->setDefaultHandled();
1316 if (evt->type() == keyupEvent && evt->isKeyboardEvent()) {
1317 bool clickElement = false;
1319 String key = static_cast<KeyboardEvent*>(evt)->keyIdentifier();
1321 if (key == "U+0020") {
1322 switch (inputType()) {
1329 // Simulate mouse click for spacebar for these types of elements.
1330 // The AppKit already does this for some, but not all, of them.
1331 clickElement = true;
1334 // If an unselected radio is tabbed into (because the entire group has nothing
1335 // checked, or because of some explicit .focus() call), then allow space to check it.
1337 clickElement = true;
1351 dispatchSimulatedClick(evt);
1352 evt->setDefaultHandled();
1357 if (clickDefaultFormButton) {
1358 if (isSearchField()) {
1362 // Fire onChange for text fields.
1363 RenderObject* r = renderer();
1364 if (r && r->isTextField() && r->isEdited()) {
1366 // Refetch the renderer since arbitrary JS code run during onchange can do anything, including destroying it.
1369 r->setEdited(false);
1371 // Form may never have been present, or may have been destroyed by the change event.
1373 form()->submitClick(evt);
1374 evt->setDefaultHandled();
1378 if (evt->isBeforeTextInsertedEvent()) {
1379 // Make sure that the text to be inserted will not violate the maxLength.
1380 int oldLen = numGraphemeClusters(value().impl());
1381 ASSERT(oldLen <= maxLength());
1382 int selectionLen = numGraphemeClusters(plainText(document()->frame()->selection()->selection().toRange().get()).impl());
1383 ASSERT(oldLen >= selectionLen);
1384 int maxNewLen = maxLength() - (oldLen - selectionLen);
1386 // Truncate the inserted text to avoid violating the maxLength and other constraints.
1387 BeforeTextInsertedEvent* textEvent = static_cast<BeforeTextInsertedEvent*>(evt);
1388 textEvent->setText(constrainValue(textEvent->text(), maxNewLen));
1391 if (isTextField() && renderer() && (evt->isMouseEvent() || evt->isDragEvent() || evt->isWheelEvent() || evt->type() == blurEvent || evt->type() == focusEvent))
1392 static_cast<RenderTextControl*>(renderer())->forwardEvent(evt);
1394 if (inputType() == RANGE && renderer()) {
1395 RenderSlider* slider = static_cast<RenderSlider*>(renderer());
1396 if (evt->isMouseEvent() && evt->type() == mousedownEvent && static_cast<MouseEvent*>(evt)->button() == LeftButton) {
1397 MouseEvent* mEvt = static_cast<MouseEvent*>(evt);
1398 if (!slider->mouseEventIsInThumb(mEvt)) {
1399 IntPoint eventOffset(mEvt->offsetX(), mEvt->offsetY());
1400 if (mEvt->target() != this) {
1401 IntRect rect = renderer()->absoluteBoundingBoxRect();
1402 eventOffset.setX(mEvt->pageX() - rect.x());
1403 eventOffset.setY(mEvt->pageY() - rect.y());
1405 slider->setValueForPosition(slider->positionForOffset(eventOffset));
1408 if (evt->isMouseEvent() || evt->isDragEvent() || evt->isWheelEvent())
1409 slider->forwardEvent(evt);
1413 bool HTMLInputElement::isURLAttribute(Attribute *attr) const
1415 return (attr->name() == srcAttr);
1418 String HTMLInputElement::defaultValue() const
1420 return getAttribute(valueAttr);
1423 void HTMLInputElement::setDefaultValue(const String &value)
1425 setAttribute(valueAttr, value);
1428 bool HTMLInputElement::defaultChecked() const
1430 return !getAttribute(checkedAttr).isNull();
1433 void HTMLInputElement::setDefaultChecked(bool defaultChecked)
1435 setAttribute(checkedAttr, defaultChecked ? "" : 0);
1438 String HTMLInputElement::accept() const
1440 return getAttribute(acceptAttr);
1443 void HTMLInputElement::setAccept(const String &value)
1445 setAttribute(acceptAttr, value);
1448 String HTMLInputElement::accessKey() const
1450 return getAttribute(accesskeyAttr);
1453 void HTMLInputElement::setAccessKey(const String &value)
1455 setAttribute(accesskeyAttr, value);
1458 String HTMLInputElement::align() const
1460 return getAttribute(alignAttr);
1463 void HTMLInputElement::setAlign(const String &value)
1465 setAttribute(alignAttr, value);
1468 String HTMLInputElement::alt() const
1470 return getAttribute(altAttr);
1473 void HTMLInputElement::setAlt(const String &value)
1475 setAttribute(altAttr, value);
1478 void HTMLInputElement::setMaxLength(int _maxLength)
1480 setAttribute(maxlengthAttr, String::number(_maxLength));
1483 void HTMLInputElement::setSize(unsigned _size)
1485 setAttribute(sizeAttr, String::number(_size));
1488 KURL HTMLInputElement::src() const
1490 return document()->completeURL(getAttribute(srcAttr));
1493 void HTMLInputElement::setSrc(const String &value)
1495 setAttribute(srcAttr, value);
1498 String HTMLInputElement::useMap() const
1500 return getAttribute(usemapAttr);
1503 void HTMLInputElement::setUseMap(const String &value)
1505 setAttribute(usemapAttr, value);
1508 FileList* HTMLInputElement::files()
1510 if (inputType() != FILE)
1512 return m_fileList.get();
1515 String HTMLInputElement::constrainValue(const String& proposedValue) const
1517 return constrainValue(proposedValue, m_maxLen);
1520 void HTMLInputElement::recheckValue()
1522 String oldValue = value();
1523 String newValue = constrainValue(oldValue);
1524 if (newValue != oldValue)
1528 String HTMLInputElement::constrainValue(const String& proposedValue, int maxLen) const
1530 if (isTextField()) {
1531 StringImpl* s = proposedValue.impl();
1532 int newLen = numCharactersInGraphemeClusters(s, maxLen);
1533 for (int i = 0; i < newLen; ++i) {
1534 const UChar current = (*s)[i];
1535 if (current < ' ' && current != '\t') {
1540 if (newLen < static_cast<int>(proposedValue.length()))
1541 return proposedValue.substring(0, newLen);
1543 return proposedValue;
1546 void HTMLInputElement::addSearchResult()
1548 ASSERT(isSearchField());
1550 static_cast<RenderTextControl*>(renderer())->addSearchResult();
1553 void HTMLInputElement::onSearch()
1555 ASSERT(isSearchField());
1557 static_cast<RenderTextControl*>(renderer())->stopSearchEventTimer();
1558 dispatchHTMLEvent(searchEvent, true, false);
1561 Selection HTMLInputElement::selection() const
1563 if (!renderer() || !isTextField() || cachedSelStart == -1 || cachedSelEnd == -1)
1565 return static_cast<RenderTextControl*>(renderer())->selection(cachedSelStart, cachedSelEnd);
1568 void HTMLInputElement::didRestoreFromCache()
1570 ASSERT(inputType() == PASSWORD);
1574 void HTMLInputElement::willMoveToNewOwnerDocument()
1576 if (inputType() == PASSWORD)
1577 document()->unregisterForCacheCallbacks(this);
1579 document()->checkedRadioButtons().removeButton(this);
1581 HTMLFormControlElementWithState::willMoveToNewOwnerDocument();
1584 void HTMLInputElement::didMoveToNewOwnerDocument()
1586 if (inputType() == PASSWORD)
1587 document()->registerForCacheCallbacks(this);
1589 HTMLFormControlElementWithState::didMoveToNewOwnerDocument();
1592 void HTMLInputElement::getSubresourceAttributeStrings(Vector<String>& urls) const
1594 urls.append(src().string());