/* * 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-2017 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 "Autofill.h" #include "ControlStates.h" #include "ElementAncestorIterator.h" #include "Event.h" #include "EventHandler.h" #include "EventNames.h" #include "Frame.h" #include "FrameView.h" #include "HTMLFieldSetElement.h" #include "HTMLFormElement.h" #include "HTMLInputElement.h" #include "HTMLLegendElement.h" #include "HTMLTextAreaElement.h" #include "RenderBox.h" #include "RenderTheme.h" #include "StyleTreeResolver.h" #include "ValidationMessage.h" #include #include namespace WebCore { using namespace HTMLNames; HTMLFormControlElement::HTMLFormControlElement(const QualifiedName& tagName, Document& document, HTMLFormElement* form) : LabelableElement(tagName, document) , FormAssociatedElement(form) , m_disabled(false) , m_isReadOnly(false) , m_isRequired(false) , m_valueMatchesRenderer(false) , m_disabledByAncestorFieldset(false) , m_dataListAncestorState(Unknown) , m_willValidateInitialized(false) , m_willValidate(true) , m_isValid(true) , m_wasChangedSinceLastFormControlChangeEvent(false) , m_hasAutofocused(false) { setHasCustomStyleResolveCallbacks(); } HTMLFormControlElement::~HTMLFormControlElement() { // The calls willChangeForm() and didChangeForm() are virtual, we want the // form to be reset while this object still exists. setForm(nullptr); } String HTMLFormControlElement::formEnctype() const { const AtomicString& formEnctypeAttr = attributeWithoutSynchronization(formenctypeAttr); if (formEnctypeAttr.isNull()) return emptyString(); return FormSubmission::Attributes::parseEncodingType(formEnctypeAttr); } void HTMLFormControlElement::setFormEnctype(const String& value) { setAttributeWithoutSynchronization(formenctypeAttr, value); } String HTMLFormControlElement::formMethod() const { auto& formMethodAttr = attributeWithoutSynchronization(formmethodAttr); if (formMethodAttr.isNull()) return emptyString(); return FormSubmission::Attributes::methodString(FormSubmission::Attributes::parseMethodType(formMethodAttr)); } void HTMLFormControlElement::setFormMethod(const String& value) { setAttributeWithoutSynchronization(formmethodAttr, value); } bool HTMLFormControlElement::formNoValidate() const { return hasAttributeWithoutSynchronization(formnovalidateAttr); } String HTMLFormControlElement::formAction() const { const AtomicString& value = attributeWithoutSynchronization(formactionAttr); if (value.isEmpty()) return document().url(); return getURLAttribute(formactionAttr); } void HTMLFormControlElement::setFormAction(const AtomicString& value) { setAttributeWithoutSynchronization(formactionAttr, value); } bool HTMLFormControlElement::computeIsDisabledByFieldsetAncestor() const { Element* previousAncestor = nullptr; for (Element* ancestor = parentElement(); ancestor; ancestor = ancestor->parentElement()) { if (is(*ancestor) && ancestor->hasAttributeWithoutSynchronization(disabledAttr)) { HTMLFieldSetElement& fieldSetAncestor = downcast(*ancestor); bool isInFirstLegend = is(previousAncestor) && previousAncestor == fieldSetAncestor.legend(); return !isInFirstLegend; } previousAncestor = ancestor; } return false; } void HTMLFormControlElement::setAncestorDisabled(bool isDisabled) { ASSERT(computeIsDisabledByFieldsetAncestor() == isDisabled); bool oldValue = m_disabledByAncestorFieldset; m_disabledByAncestorFieldset = isDisabled; if (oldValue != m_disabledByAncestorFieldset) disabledStateChanged(); } void HTMLFormControlElement::parseAttribute(const QualifiedName& name, const AtomicString& value) { if (name == formAttr) formAttributeChanged(); else if (name == disabledAttr) { if (canBeActuallyDisabled()) { bool oldDisabled = m_disabled; m_disabled = !value.isNull(); if (oldDisabled != m_disabled) disabledAttributeChanged(); } } else if (name == readonlyAttr) { bool wasReadOnly = m_isReadOnly; m_isReadOnly = !value.isNull(); if (wasReadOnly != m_isReadOnly) readOnlyAttributeChanged(); } else if (name == requiredAttr) { bool wasRequired = m_isRequired; m_isRequired = !value.isNull(); if (wasRequired != m_isRequired) requiredAttributeChanged(); } else HTMLElement::parseAttribute(name, value); } void HTMLFormControlElement::disabledAttributeChanged() { disabledStateChanged(); } void HTMLFormControlElement::disabledStateChanged() { setNeedsWillValidateCheck(); invalidateStyleForSubtree(); if (renderer() && renderer()->style().hasAppearance()) renderer()->theme().stateChanged(*renderer(), ControlStates::EnabledState); } void HTMLFormControlElement::readOnlyAttributeChanged() { setNeedsWillValidateCheck(); invalidateStyleForSubtree(); } void HTMLFormControlElement::requiredAttributeChanged() { updateValidity(); // Style recalculation is needed because style selectors may include // :required and :optional pseudo-classes. invalidateStyleForSubtree(); } static bool shouldAutofocus(HTMLFormControlElement* element) { if (!element->renderer()) return false; if (!element->hasAttributeWithoutSynchronization(autofocusAttr)) return false; if (!element->isConnected() || !element->document().renderView()) return false; if (element->document().isSandboxed(SandboxAutomaticFeatures)) { // FIXME: This message should be moved off the console once a solution to https://bugs.webkit.org/show_bug.cgi?id=103274 exists. element->document().addConsoleMessage(MessageSource::Security, MessageLevel::Error, ASCIILiteral("Blocked autofocusing on a form control because the form's frame is sandboxed and the 'allow-scripts' permission is not set.")); return false; } if (element->hasAutofocused()) return false; // FIXME: Should this set of hasTagName checks be replaced by a // virtual member function? if (is(*element)) return !downcast(*element).isInputTypeHidden(); if (element->hasTagName(selectTag)) return true; if (element->hasTagName(keygenTag)) return true; if (element->hasTagName(buttonTag)) return true; if (is(*element)) return true; return false; } void HTMLFormControlElement::didAttachRenderers() { // 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(); if (shouldAutofocus(this)) { setAutofocused(); RefPtr element = this; auto* frameView = document().view(); if (frameView && frameView->isInLayout()) { frameView->queuePostLayoutCallback([element] { element->focus(); }); } else { Style::queuePostResolutionCallback([element] { element->focus(); }); } } } void HTMLFormControlElement::didMoveToNewDocument(Document& oldDocument) { FormAssociatedElement::didMoveToNewDocument(oldDocument); HTMLElement::didMoveToNewDocument(oldDocument); } static void addInvalidElementToAncestorFromInsertionPoint(const HTMLFormControlElement& element, ContainerNode* insertionPoint) { if (!is(insertionPoint)) return; for (auto& ancestor : lineageOfType(downcast(*insertionPoint))) ancestor.addInvalidDescendant(element); } static void removeInvalidElementToAncestorFromInsertionPoint(const HTMLFormControlElement& element, ContainerNode* insertionPoint) { if (!is(insertionPoint)) return; for (auto& ancestor : lineageOfType(downcast(*insertionPoint))) ancestor.removeInvalidDescendant(element); } Node::InsertionNotificationRequest HTMLFormControlElement::insertedInto(ContainerNode& insertionPoint) { m_dataListAncestorState = Unknown; setNeedsWillValidateCheck(); if (willValidate() && !isValidFormControlElement()) addInvalidElementToAncestorFromInsertionPoint(*this, &insertionPoint); if (document().hasDisabledFieldsetElement()) setAncestorDisabled(computeIsDisabledByFieldsetAncestor()); HTMLElement::insertedInto(insertionPoint); FormAssociatedElement::insertedInto(insertionPoint); return InsertionShouldCallFinishedInsertingSubtree; } void HTMLFormControlElement::finishedInsertingSubtree() { resetFormOwner(); } void HTMLFormControlElement::removedFrom(ContainerNode& insertionPoint) { bool wasMatchingInvalidPseudoClass = willValidate() && !isValidFormControlElement(); m_validationMessage = nullptr; if (m_disabledByAncestorFieldset) setAncestorDisabled(computeIsDisabledByFieldsetAncestor()); m_dataListAncestorState = Unknown; HTMLElement::removedFrom(insertionPoint); FormAssociatedElement::removedFrom(insertionPoint); if (wasMatchingInvalidPseudoClass) removeInvalidElementToAncestorFromInsertionPoint(*this, &insertionPoint); } void HTMLFormControlElement::setChangedSinceLastFormControlChangeEvent(bool changed) { m_wasChangedSinceLastFormControlChangeEvent = changed; } void HTMLFormControlElement::dispatchChangeEvent() { dispatchScopedEvent(Event::create(eventNames().changeEvent, true, false)); } void HTMLFormControlElement::dispatchFormControlChangeEvent() { dispatchChangeEvent(); setChangedSinceLastFormControlChangeEvent(false); } void HTMLFormControlElement::dispatchFormControlInputEvent() { setChangedSinceLastFormControlChangeEvent(true); dispatchInputEvent(); } bool HTMLFormControlElement::isDisabledFormControl() const { return m_disabled || m_disabledByAncestorFieldset; } bool HTMLFormControlElement::isRequired() const { return m_isRequired; } void HTMLFormControlElement::didRecalcStyle(Style::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()) { RefPtr element = this; Style::queuePostResolutionCallback([element]{ if (auto* renderer = element->renderer()) renderer->updateFromElement(); }); } } bool HTMLFormControlElement::supportsFocus() const { return !isDisabledFormControl(); } bool HTMLFormControlElement::isFocusable() const { // If there's a renderer, make sure the size isn't empty, but if there's no renderer, // it might still be focusable if it's in a canvas subtree (handled in Node::isFocusable). if (renderer() && (!is(*renderer()) || downcast(*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 { return isFocusable() && document().frame() && document().frame()->eventHandler().tabsToAllFormControls(event); } bool HTMLFormControlElement::isMouseFocusable() const { #if PLATFORM(GTK) return HTMLElement::isMouseFocusable(); #else return false; #endif } bool HTMLFormControlElement::matchesValidPseudoClass() const { return willValidate() && isValidFormControlElement(); } bool HTMLFormControlElement::matchesInvalidPseudoClass() const { return willValidate() && !isValidFormControlElement(); } int HTMLFormControlElement::tabIndex() const { // Skip the supportsFocus check in HTMLElement. return Element::tabIndex(); } bool HTMLFormControlElement::computeWillValidate() const { if (m_dataListAncestorState == Unknown) { for (ContainerNode* ancestor = parentNode(); ancestor; ancestor = ancestor->parentNode()) { if (ancestor->hasTagName(datalistTag)) { m_dataListAncestorState = InsideDataList; break; } } if (m_dataListAncestorState == Unknown) m_dataListAncestorState = NotInsideDataList; } return m_dataListAncestorState == NotInsideDataList && !isDisabledOrReadOnly(); } bool HTMLFormControlElement::willValidate() const { if (!m_willValidateInitialized || m_dataListAncestorState == Unknown) { m_willValidateInitialized = true; bool newWillValidate = computeWillValidate(); if (m_willValidate != newWillValidate) m_willValidate = newWillValidate; } else { // If the following assertion fails, setNeedsWillValidateCheck() is not // called correctly when something which changes computeWillValidate() result // is updated. ASSERT(m_willValidate == computeWillValidate()); } return m_willValidate; } void HTMLFormControlElement::setNeedsWillValidateCheck() { // We need to recalculate willValidate immediately because willValidate change can causes style change. bool newWillValidate = computeWillValidate(); if (m_willValidateInitialized && m_willValidate == newWillValidate) return; bool wasValid = m_isValid; m_willValidateInitialized = true; m_willValidate = newWillValidate; updateValidity(); invalidateStyleForSubtree(); if (!m_willValidate && !wasValid) { removeInvalidElementToAncestorFromInsertionPoint(*this, parentNode()); if (HTMLFormElement* form = this->form()) form->removeInvalidAssociatedFormControlIfNeeded(*this); } if (!m_willValidate) hideVisibleValidationMessage(); } void HTMLFormControlElement::updateVisibleValidationMessage() { Page* page = document().page(); if (!page) return; String message; if (renderer() && willValidate()) message = validationMessage().stripWhiteSpace(); if (!m_validationMessage) m_validationMessage = std::make_unique(this); m_validationMessage->updateValidationMessage(message); } void HTMLFormControlElement::hideVisibleValidationMessage() { if (m_validationMessage) m_validationMessage->requestToHideMessage(); } bool HTMLFormControlElement::checkValidity(Vector>* unhandledInvalidControls) { if (!willValidate() || isValidFormControlElement()) return true; // An event handler can deref this object. Ref protectedThis(*this); Ref originalDocument(document()); bool needsDefaultAction = dispatchEvent(Event::create(eventNames().invalidEvent, false, true)); if (needsDefaultAction && unhandledInvalidControls && isConnected() && originalDocument.ptr() == &document()) unhandledInvalidControls->append(this); return false; } bool HTMLFormControlElement::reportValidity() { Vector> elements; if (checkValidity(&elements)) return true; if (elements.isEmpty()) return false; // Needs to update layout now because we'd like to call isFocusable(), which // has !renderer()->needsLayout() assertion. document().updateLayoutIgnorePendingStylesheets(); if (isConnected() && isFocusable()) { focusAndShowValidationMessage(); return false; } if (document().frame()) { String message = makeString("An invalid form control with name='", name(), "' is not focusable."); document().addConsoleMessage(MessageSource::Rendering, MessageLevel::Error, message); } return false; } void HTMLFormControlElement::focusAndShowValidationMessage() { // Calling focus() will scroll the element into view. focus(); updateVisibleValidationMessage(); } inline bool HTMLFormControlElement::isValidFormControlElement() const { // If the following assertion fails, updateValidity() is not called // correctly when something which changes validity is updated. ASSERT(m_isValid == isValid()); return m_isValid; } void HTMLFormControlElement::willChangeForm() { if (HTMLFormElement* form = this->form()) form->removeInvalidAssociatedFormControlIfNeeded(*this); FormAssociatedElement::willChangeForm(); } void HTMLFormControlElement::didChangeForm() { FormAssociatedElement::didChangeForm(); if (HTMLFormElement* form = this->form()) { if (m_willValidateInitialized && m_willValidate && !isValidFormControlElement()) form->registerInvalidAssociatedFormControl(*this); } } void HTMLFormControlElement::updateValidity() { bool willValidate = this->willValidate(); bool wasValid = m_isValid; m_isValid = isValid(); if (willValidate && m_isValid != wasValid) { // Update style for pseudo classes such as :valid :invalid. invalidateStyleForSubtree(); if (!m_isValid) { addInvalidElementToAncestorFromInsertionPoint(*this, parentNode()); if (HTMLFormElement* form = this->form()) form->registerInvalidAssociatedFormControl(*this); } else { removeInvalidElementToAncestorFromInsertionPoint(*this, parentNode()); if (HTMLFormElement* form = this->form()) form->removeInvalidAssociatedFormControlIfNeeded(*this); } } // Updates only if this control already has a validtion message. if (m_validationMessage && m_validationMessage->isVisible()) { // Calls updateVisibleValidationMessage() even if m_isValid is not // changed because a validation message can be chagned. updateVisibleValidationMessage(); } } void HTMLFormControlElement::setCustomValidity(const String& error) { FormAssociatedElement::setCustomValidity(error); updateValidity(); } bool HTMLFormControlElement::validationMessageShadowTreeContains(const Node& node) const { return m_validationMessage && m_validationMessage->shadowTreeContains(node); } void HTMLFormControlElement::dispatchBlurEvent(RefPtr&& newFocusedElement) { HTMLElement::dispatchBlurEvent(WTFMove(newFocusedElement)); hideVisibleValidationMessage(); } #if ENABLE(IOS_AUTOCORRECT_AND_AUTOCAPITALIZE) // FIXME: We should look to share this code with class HTMLFormElement instead of duplicating the logic. bool HTMLFormControlElement::shouldAutocorrect() const { const AtomicString& autocorrectValue = attributeWithoutSynchronization(autocorrectAttr); if (!autocorrectValue.isEmpty()) return !equalLettersIgnoringASCIICase(autocorrectValue, "off"); if (HTMLFormElement* form = this->form()) return form->shouldAutocorrect(); return true; } AutocapitalizeType HTMLFormControlElement::autocapitalizeType() const { AutocapitalizeType type = HTMLElement::autocapitalizeType(); if (type == AutocapitalizeTypeDefault) { if (HTMLFormElement* form = this->form()) return form->autocapitalizeType(); } return type; } #endif HTMLFormControlElement* HTMLFormControlElement::enclosingFormControlElement(Node* node) { for (; node; node = node->parentNode()) { if (is(*node)) return downcast(node); } return nullptr; } String HTMLFormControlElement::autocomplete() const { return autofillData().idlExposedValue; } void HTMLFormControlElement::setAutocomplete(const String& value) { setAttributeWithoutSynchronization(autocompleteAttr, value); } AutofillMantle HTMLFormControlElement::autofillMantle() const { return is(*this) && downcast(this)->isInputTypeHidden() ? AutofillMantle::Anchor : AutofillMantle::Expectation; } AutofillData HTMLFormControlElement::autofillData() const { // FIXME: We could cache the AutofillData if we we had an efficient way to invalidate the cache when // the autofill mantle changed (due to a type change on an element) or the element's form // owner's autocomplete attribute changed or the form owner itself changed. return AutofillData::createFromHTMLFormControlElement(*this); } } // namespace Webcore