Replace 0 and NULL with nullptr in WebCore/dom.
[WebKit-https.git] / Source / WebCore / dom / CheckedRadioButtons.cpp
1 /*
2  * Copyright (C) 2007, 2008, 2009 Apple Inc. All rights reserved.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  *
19  */
20
21 #include "config.h"
22 #include "CheckedRadioButtons.h"
23
24 #include "HTMLInputElement.h"
25 #include <wtf/HashSet.h>
26
27 namespace WebCore {
28
29 class RadioButtonGroup {
30     WTF_MAKE_FAST_ALLOCATED;
31 public:
32     RadioButtonGroup();
33     bool isEmpty() const { return m_members.isEmpty(); }
34     bool isRequired() const { return m_requiredCount; }
35     HTMLInputElement* checkedButton() const { return m_checkedButton; }
36     void add(HTMLInputElement*);
37     void updateCheckedState(HTMLInputElement*);
38     void requiredAttributeChanged(HTMLInputElement*);
39     void remove(HTMLInputElement*);
40     bool contains(HTMLInputElement*) const;
41
42 private:
43     void updateValidityForAllButtons();
44     bool isValid() const;
45     void setCheckedButton(HTMLInputElement*);
46
47     HashSet<HTMLInputElement*> m_members;
48     HTMLInputElement* m_checkedButton;
49     size_t m_requiredCount;
50 };
51
52 RadioButtonGroup::RadioButtonGroup()
53     : m_checkedButton(nullptr)
54     , m_requiredCount(0)
55 {
56 }
57
58 inline bool RadioButtonGroup::isValid() const
59 {
60     return !isRequired() || m_checkedButton;
61 }
62
63 void RadioButtonGroup::setCheckedButton(HTMLInputElement* button)
64 {
65     HTMLInputElement* oldCheckedButton = m_checkedButton;
66     if (oldCheckedButton == button)
67         return;
68     m_checkedButton = button;
69     if (oldCheckedButton)
70         oldCheckedButton->setChecked(false);
71 }
72
73 void RadioButtonGroup::add(HTMLInputElement* button)
74 {
75     ASSERT(button->isRadioButton());
76     if (!m_members.add(button).isNewEntry)
77         return;
78     bool groupWasValid = isValid();
79     if (button->isRequired())
80         ++m_requiredCount;
81     if (button->checked())
82         setCheckedButton(button);
83
84     bool groupIsValid = isValid();
85     if (groupWasValid != groupIsValid)
86         updateValidityForAllButtons();
87     else if (!groupIsValid) {
88         // A radio button not in a group is always valid. We need to make it
89         // invalid only if the group is invalid.
90         button->updateValidity();
91     }
92 }
93
94 void RadioButtonGroup::updateCheckedState(HTMLInputElement* button)
95 {
96     ASSERT(button->isRadioButton());
97     ASSERT(m_members.contains(button));
98     bool wasValid = isValid();
99     if (button->checked())
100         setCheckedButton(button);
101     else {
102         if (m_checkedButton == button)
103             m_checkedButton = nullptr;
104     }
105     if (wasValid != isValid())
106         updateValidityForAllButtons();
107 }
108
109 void RadioButtonGroup::requiredAttributeChanged(HTMLInputElement* button)
110 {
111     ASSERT(button->isRadioButton());
112     ASSERT(m_members.contains(button));
113     bool wasValid = isValid();
114     if (button->isRequired())
115         ++m_requiredCount;
116     else {
117         ASSERT(m_requiredCount);
118         --m_requiredCount;
119     }
120     if (wasValid != isValid())
121         updateValidityForAllButtons();
122 }
123
124 void RadioButtonGroup::remove(HTMLInputElement* button)
125 {
126     ASSERT(button->isRadioButton());
127     HashSet<HTMLInputElement*>::iterator it = m_members.find(button);
128     if (it == m_members.end())
129         return;
130     bool wasValid = isValid();
131     m_members.remove(it);
132     if (button->isRequired()) {
133         ASSERT(m_requiredCount);
134         --m_requiredCount;
135     }
136     if (m_checkedButton == button)
137         m_checkedButton = nullptr;
138
139     if (m_members.isEmpty()) {
140         ASSERT(!m_requiredCount);
141         ASSERT(!m_checkedButton);
142     } else if (wasValid != isValid())
143         updateValidityForAllButtons();
144     if (!wasValid) {
145         // A radio button not in a group is always valid. We need to make it
146         // valid only if the group was invalid.
147         button->updateValidity();
148     }
149 }
150
151 void RadioButtonGroup::updateValidityForAllButtons()
152 {
153     for (auto& button : m_members) {
154         ASSERT(button->isRadioButton());
155         button->updateValidity();
156     }
157 }
158
159 bool RadioButtonGroup::contains(HTMLInputElement* button) const
160 {
161     return m_members.contains(button);
162 }
163
164 // ----------------------------------------------------------------
165
166 // Explicity define empty constructor and destructor in order to prevent the
167 // compiler from generating them as inlines. So we don't need to to define
168 // RadioButtonGroup in the header.
169 CheckedRadioButtons::CheckedRadioButtons()
170 {
171 }
172
173 CheckedRadioButtons::~CheckedRadioButtons()
174 {
175 }
176
177 void CheckedRadioButtons::addButton(HTMLInputElement* element)
178 {
179     ASSERT(element->isRadioButton());
180     if (element->name().isEmpty())
181         return;
182
183     if (!m_nameToGroupMap)
184         m_nameToGroupMap = std::make_unique<NameToGroupMap>();
185
186     auto& group = m_nameToGroupMap->add(element->name().impl(), nullptr).iterator->value;
187     if (!group)
188         group = std::make_unique<RadioButtonGroup>();
189     group->add(element);
190 }
191
192 void CheckedRadioButtons::updateCheckedState(HTMLInputElement* element)
193 {
194     ASSERT(element->isRadioButton());
195     if (element->name().isEmpty())
196         return;
197     ASSERT(m_nameToGroupMap);
198     if (!m_nameToGroupMap)
199         return;
200     RadioButtonGroup* group = m_nameToGroupMap->get(element->name().impl());
201     ASSERT(group);
202     group->updateCheckedState(element);
203 }
204
205 void CheckedRadioButtons::requiredAttributeChanged(HTMLInputElement* element)
206 {
207     ASSERT(element->isRadioButton());
208     if (element->name().isEmpty())
209         return;
210     ASSERT(m_nameToGroupMap);
211     if (!m_nameToGroupMap)
212         return;
213     RadioButtonGroup* group = m_nameToGroupMap->get(element->name().impl());
214     ASSERT(group);
215     group->requiredAttributeChanged(element);
216 }
217
218 HTMLInputElement* CheckedRadioButtons::checkedButtonForGroup(const AtomicString& name) const
219 {
220     if (!m_nameToGroupMap)
221         return 0;
222     m_nameToGroupMap->checkConsistency();
223     RadioButtonGroup* group = m_nameToGroupMap->get(name.impl());
224     return group ? group->checkedButton() : nullptr;
225 }
226
227 bool CheckedRadioButtons::isInRequiredGroup(HTMLInputElement* element) const
228 {
229     ASSERT(element->isRadioButton());
230     if (element->name().isEmpty())
231         return false;
232     if (!m_nameToGroupMap)
233         return false;
234     RadioButtonGroup* group = m_nameToGroupMap->get(element->name().impl());
235     return group && group->isRequired() && group->contains(element);
236 }
237
238 void CheckedRadioButtons::removeButton(HTMLInputElement* element)
239 {
240     ASSERT(element->isRadioButton());
241     if (element->name().isEmpty())
242         return;
243     if (!m_nameToGroupMap)
244         return;
245
246     m_nameToGroupMap->checkConsistency();
247     NameToGroupMap::iterator it = m_nameToGroupMap->find(element->name().impl());
248     if (it == m_nameToGroupMap->end())
249         return;
250     it->value->remove(element);
251     if (it->value->isEmpty()) {
252         // FIXME: We may skip deallocating the empty RadioButtonGroup for
253         // performance improvement. If we do so, we need to change the key type
254         // of m_nameToGroupMap from AtomicStringImpl* to RefPtr<AtomicStringImpl>.
255         m_nameToGroupMap->remove(it);
256         if (m_nameToGroupMap->isEmpty())
257             m_nameToGroupMap = nullptr;
258     }
259 }
260
261 } // namespace