blob: d2cf8cd9da1a8ae51defedc6a84042bd890eba61 [file] [log] [blame]
/*
* 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 "third_party/blink/renderer/core/html/forms/listed_element.h"
#include "third_party/blink/renderer/core/dom/element_traversal.h"
#include "third_party/blink/renderer/core/dom/id_target_observer.h"
#include "third_party/blink/renderer/core/dom/node_traversal.h"
#include "third_party/blink/renderer/core/html/custom/element_internals.h"
#include "third_party/blink/renderer/core/html/forms/html_field_set_element.h"
#include "third_party/blink/renderer/core/html/forms/html_form_control_element.h"
#include "third_party/blink/renderer/core/html/forms/html_form_element.h"
#include "third_party/blink/renderer/core/html/forms/html_legend_element.h"
#include "third_party/blink/renderer/core/html/forms/validity_state.h"
#include "third_party/blink/renderer/core/html/html_object_element.h"
#include "third_party/blink/renderer/core/html_names.h"
namespace blink {
using namespace html_names;
class FormAttributeTargetObserver : public IdTargetObserver {
public:
static FormAttributeTargetObserver* Create(const AtomicString& id,
ListedElement*);
FormAttributeTargetObserver(const AtomicString& id, ListedElement*);
void Trace(blink::Visitor*) override;
void IdTargetChanged() override;
private:
Member<ListedElement> element_;
};
ListedElement::ListedElement() : form_was_set_by_parser_(false) {}
ListedElement::~ListedElement() {
// We can't call setForm here because it contains virtual calls.
}
void ListedElement::Trace(blink::Visitor* visitor) {
visitor->Trace(form_attribute_target_observer_);
visitor->Trace(form_);
visitor->Trace(validity_state_);
}
ValidityState* ListedElement::validity() {
if (!validity_state_)
validity_state_ = ValidityState::Create(this);
return validity_state_.Get();
}
void ListedElement::DidMoveToNewDocument(Document& old_document) {
HTMLElement* element = ToHTMLElement(this);
if (element->FastHasAttribute(kFormAttr))
SetFormAttributeTargetObserver(nullptr);
}
void ListedElement::InsertedInto(ContainerNode& insertion_point) {
ancestor_disabled_state_ = AncestorDisabledState::kUnknown;
// Force traversal to find ancestor
may_have_field_set_ancestor_ = true;
if (!form_was_set_by_parser_ || !form_ ||
NodeTraversal::HighestAncestorOrSelf(insertion_point) !=
NodeTraversal::HighestAncestorOrSelf(*form_.Get()))
ResetFormOwner();
if (!insertion_point.isConnected())
return;
HTMLElement* element = ToHTMLElement(this);
if (element->FastHasAttribute(kFormAttr))
ResetFormAttributeTargetObserver();
}
void ListedElement::RemovedFrom(ContainerNode& insertion_point) {
ancestor_disabled_state_ = AncestorDisabledState::kUnknown;
HTMLElement* element = ToHTMLElement(this);
if (insertion_point.isConnected() && element->FastHasAttribute(kFormAttr)) {
SetFormAttributeTargetObserver(nullptr);
ResetFormOwner();
return;
}
// 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.
if (form_ && NodeTraversal::HighestAncestorOrSelf(*element) !=
NodeTraversal::HighestAncestorOrSelf(*form_.Get()))
ResetFormOwner();
}
HTMLFormElement* ListedElement::FindAssociatedForm(
const HTMLElement* element,
const AtomicString& form_id,
HTMLFormElement* form_ancestor) {
// 3. If the element is reassociateable, has a form content attribute, and
// is itself in a Document, then run these substeps:
if (!form_id.IsNull() && element->isConnected()) {
// 3.1. If the first element in the Document to have an ID that is
// case-sensitively equal to the element's form content attribute's
// value is a form element, then associate the form-associated element
// with that form element.
// 3.2. Abort the "reset the form owner" steps.
Element* new_form_candidate =
element->GetTreeScope().getElementById(form_id);
return ToHTMLFormElementOrNull(new_form_candidate);
}
// 4. Otherwise, if the form-associated element in question has an ancestor
// form element, then associate the form-associated element with the nearest
// such ancestor form element.
return form_ancestor;
}
void ListedElement::FormRemovedFromTree(const Node& form_root) {
DCHECK(form_);
if (NodeTraversal::HighestAncestorOrSelf(ToHTMLElement(*this)) == form_root)
return;
ResetFormOwner();
}
void ListedElement::AssociateByParser(HTMLFormElement* form) {
if (form && form->isConnected()) {
form_was_set_by_parser_ = true;
SetForm(form);
form->DidAssociateByParser();
}
}
void ListedElement::SetForm(HTMLFormElement* new_form) {
if (form_.Get() == new_form)
return;
WillChangeForm();
if (form_)
form_->Disassociate(*this);
if (new_form) {
form_ = new_form;
form_->Associate(*this);
} else {
form_ = nullptr;
}
DidChangeForm();
}
void ListedElement::WillChangeForm() {}
void ListedElement::DidChangeForm() {
if (!form_was_set_by_parser_ && form_ && form_->isConnected()) {
HTMLElement* element = ToHTMLElement(this);
element->GetDocument().DidAssociateFormControl(element);
}
}
void ListedElement::ResetFormOwner() {
form_was_set_by_parser_ = false;
HTMLElement* element = ToHTMLElement(this);
const AtomicString& form_id(element->FastGetAttribute(kFormAttr));
HTMLFormElement* nearest_form = element->FindFormAncestor();
// 1. If the element's form owner is not null, and either the element is not
// reassociateable or its form content attribute is not present, and the
// element's form owner is its nearest form element ancestor after the
// change to the ancestor chain, then do nothing, and abort these steps.
if (form_ && form_id.IsNull() && form_.Get() == nearest_form)
return;
SetForm(FindAssociatedForm(element, form_id, nearest_form));
}
void ListedElement::FormAttributeChanged() {
ResetFormOwner();
ResetFormAttributeTargetObserver();
}
bool ListedElement::CustomError() const {
const HTMLElement* element = ToHTMLElement(this);
return element->willValidate() && !custom_validation_message_.IsEmpty();
}
bool ListedElement::HasBadInput() const {
return false;
}
bool ListedElement::PatternMismatch() const {
return false;
}
bool ListedElement::RangeOverflow() const {
return false;
}
bool ListedElement::RangeUnderflow() const {
return false;
}
bool ListedElement::StepMismatch() const {
return false;
}
bool ListedElement::TooLong() const {
return false;
}
bool ListedElement::TooShort() const {
return false;
}
bool ListedElement::TypeMismatch() const {
return false;
}
bool ListedElement::Valid() const {
bool some_error = TypeMismatch() || StepMismatch() || RangeUnderflow() ||
RangeOverflow() || TooLong() || TooShort() ||
PatternMismatch() || ValueMissing() || HasBadInput() ||
CustomError();
return !some_error;
}
bool ListedElement::ValueMissing() const {
return false;
}
String ListedElement::CustomValidationMessage() const {
return custom_validation_message_;
}
String ListedElement::validationMessage() const {
return CustomError() ? custom_validation_message_ : String();
}
String ListedElement::ValidationSubMessage() const {
return String();
}
void ListedElement::setCustomValidity(const String& error) {
custom_validation_message_ = error;
}
void ListedElement::DisabledAttributeChanged() {
HTMLElement& element = ToHTMLElement(*this);
element.PseudoStateChanged(CSSSelector::kPseudoDisabled);
element.PseudoStateChanged(CSSSelector::kPseudoEnabled);
}
void ListedElement::UpdateAncestorDisabledState() const {
if (!may_have_field_set_ancestor_) {
ancestor_disabled_state_ = AncestorDisabledState::kEnabled;
return;
}
may_have_field_set_ancestor_ = false;
// <fieldset> element of which |disabled| attribute affects the
// target element.
HTMLFieldSetElement* disabled_fieldset_ancestor = nullptr;
ContainerNode* last_legend_ancestor = nullptr;
for (HTMLElement* ancestor =
Traversal<HTMLElement>::FirstAncestor(ToHTMLElement(*this));
ancestor; ancestor = Traversal<HTMLElement>::FirstAncestor(*ancestor)) {
if (IsHTMLLegendElement(*ancestor)) {
last_legend_ancestor = ancestor;
continue;
}
if (!IsHTMLFieldSetElement(*ancestor))
continue;
may_have_field_set_ancestor_ = true;
if (ancestor->IsDisabledFormControl()) {
auto* fieldset = ToHTMLFieldSetElement(ancestor);
if (last_legend_ancestor && last_legend_ancestor == fieldset->Legend())
continue;
disabled_fieldset_ancestor = fieldset;
break;
}
}
ancestor_disabled_state_ = disabled_fieldset_ancestor
? AncestorDisabledState::kDisabled
: AncestorDisabledState::kEnabled;
}
void ListedElement::AncestorDisabledStateWasChanged() {
ancestor_disabled_state_ = AncestorDisabledState::kUnknown;
DisabledAttributeChanged();
}
bool ListedElement::IsActuallyDisabled() const {
if (ToHTMLElement(*this).FastHasAttribute(html_names::kDisabledAttr))
return true;
if (ancestor_disabled_state_ == AncestorDisabledState::kUnknown)
UpdateAncestorDisabledState();
return ancestor_disabled_state_ == AncestorDisabledState::kDisabled;
}
void ListedElement::SetFormAttributeTargetObserver(
FormAttributeTargetObserver* new_observer) {
if (form_attribute_target_observer_)
form_attribute_target_observer_->Unregister();
form_attribute_target_observer_ = new_observer;
}
void ListedElement::ResetFormAttributeTargetObserver() {
HTMLElement* element = ToHTMLElement(this);
const AtomicString& form_id(element->FastGetAttribute(kFormAttr));
if (!form_id.IsNull() && element->isConnected()) {
SetFormAttributeTargetObserver(
FormAttributeTargetObserver::Create(form_id, this));
} else {
SetFormAttributeTargetObserver(nullptr);
}
}
void ListedElement::FormAttributeTargetChanged() {
ResetFormOwner();
}
const AtomicString& ListedElement::GetName() const {
const AtomicString& name = ToHTMLElement(this)->GetNameAttribute();
return name.IsNull() ? g_empty_atom : name;
}
bool ListedElement::IsFormControlElementWithState() const {
return false;
}
bool ListedElement::IsElementInternals() const {
return false;
}
const HTMLElement& ToHTMLElement(const ListedElement& listed_element) {
if (listed_element.IsFormControlElement())
return ToHTMLFormControlElement(listed_element);
if (listed_element.IsElementInternals())
return To<ElementInternals>(listed_element).Target();
return ToHTMLObjectElementFromListedElement(listed_element);
}
const HTMLElement* ToHTMLElement(const ListedElement* listed_element) {
DCHECK(listed_element);
return &ToHTMLElement(*listed_element);
}
HTMLElement* ToHTMLElement(ListedElement* listed_element) {
return const_cast<HTMLElement*>(
ToHTMLElement(static_cast<const ListedElement*>(listed_element)));
}
HTMLElement& ToHTMLElement(ListedElement& listed_element) {
return const_cast<HTMLElement&>(
ToHTMLElement(static_cast<const ListedElement&>(listed_element)));
}
FormAttributeTargetObserver* FormAttributeTargetObserver::Create(
const AtomicString& id,
ListedElement* element) {
return MakeGarbageCollected<FormAttributeTargetObserver>(id, element);
}
FormAttributeTargetObserver::FormAttributeTargetObserver(const AtomicString& id,
ListedElement* element)
: IdTargetObserver(
ToHTMLElement(element)->GetTreeScope().GetIdTargetObserverRegistry(),
id),
element_(element) {}
void FormAttributeTargetObserver::Trace(blink::Visitor* visitor) {
visitor->Trace(element_);
IdTargetObserver::Trace(visitor);
}
void FormAttributeTargetObserver::IdTargetChanged() {
element_->FormAttributeTargetChanged();
}
} // namespace blink