1/*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * (C) 2001 Dirk Mueller (mueller@kde.org)
5 * Copyright (C) 2004, 2005, 2006, 2010 Apple Inc. All rights reserved.
6 * (C) 2006 Alexey Proskuryakov (ap@nypop.com)
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details.
17 *
18 * You should have received a copy of the GNU Library General Public License
19 * along with this library; see the file COPYING.LIB. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 *
23 */
24
25#include "config.h"
26#include "HTMLFieldSetElement.h"
27
28#include "ElementIterator.h"
29#include "GenericCachedHTMLCollection.h"
30#include "HTMLFormControlsCollection.h"
31#include "HTMLLegendElement.h"
32#include "HTMLNames.h"
33#include "HTMLObjectElement.h"
34#include "NodeRareData.h"
35#include "RenderElement.h"
36#include "ScriptDisallowedScope.h"
37#include <wtf/IsoMallocInlines.h>
38#include <wtf/StdLibExtras.h>
39
40namespace WebCore {
41
42WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLFieldSetElement);
43
44using namespace HTMLNames;
45
46inline HTMLFieldSetElement::HTMLFieldSetElement(const QualifiedName& tagName, Document& document, HTMLFormElement* form)
47 : HTMLFormControlElement(tagName, document, form)
48{
49 ASSERT(hasTagName(fieldsetTag));
50}
51
52HTMLFieldSetElement::~HTMLFieldSetElement()
53{
54 if (m_hasDisabledAttribute)
55 document().removeDisabledFieldsetElement();
56}
57
58Ref<HTMLFieldSetElement> HTMLFieldSetElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form)
59{
60 return adoptRef(*new HTMLFieldSetElement(tagName, document, form));
61}
62
63static void updateFromControlElementsAncestorDisabledStateUnder(HTMLElement& startNode, bool isDisabled)
64{
65 RefPtr<HTMLFormControlElement> control;
66 if (is<HTMLFormControlElement>(startNode))
67 control = &downcast<HTMLFormControlElement>(startNode);
68 else
69 control = Traversal<HTMLFormControlElement>::firstWithin(startNode);
70 while (control) {
71 control->setAncestorDisabled(isDisabled);
72 // Don't call setAncestorDisabled(false) on form controls inside disabled fieldsets.
73 if (is<HTMLFieldSetElement>(*control) && control->hasAttributeWithoutSynchronization(disabledAttr))
74 control = Traversal<HTMLFormControlElement>::nextSkippingChildren(*control, &startNode);
75 else
76 control = Traversal<HTMLFormControlElement>::next(*control, &startNode);
77 }
78}
79
80void HTMLFieldSetElement::disabledAttributeChanged()
81{
82 bool hasDisabledAttribute = hasAttributeWithoutSynchronization(disabledAttr);
83 if (m_hasDisabledAttribute != hasDisabledAttribute) {
84 m_hasDisabledAttribute = hasDisabledAttribute;
85 if (hasDisabledAttribute)
86 document().addDisabledFieldsetElement();
87 else
88 document().removeDisabledFieldsetElement();
89 }
90
91 HTMLFormControlElement::disabledAttributeChanged();
92}
93
94void HTMLFieldSetElement::disabledStateChanged()
95{
96 // This element must be updated before the style of nodes in its subtree gets recalculated.
97 HTMLFormControlElement::disabledStateChanged();
98
99 if (disabledByAncestorFieldset())
100 return;
101
102 bool thisFieldsetIsDisabled = hasAttributeWithoutSynchronization(disabledAttr);
103 bool hasSeenFirstLegendElement = false;
104 for (RefPtr<HTMLElement> control = Traversal<HTMLElement>::firstChild(*this); control; control = Traversal<HTMLElement>::nextSibling(*control)) {
105 if (!hasSeenFirstLegendElement && is<HTMLLegendElement>(*control)) {
106 hasSeenFirstLegendElement = true;
107 updateFromControlElementsAncestorDisabledStateUnder(*control, false /* isDisabled */);
108 continue;
109 }
110 updateFromControlElementsAncestorDisabledStateUnder(*control, thisFieldsetIsDisabled);
111 }
112}
113
114void HTMLFieldSetElement::childrenChanged(const ChildChange& change)
115{
116 HTMLFormControlElement::childrenChanged(change);
117 if (!hasAttributeWithoutSynchronization(disabledAttr))
118 return;
119
120 RefPtr<HTMLLegendElement> legend = Traversal<HTMLLegendElement>::firstChild(*this);
121 if (!legend)
122 return;
123
124 // We only care about the first legend element (in which form controls are not disabled by this element) changing here.
125 updateFromControlElementsAncestorDisabledStateUnder(*legend, false /* isDisabled */);
126 while ((legend = Traversal<HTMLLegendElement>::nextSibling(*legend)))
127 updateFromControlElementsAncestorDisabledStateUnder(*legend, true);
128}
129
130void HTMLFieldSetElement::didMoveToNewDocument(Document& oldDocument, Document& newDocument)
131{
132 ASSERT_WITH_SECURITY_IMPLICATION(&document() == &newDocument);
133 HTMLFormControlElement::didMoveToNewDocument(oldDocument, newDocument);
134 if (m_hasDisabledAttribute) {
135 oldDocument.removeDisabledFieldsetElement();
136 newDocument.addDisabledFieldsetElement();
137 }
138}
139
140bool HTMLFieldSetElement::matchesValidPseudoClass() const
141{
142 return m_invalidDescendants.isEmpty();
143}
144
145bool HTMLFieldSetElement::matchesInvalidPseudoClass() const
146{
147 return !m_invalidDescendants.isEmpty();
148}
149
150bool HTMLFieldSetElement::supportsFocus() const
151{
152 return HTMLElement::supportsFocus();
153}
154
155const AtomicString& HTMLFieldSetElement::formControlType() const
156{
157 static NeverDestroyed<const AtomicString> fieldset("fieldset", AtomicString::ConstructFromLiteral);
158 return fieldset;
159}
160
161RenderPtr<RenderElement> HTMLFieldSetElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
162{
163 return RenderElement::createFor(*this, WTFMove(style), RenderElement::OnlyCreateBlockAndFlexboxRenderers);
164}
165
166HTMLLegendElement* HTMLFieldSetElement::legend() const
167{
168 return const_cast<HTMLLegendElement*>(childrenOfType<HTMLLegendElement>(*this).first());
169}
170
171Ref<HTMLCollection> HTMLFieldSetElement::elements()
172{
173 return ensureRareData().ensureNodeLists().addCachedCollection<GenericCachedHTMLCollection<CollectionTypeTraits<FieldSetElements>::traversalType>>(*this, FieldSetElements);
174}
175
176void HTMLFieldSetElement::addInvalidDescendant(const HTMLFormControlElement& invalidFormControlElement)
177{
178 ASSERT_WITH_MESSAGE(!is<HTMLFieldSetElement>(invalidFormControlElement), "FieldSet are never candidates for constraint validation.");
179 ASSERT(static_cast<const Element&>(invalidFormControlElement).matchesInvalidPseudoClass());
180 ASSERT_WITH_MESSAGE(!m_invalidDescendants.contains(&invalidFormControlElement), "Updating the fieldset on validity change is not an efficient operation, it should only be done when necessary.");
181
182 if (m_invalidDescendants.isEmpty())
183 invalidateStyleForSubtree();
184 m_invalidDescendants.add(&invalidFormControlElement);
185}
186
187void HTMLFieldSetElement::removeInvalidDescendant(const HTMLFormControlElement& formControlElement)
188{
189 ASSERT_WITH_MESSAGE(!is<HTMLFieldSetElement>(formControlElement), "FieldSet are never candidates for constraint validation.");
190 ASSERT_WITH_MESSAGE(m_invalidDescendants.contains(&formControlElement), "Updating the fieldset on validity change is not an efficient operation, it should only be done when necessary.");
191
192 m_invalidDescendants.remove(&formControlElement);
193 if (m_invalidDescendants.isEmpty())
194 invalidateStyleForSubtree();
195}
196
197} // namespace
198