blob: 9c4c2743f0faa68ad0a82455c35c5b86f8951f9d [file] [log] [blame]
/*
* This file is part of the DOM implementation for KDE.
*
* 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 Apple Computer, Inc.
* (C) 2006 Alexey Proskuryakov (ap@nypop.com)
*
* 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., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
*/
#include "config.h"
#include "HTMLInputElement.h"
#include "BeforeTextInsertedEvent.h"
#include "Document.h"
#include "EventNames.h"
#include "FormDataList.h"
#include "Frame.h"
#include "HTMLFormElement.h"
#include "LocalizedStrings.h"
#include "RenderTextField.h"
#include "SelectionController.h"
#include "CSSPropertyNames.h"
#include "dom2_eventsimpl.h"
#include "html_imageimpl.h"
#include "HTMLNames.h"
#include "render_button.h"
#include "render_form.h"
#include "RenderTheme.h"
using WebCore::ControlState;
namespace WebCore {
using namespace EventNames;
using namespace HTMLNames;
HTMLInputElement::HTMLInputElement(Document *doc, HTMLFormElement *f)
: HTMLGenericFormElement(inputTag, doc, f)
{
init();
}
HTMLInputElement::HTMLInputElement(const QualifiedName& tagName, Document *doc, HTMLFormElement *f)
: HTMLGenericFormElement(tagName, doc, f)
{
init();
}
void HTMLInputElement::init()
{
m_imageLoader = 0;
m_valueMatchesRenderer = false;
m_type = TEXT;
m_maxLen = 1024;
m_size = 20;
m_checked = false;
m_defaultChecked = false;
m_useDefaultChecked = true;
m_indeterminate = false;
m_haveType = false;
m_activeSubmit = false;
m_autocomplete = true;
m_inited = false;
xPos = 0;
yPos = 0;
m_maxResults = -1;
if (m_form)
m_autocomplete = m_form->autoComplete();
}
HTMLInputElement::~HTMLInputElement()
{
document()->deregisterMaintainsState(this);
delete m_imageLoader;
}
const AtomicString& HTMLInputElement::name() const
{
return m_name.isNull() ? emptyAtom : m_name;
}
bool HTMLInputElement::isKeyboardFocusable() const
{
// If text fields can be focused, then they should always be keyboardFocusable
// FIXME: When other text fields switch to the Non-NSView implementation, we should add them here.
if (m_type == TEXT)
return HTMLGenericFormElement::isFocusable();
// If the base class says we can't be focused, then we can stop now.
if (!HTMLGenericFormElement::isKeyboardFocusable())
return false;
if (m_type == RADIO) {
// Unnamed radio buttons are never focusable (matches WinIE).
if (name().isEmpty())
return false;
// Never allow keyboard tabbing to leave you in the same radio group. Always
// skip any other elements in the group.
Node* currentFocusNode = document()->focusNode();
if (currentFocusNode && currentFocusNode->hasTagName(inputTag)) {
HTMLInputElement* focusedInput = static_cast<HTMLInputElement*>(currentFocusNode);
if (focusedInput->inputType() == RADIO && focusedInput->form() == m_form &&
focusedInput->name() == name())
return false;
}
// Allow keyboard focus if we're checked or if nothing in the group is checked.
return checked() || !document()->checkedRadioButtonForGroup(name().impl(), m_form);
}
return true;
}
bool HTMLInputElement::isMouseFocusable() const
{
// FIXME: When other text fields switch to the Non-NSView implementation, we should add them here.
if (m_type == TEXT)
return HTMLGenericFormElement::isFocusable();
return HTMLGenericFormElement::isMouseFocusable();
}
void HTMLInputElement::focus()
{
// FIXME: When other text fields switch to the Non-NSView implementation, we should add them here.
if (m_type == TEXT) {
Document* doc = document();
doc->updateLayout();
if (isFocusable()) {
doc->setFocusNode(this);
select();
doc->frame()->revealSelection();
}
} else
HTMLGenericFormElement::focus();
}
void HTMLInputElement::setType(const String& t)
{
if (t.isEmpty()) {
int exccode;
removeAttribute(typeAttr, exccode);
}
else
setAttribute(typeAttr, t);
}
void HTMLInputElement::setInputType(const String& t)
{
typeEnum 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 {
if (inputType() == RADIO && !name().isEmpty()) {
if (document()->checkedRadioButtonForGroup(name().impl(), m_form) == this)
document()->removeRadioButtonGroup(name().impl(), m_form);
}
bool wasAttached = m_attached;
if (wasAttached)
detach();
bool didStoreValue = storesValueSeparateFromAttribute();
bool didMaintainState = maintainsState();
m_type = newType;
bool willStoreValue = storesValueSeparateFromAttribute();
bool willMaintainState = maintainsState();
if (didStoreValue && !willStoreValue && !m_value.isNull()) {
setAttribute(valueAttr, m_value);
m_value = String();
}
if (!didStoreValue && willStoreValue) {
m_value = getAttribute(valueAttr);
}
if (willMaintainState && !didMaintainState)
document()->registerMaintainsState(this);
else if (!willMaintainState && didMaintainState)
document()->deregisterMaintainsState(this);
if (wasAttached)
attach();
// If our type morphs into a radio button and we are checked, then go ahead
// and signal this to the form.
if (m_type == RADIO && checked())
document()->radioButtonChecked(this, m_form);
}
}
m_haveType = true;
if (m_type != IMAGE && m_imageLoader) {
delete m_imageLoader;
m_imageLoader = 0;
}
}
String HTMLInputElement::type() const
{
// needs to be lowercase according to DOM spec
switch (m_type) {
case TEXT: return "text";
case PASSWORD: return "password";
case CHECKBOX: return "checkbox";
case RADIO: return "radio";
case SUBMIT: return "submit";
case RESET: return "reset";
case FILE: return "file";
case HIDDEN: return "hidden";
case IMAGE: return "image";
case BUTTON: return "button";
case SEARCH: return "search";
case RANGE: return "range";
case ISINDEX: return "";
}
return "";
}
DeprecatedString HTMLInputElement::state( )
{
assert(m_type != PASSWORD); // should never save/restore password fields
DeprecatedString state = HTMLGenericFormElement::state();
switch (m_type) {
case CHECKBOX:
case RADIO:
return state + (checked() ? "on" : "off");
default:
return state + value().deprecatedString()+'.'; // Make sure the string is not empty!
}
}
void HTMLInputElement::restoreState(DeprecatedStringList &states)
{
assert(m_type != PASSWORD); // should never save/restore password fields
DeprecatedString state = HTMLGenericFormElement::findMatchingState(states);
if (state.isNull()) return;
switch (m_type) {
case CHECKBOX:
case RADIO:
setChecked((state == "on"));
break;
default:
setValue(String(state.left(state.length()-1)));
break;
}
}
bool HTMLInputElement::canHaveSelection()
{
switch (m_type) {
case TEXT:
case PASSWORD:
case SEARCH:
return true;
default:
break;
}
return false;
}
int HTMLInputElement::selectionStart()
{
if (!renderer())
return 0;
switch (m_type) {
case TEXT:
return static_cast<RenderTextField *>(renderer())->selectionStart();
case PASSWORD:
case SEARCH:
return static_cast<RenderLineEdit *>(renderer())->selectionStart();
default:
break;
}
return 0;
}
int HTMLInputElement::selectionEnd()
{
if (!renderer())
return 0;
switch (m_type) {
case TEXT:
return static_cast<RenderTextField *>(renderer())->selectionEnd();
case PASSWORD:
case SEARCH:
return static_cast<RenderLineEdit *>(renderer())->selectionEnd();
default:
break;
}
return 0;
}
void HTMLInputElement::setSelectionStart(int start)
{
if (!renderer())
return;
switch (m_type) {
case TEXT:
static_cast<RenderTextField *>(renderer())->setSelectionStart(start);
break;
case PASSWORD:
case SEARCH:
static_cast<RenderLineEdit *>(renderer())->setSelectionStart(start);
break;
default:
break;
}
}
void HTMLInputElement::setSelectionEnd(int end)
{
if (!renderer())
return;
switch (m_type) {
case TEXT:
static_cast<RenderTextField *>(renderer())->setSelectionEnd(end);
break;
case PASSWORD:
case SEARCH:
static_cast<RenderLineEdit *>(renderer())->setSelectionEnd(end);
break;
default:
break;
}
}
void HTMLInputElement::select( )
{
if (!renderer())
return;
switch (m_type) {
case FILE:
static_cast<RenderFileButton*>(renderer())->select();
break;
case TEXT:
static_cast<RenderTextField *>(renderer())->select();
break;
case PASSWORD:
case SEARCH:
static_cast<RenderLineEdit*>(renderer())->select();
break;
case BUTTON:
case CHECKBOX:
case HIDDEN:
case IMAGE:
case ISINDEX:
case RADIO:
case RANGE:
case RESET:
case SUBMIT:
break;
}
}
void HTMLInputElement::setSelectionRange(int start, int end)
{
if (!renderer())
return;
switch (m_type) {
case TEXT:
static_cast<RenderTextField *>(renderer())->setSelectionRange(start, end);
break;
case PASSWORD:
case SEARCH:
static_cast<RenderLineEdit *>(renderer())->setSelectionRange(start, end);
break;
default:
break;
}
}
void HTMLInputElement::click(bool sendMouseEvents, bool showPressedLook)
{
switch (inputType()) {
case HIDDEN:
// a no-op for this type
break;
case SUBMIT:
case RESET:
case BUTTON:
HTMLGenericFormElement::click(sendMouseEvents, showPressedLook);
break;
case FILE:
if (renderer()) {
static_cast<RenderFileButton *>(renderer())->click(sendMouseEvents);
break;
}
HTMLGenericFormElement::click(sendMouseEvents, showPressedLook);
break;
case CHECKBOX:
case RADIO:
case IMAGE:
case ISINDEX:
case PASSWORD:
case SEARCH:
case RANGE:
case TEXT:
HTMLGenericFormElement::click(sendMouseEvents, showPressedLook);
break;
}
}
void HTMLInputElement::accessKeyAction(bool sendToAnyElement)
{
switch (inputType()) {
case HIDDEN:
// a no-op for this type
break;
case TEXT:
case PASSWORD:
case SEARCH:
case ISINDEX:
focus();
break;
case CHECKBOX:
case RADIO:
case SUBMIT:
case RESET:
case IMAGE:
case BUTTON:
case FILE:
case RANGE:
// focus
focus();
// send the mouse button events iff the
// caller specified sendToAnyElement
click(sendToAnyElement);
break;
}
}
bool HTMLInputElement::mapToEntry(const QualifiedName& attrName, MappedAttributeEntry& result) const
{
if (attrName == widthAttr ||
attrName == heightAttr ||
attrName == vspaceAttr ||
attrName == hspaceAttr) {
result = eUniversal;
return false;
}
if (attrName == alignAttr) {
result = eReplaced; // Share with <img> since the alignment behavior is the same.
return false;
}
return HTMLElement::mapToEntry(attrName, result);
}
void HTMLInputElement::parseMappedAttribute(MappedAttribute *attr)
{
if (attr->name() == nameAttr) {
if (m_type == RADIO && checked()) {
// Remove the radio from its old group.
if (m_type == RADIO && !m_name.isEmpty())
document()->removeRadioButtonGroup(m_name.impl(), m_form);
}
// Update our cached reference to the name.
m_name = attr->value();
if (m_type == RADIO) {
// In case we parsed the checked attribute first, call setChecked if the element is checked by default.
if (m_useDefaultChecked)
setChecked(m_defaultChecked);
// Add the button to its new group.
if (checked())
document()->radioButtonChecked(this, m_form);
}
} else if (attr->name() == autocompleteAttr) {
m_autocomplete = !equalIgnoringCase(attr->value(), "off");
} 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_value.isNull())
setChanged();
m_valueMatchesRenderer = false;
} else if (attr->name() == checkedAttr) {
m_defaultChecked = !attr->isNull();
if (m_useDefaultChecked) {
setChecked(m_defaultChecked);
m_useDefaultChecked = true;
}
} else if (attr->name() == maxlengthAttr) {
m_maxLen = !attr->isNull() ? attr->value().toInt() : 1024;
if (m_maxLen <= 0 || m_maxLen > 1024)
m_maxLen = 1024;
setChanged();
} else if (attr->name() == sizeAttr) {
m_size = !attr->isNull() ? attr->value().toInt() : 20;
} else if (attr->name() == altAttr) {
if (renderer() && m_type == IMAGE)
static_cast<RenderImage*>(renderer())->updateAltText();
} else if (attr->name() == srcAttr) {
if (renderer() && m_type == IMAGE) {
if (!m_imageLoader)
m_imageLoader = new HTMLImageLoader(this);
m_imageLoader->updateFromElement();
}
} else if (attr->name() == usemapAttr ||
attr->name() == accesskeyAttr) {
// FIXME: ignore for the moment
} else if (attr->name() == vspaceAttr) {
addCSSLength(attr, CSS_PROP_MARGIN_TOP, attr->value());
addCSSLength(attr, CSS_PROP_MARGIN_BOTTOM, attr->value());
} else if (attr->name() == hspaceAttr) {
addCSSLength(attr, CSS_PROP_MARGIN_LEFT, attr->value());
addCSSLength(attr, CSS_PROP_MARGIN_RIGHT, attr->value());
} else if (attr->name() == alignAttr) {
addHTMLAlignment(attr);
} else if (attr->name() == widthAttr) {
addCSSLength(attr, CSS_PROP_WIDTH, attr->value());
} else if (attr->name() == heightAttr) {
addCSSLength(attr, CSS_PROP_HEIGHT, attr->value());
} else if (attr->name() == onfocusAttr) {
setHTMLEventListener(focusEvent, attr);
} else if (attr->name() == onblurAttr) {
setHTMLEventListener(blurEvent, attr);
} else if (attr->name() == onselectAttr) {
setHTMLEventListener(selectEvent, attr);
} else if (attr->name() == onchangeAttr) {
setHTMLEventListener(changeEvent, attr);
} else if (attr->name() == oninputAttr) {
setHTMLEventListener(inputEvent, attr);
}
// Search field and slider attributes all just cause updateFromElement to be called through style
// recalcing.
else if (attr->name() == onsearchAttr) {
setHTMLEventListener(searchEvent, attr);
} else if (attr->name() == resultsAttr) {
m_maxResults = !attr->isNull() ? attr->value().toInt() : -1;
setChanged();
} else if (attr->name() == autosaveAttr ||
attr->name() == incrementalAttr ||
attr->name() == placeholderAttr ||
attr->name() == minAttr ||
attr->name() == maxAttr ||
attr->name() == precisionAttr) {
setChanged();
} else
HTMLGenericFormElement::parseMappedAttribute(attr);
}
bool HTMLInputElement::rendererIsNeeded(RenderStyle *style)
{
switch(m_type)
{
case TEXT:
case PASSWORD:
case SEARCH:
case RANGE:
case ISINDEX:
case CHECKBOX:
case RADIO:
case SUBMIT:
case IMAGE:
case RESET:
case FILE:
case BUTTON: return HTMLGenericFormElement::rendererIsNeeded(style);
case HIDDEN: return false;
}
assert(false);
return false;
}
RenderObject *HTMLInputElement::createRenderer(RenderArena *arena, RenderStyle *style)
{
switch(m_type)
{
case TEXT: return new (arena) RenderTextField(this);
case PASSWORD:
case SEARCH:
case ISINDEX: return new (arena) RenderLineEdit(this);
case CHECKBOX:
case RADIO:
return RenderObject::createObject(this, style);
case SUBMIT:
case RESET:
case BUTTON:
return new (arena) RenderButton(this);
case IMAGE: return new (arena) RenderImageButton(this);
case FILE: return new (arena) RenderFileButton(this);
case RANGE: return new (arena) RenderSlider(this);
case HIDDEN: break;
}
assert(false);
return 0;
}
void HTMLInputElement::attach()
{
if (!m_inited) {
if (!m_haveType)
setInputType(getAttribute(typeAttr));
// FIXME: This needs to be dynamic, doesn't it, since someone could set this
// after attachment?
String val = getAttribute(valueAttr);
if ((unsigned) m_type <= ISINDEX && !val.isEmpty()) {
// remove newline stuff..
DeprecatedString nvalue;
for (unsigned int i = 0; i < val.length(); ++i)
if (val[i] >= ' ')
nvalue += val[i];
if (val.length() != nvalue.length())
setAttribute(valueAttr, nvalue);
}
m_inited = true;
}
// Disallow the width attribute on inputs other than HIDDEN and IMAGE.
// Dumb Web sites will try to set the width as an attribute on form controls that aren't
// images or hidden.
if (hasMappedAttributes() && m_type != HIDDEN && m_type != IMAGE && !getAttribute(widthAttr).isEmpty()) {
int excCode;
removeAttribute(widthAttr, excCode);
}
HTMLGenericFormElement::attach();
if (m_type == IMAGE) {
if (!m_imageLoader)
m_imageLoader = new HTMLImageLoader(this);
m_imageLoader->updateFromElement();
if (renderer()) {
RenderImage* imageObj = static_cast<RenderImage*>(renderer());
imageObj->setCachedImage(m_imageLoader->image());
}
}
// note we don't deal with calling passwordFieldRemoved() on detach, because the timing
// was such that it cleared our state too early
if (m_type == PASSWORD)
document()->passwordFieldAdded();
}
void HTMLInputElement::detach()
{
HTMLGenericFormElement::detach();
m_valueMatchesRenderer = 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. Therefore, we behave
// differently and can use different buttons than the
// author intended.
// Was: (m_type == SUBMIT && !name().isEmpty())
return !m_disabled && (m_type == IMAGE || m_type == 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
if (name().isEmpty() && m_type != IMAGE) return false;
switch (m_type) {
case HIDDEN:
case TEXT:
case SEARCH:
case RANGE:
case PASSWORD:
// 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:
// those buttons are never successful
return false;
case IMAGE:
if (m_activeSubmit) {
encoding.appendData(name().isEmpty() ? "x" : (name() + ".x"), clickX());
encoding.appendData(name().isEmpty() ? "y" : (name() + ".y"), clickY());
if (!name().isEmpty() && !value().isEmpty())
encoding.appendData(name(), value());
return true;
}
break;
case SUBMIT:
if (m_activeSubmit) {
String enc_str = valueWithDefault();
if (!enc_str.isEmpty()) {
encoding.appendData(name(), enc_str);
return true;
}
}
break;
case FILE:
{
// can't submit file on GET
// don't submit if display: none or display: hidden
if(!multipart || !renderer() || renderer()->style()->visibility() != WebCore::VISIBLE)
return false;
// if no filename at all is entered, return successful, however empty
// null would be more logical but netscape posts an empty file. argh.
if (value().isEmpty()) {
encoding.appendData(name(), DeprecatedString(""));
return true;
}
encoding.appendFile(name(), value());
return true;
}
case ISINDEX:
encoding.appendData(name(), value());
return true;
}
return false;
}
void HTMLInputElement::reset()
{
if (storesValueSeparateFromAttribute())
setValue(String());
setChecked(m_defaultChecked);
m_useDefaultChecked = true;
}
void HTMLInputElement::setChecked(bool nowChecked)
{
// We mimic WinIE and don't allow unnamed radio buttons to be checked.
if (checked() == nowChecked || (m_type == RADIO && name().isEmpty()))
return;
if (m_type == RADIO && nowChecked)
document()->radioButtonChecked(this, m_form);
m_useDefaultChecked = false;
m_checked = nowChecked;
setChanged();
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 (inDocument() && (m_type != RADIO || nowChecked))
onChange();
}
void HTMLInputElement::setIndeterminate(bool _indeterminate)
{
// Only checkboxes honor indeterminate.
if (m_type != CHECKBOX || indeterminate() == _indeterminate)
return;
m_indeterminate = _indeterminate;
setChanged();
if (renderer() && renderer()->style()->hasAppearance())
theme()->stateChanged(renderer(), CheckedState);
}
void HTMLInputElement::copyNonAttributeProperties(const Element *source)
{
const HTMLInputElement *sourceElem = static_cast<const HTMLInputElement *>(source);
m_value = sourceElem->m_value;
m_checked = sourceElem->m_checked;
m_indeterminate = sourceElem->m_indeterminate;
}
String HTMLInputElement::value() const
{
String value = m_value;
// It's important *not* to fall back to the value attribute for file inputs,
// because that would allow a malicious web page to upload files by setting the
// value attribute in markup.
if (value.isNull() && m_type != FILE)
value = getAttribute(valueAttr);
// If no attribute exists, then just use "on" or "" based off the checked() state of the control.
if (value.isNull() && (m_type == CHECKBOX || m_type == RADIO))
return String(checked() ? "on" : "");
return value;
}
String HTMLInputElement::valueWithDefault() const
{
String v = value();
if (v.isNull()) {
switch (m_type) {
case RESET:
v = resetButtonDefaultLabel();
break;
case SUBMIT:
v = submitButtonDefaultLabel();
break;
case BUTTON:
case CHECKBOX:
case FILE:
case HIDDEN:
case IMAGE:
case ISINDEX:
case PASSWORD:
case RADIO:
case RANGE:
case SEARCH:
case TEXT:
break;
}
}
return v;
}
void HTMLInputElement::setValue(const String &value)
{
if (m_type == FILE) return;
m_valueMatchesRenderer = false;
if (storesValueSeparateFromAttribute()) {
m_value = value;
if (renderer())
renderer()->updateFromElement();
setChanged();
} else {
setAttribute(valueAttr, value);
}
}
void HTMLInputElement::setValueFromRenderer(const String &value)
{
m_value = value;
m_valueMatchesRenderer = true;
// Fire the "input" DOM event.
dispatchHTMLEvent(inputEvent, true, false);
}
bool HTMLInputElement::storesValueSeparateFromAttribute() const
{
switch (m_type) {
case BUTTON:
case CHECKBOX:
case FILE:
case HIDDEN:
case IMAGE:
case RADIO:
case RANGE:
case RESET:
case SUBMIT:
return false;
case ISINDEX:
case PASSWORD:
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 ((m_type == CHECKBOX || m_type == RADIO) && evt->isMouseEvent() && evt->type() == clickEvent &&
static_cast<MouseEvent*>(evt)->button() == 0) {
if (m_type == 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());
}
} else {
// For radio buttons, store the current selected radio object.
if (name().isEmpty() || checked())
return 0; // Unnamed radio buttons dont get checked. Checked buttons just stay checked.
// FIXME: Need to learn to work without a form.
// We really want radio groups to end up in sane states, i.e., to have something checked.
// Therefore if nothing is currently selected, we won't allow this action to be "undone", since
// we want some object in the radio group to actually get selected.
HTMLInputElement* currRadio = document()->checkedRadioButtonForGroup(name().impl(), m_form);
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);
}
}
return result;
}
void HTMLInputElement::postDispatchEventHandler(Event *evt, void* data)
{
if ((m_type == CHECKBOX || m_type == RADIO) && evt->isMouseEvent() && evt->type() == clickEvent &&
static_cast<MouseEvent*>(evt)->button() == 0) {
if (m_type == 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();
}
}
}
void HTMLInputElement::defaultEventHandler(Event *evt)
{
if (m_type == IMAGE && evt->isMouseEvent() && evt->type() == 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()) {
xPos = 0;
yPos = 0;
} else {
int offsetX, offsetY;
renderer()->absolutePosition(offsetX, offsetY);
xPos = me->clientX() - offsetX;
yPos = me->clientY() - offsetY;
}
me->setDefaultHandled();
}
// 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. Javacsript code wishing to activate the element
// must dispatch a DOMActivate event - a click event will not do the job.
if (evt->type() == DOMActivateEvent) {
if (m_type == IMAGE || m_type == SUBMIT || m_type == RESET) {
if (!m_form)
return;
if (m_type == RESET)
m_form->reset();
else {
m_activeSubmit = true;
if (!m_form->prepareSubmit()) {
xPos = 0;
yPos = 0;
}
m_activeSubmit = false;
}
}
}
// 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() == keypressEvent && evt->isKeyboardEvent()) {
bool clickElement = false;
bool clickDefaultFormButton = false;
String key = static_cast<KeyboardEvent *>(evt)->keyIdentifier();
if (key == "U+000020") {
switch (m_type) {
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 (key == "Enter") {
switch (m_type) {
case BUTTON:
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 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.
}
}
if (m_type == 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() != m_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()->setFocusNode(inputElt);
inputElt->click(false, false);
evt->setDefaultHandled();
break;
}
}
}
}
}
if (clickElement) {
click(false);
evt->setDefaultHandled();
} else if (clickDefaultFormButton && m_form) {
m_form->submitClick();
evt->setDefaultHandled();
}
}
if (evt->isBeforeTextInsertedEvent()) {
// Make sure that the text to be inserted will not violate the maxLength
unsigned ml = maxLength();
unsigned currentLength = value().length();
String text = static_cast<BeforeTextInsertedEvent *>(evt)->text();
int selectionLength = document()->frame()->selection().toString().length();
// Truncate the inserted text if necessary
if (currentLength + text.length() - selectionLength > ml) {
ASSERT(currentLength <= ml);
text.truncate(ml - currentLength);
}
}
if (m_type == TEXT && (evt->isMouseEvent() || evt->isDragEvent() || evt->isWheelEvent()) && renderer())
static_cast<RenderTextField*>(renderer())->forwardEvent(evt);
HTMLGenericFormElement::defaultEventHandler(evt);
}
bool HTMLInputElement::isEditable()
{
return ((m_type == TEXT) || (m_type == PASSWORD) ||
(m_type == SEARCH) || (m_type == ISINDEX) || (m_type == FILE));
}
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);
}
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);
}
void HTMLInputElement::setMaxLength(int _maxLength)
{
setAttribute(maxlengthAttr, String::number(_maxLength));
}
void HTMLInputElement::setSize(unsigned _size)
{
setAttribute(sizeAttr, String::number(_size));
}
String 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);
}
} // namespace