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 "HTMLCollection.h"
30#include "HTMLLegendElement.h"
31#include "HTMLNames.h"
32#include "HTMLObjectElement.h"
33#include "RenderFieldset.h"
34#include <wtf/StdLibExtras.h>
35
36namespace WebCore {
37
38using namespace HTMLNames;
39
40inline HTMLFieldSetElement::HTMLFieldSetElement(const QualifiedName& tagName, Document& document, HTMLFormElement* form)
41    : HTMLFormControlElement(tagName, document, form)
42    , m_documentVersion(0)
43{
44    ASSERT(hasTagName(fieldsetTag));
45}
46
47HTMLFieldSetElement::~HTMLFieldSetElement()
48{
49    if (hasAttribute(disabledAttr))
50        document().removeDisabledFieldsetElement();
51}
52
53PassRefPtr<HTMLFieldSetElement> HTMLFieldSetElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form)
54{
55    return adoptRef(new HTMLFieldSetElement(tagName, document, form));
56}
57
58static void updateFromControlElementsAncestorDisabledStateUnder(HTMLElement& startNode, bool isDisabled)
59{
60    HTMLFormControlElement* control;
61    if (isHTMLFormControlElement(startNode))
62        control = &toHTMLFormControlElement(startNode);
63    else
64        control = Traversal<HTMLFormControlElement>::firstWithin(&startNode);
65    while (control) {
66        control->setAncestorDisabled(isDisabled);
67        // Don't call setAncestorDisabled(false) on form contorls inside disabled fieldsets.
68        if (isHTMLFieldSetElement(control) && control->hasAttribute(disabledAttr))
69            control = Traversal<HTMLFormControlElement>::nextSkippingChildren(control, &startNode);
70        else
71            control = Traversal<HTMLFormControlElement>::next(control, &startNode);
72    }
73}
74
75void HTMLFieldSetElement::disabledAttributeChanged()
76{
77    if (hasAttribute(disabledAttr))
78        document().addDisabledFieldsetElement();
79    else
80        document().removeDisabledFieldsetElement();
81
82    HTMLFormControlElement::disabledAttributeChanged();
83}
84
85void HTMLFieldSetElement::disabledStateChanged()
86{
87    // This element must be updated before the style of nodes in its subtree gets recalculated.
88    HTMLFormControlElement::disabledStateChanged();
89
90    if (disabledByAncestorFieldset())
91        return;
92
93    bool thisFieldsetIsDisabled = hasAttribute(disabledAttr);
94    bool hasSeenFirstLegendElement = false;
95    for (HTMLElement* control = Traversal<HTMLElement>::firstChild(this); control; control = Traversal<HTMLElement>::nextSibling(control)) {
96        if (!hasSeenFirstLegendElement && isHTMLLegendElement(control)) {
97            hasSeenFirstLegendElement = true;
98            updateFromControlElementsAncestorDisabledStateUnder(*control, false /* isDisabled */);
99            continue;
100        }
101        updateFromControlElementsAncestorDisabledStateUnder(*control, thisFieldsetIsDisabled);
102    }
103}
104
105void HTMLFieldSetElement::childrenChanged(const ChildChange& change)
106{
107    HTMLFormControlElement::childrenChanged(change);
108    if (!hasAttribute(disabledAttr))
109        return;
110
111    HTMLLegendElement* legend = Traversal<HTMLLegendElement>::firstChild(this);
112    if (!legend)
113        return;
114
115    // We only care about the first legend element (in which form contorls are not disabled by this element) changing here.
116    updateFromControlElementsAncestorDisabledStateUnder(*legend, false /* isDisabled */);
117    while ((legend = Traversal<HTMLLegendElement>::nextSibling(legend)))
118        updateFromControlElementsAncestorDisabledStateUnder(*legend, true);
119}
120
121void HTMLFieldSetElement::didMoveToNewDocument(Document* oldDocument)
122{
123    HTMLFormControlElement::didMoveToNewDocument(oldDocument);
124    if (hasAttribute(disabledAttr)) {
125        if (oldDocument)
126            oldDocument->removeDisabledFieldsetElement();
127        document().addDisabledFieldsetElement();
128    }
129}
130
131bool HTMLFieldSetElement::supportsFocus() const
132{
133    return HTMLElement::supportsFocus();
134}
135
136const AtomicString& HTMLFieldSetElement::formControlType() const
137{
138    DEPRECATED_DEFINE_STATIC_LOCAL(const AtomicString, fieldset, ("fieldset", AtomicString::ConstructFromLiteral));
139    return fieldset;
140}
141
142RenderPtr<RenderElement> HTMLFieldSetElement::createElementRenderer(PassRef<RenderStyle> style)
143{
144    return createRenderer<RenderFieldset>(*this, WTF::move(style));
145}
146
147HTMLLegendElement* HTMLFieldSetElement::legend() const
148{
149    return const_cast<HTMLLegendElement*>(childrenOfType<HTMLLegendElement>(*this).first());
150}
151
152PassRefPtr<HTMLCollection> HTMLFieldSetElement::elements()
153{
154    return ensureCachedHTMLCollection(FormControls);
155}
156
157void HTMLFieldSetElement::refreshElementsIfNeeded() const
158{
159    uint64_t documentVersion = document().domTreeVersion();
160    if (m_documentVersion == documentVersion)
161        return;
162
163    m_documentVersion = documentVersion;
164
165    m_associatedElements.clear();
166
167    for (auto& element : descendantsOfType<Element>(const_cast<HTMLFieldSetElement&>(*this))) {
168        if (element.hasTagName(objectTag))
169            m_associatedElements.append(&toHTMLObjectElement(element));
170        else if (element.isFormControlElement())
171            m_associatedElements.append(&toHTMLFormControlElement(element));
172    }
173}
174
175const Vector<FormAssociatedElement*>& HTMLFieldSetElement::associatedElements() const
176{
177    refreshElementsIfNeeded();
178    return m_associatedElements;
179}
180
181unsigned HTMLFieldSetElement::length() const
182{
183    refreshElementsIfNeeded();
184    unsigned length = 0;
185    for (unsigned i = 0; i < m_associatedElements.size(); ++i) {
186        if (m_associatedElements[i]->isEnumeratable())
187            ++length;
188    }
189    return length;
190}
191
192} // namespace
193