Index: third_party/WebKit/WebCore/html/HTMLInputElement.cpp |
=================================================================== |
--- third_party/WebKit/WebCore/html/HTMLInputElement.cpp (revision 9383) |
+++ third_party/WebKit/WebCore/html/HTMLInputElement.cpp (working copy) |
@@ -1,1560 +1,1560 @@ |
-/* |
- * 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. |
- * (C) 2006 Alexey Proskuryakov (ap@nypop.com) |
- * Copyright (C) 2007 Samuel Weinig (sam@webkit.org) |
- * |
- * This library is free software; you can redistribute it and/or |
- * modify it under the terms of the GNU Library General Public |
- * License as published by the Free Software Foundation; either |
- * version 2 of the License, or (at your option) any later version. |
- * |
- * This library is distributed in the hope that it will be useful, |
- * but WITHOUT ANY WARRANTY; without even the implied warranty of |
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
- * Library General Public License for more details. |
- * |
- * You should have received a copy of the GNU Library General Public License |
- * along with this library; see the file COPYING.LIB. If not, write to |
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
- * Boston, MA 02110-1301, USA. |
- * |
- */ |
- |
-#include "config.h" |
-#include "HTMLInputElement.h" |
- |
-#include "ChromeClient.h" |
-#include "CSSPropertyNames.h" |
-#include "Document.h" |
-#include "Editor.h" |
-#include "Event.h" |
-#include "EventHandler.h" |
-#include "EventNames.h" |
-#include "File.h" |
-#include "FileList.h" |
-#include "FocusController.h" |
-#include "FormDataList.h" |
-#include "Frame.h" |
-#include "HTMLFormElement.h" |
-#include "HTMLImageLoader.h" |
-#include "HTMLNames.h" |
-#include "KeyboardEvent.h" |
-#include "LocalizedStrings.h" |
-#include "MouseEvent.h" |
-#include "Page.h" |
-#include "RenderButton.h" |
-#include "RenderFileUploadControl.h" |
-#include "RenderImage.h" |
-#include "RenderSlider.h" |
-#include "RenderText.h" |
-#include "RenderTextControlSingleLine.h" |
-#include "RenderTheme.h" |
-#include "TextEvent.h" |
-#include <wtf/StdLibExtras.h> |
- |
-using namespace std; |
- |
-namespace WebCore { |
- |
-using namespace HTMLNames; |
- |
-const int maxSavedResults = 256; |
- |
-HTMLInputElement::HTMLInputElement(const QualifiedName& tagName, Document* doc, HTMLFormElement* f) |
- : HTMLFormControlElementWithState(tagName, doc, f) |
- , m_data(this, this) |
- , 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)); |
-} |
- |
-HTMLInputElement::~HTMLInputElement() |
-{ |
- if (needsActivationCallback()) |
- document()->unregisterForDocumentActivationCallbacks(this); |
- |
- document()->checkedRadioButtons().removeButton(this); |
- |
- // Need to remove this from the form while it is still an HTMLInputElement, |
- // so can't wait for the base class's destructor to do it. |
- removeFromForm(); |
-} |
- |
-const AtomicString& HTMLInputElement::name() const |
-{ |
- return m_data.name(); |
-} |
- |
-bool HTMLInputElement::autoComplete() const |
-{ |
- if (m_autocomplete != Uninitialized) |
- return m_autocomplete == On; |
- |
- // Assuming we're still in a Form, respect the Form's setting |
- if (HTMLFormElement* form = this->form()) |
- return form->autoComplete(); |
- |
- // The default is true |
- return true; |
-} |
- |
- |
-static inline HTMLFormElement::CheckedRadioButtons& checkedRadioButtons(const HTMLInputElement *element) |
-{ |
- if (HTMLFormElement* form = element->form()) |
- return form->checkedRadioButtons(); |
- |
- return element->document()->checkedRadioButtons(); |
-} |
- |
-bool HTMLInputElement::isKeyboardFocusable(KeyboardEvent* event) const |
-{ |
- // If text fields can be focused, then they should always be keyboard focusable |
- if (isTextField()) |
- return HTMLFormControlElementWithState::isFocusable(); |
- |
- // If the base class says we can't be focused, then we can stop now. |
- if (!HTMLFormControlElementWithState::isKeyboardFocusable(event)) |
- return false; |
- |
- if (inputType() == RADIO) { |
- |
- // Never allow keyboard tabbing to leave you in the same radio group. Always |
- // skip any other elements in the group. |
- Node* currentFocusedNode = document()->focusedNode(); |
- if (currentFocusedNode && currentFocusedNode->hasTagName(inputTag)) { |
- HTMLInputElement* focusedInput = static_cast<HTMLInputElement*>(currentFocusedNode); |
- if (focusedInput->inputType() == RADIO && focusedInput->form() == form() && |
- focusedInput->name() == name()) |
- return false; |
- } |
- |
- // Allow keyboard focus if we're checked or if nothing in the group is checked. |
- return checked() || !checkedRadioButtons(this).checkedButtonForGroup(name()); |
- } |
- |
- return true; |
-} |
- |
-bool HTMLInputElement::isMouseFocusable() const |
-{ |
- if (isTextField()) |
- return HTMLFormControlElementWithState::isFocusable(); |
- return HTMLFormControlElementWithState::isMouseFocusable(); |
-} |
- |
-void HTMLInputElement::updateFocusAppearance(bool restorePreviousSelection) |
-{ |
- if (isTextField()) |
- InputElement::updateFocusAppearance(m_data, document(), restorePreviousSelection); |
- else |
- HTMLFormControlElementWithState::updateFocusAppearance(restorePreviousSelection); |
-} |
- |
-void HTMLInputElement::aboutToUnload() |
-{ |
- InputElement::aboutToUnload(m_data, document()); |
-} |
- |
-bool HTMLInputElement::shouldUseInputMethod() const |
-{ |
- return m_type == TEXT || m_type == SEARCH || m_type == ISINDEX; |
-} |
- |
-void HTMLInputElement::dispatchFocusEvent() |
-{ |
- InputElement::dispatchFocusEvent(m_data, document()); |
- |
- if (isTextField()) |
- m_autofilled = false; |
- |
- HTMLFormControlElementWithState::dispatchFocusEvent(); |
-} |
- |
-void HTMLInputElement::dispatchBlurEvent() |
-{ |
- InputElement::dispatchBlurEvent(m_data, document()); |
- HTMLFormControlElementWithState::dispatchBlurEvent(); |
-} |
- |
-void HTMLInputElement::setType(const String& t) |
-{ |
- if (t.isEmpty()) { |
- int exccode; |
- removeAttribute(typeAttr, exccode); |
- } else |
- setAttribute(typeAttr, t); |
-} |
- |
-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 |
- newType = TEXT; |
- |
- // 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 |
- // field's value to something like /etc/passwd and then change it to a file field. |
- if (inputType() != newType) { |
- if (newType == FILE && m_haveType) |
- // Set the attribute back to the old value. |
- // Useful in case we were called from inside parseMappedAttribute. |
- setAttribute(typeAttr, type()); |
- else { |
- checkedRadioButtons(this).removeButton(this); |
- |
- if (newType == FILE && !m_fileList) |
- m_fileList = FileList::create(); |
- |
- bool wasAttached = attached(); |
- if (wasAttached) |
- detach(); |
- |
- bool didStoreValue = storesValueSeparateFromAttribute(); |
- bool wasPasswordField = inputType() == PASSWORD; |
- bool didRespectHeightAndWidth = respectHeightAndWidthAttrs(); |
- m_type = newType; |
- bool willStoreValue = storesValueSeparateFromAttribute(); |
- bool isPasswordField = inputType() == PASSWORD; |
- bool willRespectHeightAndWidth = respectHeightAndWidthAttrs(); |
- |
- if (didStoreValue && !willStoreValue && !m_data.value().isNull()) { |
- setAttribute(valueAttr, m_data.value()); |
- m_data.setValue(String()); |
- } |
- if (!didStoreValue && willStoreValue) |
- m_data.setValue(constrainValue(getAttribute(valueAttr))); |
- else |
- InputElement::updateValueIfNeeded(m_data); |
- |
- if (wasPasswordField && !isPasswordField) |
- unregisterForActivationCallbackIfNeeded(); |
- else if (!wasPasswordField && isPasswordField) |
- registerForActivationCallbackIfNeeded(); |
- |
- if (didRespectHeightAndWidth != willRespectHeightAndWidth) { |
- NamedMappedAttrMap* map = mappedAttributes(); |
- if (Attribute* height = map->getAttributeItem(heightAttr)) |
- attributeChanged(height, false); |
- if (Attribute* width = map->getAttributeItem(widthAttr)) |
- attributeChanged(width, false); |
- if (Attribute* align = map->getAttributeItem(alignAttr)) |
- attributeChanged(align, false); |
- } |
- |
- if (wasAttached) { |
- attach(); |
- if (document()->focusedNode() == this) |
- updateFocusAppearance(true); |
- } |
- |
- checkedRadioButtons(this).addButton(this); |
- } |
- |
- InputElement::notifyFormStateChanged(m_data, document()); |
- } |
- m_haveType = true; |
- |
- if (inputType() != IMAGE && m_imageLoader) |
- m_imageLoader.clear(); |
-} |
- |
-const AtomicString& HTMLInputElement::type() 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 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 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 TEXT: { |
- DEFINE_STATIC_LOCAL(const AtomicString, text, ("text")); |
- return text; |
- } |
- } |
- return emptyAtom; |
-} |
- |
-bool HTMLInputElement::saveState(String& result) const |
-{ |
- if (!autoComplete()) |
- return false; |
- |
- switch (inputType()) { |
- case BUTTON: |
- case FILE: |
- case HIDDEN: |
- case IMAGE: |
- case ISINDEX: |
- case RANGE: |
- case RESET: |
- case SEARCH: |
- case SUBMIT: |
- case TEXT: |
- result = value(); |
- return true; |
- case CHECKBOX: |
- case RADIO: |
- result = checked() ? "on" : "off"; |
- return true; |
- case PASSWORD: |
- return false; |
- } |
- ASSERT_NOT_REACHED(); |
- return false; |
-} |
- |
-void HTMLInputElement::restoreState(const String& state) |
-{ |
- ASSERT(inputType() != PASSWORD); // should never save/restore password fields |
- switch (inputType()) { |
- case BUTTON: |
- case FILE: |
- case HIDDEN: |
- case IMAGE: |
- case ISINDEX: |
- case RANGE: |
- case RESET: |
- case SEARCH: |
- case SUBMIT: |
- case TEXT: |
- setValue(state); |
- break; |
- case CHECKBOX: |
- case RADIO: |
- setChecked(state == "on"); |
- break; |
- case PASSWORD: |
- break; |
- } |
-} |
- |
-bool HTMLInputElement::canStartSelection() const |
-{ |
- if (!isTextField()) |
- return false; |
- return HTMLFormControlElementWithState::canStartSelection(); |
-} |
- |
-bool HTMLInputElement::canHaveSelection() const |
-{ |
- return isTextField(); |
-} |
- |
-int HTMLInputElement::selectionStart() const |
-{ |
- if (!isTextField()) |
- return 0; |
- if (document()->focusedNode() != this && m_data.cachedSelectionStart() != -1) |
- return m_data.cachedSelectionStart(); |
- if (!renderer()) |
- return 0; |
- return static_cast<RenderTextControl*>(renderer())->selectionStart(); |
-} |
- |
-int HTMLInputElement::selectionEnd() const |
-{ |
- if (!isTextField()) |
- return 0; |
- if (document()->focusedNode() != this && m_data.cachedSelectionEnd() != -1) |
- return m_data.cachedSelectionEnd(); |
- if (!renderer()) |
- return 0; |
- return static_cast<RenderTextControl*>(renderer())->selectionEnd(); |
-} |
- |
-void HTMLInputElement::setSelectionStart(int start) |
-{ |
- if (!isTextField()) |
- return; |
- if (!renderer()) |
- return; |
- static_cast<RenderTextControl*>(renderer())->setSelectionStart(start); |
-} |
- |
-void HTMLInputElement::setSelectionEnd(int end) |
-{ |
- if (!isTextField()) |
- return; |
- if (!renderer()) |
- return; |
- static_cast<RenderTextControl*>(renderer())->setSelectionEnd(end); |
-} |
- |
-void HTMLInputElement::select() |
-{ |
- if (!isTextField()) |
- return; |
- if (!renderer()) |
- return; |
- static_cast<RenderTextControl*>(renderer())->select(); |
-} |
- |
-void HTMLInputElement::setSelectionRange(int start, int end) |
-{ |
- InputElement::updateSelectionRange(m_data, start, end); |
-} |
- |
-void HTMLInputElement::accessKeyAction(bool sendToAnyElement) |
-{ |
- switch (inputType()) { |
- case BUTTON: |
- case CHECKBOX: |
- case FILE: |
- case IMAGE: |
- case RADIO: |
- case RANGE: |
- case RESET: |
- case SUBMIT: |
- focus(false); |
- // send the mouse button events iff the caller specified sendToAnyElement |
- dispatchSimulatedClick(0, sendToAnyElement); |
- break; |
- case HIDDEN: |
- // a no-op for this type |
- break; |
- case ISINDEX: |
- case PASSWORD: |
- case SEARCH: |
- case TEXT: |
- // should never restore previous selection here |
- focus(false); |
- break; |
- } |
-} |
- |
-bool HTMLInputElement::mapToEntry(const QualifiedName& attrName, MappedAttributeEntry& result) const |
-{ |
- if (((attrName == heightAttr || attrName == widthAttr) && respectHeightAndWidthAttrs()) || |
- attrName == vspaceAttr || |
- attrName == hspaceAttr) { |
- result = eUniversal; |
- return false; |
- } |
- |
- if (attrName == alignAttr) { |
- if (inputType() == IMAGE) { |
- // Share with <img> since the alignment behavior is the same. |
- result = eReplaced; |
- return false; |
- } |
- } |
- |
- return HTMLElement::mapToEntry(attrName, result); |
-} |
- |
-void HTMLInputElement::parseMappedAttribute(MappedAttribute *attr) |
-{ |
- if (attr->name() == nameAttr) { |
- checkedRadioButtons(this).removeButton(this); |
- m_data.setName(attr->value()); |
- checkedRadioButtons(this).addButton(this); |
- } else if (attr->name() == autocompleteAttr) { |
- if (equalIgnoringCase(attr->value(), "off")) { |
- m_autocomplete = Off; |
- registerForActivationCallbackIfNeeded(); |
- } else { |
- if (m_autocomplete == Off) |
- unregisterForActivationCallbackIfNeeded(); |
- if (attr->isEmpty()) |
- m_autocomplete = Uninitialized; |
- else |
- m_autocomplete = On; |
- } |
- } else if (attr->name() == typeAttr) { |
- 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_data.value().isNull()) |
- setChanged(); |
- setValueMatchesRenderer(false); |
- } else if (attr->name() == checkedAttr) { |
- m_defaultChecked = !attr->isNull(); |
- if (m_useDefaultChecked) { |
- setChecked(m_defaultChecked); |
- m_useDefaultChecked = true; |
- } |
- } else if (attr->name() == maxlengthAttr) |
- InputElement::parseMaxLengthAttribute(m_data, attr); |
- else if (attr->name() == sizeAttr) |
- InputElement::parseSizeAttribute(m_data, attr); |
- else if (attr->name() == altAttr) { |
- if (renderer() && inputType() == IMAGE) |
- toRenderImage(renderer())->updateAltText(); |
- } else if (attr->name() == srcAttr) { |
- if (renderer() && inputType() == IMAGE) { |
- if (!m_imageLoader) |
- m_imageLoader.set(new HTMLImageLoader(this)); |
- m_imageLoader->updateFromElementIgnoringPreviousError(); |
- } |
- } else if (attr->name() == usemapAttr || |
- attr->name() == accesskeyAttr) { |
- // FIXME: ignore for the moment |
- } else if (attr->name() == vspaceAttr) { |
- addCSSLength(attr, CSSPropertyMarginTop, attr->value()); |
- addCSSLength(attr, CSSPropertyMarginBottom, attr->value()); |
- } else if (attr->name() == hspaceAttr) { |
- addCSSLength(attr, CSSPropertyMarginLeft, attr->value()); |
- addCSSLength(attr, CSSPropertyMarginRight, attr->value()); |
- } else if (attr->name() == alignAttr) { |
- if (inputType() == IMAGE) |
- addHTMLAlignment(attr); |
- } else if (attr->name() == widthAttr) { |
- if (respectHeightAndWidthAttrs()) |
- addCSSLength(attr, CSSPropertyWidth, attr->value()); |
- } else if (attr->name() == heightAttr) { |
- if (respectHeightAndWidthAttrs()) |
- addCSSLength(attr, CSSPropertyHeight, attr->value()); |
- } else if (attr->name() == onfocusAttr) { |
- setInlineEventListenerForTypeAndAttribute(eventNames().focusEvent, attr); |
- } else if (attr->name() == onblurAttr) { |
- setInlineEventListenerForTypeAndAttribute(eventNames().blurEvent, attr); |
- } else if (attr->name() == onselectAttr) { |
- setInlineEventListenerForTypeAndAttribute(eventNames().selectEvent, attr); |
- } else if (attr->name() == onchangeAttr) { |
- setInlineEventListenerForTypeAndAttribute(eventNames().changeEvent, attr); |
- } else if (attr->name() == oninputAttr) { |
- setInlineEventListenerForTypeAndAttribute(eventNames().inputEvent, 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); |
- } else if (attr->name() == resultsAttr) { |
- int oldResults = m_maxResults; |
- m_maxResults = !attr->isNull() ? min(attr->value().toInt(), maxSavedResults) : -1; |
- // FIXME: Detaching just for maxResults change is not ideal. We should figure out the right |
- // time to relayout for this change. |
- if (m_maxResults != oldResults && (m_maxResults <= 0 || oldResults <= 0) && attached()) { |
- detach(); |
- attach(); |
- } |
- setChanged(); |
- } else if (attr->name() == placeholderAttr) { |
- if (isTextField()) |
- InputElement::updatePlaceholderVisibility(m_data, document(), true); |
- } else if (attr->name() == autosaveAttr || |
- attr->name() == incrementalAttr || |
- attr->name() == minAttr || |
- attr->name() == maxAttr || |
- attr->name() == multipleAttr || |
- attr->name() == precisionAttr) |
- setChanged(); |
- else |
- HTMLFormControlElementWithState::parseMappedAttribute(attr); |
-} |
- |
-bool HTMLInputElement::rendererIsNeeded(RenderStyle *style) |
-{ |
- switch (inputType()) { |
- case BUTTON: |
- case CHECKBOX: |
- case FILE: |
- case IMAGE: |
- case ISINDEX: |
- case PASSWORD: |
- case RADIO: |
- case RANGE: |
- case RESET: |
- case SEARCH: |
- case SUBMIT: |
- case TEXT: |
- return HTMLFormControlElementWithState::rendererIsNeeded(style); |
- case HIDDEN: |
- return false; |
- } |
- ASSERT(false); |
- return false; |
-} |
- |
-RenderObject *HTMLInputElement::createRenderer(RenderArena *arena, RenderStyle *style) |
-{ |
- switch (inputType()) { |
- case BUTTON: |
- case RESET: |
- case SUBMIT: |
- return new (arena) RenderButton(this); |
- case CHECKBOX: |
- case RADIO: |
- return RenderObject::createObject(this, style); |
- case FILE: |
- return new (arena) RenderFileUploadControl(this); |
- case HIDDEN: |
- break; |
- case IMAGE: |
- return new (arena) RenderImage(this); |
- case RANGE: |
- return new (arena) RenderSlider(this); |
- case ISINDEX: |
- case PASSWORD: |
- case SEARCH: |
- case TEXT: |
- return new (arena) RenderTextControlSingleLine(this); |
- } |
- ASSERT(false); |
- return 0; |
-} |
- |
-void HTMLInputElement::attach() |
-{ |
- if (!m_inited) { |
- if (!m_haveType) |
- setInputType(getAttribute(typeAttr)); |
- m_inited = true; |
- } |
- |
- HTMLFormControlElementWithState::attach(); |
- |
- if (inputType() == IMAGE) { |
- if (!m_imageLoader) |
- m_imageLoader.set(new HTMLImageLoader(this)); |
- m_imageLoader->updateFromElement(); |
- if (renderer()) { |
- RenderImage* imageObj = toRenderImage(renderer()); |
- imageObj->setCachedImage(m_imageLoader->image()); |
- |
- // If we have no image at all because we have no src attribute, set |
- // image height and width for the alt text instead. |
- if (!m_imageLoader->image() && !imageObj->cachedImage()) |
- imageObj->setImageSizeForAltText(); |
- } |
- } |
-} |
- |
-void HTMLInputElement::detach() |
-{ |
- HTMLFormControlElementWithState::detach(); |
- setValueMatchesRenderer(false); |
-} |
- |
-String HTMLInputElement::altText() const |
-{ |
- // http://www.w3.org/TR/1998/REC-html40-19980424/appendix/notes.html#altgen |
- // also heavily discussed by Hixie on bugzilla |
- // note this is intentionally different to HTMLImageElement::altText() |
- String alt = getAttribute(altAttr); |
- // fall back to title attribute |
- if (alt.isNull()) |
- alt = getAttribute(titleAttr); |
- if (alt.isNull()) |
- alt = getAttribute(valueAttr); |
- if (alt.isEmpty()) |
- alt = inputElementAltText(); |
- return alt; |
-} |
- |
-bool HTMLInputElement::isSuccessfulSubmitButton() const |
-{ |
- // HTML spec says that buttons must have names to be considered successful. |
- // However, other browsers do not impose this constraint. So we do likewise. |
- return !disabled() && (inputType() == IMAGE || inputType() == SUBMIT); |
-} |
- |
-bool HTMLInputElement::isActivatedSubmit() const |
-{ |
- return m_activeSubmit; |
-} |
- |
-void HTMLInputElement::setActivatedSubmit(bool flag) |
-{ |
- m_activeSubmit = flag; |
-} |
- |
-bool HTMLInputElement::appendFormData(FormDataList& encoding, bool multipart) |
-{ |
- // image generates its own names, but for other types there is no form data unless there's a name |
- if (name().isEmpty() && inputType() != IMAGE) |
- return false; |
- |
- switch (inputType()) { |
- case HIDDEN: |
- case ISINDEX: |
- case PASSWORD: |
- case RANGE: |
- case SEARCH: |
- case TEXT: |
- // always successful |
- encoding.appendData(name(), value()); |
- return true; |
- |
- case CHECKBOX: |
- case RADIO: |
- if (checked()) { |
- encoding.appendData(name(), value()); |
- return true; |
- } |
- break; |
- |
- case BUTTON: |
- case RESET: |
- // these types of buttons are never successful |
- return false; |
- |
- case IMAGE: |
- if (m_activeSubmit) { |
- 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; |
- } |
- break; |
- |
- case SUBMIT: |
- if (m_activeSubmit) { |
- String enc_str = valueWithDefault(); |
- encoding.appendData(name(), enc_str); |
- return true; |
- } |
- break; |
- |
- case FILE: { |
- // Can't submit file on GET. |
- if (!multipart) |
- return false; |
- |
- // If no filename at all is entered, return successful but empty. |
- // Null would be more logical, but Netscape posts an empty file. Argh. |
- unsigned numFiles = m_fileList->length(); |
- if (!numFiles) { |
- encoding.appendFile(name(), File::create("")); |
- return true; |
- } |
- |
- for (unsigned i = 0; i < numFiles; ++i) |
- encoding.appendFile(name(), m_fileList->item(i)); |
- return true; |
- } |
- } |
- return false; |
-} |
- |
-void HTMLInputElement::reset() |
-{ |
- if (storesValueSeparateFromAttribute()) |
- setValue(String()); |
- |
- setChecked(m_defaultChecked); |
- m_useDefaultChecked = true; |
-} |
- |
-void HTMLInputElement::setChecked(bool nowChecked, bool sendChangeEvent) |
-{ |
- if (checked() == nowChecked) |
- return; |
- |
- checkedRadioButtons(this).removeButton(this); |
- |
- m_useDefaultChecked = false; |
- m_checked = nowChecked; |
- setChanged(); |
- |
- checkedRadioButtons(this).addButton(this); |
- |
- if (renderer() && renderer()->style()->hasAppearance()) |
- theme()->stateChanged(renderer(), CheckedState); |
- |
- // Only send a change event for items in the document (avoid firing during |
- // parsing) and don't send a change event for a radio button that's getting |
- // unchecked to match other browsers. DOM is not a useful standard for this |
- // 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(); |
-} |
- |
-void HTMLInputElement::setIndeterminate(bool _indeterminate) |
-{ |
- // Only checkboxes honor indeterminate. |
- if (inputType() != CHECKBOX || indeterminate() == _indeterminate) |
- return; |
- |
- m_indeterminate = _indeterminate; |
- |
- setChanged(); |
- |
- if (renderer() && renderer()->style()->hasAppearance()) |
- theme()->stateChanged(renderer(), CheckedState); |
-} |
- |
-int HTMLInputElement::size() const |
-{ |
- return m_data.size(); |
-} |
- |
-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; |
- m_indeterminate = sourceElement->m_indeterminate; |
- |
- HTMLFormControlElementWithState::copyNonAttributeProperties(source); |
-} |
- |
-String HTMLInputElement::value() const |
-{ |
- // The HTML5 spec (as of the 10/24/08 working draft) says that the value attribute isn't applicable to the file upload control |
- // but we don't want to break existing websites, who may be relying on being able to get the file name as a value. |
- if (inputType() == FILE) { |
- if (!m_fileList->isEmpty()) |
- return m_fileList->item(0)->fileName(); |
- return String(); |
- } |
- |
- String value = m_data.value(); |
- if (value.isNull()) { |
- value = constrainValue(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" : ""; |
- } |
- |
- return value; |
-} |
- |
-String HTMLInputElement::valueWithDefault() const |
-{ |
- String v = value(); |
- if (v.isNull()) { |
- switch (inputType()) { |
- case BUTTON: |
- case CHECKBOX: |
- case FILE: |
- case HIDDEN: |
- case IMAGE: |
- case ISINDEX: |
- case PASSWORD: |
- case RADIO: |
- case RANGE: |
- case SEARCH: |
- case TEXT: |
- break; |
- case RESET: |
- v = resetButtonDefaultLabel(); |
- break; |
- case SUBMIT: |
- v = submitButtonDefaultLabel(); |
- break; |
- } |
- } |
- return v; |
-} |
- |
-void HTMLInputElement::setValue(const String& value) |
-{ |
- // 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 |
- // but we don't want to break existing websites, who may be relying on this method to clear things. |
- if (inputType() == FILE && !value.isEmpty()) |
- return; |
- |
- if (isTextField()) |
- InputElement::updatePlaceholderVisibility(m_data, document()); |
- |
- setValueMatchesRenderer(false); |
- if (storesValueSeparateFromAttribute()) { |
- if (inputType() == FILE) |
- m_fileList->clear(); |
- else { |
- m_data.setValue(constrainValue(value)); |
- if (isTextField() && inDocument()) |
- document()->updateRendering(); |
- } |
- if (renderer()) |
- renderer()->updateFromElement(); |
- setChanged(); |
- } else |
- setAttribute(valueAttr, constrainValue(value)); |
- |
- if (isTextField()) { |
- unsigned max = m_data.value().length(); |
- if (document()->focusedNode() == this) |
- InputElement::updateSelectionRange(m_data, max, max); |
- else |
- cacheSelection(max, max); |
- } |
- InputElement::notifyFormStateChanged(m_data, document()); |
-} |
- |
-String HTMLInputElement::placeholderValue() const |
-{ |
- return getAttribute(placeholderAttr).string(); |
-} |
- |
-bool HTMLInputElement::searchEventsShouldBeDispatched() const |
-{ |
- return hasAttribute(incrementalAttr); |
-} |
- |
-void HTMLInputElement::setValueFromRenderer(const String& value) |
-{ |
- // File upload controls will always use setFileListFromRenderer. |
- ASSERT(inputType() != FILE); |
- InputElement::setValueFromRenderer(m_data, document(), value); |
-} |
- |
-void HTMLInputElement::setFileListFromRenderer(const Vector<String>& paths) |
-{ |
- m_fileList->clear(); |
- int size = paths.size(); |
- for (int i = 0; i < size; i++) |
- m_fileList->append(File::create(paths[i])); |
- |
- setValueMatchesRenderer(); |
- InputElement::notifyFormStateChanged(m_data, document()); |
-} |
- |
-bool HTMLInputElement::storesValueSeparateFromAttribute() const |
-{ |
- switch (inputType()) { |
- case BUTTON: |
- case CHECKBOX: |
- case HIDDEN: |
- case IMAGE: |
- case RADIO: |
- case RESET: |
- case SUBMIT: |
- return false; |
- case FILE: |
- case ISINDEX: |
- case PASSWORD: |
- case RANGE: |
- case SEARCH: |
- case TEXT: |
- return true; |
- } |
- return false; |
-} |
- |
-void* HTMLInputElement::preDispatchEventHandler(Event *evt) |
-{ |
- // preventDefault or "return false" are used to reverse the automatic checking/selection we do here. |
- // This result gives us enough info to perform the "undo" in postDispatch of the action we take here. |
- void* result = 0; |
- if ((inputType() == CHECKBOX || inputType() == RADIO) && evt->isMouseEvent() |
- && evt->type() == eventNames().clickEvent && static_cast<MouseEvent*>(evt)->button() == LeftButton) { |
- 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; |
- setIndeterminate(false); |
- } else { |
- if (checked()) |
- result = (void*)0x1; |
- 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. |
- // 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. |
- HTMLInputElement* currRadio = checkedRadioButtons(this).checkedButtonForGroup(name()); |
- 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; |
- } |
- setChecked(true, true); |
- } |
- } |
- return result; |
-} |
- |
-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); |
- } |
- } |
- input->deref(); |
- } |
- |
- // Left clicks on radio buttons and check boxes already performed default actions in preDispatchEventHandler(). |
- evt->setDefaultHandled(); |
- } |
-} |
- |
-void HTMLInputElement::defaultEventHandler(Event* evt) |
-{ |
- bool clickDefaultFormButton = false; |
- |
- if (isTextField() && evt->type() == eventNames().textInputEvent && evt->isTextEvent() && static_cast<TextEvent*>(evt)->data() == "\n") |
- clickDefaultFormButton = true; |
- |
- if (inputType() == IMAGE && evt->isMouseEvent() && evt->type() == eventNames().clickEvent) { |
- // record the mouse position for when we get the DOMActivate event |
- MouseEvent* me = static_cast<MouseEvent*>(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()) { |
- m_xPos = 0; |
- m_yPos = 0; |
- } else { |
- // FIXME: This doesn't work correctly with transforms. |
- IntPoint absOffset = roundedIntPoint(renderer()->localToAbsolute()); |
- 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))) { |
- evt->setDefaultHandled(); |
- return; |
- } |
- |
- if (inputType() == RADIO && evt->isMouseEvent() |
- && evt->type() == eventNames().clickEvent && static_cast<MouseEvent*>(evt)->button() == LeftButton) { |
- evt->setDefaultHandled(); |
- return; |
- } |
- |
- // Let the key handling done in Node take precedence over the event handling here for editable text fields |
- if (!clickDefaultFormButton) { |
- HTMLFormControlElementWithState::defaultEventHandler(evt); |
- if (evt->defaultHandled()) |
- return; |
- } |
- |
- // DOMActivate events cause the input to be "activated" - in the case of image and submit inputs, this means |
- // actually submitting the form. For reset inputs, the form is reset. These events are sent when the user clicks |
- // on the element, or presses enter while it is the active element. JavaScript code wishing to activate the element |
- // must dispatch a DOMActivate event - a click event will not do the job. |
- if (evt->type() == eventNames().DOMActivateEvent && !disabled()) { |
- if (inputType() == IMAGE || inputType() == SUBMIT || inputType() == RESET) { |
- if (!form()) |
- return; |
- if (inputType() == RESET) |
- form()->reset(); |
- else { |
- m_activeSubmit = true; |
- // 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 m_xPos and m_yPos altogether! |
- if (!form()->prepareSubmit(evt)) { |
- m_xPos = 0; |
- m_yPos = 0; |
- } |
- m_activeSubmit = false; |
- } |
- } else if (inputType() == FILE && renderer()) |
- static_cast<RenderFileUploadControl*>(renderer())->click(); |
- } |
- |
- // Use key press event here since sending simulated mouse events |
- // on key down blocks the proper sending of the key press event. |
- if (evt->type() == eventNames().keypressEvent && evt->isKeyboardEvent()) { |
- bool clickElement = false; |
- |
- int charCode = static_cast<KeyboardEvent*>(evt)->charCode(); |
- |
- if (charCode == '\r') { |
- switch (inputType()) { |
- case CHECKBOX: |
- case HIDDEN: |
- case ISINDEX: |
- case PASSWORD: |
- case RANGE: |
- case SEARCH: |
- case TEXT: |
- // Simulate mouse click on the default form button for enter for these types of elements. |
- clickDefaultFormButton = true; |
- break; |
- case BUTTON: |
- case FILE: |
- case IMAGE: |
- case RESET: |
- case SUBMIT: |
- // Simulate mouse click for enter for these types of elements. |
- clickElement = true; |
- break; |
- case RADIO: |
- break; // Don't do anything for enter on a radio button. |
- } |
- } else if (charCode == ' ') { |
- switch (inputType()) { |
- case BUTTON: |
- case CHECKBOX: |
- case FILE: |
- case IMAGE: |
- case RESET: |
- case SUBMIT: |
- case RADIO: |
- // Prevent scrolling down the page. |
- evt->setDefaultHandled(); |
- return; |
- default: |
- break; |
- } |
- } |
- |
- if (clickElement) { |
- dispatchSimulatedClick(evt); |
- evt->setDefaultHandled(); |
- return; |
- } |
- } |
- |
- if (evt->type() == eventNames().keydownEvent && evt->isKeyboardEvent()) { |
- String key = static_cast<KeyboardEvent*>(evt)->keyIdentifier(); |
- |
- if (key == "U+0020") { |
- switch (inputType()) { |
- case BUTTON: |
- case CHECKBOX: |
- case FILE: |
- case IMAGE: |
- case RESET: |
- case SUBMIT: |
- case RADIO: |
- setActive(true, true); |
- // No setDefaultHandled() - IE dispatches a keypress in this case. |
- return; |
- default: |
- break; |
- } |
- } |
- |
- if (inputType() == RADIO && (key == "Up" || key == "Down" || key == "Left" || key == "Right")) { |
- // Left and up mean "previous radio button". |
- // Right and down mean "next radio button". |
- // Tested in WinIE, and even for RTL, left still means previous radio button (and so moves |
- // to the right). Seems strange, but we'll match it. |
- bool forward = (key == "Down" || key == "Right"); |
- |
- // We can only stay within the form's children if the form hasn't been demoted to a leaf because |
- // of malformed HTML. |
- Node* n = this; |
- while ((n = (forward ? n->traverseNextNode() : n->traversePreviousNode()))) { |
- // Once we encounter a form element, we know we're through. |
- if (n->hasTagName(formTag)) |
- break; |
- |
- // Look for more radio buttons. |
- if (n->hasTagName(inputTag)) { |
- HTMLInputElement* elt = static_cast<HTMLInputElement*>(n); |
- if (elt->form() != form()) |
- break; |
- if (n->hasTagName(inputTag)) { |
- HTMLInputElement* inputElt = static_cast<HTMLInputElement*>(n); |
- if (inputElt->inputType() == RADIO && inputElt->name() == name() && inputElt->isFocusable()) { |
- inputElt->setChecked(true); |
- document()->setFocusedNode(inputElt); |
- inputElt->dispatchSimulatedClick(evt, false, false); |
- evt->setDefaultHandled(); |
- break; |
- } |
- } |
- } |
- } |
- } |
- } |
- |
- if (evt->type() == eventNames().keyupEvent && evt->isKeyboardEvent()) { |
- bool clickElement = false; |
- |
- String key = static_cast<KeyboardEvent*>(evt)->keyIdentifier(); |
- |
- if (key == "U+0020") { |
- switch (inputType()) { |
- case BUTTON: |
- case CHECKBOX: |
- case FILE: |
- case IMAGE: |
- case RESET: |
- case SUBMIT: |
- // Simulate mouse click for spacebar for these types of elements. |
- // The AppKit already does this for some, but not all, of them. |
- clickElement = true; |
- break; |
- case RADIO: |
- // If an unselected radio is tabbed into (because the entire group has nothing |
- // checked, or because of some explicit .focus() call), then allow space to check it. |
- if (!checked()) |
- clickElement = true; |
- break; |
- case HIDDEN: |
- case ISINDEX: |
- case PASSWORD: |
- case RANGE: |
- case SEARCH: |
- case TEXT: |
- break; |
- } |
- } |
- |
- if (clickElement) { |
- if (active()) |
- dispatchSimulatedClick(evt); |
- evt->setDefaultHandled(); |
- return; |
- } |
- } |
- |
- if (clickDefaultFormButton) { |
- if (isSearchField()) { |
- addSearchResult(); |
- onSearch(); |
- } |
- // Fire onChange for text fields. |
- RenderObject* r = renderer(); |
- if (r && r->isTextField() && r->isEdited()) { |
- onChange(); |
- // Refetch the renderer since arbitrary JS code run during onchange can do anything, including destroying it. |
- r = renderer(); |
- if (r) |
- r->setEdited(false); |
- } |
- // Form may never have been present, or may have been destroyed by the change event. |
- if (form()) |
- form()->submitClick(evt); |
- evt->setDefaultHandled(); |
- return; |
- } |
- |
- if (evt->isBeforeTextInsertedEvent()) |
- InputElement::handleBeforeTextInsertedEvent(m_data, 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); |
- } |
-} |
- |
-bool HTMLInputElement::isURLAttribute(Attribute *attr) const |
-{ |
- return (attr->name() == srcAttr); |
-} |
- |
-String HTMLInputElement::defaultValue() const |
-{ |
- return getAttribute(valueAttr); |
-} |
- |
-void HTMLInputElement::setDefaultValue(const String &value) |
-{ |
- setAttribute(valueAttr, value); |
-} |
- |
-bool HTMLInputElement::defaultChecked() const |
-{ |
- return !getAttribute(checkedAttr).isNull(); |
-} |
- |
-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); |
-} |
- |
-void HTMLInputElement::setAccept(const String &value) |
-{ |
- setAttribute(acceptAttr, value); |
-} |
- |
-String HTMLInputElement::accessKey() const |
-{ |
- return getAttribute(accesskeyAttr); |
-} |
- |
-void HTMLInputElement::setAccessKey(const String &value) |
-{ |
- setAttribute(accesskeyAttr, value); |
-} |
- |
-String HTMLInputElement::align() const |
-{ |
- return getAttribute(alignAttr); |
-} |
- |
-void HTMLInputElement::setAlign(const String &value) |
-{ |
- setAttribute(alignAttr, value); |
-} |
- |
-String HTMLInputElement::alt() const |
-{ |
- return getAttribute(altAttr); |
-} |
- |
-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)); |
-} |
- |
-void HTMLInputElement::setSize(unsigned _size) |
-{ |
- setAttribute(sizeAttr, String::number(_size)); |
-} |
- |
-KURL HTMLInputElement::src() const |
-{ |
- return document()->completeURL(getAttribute(srcAttr)); |
-} |
- |
-void HTMLInputElement::setSrc(const String &value) |
-{ |
- setAttribute(srcAttr, value); |
-} |
- |
-String HTMLInputElement::useMap() const |
-{ |
- return getAttribute(usemapAttr); |
-} |
- |
-void HTMLInputElement::setUseMap(const String &value) |
-{ |
- setAttribute(usemapAttr, value); |
-} |
- |
-void HTMLInputElement::setAutofilled(bool b) |
-{ |
- if (b == m_autofilled) |
- return; |
- |
- m_autofilled = b; |
- setChanged(); |
-} |
- |
-FileList* HTMLInputElement::files() |
-{ |
- if (inputType() != FILE) |
- return 0; |
- return m_fileList.get(); |
-} |
- |
-String HTMLInputElement::constrainValue(const String& proposedValue) const |
-{ |
- return InputElement::constrainValue(m_data, proposedValue, m_data.maxLength()); |
-} |
- |
-bool HTMLInputElement::needsActivationCallback() |
-{ |
- return inputType() == PASSWORD || m_autocomplete == Off; |
-} |
- |
-void HTMLInputElement::registerForActivationCallbackIfNeeded() |
-{ |
- if (needsActivationCallback()) |
- document()->registerForDocumentActivationCallbacks(this); |
-} |
- |
-void HTMLInputElement::unregisterForActivationCallbackIfNeeded() |
-{ |
- if (!needsActivationCallback()) |
- document()->unregisterForDocumentActivationCallbacks(this); |
-} |
- |
-void HTMLInputElement::cacheSelection(int start, int end) |
-{ |
- m_data.setCachedSelectionStart(start); |
- m_data.setCachedSelectionEnd(end); |
-} |
- |
-void HTMLInputElement::addSearchResult() |
-{ |
- ASSERT(isSearchField()); |
- if (renderer()) |
- static_cast<RenderTextControlSingleLine*>(renderer())->addSearchResult(); |
-} |
- |
-void HTMLInputElement::onSearch() |
-{ |
- ASSERT(isSearchField()); |
- if (renderer()) |
- static_cast<RenderTextControlSingleLine*>(renderer())->stopSearchEventTimer(); |
- dispatchEventForType(eventNames().searchEvent, true, false); |
-} |
- |
-Selection HTMLInputElement::selection() const |
-{ |
- if (!renderer() || !isTextField() || m_data.cachedSelectionStart() == -1 || m_data.cachedSelectionEnd() == -1) |
- return Selection(); |
- return static_cast<RenderTextControl*>(renderer())->selection(m_data.cachedSelectionStart(), m_data.cachedSelectionEnd()); |
-} |
- |
-void HTMLInputElement::documentDidBecomeActive() |
-{ |
- ASSERT(needsActivationCallback()); |
- reset(); |
-} |
- |
-void HTMLInputElement::willMoveToNewOwnerDocument() |
-{ |
- // Always unregister for cache callbacks when leaving a document, even if we would otherwise like to be registered |
- if (needsActivationCallback()) |
- document()->unregisterForDocumentActivationCallbacks(this); |
- |
- document()->checkedRadioButtons().removeButton(this); |
- |
- HTMLFormControlElementWithState::willMoveToNewOwnerDocument(); |
-} |
- |
-void HTMLInputElement::didMoveToNewOwnerDocument() |
-{ |
- registerForActivationCallbackIfNeeded(); |
- |
- HTMLFormControlElementWithState::didMoveToNewOwnerDocument(); |
-} |
- |
-void HTMLInputElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const |
-{ |
- HTMLFormControlElementWithState::addSubresourceAttributeURLs(urls); |
- |
- addSubresourceURL(urls, src()); |
-} |
- |
-bool HTMLInputElement::willValidate() const |
-{ |
- // FIXME: This shall check for new WF2 input types too |
- return HTMLFormControlElementWithState::willValidate() && inputType() != HIDDEN && |
- inputType() != BUTTON && inputType() != RESET; |
-} |
- |
-bool HTMLInputElement::placeholderShouldBeVisible() const |
-{ |
- return m_data.placeholderShouldBeVisible(); |
-} |
- |
-} // namespace |
- |
- |
+/* |
+ * 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. |
+ * (C) 2006 Alexey Proskuryakov (ap@nypop.com) |
+ * Copyright (C) 2007 Samuel Weinig (sam@webkit.org) |
+ * |
+ * This library is free software; you can redistribute it and/or |
+ * modify it under the terms of the GNU Library General Public |
+ * License as published by the Free Software Foundation; either |
+ * version 2 of the License, or (at your option) any later version. |
+ * |
+ * This library is distributed in the hope that it will be useful, |
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of |
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
+ * Library General Public License for more details. |
+ * |
+ * You should have received a copy of the GNU Library General Public License |
+ * along with this library; see the file COPYING.LIB. If not, write to |
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
+ * Boston, MA 02110-1301, USA. |
+ * |
+ */ |
+ |
+#include "config.h" |
+#include "HTMLInputElement.h" |
+ |
+#include "ChromeClient.h" |
+#include "CSSPropertyNames.h" |
+#include "Document.h" |
+#include "Editor.h" |
+#include "Event.h" |
+#include "EventHandler.h" |
+#include "EventNames.h" |
+#include "File.h" |
+#include "FileList.h" |
+#include "FocusController.h" |
+#include "FormDataList.h" |
+#include "Frame.h" |
+#include "HTMLFormElement.h" |
+#include "HTMLImageLoader.h" |
+#include "HTMLNames.h" |
+#include "KeyboardEvent.h" |
+#include "LocalizedStrings.h" |
+#include "MouseEvent.h" |
+#include "Page.h" |
+#include "RenderButton.h" |
+#include "RenderFileUploadControl.h" |
+#include "RenderImage.h" |
+#include "RenderSlider.h" |
+#include "RenderText.h" |
+#include "RenderTextControlSingleLine.h" |
+#include "RenderTheme.h" |
+#include "TextEvent.h" |
+#include <wtf/StdLibExtras.h> |
+ |
+using namespace std; |
+ |
+namespace WebCore { |
+ |
+using namespace HTMLNames; |
+ |
+const int maxSavedResults = 256; |
+ |
+HTMLInputElement::HTMLInputElement(const QualifiedName& tagName, Document* doc, HTMLFormElement* f) |
+ : HTMLFormControlElementWithState(tagName, doc, f) |
+ , m_data(this, this) |
+ , 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)); |
+} |
+ |
+HTMLInputElement::~HTMLInputElement() |
+{ |
+ if (needsActivationCallback()) |
+ document()->unregisterForDocumentActivationCallbacks(this); |
+ |
+ document()->checkedRadioButtons().removeButton(this); |
+ |
+ // Need to remove this from the form while it is still an HTMLInputElement, |
+ // so can't wait for the base class's destructor to do it. |
+ removeFromForm(); |
+} |
+ |
+const AtomicString& HTMLInputElement::name() const |
+{ |
+ return m_data.name(); |
+} |
+ |
+bool HTMLInputElement::autoComplete() const |
+{ |
+ if (m_autocomplete != Uninitialized) |
+ return m_autocomplete == On; |
+ |
+ // Assuming we're still in a Form, respect the Form's setting |
+ if (HTMLFormElement* form = this->form()) |
+ return form->autoComplete(); |
+ |
+ // The default is true |
+ return true; |
+} |
+ |
+ |
+static inline HTMLFormElement::CheckedRadioButtons& checkedRadioButtons(const HTMLInputElement *element) |
+{ |
+ if (HTMLFormElement* form = element->form()) |
+ return form->checkedRadioButtons(); |
+ |
+ return element->document()->checkedRadioButtons(); |
+} |
+ |
+bool HTMLInputElement::isKeyboardFocusable(KeyboardEvent* event) const |
+{ |
+ // If text fields can be focused, then they should always be keyboard focusable |
+ if (isTextField()) |
+ return HTMLFormControlElementWithState::isFocusable(); |
+ |
+ // If the base class says we can't be focused, then we can stop now. |
+ if (!HTMLFormControlElementWithState::isKeyboardFocusable(event)) |
+ return false; |
+ |
+ if (inputType() == RADIO) { |
+ |
+ // Never allow keyboard tabbing to leave you in the same radio group. Always |
+ // skip any other elements in the group. |
+ Node* currentFocusedNode = document()->focusedNode(); |
+ if (currentFocusedNode && currentFocusedNode->hasTagName(inputTag)) { |
+ HTMLInputElement* focusedInput = static_cast<HTMLInputElement*>(currentFocusedNode); |
+ if (focusedInput->inputType() == RADIO && focusedInput->form() == form() && |
+ focusedInput->name() == name()) |
+ return false; |
+ } |
+ |
+ // Allow keyboard focus if we're checked or if nothing in the group is checked. |
+ return checked() || !checkedRadioButtons(this).checkedButtonForGroup(name()); |
+ } |
+ |
+ return true; |
+} |
+ |
+bool HTMLInputElement::isMouseFocusable() const |
+{ |
+ if (isTextField()) |
+ return HTMLFormControlElementWithState::isFocusable(); |
+ return HTMLFormControlElementWithState::isMouseFocusable(); |
+} |
+ |
+void HTMLInputElement::updateFocusAppearance(bool restorePreviousSelection) |
+{ |
+ if (isTextField()) |
+ InputElement::updateFocusAppearance(m_data, document(), restorePreviousSelection); |
+ else |
+ HTMLFormControlElementWithState::updateFocusAppearance(restorePreviousSelection); |
+} |
+ |
+void HTMLInputElement::aboutToUnload() |
+{ |
+ InputElement::aboutToUnload(m_data, document()); |
+} |
+ |
+bool HTMLInputElement::shouldUseInputMethod() const |
+{ |
+ return m_type == TEXT || m_type == SEARCH || m_type == ISINDEX; |
+} |
+ |
+void HTMLInputElement::dispatchFocusEvent() |
+{ |
+ InputElement::dispatchFocusEvent(m_data, document()); |
+ |
+ if (isTextField()) |
+ m_autofilled = false; |
+ |
+ HTMLFormControlElementWithState::dispatchFocusEvent(); |
+} |
+ |
+void HTMLInputElement::dispatchBlurEvent() |
+{ |
+ InputElement::dispatchBlurEvent(m_data, document()); |
+ HTMLFormControlElementWithState::dispatchBlurEvent(); |
+} |
+ |
+void HTMLInputElement::setType(const String& t) |
+{ |
+ if (t.isEmpty()) { |
+ int exccode; |
+ removeAttribute(typeAttr, exccode); |
+ } else |
+ setAttribute(typeAttr, t); |
+} |
+ |
+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 |
+ newType = TEXT; |
+ |
+ // 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 |
+ // field's value to something like /etc/passwd and then change it to a file field. |
+ if (inputType() != newType) { |
+ if (newType == FILE && m_haveType) |
+ // Set the attribute back to the old value. |
+ // Useful in case we were called from inside parseMappedAttribute. |
+ setAttribute(typeAttr, type()); |
+ else { |
+ checkedRadioButtons(this).removeButton(this); |
+ |
+ if (newType == FILE && !m_fileList) |
+ m_fileList = FileList::create(); |
+ |
+ bool wasAttached = attached(); |
+ if (wasAttached) |
+ detach(); |
+ |
+ bool didStoreValue = storesValueSeparateFromAttribute(); |
+ bool wasPasswordField = inputType() == PASSWORD; |
+ bool didRespectHeightAndWidth = respectHeightAndWidthAttrs(); |
+ m_type = newType; |
+ bool willStoreValue = storesValueSeparateFromAttribute(); |
+ bool isPasswordField = inputType() == PASSWORD; |
+ bool willRespectHeightAndWidth = respectHeightAndWidthAttrs(); |
+ |
+ if (didStoreValue && !willStoreValue && !m_data.value().isNull()) { |
+ setAttribute(valueAttr, m_data.value()); |
+ m_data.setValue(String()); |
+ } |
+ if (!didStoreValue && willStoreValue) |
+ m_data.setValue(constrainValue(getAttribute(valueAttr))); |
+ else |
+ InputElement::updateValueIfNeeded(m_data); |
+ |
+ if (wasPasswordField && !isPasswordField) |
+ unregisterForActivationCallbackIfNeeded(); |
+ else if (!wasPasswordField && isPasswordField) |
+ registerForActivationCallbackIfNeeded(); |
+ |
+ if (didRespectHeightAndWidth != willRespectHeightAndWidth) { |
+ NamedMappedAttrMap* map = mappedAttributes(); |
+ if (Attribute* height = map->getAttributeItem(heightAttr)) |
+ attributeChanged(height, false); |
+ if (Attribute* width = map->getAttributeItem(widthAttr)) |
+ attributeChanged(width, false); |
+ if (Attribute* align = map->getAttributeItem(alignAttr)) |
+ attributeChanged(align, false); |
+ } |
+ |
+ if (wasAttached) { |
+ attach(); |
+ if (document()->focusedNode() == this) |
+ updateFocusAppearance(true); |
+ } |
+ |
+ checkedRadioButtons(this).addButton(this); |
+ } |
+ |
+ InputElement::notifyFormStateChanged(m_data, document()); |
+ } |
+ m_haveType = true; |
+ |
+ if (inputType() != IMAGE && m_imageLoader) |
+ m_imageLoader.clear(); |
+} |
+ |
+const AtomicString& HTMLInputElement::type() 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 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 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 TEXT: { |
+ DEFINE_STATIC_LOCAL(const AtomicString, text, ("text")); |
+ return text; |
+ } |
+ } |
+ return emptyAtom; |
+} |
+ |
+bool HTMLInputElement::saveState(String& result) const |
+{ |
+ if (!autoComplete()) |
+ return false; |
+ |
+ switch (inputType()) { |
+ case BUTTON: |
+ case FILE: |
+ case HIDDEN: |
+ case IMAGE: |
+ case ISINDEX: |
+ case RANGE: |
+ case RESET: |
+ case SEARCH: |
+ case SUBMIT: |
+ case TEXT: |
+ result = value(); |
+ return true; |
+ case CHECKBOX: |
+ case RADIO: |
+ result = checked() ? "on" : "off"; |
+ return true; |
+ case PASSWORD: |
+ return false; |
+ } |
+ ASSERT_NOT_REACHED(); |
+ return false; |
+} |
+ |
+void HTMLInputElement::restoreState(const String& state) |
+{ |
+ ASSERT(inputType() != PASSWORD); // should never save/restore password fields |
+ switch (inputType()) { |
+ case BUTTON: |
+ case FILE: |
+ case HIDDEN: |
+ case IMAGE: |
+ case ISINDEX: |
+ case RANGE: |
+ case RESET: |
+ case SEARCH: |
+ case SUBMIT: |
+ case TEXT: |
+ setValue(state); |
+ break; |
+ case CHECKBOX: |
+ case RADIO: |
+ setChecked(state == "on"); |
+ break; |
+ case PASSWORD: |
+ break; |
+ } |
+} |
+ |
+bool HTMLInputElement::canStartSelection() const |
+{ |
+ if (!isTextField()) |
+ return false; |
+ return HTMLFormControlElementWithState::canStartSelection(); |
+} |
+ |
+bool HTMLInputElement::canHaveSelection() const |
+{ |
+ return isTextField(); |
+} |
+ |
+int HTMLInputElement::selectionStart() const |
+{ |
+ if (!isTextField()) |
+ return 0; |
+ if (document()->focusedNode() != this && m_data.cachedSelectionStart() != -1) |
+ return m_data.cachedSelectionStart(); |
+ if (!renderer()) |
+ return 0; |
+ return static_cast<RenderTextControl*>(renderer())->selectionStart(); |
+} |
+ |
+int HTMLInputElement::selectionEnd() const |
+{ |
+ if (!isTextField()) |
+ return 0; |
+ if (document()->focusedNode() != this && m_data.cachedSelectionEnd() != -1) |
+ return m_data.cachedSelectionEnd(); |
+ if (!renderer()) |
+ return 0; |
+ return static_cast<RenderTextControl*>(renderer())->selectionEnd(); |
+} |
+ |
+void HTMLInputElement::setSelectionStart(int start) |
+{ |
+ if (!isTextField()) |
+ return; |
+ if (!renderer()) |
+ return; |
+ static_cast<RenderTextControl*>(renderer())->setSelectionStart(start); |
+} |
+ |
+void HTMLInputElement::setSelectionEnd(int end) |
+{ |
+ if (!isTextField()) |
+ return; |
+ if (!renderer()) |
+ return; |
+ static_cast<RenderTextControl*>(renderer())->setSelectionEnd(end); |
+} |
+ |
+void HTMLInputElement::select() |
+{ |
+ if (!isTextField()) |
+ return; |
+ if (!renderer()) |
+ return; |
+ static_cast<RenderTextControl*>(renderer())->select(); |
+} |
+ |
+void HTMLInputElement::setSelectionRange(int start, int end) |
+{ |
+ InputElement::updateSelectionRange(m_data, start, end); |
+} |
+ |
+void HTMLInputElement::accessKeyAction(bool sendToAnyElement) |
+{ |
+ switch (inputType()) { |
+ case BUTTON: |
+ case CHECKBOX: |
+ case FILE: |
+ case IMAGE: |
+ case RADIO: |
+ case RANGE: |
+ case RESET: |
+ case SUBMIT: |
+ focus(false); |
+ // send the mouse button events iff the caller specified sendToAnyElement |
+ dispatchSimulatedClick(0, sendToAnyElement); |
+ break; |
+ case HIDDEN: |
+ // a no-op for this type |
+ break; |
+ case ISINDEX: |
+ case PASSWORD: |
+ case SEARCH: |
+ case TEXT: |
+ // should never restore previous selection here |
+ focus(false); |
+ break; |
+ } |
+} |
+ |
+bool HTMLInputElement::mapToEntry(const QualifiedName& attrName, MappedAttributeEntry& result) const |
+{ |
+ if (((attrName == heightAttr || attrName == widthAttr) && respectHeightAndWidthAttrs()) || |
+ attrName == vspaceAttr || |
+ attrName == hspaceAttr) { |
+ result = eUniversal; |
+ return false; |
+ } |
+ |
+ if (attrName == alignAttr) { |
+ if (inputType() == IMAGE) { |
+ // Share with <img> since the alignment behavior is the same. |
+ result = eReplaced; |
+ return false; |
+ } |
+ } |
+ |
+ return HTMLElement::mapToEntry(attrName, result); |
+} |
+ |
+void HTMLInputElement::parseMappedAttribute(MappedAttribute *attr) |
+{ |
+ if (attr->name() == nameAttr) { |
+ checkedRadioButtons(this).removeButton(this); |
+ m_data.setName(attr->value()); |
+ checkedRadioButtons(this).addButton(this); |
+ } else if (attr->name() == autocompleteAttr) { |
+ if (equalIgnoringCase(attr->value(), "off")) { |
+ m_autocomplete = Off; |
+ registerForActivationCallbackIfNeeded(); |
+ } else { |
+ if (m_autocomplete == Off) |
+ unregisterForActivationCallbackIfNeeded(); |
+ if (attr->isEmpty()) |
+ m_autocomplete = Uninitialized; |
+ else |
+ m_autocomplete = On; |
+ } |
+ } else if (attr->name() == typeAttr) { |
+ 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_data.value().isNull()) |
+ setChanged(); |
+ setValueMatchesRenderer(false); |
+ } else if (attr->name() == checkedAttr) { |
+ m_defaultChecked = !attr->isNull(); |
+ if (m_useDefaultChecked) { |
+ setChecked(m_defaultChecked); |
+ m_useDefaultChecked = true; |
+ } |
+ } else if (attr->name() == maxlengthAttr) |
+ InputElement::parseMaxLengthAttribute(m_data, attr); |
+ else if (attr->name() == sizeAttr) |
+ InputElement::parseSizeAttribute(m_data, attr); |
+ else if (attr->name() == altAttr) { |
+ if (renderer() && inputType() == IMAGE) |
+ toRenderImage(renderer())->updateAltText(); |
+ } else if (attr->name() == srcAttr) { |
+ if (renderer() && inputType() == IMAGE) { |
+ if (!m_imageLoader) |
+ m_imageLoader.set(new HTMLImageLoader(this)); |
+ m_imageLoader->updateFromElementIgnoringPreviousError(); |
+ } |
+ } else if (attr->name() == usemapAttr || |
+ attr->name() == accesskeyAttr) { |
+ // FIXME: ignore for the moment |
+ } else if (attr->name() == vspaceAttr) { |
+ addCSSLength(attr, CSSPropertyMarginTop, attr->value()); |
+ addCSSLength(attr, CSSPropertyMarginBottom, attr->value()); |
+ } else if (attr->name() == hspaceAttr) { |
+ addCSSLength(attr, CSSPropertyMarginLeft, attr->value()); |
+ addCSSLength(attr, CSSPropertyMarginRight, attr->value()); |
+ } else if (attr->name() == alignAttr) { |
+ if (inputType() == IMAGE) |
+ addHTMLAlignment(attr); |
+ } else if (attr->name() == widthAttr) { |
+ if (respectHeightAndWidthAttrs()) |
+ addCSSLength(attr, CSSPropertyWidth, attr->value()); |
+ } else if (attr->name() == heightAttr) { |
+ if (respectHeightAndWidthAttrs()) |
+ addCSSLength(attr, CSSPropertyHeight, attr->value()); |
+ } else if (attr->name() == onfocusAttr) { |
+ setInlineEventListenerForTypeAndAttribute(eventNames().focusEvent, attr); |
+ } else if (attr->name() == onblurAttr) { |
+ setInlineEventListenerForTypeAndAttribute(eventNames().blurEvent, attr); |
+ } else if (attr->name() == onselectAttr) { |
+ setInlineEventListenerForTypeAndAttribute(eventNames().selectEvent, attr); |
+ } else if (attr->name() == onchangeAttr) { |
+ setInlineEventListenerForTypeAndAttribute(eventNames().changeEvent, attr); |
+ } else if (attr->name() == oninputAttr) { |
+ setInlineEventListenerForTypeAndAttribute(eventNames().inputEvent, 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); |
+ } else if (attr->name() == resultsAttr) { |
+ int oldResults = m_maxResults; |
+ m_maxResults = !attr->isNull() ? min(attr->value().toInt(), maxSavedResults) : -1; |
+ // FIXME: Detaching just for maxResults change is not ideal. We should figure out the right |
+ // time to relayout for this change. |
+ if (m_maxResults != oldResults && (m_maxResults <= 0 || oldResults <= 0) && attached()) { |
+ detach(); |
+ attach(); |
+ } |
+ setChanged(); |
+ } else if (attr->name() == placeholderAttr) { |
+ if (isTextField()) |
+ InputElement::updatePlaceholderVisibility(m_data, document(), true); |
+ } else if (attr->name() == autosaveAttr || |
+ attr->name() == incrementalAttr || |
+ attr->name() == minAttr || |
+ attr->name() == maxAttr || |
+ attr->name() == multipleAttr || |
+ attr->name() == precisionAttr) |
+ setChanged(); |
+ else |
+ HTMLFormControlElementWithState::parseMappedAttribute(attr); |
+} |
+ |
+bool HTMLInputElement::rendererIsNeeded(RenderStyle *style) |
+{ |
+ switch (inputType()) { |
+ case BUTTON: |
+ case CHECKBOX: |
+ case FILE: |
+ case IMAGE: |
+ case ISINDEX: |
+ case PASSWORD: |
+ case RADIO: |
+ case RANGE: |
+ case RESET: |
+ case SEARCH: |
+ case SUBMIT: |
+ case TEXT: |
+ return HTMLFormControlElementWithState::rendererIsNeeded(style); |
+ case HIDDEN: |
+ return false; |
+ } |
+ ASSERT(false); |
+ return false; |
+} |
+ |
+RenderObject *HTMLInputElement::createRenderer(RenderArena *arena, RenderStyle *style) |
+{ |
+ switch (inputType()) { |
+ case BUTTON: |
+ case RESET: |
+ case SUBMIT: |
+ return new (arena) RenderButton(this); |
+ case CHECKBOX: |
+ case RADIO: |
+ return RenderObject::createObject(this, style); |
+ case FILE: |
+ return new (arena) RenderFileUploadControl(this); |
+ case HIDDEN: |
+ break; |
+ case IMAGE: |
+ return new (arena) RenderImage(this); |
+ case RANGE: |
+ return new (arena) RenderSlider(this); |
+ case ISINDEX: |
+ case PASSWORD: |
+ case SEARCH: |
+ case TEXT: |
+ return new (arena) RenderTextControlSingleLine(this); |
+ } |
+ ASSERT(false); |
+ return 0; |
+} |
+ |
+void HTMLInputElement::attach() |
+{ |
+ if (!m_inited) { |
+ if (!m_haveType) |
+ setInputType(getAttribute(typeAttr)); |
+ m_inited = true; |
+ } |
+ |
+ HTMLFormControlElementWithState::attach(); |
+ |
+ if (inputType() == IMAGE) { |
+ if (!m_imageLoader) |
+ m_imageLoader.set(new HTMLImageLoader(this)); |
+ m_imageLoader->updateFromElement(); |
+ if (renderer()) { |
+ RenderImage* imageObj = toRenderImage(renderer()); |
+ imageObj->setCachedImage(m_imageLoader->image()); |
+ |
+ // If we have no image at all because we have no src attribute, set |
+ // image height and width for the alt text instead. |
+ if (!m_imageLoader->image() && !imageObj->cachedImage()) |
+ imageObj->setImageSizeForAltText(); |
+ } |
+ } |
+} |
+ |
+void HTMLInputElement::detach() |
+{ |
+ HTMLFormControlElementWithState::detach(); |
+ setValueMatchesRenderer(false); |
+} |
+ |
+String HTMLInputElement::altText() const |
+{ |
+ // http://www.w3.org/TR/1998/REC-html40-19980424/appendix/notes.html#altgen |
+ // also heavily discussed by Hixie on bugzilla |
+ // note this is intentionally different to HTMLImageElement::altText() |
+ String alt = getAttribute(altAttr); |
+ // fall back to title attribute |
+ if (alt.isNull()) |
+ alt = getAttribute(titleAttr); |
+ if (alt.isNull()) |
+ alt = getAttribute(valueAttr); |
+ if (alt.isEmpty()) |
+ alt = inputElementAltText(); |
+ return alt; |
+} |
+ |
+bool HTMLInputElement::isSuccessfulSubmitButton() const |
+{ |
+ // HTML spec says that buttons must have names to be considered successful. |
+ // However, other browsers do not impose this constraint. So we do likewise. |
+ return !disabled() && (inputType() == IMAGE || inputType() == SUBMIT); |
+} |
+ |
+bool HTMLInputElement::isActivatedSubmit() const |
+{ |
+ return m_activeSubmit; |
+} |
+ |
+void HTMLInputElement::setActivatedSubmit(bool flag) |
+{ |
+ m_activeSubmit = flag; |
+} |
+ |
+bool HTMLInputElement::appendFormData(FormDataList& encoding, bool multipart) |
+{ |
+ // image generates its own names, but for other types there is no form data unless there's a name |
+ if (name().isEmpty() && inputType() != IMAGE) |
+ return false; |
+ |
+ switch (inputType()) { |
+ case HIDDEN: |
+ case ISINDEX: |
+ case PASSWORD: |
+ case RANGE: |
+ case SEARCH: |
+ case TEXT: |
+ // always successful |
+ encoding.appendData(name(), value()); |
+ return true; |
+ |
+ case CHECKBOX: |
+ case RADIO: |
+ if (checked()) { |
+ encoding.appendData(name(), value()); |
+ return true; |
+ } |
+ break; |
+ |
+ case BUTTON: |
+ case RESET: |
+ // these types of buttons are never successful |
+ return false; |
+ |
+ case IMAGE: |
+ if (m_activeSubmit) { |
+ 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; |
+ } |
+ break; |
+ |
+ case SUBMIT: |
+ if (m_activeSubmit) { |
+ String enc_str = valueWithDefault(); |
+ encoding.appendData(name(), enc_str); |
+ return true; |
+ } |
+ break; |
+ |
+ case FILE: { |
+ // Can't submit file on GET. |
+ if (!multipart) |
+ return false; |
+ |
+ // If no filename at all is entered, return successful but empty. |
+ // Null would be more logical, but Netscape posts an empty file. Argh. |
+ unsigned numFiles = m_fileList->length(); |
+ if (!numFiles) { |
+ encoding.appendFile(name(), File::create("")); |
+ return true; |
+ } |
+ |
+ for (unsigned i = 0; i < numFiles; ++i) |
+ encoding.appendFile(name(), m_fileList->item(i)); |
+ return true; |
+ } |
+ } |
+ return false; |
+} |
+ |
+void HTMLInputElement::reset() |
+{ |
+ if (storesValueSeparateFromAttribute()) |
+ setValue(String()); |
+ |
+ setChecked(m_defaultChecked); |
+ m_useDefaultChecked = true; |
+} |
+ |
+void HTMLInputElement::setChecked(bool nowChecked, bool sendChangeEvent) |
+{ |
+ if (checked() == nowChecked) |
+ return; |
+ |
+ checkedRadioButtons(this).removeButton(this); |
+ |
+ m_useDefaultChecked = false; |
+ m_checked = nowChecked; |
+ setChanged(); |
+ |
+ checkedRadioButtons(this).addButton(this); |
+ |
+ if (renderer() && renderer()->style()->hasAppearance()) |
+ theme()->stateChanged(renderer(), CheckedState); |
+ |
+ // Only send a change event for items in the document (avoid firing during |
+ // parsing) and don't send a change event for a radio button that's getting |
+ // unchecked to match other browsers. DOM is not a useful standard for this |
+ // 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(); |
+} |
+ |
+void HTMLInputElement::setIndeterminate(bool _indeterminate) |
+{ |
+ // Only checkboxes honor indeterminate. |
+ if (inputType() != CHECKBOX || indeterminate() == _indeterminate) |
+ return; |
+ |
+ m_indeterminate = _indeterminate; |
+ |
+ setChanged(); |
+ |
+ if (renderer() && renderer()->style()->hasAppearance()) |
+ theme()->stateChanged(renderer(), CheckedState); |
+} |
+ |
+int HTMLInputElement::size() const |
+{ |
+ return m_data.size(); |
+} |
+ |
+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; |
+ m_indeterminate = sourceElement->m_indeterminate; |
+ |
+ HTMLFormControlElementWithState::copyNonAttributeProperties(source); |
+} |
+ |
+String HTMLInputElement::value() const |
+{ |
+ // The HTML5 spec (as of the 10/24/08 working draft) says that the value attribute isn't applicable to the file upload control |
+ // but we don't want to break existing websites, who may be relying on being able to get the file name as a value. |
+ if (inputType() == FILE) { |
+ if (!m_fileList->isEmpty()) |
+ return m_fileList->item(0)->fileName(); |
+ return String(); |
+ } |
+ |
+ String value = m_data.value(); |
+ if (value.isNull()) { |
+ value = constrainValue(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" : ""; |
+ } |
+ |
+ return value; |
+} |
+ |
+String HTMLInputElement::valueWithDefault() const |
+{ |
+ String v = value(); |
+ if (v.isNull()) { |
+ switch (inputType()) { |
+ case BUTTON: |
+ case CHECKBOX: |
+ case FILE: |
+ case HIDDEN: |
+ case IMAGE: |
+ case ISINDEX: |
+ case PASSWORD: |
+ case RADIO: |
+ case RANGE: |
+ case SEARCH: |
+ case TEXT: |
+ break; |
+ case RESET: |
+ v = resetButtonDefaultLabel(); |
+ break; |
+ case SUBMIT: |
+ v = submitButtonDefaultLabel(); |
+ break; |
+ } |
+ } |
+ return v; |
+} |
+ |
+void HTMLInputElement::setValue(const String& value) |
+{ |
+ // 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 |
+ // but we don't want to break existing websites, who may be relying on this method to clear things. |
+ if (inputType() == FILE && !value.isEmpty()) |
+ return; |
+ |
+ if (isTextField()) |
+ InputElement::updatePlaceholderVisibility(m_data, document()); |
+ |
+ setValueMatchesRenderer(false); |
+ if (storesValueSeparateFromAttribute()) { |
+ if (inputType() == FILE) |
+ m_fileList->clear(); |
+ else { |
+ m_data.setValue(constrainValue(value)); |
+ if (isTextField() && inDocument()) |
+ document()->updateRendering(); |
+ } |
+ if (renderer()) |
+ renderer()->updateFromElement(); |
+ setChanged(); |
+ } else |
+ setAttribute(valueAttr, constrainValue(value)); |
+ |
+ if (isTextField()) { |
+ unsigned max = m_data.value().length(); |
+ if (document()->focusedNode() == this) |
+ InputElement::updateSelectionRange(m_data, max, max); |
+ else |
+ cacheSelection(max, max); |
+ } |
+ InputElement::notifyFormStateChanged(m_data, document()); |
+} |
+ |
+String HTMLInputElement::placeholderValue() const |
+{ |
+ return getAttribute(placeholderAttr).string(); |
+} |
+ |
+bool HTMLInputElement::searchEventsShouldBeDispatched() const |
+{ |
+ return hasAttribute(incrementalAttr); |
+} |
+ |
+void HTMLInputElement::setValueFromRenderer(const String& value) |
+{ |
+ // File upload controls will always use setFileListFromRenderer. |
+ ASSERT(inputType() != FILE); |
+ InputElement::setValueFromRenderer(m_data, document(), value); |
+} |
+ |
+void HTMLInputElement::setFileListFromRenderer(const Vector<String>& paths) |
+{ |
+ m_fileList->clear(); |
+ int size = paths.size(); |
+ for (int i = 0; i < size; i++) |
+ m_fileList->append(File::create(paths[i])); |
+ |
+ setValueMatchesRenderer(); |
+ InputElement::notifyFormStateChanged(m_data, document()); |
+} |
+ |
+bool HTMLInputElement::storesValueSeparateFromAttribute() const |
+{ |
+ switch (inputType()) { |
+ case BUTTON: |
+ case CHECKBOX: |
+ case HIDDEN: |
+ case IMAGE: |
+ case RADIO: |
+ case RESET: |
+ case SUBMIT: |
+ return false; |
+ case FILE: |
+ case ISINDEX: |
+ case PASSWORD: |
+ case RANGE: |
+ case SEARCH: |
+ case TEXT: |
+ return true; |
+ } |
+ return false; |
+} |
+ |
+void* HTMLInputElement::preDispatchEventHandler(Event *evt) |
+{ |
+ // preventDefault or "return false" are used to reverse the automatic checking/selection we do here. |
+ // This result gives us enough info to perform the "undo" in postDispatch of the action we take here. |
+ void* result = 0; |
+ if ((inputType() == CHECKBOX || inputType() == RADIO) && evt->isMouseEvent() |
+ && evt->type() == eventNames().clickEvent && static_cast<MouseEvent*>(evt)->button() == LeftButton) { |
+ 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; |
+ setIndeterminate(false); |
+ } else { |
+ if (checked()) |
+ result = (void*)0x1; |
+ 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. |
+ // 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. |
+ HTMLInputElement* currRadio = checkedRadioButtons(this).checkedButtonForGroup(name()); |
+ 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; |
+ } |
+ setChecked(true, true); |
+ } |
+ } |
+ return result; |
+} |
+ |
+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); |
+ } |
+ } |
+ input->deref(); |
+ } |
+ |
+ // Left clicks on radio buttons and check boxes already performed default actions in preDispatchEventHandler(). |
+ evt->setDefaultHandled(); |
+ } |
+} |
+ |
+void HTMLInputElement::defaultEventHandler(Event* evt) |
+{ |
+ bool clickDefaultFormButton = false; |
+ |
+ if (isTextField() && evt->type() == eventNames().textInputEvent && evt->isTextEvent() && static_cast<TextEvent*>(evt)->data() == "\n") |
+ clickDefaultFormButton = true; |
+ |
+ if (inputType() == IMAGE && evt->isMouseEvent() && evt->type() == eventNames().clickEvent) { |
+ // record the mouse position for when we get the DOMActivate event |
+ MouseEvent* me = static_cast<MouseEvent*>(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()) { |
+ m_xPos = 0; |
+ m_yPos = 0; |
+ } else { |
+ // FIXME: This doesn't work correctly with transforms. |
+ IntPoint absOffset = roundedIntPoint(renderer()->localToAbsolute()); |
+ 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))) { |
+ evt->setDefaultHandled(); |
+ return; |
+ } |
+ |
+ 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) { |
+ HTMLFormControlElementWithState::defaultEventHandler(evt); |
+ if (evt->defaultHandled()) |
+ return; |
+ } |
+ |
+ // DOMActivate events cause the input to be "activated" - in the case of image and submit inputs, this means |
+ // actually submitting the form. For reset inputs, the form is reset. These events are sent when the user clicks |
+ // on the element, or presses enter while it is the active element. JavaScript code wishing to activate the element |
+ // must dispatch a DOMActivate event - a click event will not do the job. |
+ if (evt->type() == eventNames().DOMActivateEvent && !disabled()) { |
+ if (inputType() == IMAGE || inputType() == SUBMIT || inputType() == RESET) { |
+ if (!form()) |
+ return; |
+ if (inputType() == RESET) |
+ form()->reset(); |
+ else { |
+ m_activeSubmit = true; |
+ // 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 m_xPos and m_yPos altogether! |
+ if (!form()->prepareSubmit(evt)) { |
+ m_xPos = 0; |
+ m_yPos = 0; |
+ } |
+ m_activeSubmit = false; |
+ } |
+ } else if (inputType() == FILE && renderer()) |
+ static_cast<RenderFileUploadControl*>(renderer())->click(); |
+ } |
+ |
+ // Use key press event here since sending simulated mouse events |
+ // on key down blocks the proper sending of the key press event. |
+ if (evt->type() == eventNames().keypressEvent && evt->isKeyboardEvent()) { |
+ bool clickElement = false; |
+ |
+ int charCode = static_cast<KeyboardEvent*>(evt)->charCode(); |
+ |
+ if (charCode == '\r') { |
+ switch (inputType()) { |
+ case CHECKBOX: |
+ case HIDDEN: |
+ case ISINDEX: |
+ case PASSWORD: |
+ case RANGE: |
+ case SEARCH: |
+ case TEXT: |
+ // Simulate mouse click on the default form button for enter for these types of elements. |
+ clickDefaultFormButton = true; |
+ break; |
+ case BUTTON: |
+ case FILE: |
+ case IMAGE: |
+ case RESET: |
+ case SUBMIT: |
+ // Simulate mouse click for enter for these types of elements. |
+ clickElement = true; |
+ break; |
+ case RADIO: |
+ break; // Don't do anything for enter on a radio button. |
+ } |
+ } else if (charCode == ' ') { |
+ switch (inputType()) { |
+ case BUTTON: |
+ case CHECKBOX: |
+ case FILE: |
+ case IMAGE: |
+ case RESET: |
+ case SUBMIT: |
+ case RADIO: |
+ // Prevent scrolling down the page. |
+ evt->setDefaultHandled(); |
+ return; |
+ default: |
+ break; |
+ } |
+ } |
+ |
+ if (clickElement) { |
+ dispatchSimulatedClick(evt); |
+ evt->setDefaultHandled(); |
+ return; |
+ } |
+ } |
+ |
+ if (evt->type() == eventNames().keydownEvent && evt->isKeyboardEvent()) { |
+ String key = static_cast<KeyboardEvent*>(evt)->keyIdentifier(); |
+ |
+ if (key == "U+0020") { |
+ switch (inputType()) { |
+ case BUTTON: |
+ case CHECKBOX: |
+ case FILE: |
+ case IMAGE: |
+ case RESET: |
+ case SUBMIT: |
+ case RADIO: |
+ setActive(true, true); |
+ // No setDefaultHandled() - IE dispatches a keypress in this case. |
+ return; |
+ default: |
+ break; |
+ } |
+ } |
+ |
+ if (inputType() == RADIO && (key == "Up" || key == "Down" || key == "Left" || key == "Right")) { |
+ // Left and up mean "previous radio button". |
+ // Right and down mean "next radio button". |
+ // Tested in WinIE, and even for RTL, left still means previous radio button (and so moves |
+ // to the right). Seems strange, but we'll match it. |
+ bool forward = (key == "Down" || key == "Right"); |
+ |
+ // We can only stay within the form's children if the form hasn't been demoted to a leaf because |
+ // of malformed HTML. |
+ Node* n = this; |
+ while ((n = (forward ? n->traverseNextNode() : n->traversePreviousNode()))) { |
+ // Once we encounter a form element, we know we're through. |
+ if (n->hasTagName(formTag)) |
+ break; |
+ |
+ // Look for more radio buttons. |
+ if (n->hasTagName(inputTag)) { |
+ HTMLInputElement* elt = static_cast<HTMLInputElement*>(n); |
+ if (elt->form() != form()) |
+ break; |
+ if (n->hasTagName(inputTag)) { |
+ HTMLInputElement* inputElt = static_cast<HTMLInputElement*>(n); |
+ if (inputElt->inputType() == RADIO && inputElt->name() == name() && inputElt->isFocusable()) { |
+ inputElt->setChecked(true); |
+ document()->setFocusedNode(inputElt); |
+ inputElt->dispatchSimulatedClick(evt, false, false); |
+ evt->setDefaultHandled(); |
+ break; |
+ } |
+ } |
+ } |
+ } |
+ } |
+ } |
+ |
+ if (evt->type() == eventNames().keyupEvent && evt->isKeyboardEvent()) { |
+ bool clickElement = false; |
+ |
+ String key = static_cast<KeyboardEvent*>(evt)->keyIdentifier(); |
+ |
+ if (key == "U+0020") { |
+ switch (inputType()) { |
+ case BUTTON: |
+ case CHECKBOX: |
+ case FILE: |
+ case IMAGE: |
+ case RESET: |
+ case SUBMIT: |
+ // Simulate mouse click for spacebar for these types of elements. |
+ // The AppKit already does this for some, but not all, of them. |
+ clickElement = true; |
+ break; |
+ case RADIO: |
+ // If an unselected radio is tabbed into (because the entire group has nothing |
+ // checked, or because of some explicit .focus() call), then allow space to check it. |
+ if (!checked()) |
+ clickElement = true; |
+ break; |
+ case HIDDEN: |
+ case ISINDEX: |
+ case PASSWORD: |
+ case RANGE: |
+ case SEARCH: |
+ case TEXT: |
+ break; |
+ } |
+ } |
+ |
+ if (clickElement) { |
+ if (active()) |
+ dispatchSimulatedClick(evt); |
+ evt->setDefaultHandled(); |
+ return; |
+ } |
+ } |
+ |
+ if (clickDefaultFormButton) { |
+ if (isSearchField()) { |
+ addSearchResult(); |
+ onSearch(); |
+ } |
+ // Fire onChange for text fields. |
+ RenderObject* r = renderer(); |
+ if (r && r->isTextField() && r->isEdited()) { |
+ onChange(); |
+ // Refetch the renderer since arbitrary JS code run during onchange can do anything, including destroying it. |
+ r = renderer(); |
+ if (r) |
+ r->setEdited(false); |
+ } |
+ // Form may never have been present, or may have been destroyed by the change event. |
+ if (form()) |
+ form()->submitClick(evt); |
+ evt->setDefaultHandled(); |
+ return; |
+ } |
+ |
+ if (evt->isBeforeTextInsertedEvent()) |
+ InputElement::handleBeforeTextInsertedEvent(m_data, 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); |
+ } |
+} |
+ |
+bool HTMLInputElement::isURLAttribute(Attribute *attr) const |
+{ |
+ return (attr->name() == srcAttr); |
+} |
+ |
+String HTMLInputElement::defaultValue() const |
+{ |
+ return getAttribute(valueAttr); |
+} |
+ |
+void HTMLInputElement::setDefaultValue(const String &value) |
+{ |
+ setAttribute(valueAttr, value); |
+} |
+ |
+bool HTMLInputElement::defaultChecked() const |
+{ |
+ return !getAttribute(checkedAttr).isNull(); |
+} |
+ |
+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); |
+} |
+ |
+void HTMLInputElement::setAccept(const String &value) |
+{ |
+ setAttribute(acceptAttr, value); |
+} |
+ |
+String HTMLInputElement::accessKey() const |
+{ |
+ return getAttribute(accesskeyAttr); |
+} |
+ |
+void HTMLInputElement::setAccessKey(const String &value) |
+{ |
+ setAttribute(accesskeyAttr, value); |
+} |
+ |
+String HTMLInputElement::align() const |
+{ |
+ return getAttribute(alignAttr); |
+} |
+ |
+void HTMLInputElement::setAlign(const String &value) |
+{ |
+ setAttribute(alignAttr, value); |
+} |
+ |
+String HTMLInputElement::alt() const |
+{ |
+ return getAttribute(altAttr); |
+} |
+ |
+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)); |
+} |
+ |
+void HTMLInputElement::setSize(unsigned _size) |
+{ |
+ setAttribute(sizeAttr, String::number(_size)); |
+} |
+ |
+KURL HTMLInputElement::src() const |
+{ |
+ return document()->completeURL(getAttribute(srcAttr)); |
+} |
+ |
+void HTMLInputElement::setSrc(const String &value) |
+{ |
+ setAttribute(srcAttr, value); |
+} |
+ |
+String HTMLInputElement::useMap() const |
+{ |
+ return getAttribute(usemapAttr); |
+} |
+ |
+void HTMLInputElement::setUseMap(const String &value) |
+{ |
+ setAttribute(usemapAttr, value); |
+} |
+ |
+void HTMLInputElement::setAutofilled(bool b) |
+{ |
+ if (b == m_autofilled) |
+ return; |
+ |
+ m_autofilled = b; |
+ setChanged(); |
+} |
+ |
+FileList* HTMLInputElement::files() |
+{ |
+ if (inputType() != FILE) |
+ return 0; |
+ return m_fileList.get(); |
+} |
+ |
+String HTMLInputElement::constrainValue(const String& proposedValue) const |
+{ |
+ return InputElement::constrainValue(m_data, proposedValue, m_data.maxLength()); |
+} |
+ |
+bool HTMLInputElement::needsActivationCallback() |
+{ |
+ return inputType() == PASSWORD || m_autocomplete == Off; |
+} |
+ |
+void HTMLInputElement::registerForActivationCallbackIfNeeded() |
+{ |
+ if (needsActivationCallback()) |
+ document()->registerForDocumentActivationCallbacks(this); |
+} |
+ |
+void HTMLInputElement::unregisterForActivationCallbackIfNeeded() |
+{ |
+ if (!needsActivationCallback()) |
+ document()->unregisterForDocumentActivationCallbacks(this); |
+} |
+ |
+void HTMLInputElement::cacheSelection(int start, int end) |
+{ |
+ m_data.setCachedSelectionStart(start); |
+ m_data.setCachedSelectionEnd(end); |
+} |
+ |
+void HTMLInputElement::addSearchResult() |
+{ |
+ ASSERT(isSearchField()); |
+ if (renderer()) |
+ static_cast<RenderTextControlSingleLine*>(renderer())->addSearchResult(); |
+} |
+ |
+void HTMLInputElement::onSearch() |
+{ |
+ ASSERT(isSearchField()); |
+ if (renderer()) |
+ static_cast<RenderTextControlSingleLine*>(renderer())->stopSearchEventTimer(); |
+ dispatchEventForType(eventNames().searchEvent, true, false); |
+} |
+ |
+Selection HTMLInputElement::selection() const |
+{ |
+ if (!renderer() || !isTextField() || m_data.cachedSelectionStart() == -1 || m_data.cachedSelectionEnd() == -1) |
+ return Selection(); |
+ return static_cast<RenderTextControl*>(renderer())->selection(m_data.cachedSelectionStart(), m_data.cachedSelectionEnd()); |
+} |
+ |
+void HTMLInputElement::documentDidBecomeActive() |
+{ |
+ ASSERT(needsActivationCallback()); |
+ reset(); |
+} |
+ |
+void HTMLInputElement::willMoveToNewOwnerDocument() |
+{ |
+ // Always unregister for cache callbacks when leaving a document, even if we would otherwise like to be registered |
+ if (needsActivationCallback()) |
+ document()->unregisterForDocumentActivationCallbacks(this); |
+ |
+ document()->checkedRadioButtons().removeButton(this); |
+ |
+ HTMLFormControlElementWithState::willMoveToNewOwnerDocument(); |
+} |
+ |
+void HTMLInputElement::didMoveToNewOwnerDocument() |
+{ |
+ registerForActivationCallbackIfNeeded(); |
+ |
+ HTMLFormControlElementWithState::didMoveToNewOwnerDocument(); |
+} |
+ |
+void HTMLInputElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const |
+{ |
+ HTMLFormControlElementWithState::addSubresourceAttributeURLs(urls); |
+ |
+ addSubresourceURL(urls, src()); |
+} |
+ |
+bool HTMLInputElement::willValidate() const |
+{ |
+ // FIXME: This shall check for new WF2 input types too |
+ return HTMLFormControlElementWithState::willValidate() && inputType() != HIDDEN && |
+ inputType() != BUTTON && inputType() != RESET; |
+} |
+ |
+bool HTMLInputElement::placeholderShouldBeVisible() const |
+{ |
+ return m_data.placeholderShouldBeVisible(); |
+} |
+ |
+} // namespace |
+ |
+ |