diff -r 000000000000 -r 4f2f89ce4247 WebCore/html/HTMLFormControlElement.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebCore/html/HTMLFormControlElement.cpp Fri Sep 17 09:02:29 2010 +0300 @@ -0,0 +1,619 @@ +/* + * 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 Apple Inc. All rights reserved. + * (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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" +#include "HTMLFormControlElement.h" + +#include "Attribute.h" +#include "CharacterNames.h" +#include "Chrome.h" +#include "ChromeClient.h" +#include "Document.h" +#include "ElementRareData.h" +#include "Event.h" +#include "EventHandler.h" +#include "EventNames.h" +#include "Frame.h" +#include "HTMLFormElement.h" +#include "HTMLInputElement.h" +#include "HTMLNames.h" +#include "LegacyHTMLTreeBuilder.h" +#include "LegacyHTMLDocumentParser.h" +#include "LabelsNodeList.h" +#include "Page.h" +#include "RenderBox.h" +#include "RenderTextControl.h" +#include "RenderTheme.h" +#include "ScriptEventListener.h" +#include "ValidityState.h" +#include + +namespace WebCore { + +using namespace HTMLNames; + +HTMLFormControlElement::HTMLFormControlElement(const QualifiedName& tagName, Document* document, HTMLFormElement* form) + : HTMLElement(tagName, document) + , m_form(form) + , m_disabled(false) + , m_readOnly(false) + , m_required(false) + , m_valueMatchesRenderer(false) + , m_willValidateInitialized(false) + , m_willValidate(true) + , m_isValid(true) +{ + if (!m_form) + m_form = findFormAncestor(); + if (m_form) + m_form->registerFormElement(this); +} + +HTMLFormControlElement::~HTMLFormControlElement() +{ + if (m_form) + m_form->removeFormElement(this); +} + +bool HTMLFormControlElement::formNoValidate() const +{ + return !getAttribute(formnovalidateAttr).isNull(); +} + +ValidityState* HTMLFormControlElement::validity() +{ + if (!m_validityState) + m_validityState = ValidityState::create(this); + + return m_validityState.get(); +} + +void HTMLFormControlElement::parseMappedAttribute(Attribute* attr) +{ + if (attr->name() == disabledAttr) { + bool oldDisabled = m_disabled; + m_disabled = !attr->isNull(); + if (oldDisabled != m_disabled) { + setNeedsStyleRecalc(); + if (renderer() && renderer()->style()->hasAppearance()) + renderer()->theme()->stateChanged(renderer(), EnabledState); + } + } else if (attr->name() == readonlyAttr) { + bool oldReadOnly = m_readOnly; + m_readOnly = !attr->isNull(); + if (oldReadOnly != m_readOnly) { + setNeedsStyleRecalc(); + if (renderer() && renderer()->style()->hasAppearance()) + renderer()->theme()->stateChanged(renderer(), ReadOnlyState); + } + } else if (attr->name() == requiredAttr) { + bool oldRequired = m_required; + m_required = !attr->isNull(); + if (oldRequired != m_required) { + setNeedsValidityCheck(); + setNeedsStyleRecalc(); // Updates for :required :optional classes. + } + } else + HTMLElement::parseMappedAttribute(attr); + setNeedsWillValidateCheck(); +} + +void HTMLFormControlElement::attach() +{ + ASSERT(!attached()); + + HTMLElement::attach(); + + // The call to updateFromElement() needs to go after the call through + // to the base class's attach() because that can sometimes do a close + // on the renderer. + if (renderer()) + renderer()->updateFromElement(); + + // Focus the element if it should honour its autofocus attribute. + // We have to determine if the element is a TextArea/Input/Button/Select, + // if input type hidden ignore autofocus. So if disabled or readonly. + bool isInputTypeHidden = false; + if (hasTagName(inputTag)) + isInputTypeHidden = static_cast(this)->isInputTypeHidden(); + + if (autofocus() && renderer() && !document()->ignoreAutofocus() && !isReadOnlyFormControl() && + ((hasTagName(inputTag) && !isInputTypeHidden) || hasTagName(selectTag) || + hasTagName(buttonTag) || hasTagName(textareaTag))) + focus(); +} + +void HTMLFormControlElement::insertedIntoTree(bool deep) +{ + if (!m_form) { + // This handles the case of a new form element being created by + // JavaScript and inserted inside a form. In the case of the parser + // setting a form, we will already have a non-null value for m_form, + // and so we don't need to do anything. + m_form = findFormAncestor(); + if (m_form) + m_form->registerFormElement(this); + else + document()->checkedRadioButtons().addButton(this); + } + + HTMLElement::insertedIntoTree(deep); +} + +static inline Node* findRoot(Node* n) +{ + Node* root = n; + for (; n; n = n->parentNode()) + root = n; + return root; +} + +void HTMLFormControlElement::removedFromTree(bool deep) +{ + // If the form and element are both in the same tree, preserve the connection to the form. + // Otherwise, null out our form and remove ourselves from the form's list of elements. + LegacyHTMLTreeBuilder* treeBuilder = 0; + if (DocumentParser* parser = document()->parser()) + treeBuilder = parser->htmlTreeBuilder(); + + if (m_form && !(treeBuilder && treeBuilder->isHandlingResidualStyleAcrossBlocks()) && findRoot(this) != findRoot(m_form)) { + m_form->removeFormElement(this); + m_form = 0; + } + + HTMLElement::removedFromTree(deep); +} + +const AtomicString& HTMLFormControlElement::formControlName() const +{ + const AtomicString& name = fastGetAttribute(nameAttr); + return name.isNull() ? emptyAtom : name; +} + +void HTMLFormControlElement::setName(const AtomicString& value) +{ + setAttribute(nameAttr, value); +} + +void HTMLFormControlElement::dispatchFormControlChangeEvent() +{ + dispatchEvent(Event::create(eventNames().changeEvent, true, false)); +} + +void HTMLFormControlElement::setDisabled(bool b) +{ + setAttribute(disabledAttr, b ? "" : 0); +} + +bool HTMLFormControlElement::autofocus() const +{ + return hasAttribute(autofocusAttr); +} + +bool HTMLFormControlElement::required() const +{ + return m_required; +} + +static void updateFromElementCallback(Node* node) +{ + ASSERT_ARG(node, node->isElementNode()); + ASSERT_ARG(node, static_cast(node)->isFormControlElement()); + ASSERT(node->renderer()); + if (RenderObject* renderer = node->renderer()) + renderer->updateFromElement(); +} + +void HTMLFormControlElement::recalcStyle(StyleChange change) +{ + HTMLElement::recalcStyle(change); + + // updateFromElement() can cause the selection to change, and in turn + // trigger synchronous layout, so it must not be called during style recalc. + if (renderer()) + queuePostAttachCallback(updateFromElementCallback, this); +} + +bool HTMLFormControlElement::supportsFocus() const +{ + return !disabled(); +} + +bool HTMLFormControlElement::isFocusable() const +{ + if (!renderer() || + !renderer()->isBox() || toRenderBox(renderer())->size().isEmpty()) + return false; + // HTMLElement::isFocusable handles visibility and calls suportsFocus which + // will cover the disabled case. + return HTMLElement::isFocusable(); +} + +bool HTMLFormControlElement::isKeyboardFocusable(KeyboardEvent* event) const +{ + if (isFocusable()) + if (document()->frame()) + return document()->frame()->eventHandler()->tabsToAllControls(event); + return false; +} + +bool HTMLFormControlElement::isMouseFocusable() const +{ +#if PLATFORM(GTK) + return HTMLElement::isMouseFocusable(); +#else + return false; +#endif +} + +short HTMLFormControlElement::tabIndex() const +{ + // Skip the supportsFocus check in HTMLElement. + return Element::tabIndex(); +} + +bool HTMLFormControlElement::recalcWillValidate() const +{ + // FIXME: Should return false if this element has a datalist element as an + // ancestor. See HTML5 4.10.10 'The datalist element.' + return !m_disabled && !m_readOnly; +} + +bool HTMLFormControlElement::willValidate() const +{ + if (!m_willValidateInitialized) { + m_willValidateInitialized = true; + m_willValidate = recalcWillValidate(); + } else { + // If the following assertion fails, setNeedsWillValidateCheck() is not + // called correctly when something which changes recalcWillValidate() result + // is updated. + ASSERT(m_willValidate == recalcWillValidate()); + } + return m_willValidate; +} + +void HTMLFormControlElement::setNeedsWillValidateCheck() +{ + // We need to recalculate willValidte immediately because willValidate + // change can causes style change. + bool newWillValidate = recalcWillValidate(); + if (m_willValidateInitialized && m_willValidate == newWillValidate) + return; + m_willValidateInitialized = true; + m_willValidate = newWillValidate; + setNeedsStyleRecalc(); + // FIXME: Show/hide a validation message. +} + +String HTMLFormControlElement::validationMessage() +{ + return validity()->validationMessage(); +} + +bool HTMLFormControlElement::checkValidity(Vector >* unhandledInvalidControls) +{ + if (!willValidate() || isValidFormControlElement()) + return true; + // An event handler can deref this object. + RefPtr protector(this); + RefPtr originalDocument(document()); + bool needsDefaultAction = dispatchEvent(Event::create(eventNames().invalidEvent, false, true)); + if (needsDefaultAction && unhandledInvalidControls && inDocument() && originalDocument == document()) + unhandledInvalidControls->append(this); + return false; +} + +bool HTMLFormControlElement::isValidFormControlElement() +{ + // If the following assertion fails, setNeedsValidityCheck() is not called + // correctly when something which changes validity is updated. + ASSERT(m_isValid == validity()->valid()); + return m_isValid; +} + +void HTMLFormControlElement::setNeedsValidityCheck() +{ + bool newIsValid = validity()->valid(); + if (willValidate() && newIsValid != m_isValid) { + // Update style for pseudo classes such as :valid :invalid. + setNeedsStyleRecalc(); + } + m_isValid = newIsValid; + // FIXME: show/hide a validation message. +} + +void HTMLFormControlElement::setCustomValidity(const String& error) +{ + validity()->setCustomErrorMessage(error); +} + +void HTMLFormControlElement::dispatchFocusEvent() +{ + if (document()->page()) + document()->page()->chrome()->client()->formDidFocus(this); + + HTMLElement::dispatchFocusEvent(); +} + +void HTMLFormControlElement::dispatchBlurEvent() +{ + if (document()->page()) + document()->page()->chrome()->client()->formDidBlur(this); + + HTMLElement::dispatchBlurEvent(); +} + +HTMLFormElement* HTMLFormControlElement::virtualForm() const +{ + return m_form; +} + +bool HTMLFormControlElement::isDefaultButtonForForm() const +{ + return isSuccessfulSubmitButton() && m_form && m_form->defaultButton() == this; +} + +void HTMLFormControlElement::removeFromForm() +{ + if (!m_form) + return; + m_form->removeFormElement(this); + m_form = 0; +} + +bool HTMLFormControlElement::isLabelable() const +{ + // FIXME: Add meterTag and outputTag to the list once we support them. + return hasTagName(buttonTag) || hasTagName(inputTag) || hasTagName(keygenTag) +#if ENABLE(METER_TAG) + || hasTagName(meterTag) +#endif +#if ENABLE(PROGRESS_TAG) + || hasTagName(progressTag) +#endif + || hasTagName(selectTag) || hasTagName(textareaTag); +} + +PassRefPtr HTMLFormControlElement::labels() +{ + if (!isLabelable()) + return 0; + if (!document()) + return 0; + + NodeRareData* data = Node::ensureRareData(); + if (!data->nodeLists()) { + data->setNodeLists(NodeListsNodeData::create()); + document()->addNodeListCache(); + } + + return LabelsNodeList::create(this); +} + +HTMLFormControlElementWithState::HTMLFormControlElementWithState(const QualifiedName& tagName, Document* doc, HTMLFormElement* f) + : HTMLFormControlElement(tagName, doc, f) +{ + document()->registerFormElementWithState(this); +} + +HTMLFormControlElementWithState::~HTMLFormControlElementWithState() +{ + document()->unregisterFormElementWithState(this); +} + +void HTMLFormControlElementWithState::willMoveToNewOwnerDocument() +{ + document()->unregisterFormElementWithState(this); + HTMLFormControlElement::willMoveToNewOwnerDocument(); +} + +void HTMLFormControlElementWithState::didMoveToNewOwnerDocument() +{ + document()->registerFormElementWithState(this); + HTMLFormControlElement::didMoveToNewOwnerDocument(); +} + +bool HTMLFormControlElementWithState::autoComplete() const +{ + if (!form()) + return true; + return form()->autoComplete(); +} + +bool HTMLFormControlElementWithState::shouldSaveAndRestoreFormControlState() const +{ + // We don't save/restore control state in a form with autocomplete=off. + return autoComplete(); +} + +void HTMLFormControlElementWithState::finishParsingChildren() +{ + HTMLFormControlElement::finishParsingChildren(); + + // We don't save state of a control with shouldSaveAndRestoreFormControlState()=false. + // But we need to skip restoring process too because a control in another + // form might have the same pair of name and type and saved its state. + if (!shouldSaveAndRestoreFormControlState()) + return; + + Document* doc = document(); + if (doc->hasStateForNewFormElements()) { + String state; + if (doc->takeStateForFormElement(name().impl(), type().impl(), state)) + restoreFormControlState(state); + } +} + +void HTMLFormControlElementWithState::defaultEventHandler(Event* event) +{ + if (event->type() == eventNames().webkitEditableContentChangedEvent && renderer() && renderer()->isTextControl()) { + toRenderTextControl(renderer())->subtreeHasChanged(); + return; + } + + HTMLFormControlElement::defaultEventHandler(event); +} + +HTMLTextFormControlElement::HTMLTextFormControlElement(const QualifiedName& tagName, Document* doc, HTMLFormElement* form) + : HTMLFormControlElementWithState(tagName, doc, form) +{ +} + +HTMLTextFormControlElement::~HTMLTextFormControlElement() +{ +} + +void HTMLTextFormControlElement::dispatchFocusEvent() +{ + if (supportsPlaceholder()) + updatePlaceholderVisibility(false); + handleFocusEvent(); + HTMLFormControlElementWithState::dispatchFocusEvent(); +} + +void HTMLTextFormControlElement::dispatchBlurEvent() +{ + if (supportsPlaceholder()) + updatePlaceholderVisibility(false); + handleBlurEvent(); + HTMLFormControlElementWithState::dispatchBlurEvent(); +} + +String HTMLTextFormControlElement::strippedPlaceholder() const +{ + // According to the HTML5 specification, we need to remove CR and LF from + // the attribute value. + const AtomicString& attributeValue = getAttribute(placeholderAttr); + if (!attributeValue.contains(newlineCharacter) && !attributeValue.contains(carriageReturn)) + return attributeValue; + + Vector stripped; + unsigned length = attributeValue.length(); + stripped.reserveCapacity(length); + for (unsigned i = 0; i < length; ++i) { + UChar character = attributeValue[i]; + if (character == newlineCharacter || character == carriageReturn) + continue; + stripped.append(character); + } + return String::adopt(stripped); +} + +static bool isNotLineBreak(UChar ch) { return ch != newlineCharacter && ch != carriageReturn; } + +bool HTMLTextFormControlElement::isPlaceholderEmpty() const +{ + const AtomicString& attributeValue = getAttribute(placeholderAttr); + return attributeValue.string().find(isNotLineBreak) == -1; +} + +bool HTMLTextFormControlElement::placeholderShouldBeVisible() const +{ + return supportsPlaceholder() + && isEmptyValue() + && document()->focusedNode() != this + && !isPlaceholderEmpty(); +} + +void HTMLTextFormControlElement::updatePlaceholderVisibility(bool placeholderValueChanged) +{ + if (supportsPlaceholder() && renderer()) + toRenderTextControl(renderer())->updatePlaceholderVisibility(placeholderShouldBeVisible(), placeholderValueChanged); +} + +RenderTextControl* HTMLTextFormControlElement::textRendererAfterUpdateLayout() +{ + if (!isTextFormControl()) + return 0; + document()->updateLayoutIgnorePendingStylesheets(); + return toRenderTextControl(renderer()); +} + +void HTMLTextFormControlElement::setSelectionStart(int start) +{ + if (RenderTextControl* renderer = textRendererAfterUpdateLayout()) + renderer->setSelectionStart(start); +} + +void HTMLTextFormControlElement::setSelectionEnd(int end) +{ + if (RenderTextControl* renderer = textRendererAfterUpdateLayout()) + renderer->setSelectionEnd(end); +} + +void HTMLTextFormControlElement::select() +{ + if (RenderTextControl* renderer = textRendererAfterUpdateLayout()) + renderer->select(); +} + +void HTMLTextFormControlElement::setSelectionRange(int start, int end) +{ + if (RenderTextControl* renderer = textRendererAfterUpdateLayout()) + renderer->setSelectionRange(start, end); +} + +int HTMLTextFormControlElement::selectionStart() +{ + if (!isTextFormControl()) + return 0; + if (document()->focusedNode() != this && cachedSelectionStart() >= 0) + return cachedSelectionStart(); + if (!renderer()) + return 0; + return toRenderTextControl(renderer())->selectionStart(); +} + +int HTMLTextFormControlElement::selectionEnd() +{ + if (!isTextFormControl()) + return 0; + if (document()->focusedNode() != this && cachedSelectionEnd() >= 0) + return cachedSelectionEnd(); + if (!renderer()) + return 0; + return toRenderTextControl(renderer())->selectionEnd(); +} + +VisibleSelection HTMLTextFormControlElement::selection() const +{ + if (!renderer() || !isTextFormControl() || cachedSelectionStart() < 0 || cachedSelectionEnd() < 0) + return VisibleSelection(); + return toRenderTextControl(renderer())->selection(cachedSelectionStart(), cachedSelectionEnd()); +} + +void HTMLTextFormControlElement::parseMappedAttribute(Attribute* attr) +{ + if (attr->name() == placeholderAttr) + updatePlaceholderVisibility(true); + else if (attr->name() == onselectAttr) + setAttributeEventListener(eventNames().selectEvent, createAttributeEventListener(this, attr)); + else if (attr->name() == onchangeAttr) + setAttributeEventListener(eventNames().changeEvent, createAttributeEventListener(this, attr)); + else + HTMLFormControlElementWithState::parseMappedAttribute(attr); +} + +} // namespace Webcore