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 "core/html/forms/RadioButtonGroupScope.h"
23
24 #include "core/html/HTMLInputElement.h"
25 #include "wtf/HashSet.h"
26
27 namespace WebCore {
28
29 class RadioButtonGroup : public NoBaseWillBeGarbageCollected<RadioButtonGroup> {
30 WTF_MAKE_FAST_ALLOCATED_WILL_BE_REMOVED;
31 public:
32 static PassOwnPtrWillBeRawPtr<RadioButtonGroup> create();
isEmpty() const33 bool isEmpty() const { return m_members.isEmpty(); }
isRequired() const34 bool isRequired() const { return m_requiredCount; }
checkedButton() const35 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 void trace(Visitor*);
43
44 private:
45 RadioButtonGroup();
46 void setNeedsValidityCheckForAllButtons();
47 bool isValid() const;
48 void setCheckedButton(HTMLInputElement*);
49
50 WillBeHeapHashSet<RawPtrWillBeMember<HTMLInputElement> > m_members;
51 RawPtrWillBeMember<HTMLInputElement> m_checkedButton;
52 size_t m_requiredCount;
53 };
54
RadioButtonGroup()55 RadioButtonGroup::RadioButtonGroup()
56 : m_checkedButton(nullptr)
57 , m_requiredCount(0)
58 {
59 }
60
create()61 PassOwnPtrWillBeRawPtr<RadioButtonGroup> RadioButtonGroup::create()
62 {
63 return adoptPtrWillBeNoop(new RadioButtonGroup);
64 }
65
isValid() const66 inline bool RadioButtonGroup::isValid() const
67 {
68 return !isRequired() || m_checkedButton;
69 }
70
setCheckedButton(HTMLInputElement * button)71 void RadioButtonGroup::setCheckedButton(HTMLInputElement* button)
72 {
73 HTMLInputElement* oldCheckedButton = m_checkedButton;
74 if (oldCheckedButton == button)
75 return;
76 m_checkedButton = button;
77 if (oldCheckedButton)
78 oldCheckedButton->setChecked(false);
79 }
80
add(HTMLInputElement * button)81 void RadioButtonGroup::add(HTMLInputElement* button)
82 {
83 ASSERT(button->isRadioButton());
84 if (!m_members.add(button).isNewEntry)
85 return;
86 bool groupWasValid = isValid();
87 if (button->isRequired())
88 ++m_requiredCount;
89 if (button->checked())
90 setCheckedButton(button);
91
92 bool groupIsValid = isValid();
93 if (groupWasValid != groupIsValid) {
94 setNeedsValidityCheckForAllButtons();
95 } else if (!groupIsValid) {
96 // A radio button not in a group is always valid. We need to make it
97 // invalid only if the group is invalid.
98 button->setNeedsValidityCheck();
99 }
100 }
101
updateCheckedState(HTMLInputElement * button)102 void RadioButtonGroup::updateCheckedState(HTMLInputElement* button)
103 {
104 ASSERT(button->isRadioButton());
105 ASSERT(m_members.contains(button));
106 bool wasValid = isValid();
107 if (button->checked()) {
108 setCheckedButton(button);
109 } else {
110 if (m_checkedButton == button)
111 m_checkedButton = nullptr;
112 }
113 if (wasValid != isValid())
114 setNeedsValidityCheckForAllButtons();
115 }
116
requiredAttributeChanged(HTMLInputElement * button)117 void RadioButtonGroup::requiredAttributeChanged(HTMLInputElement* button)
118 {
119 ASSERT(button->isRadioButton());
120 ASSERT(m_members.contains(button));
121 bool wasValid = isValid();
122 if (button->isRequired()) {
123 ++m_requiredCount;
124 } else {
125 ASSERT(m_requiredCount);
126 --m_requiredCount;
127 }
128 if (wasValid != isValid())
129 setNeedsValidityCheckForAllButtons();
130 }
131
remove(HTMLInputElement * button)132 void RadioButtonGroup::remove(HTMLInputElement* button)
133 {
134 ASSERT(button->isRadioButton());
135 WillBeHeapHashSet<RawPtrWillBeMember<HTMLInputElement> >::iterator it = m_members.find(button);
136 if (it == m_members.end())
137 return;
138 bool wasValid = isValid();
139 m_members.remove(it);
140 if (button->isRequired()) {
141 ASSERT(m_requiredCount);
142 --m_requiredCount;
143 }
144 if (m_checkedButton == button)
145 m_checkedButton = nullptr;
146
147 if (m_members.isEmpty()) {
148 ASSERT(!m_requiredCount);
149 ASSERT(!m_checkedButton);
150 } else if (wasValid != isValid()) {
151 setNeedsValidityCheckForAllButtons();
152 }
153 if (!wasValid) {
154 // A radio button not in a group is always valid. We need to make it
155 // valid only if the group was invalid.
156 button->setNeedsValidityCheck();
157 }
158 }
159
setNeedsValidityCheckForAllButtons()160 void RadioButtonGroup::setNeedsValidityCheckForAllButtons()
161 {
162 typedef WillBeHeapHashSet<RawPtrWillBeMember<HTMLInputElement> >::const_iterator Iterator;
163 Iterator end = m_members.end();
164 for (Iterator it = m_members.begin(); it != end; ++it) {
165 HTMLInputElement* button = *it;
166 ASSERT(button->isRadioButton());
167 button->setNeedsValidityCheck();
168 }
169 }
170
contains(HTMLInputElement * button) const171 bool RadioButtonGroup::contains(HTMLInputElement* button) const
172 {
173 return m_members.contains(button);
174 }
175
trace(Visitor * visitor)176 void RadioButtonGroup::trace(Visitor* visitor)
177 {
178 visitor->trace(m_members);
179 visitor->trace(m_checkedButton);
180 }
181
182 // ----------------------------------------------------------------
183
184 // Explicity define empty constructor and destructor in order to prevent the
185 // compiler from generating them as inlines. So we don't need to to define
186 // RadioButtonGroup in the header.
RadioButtonGroupScope()187 RadioButtonGroupScope::RadioButtonGroupScope()
188 {
189 }
190
~RadioButtonGroupScope()191 RadioButtonGroupScope::~RadioButtonGroupScope()
192 {
193 }
194
addButton(HTMLInputElement * element)195 void RadioButtonGroupScope::addButton(HTMLInputElement* element)
196 {
197 ASSERT(element->isRadioButton());
198 if (element->name().isEmpty())
199 return;
200
201 if (!m_nameToGroupMap)
202 m_nameToGroupMap = adoptPtrWillBeNoop(new NameToGroupMap);
203
204 OwnPtrWillBeMember<RadioButtonGroup>& group = m_nameToGroupMap->add(element->name(), nullptr).storedValue->value;
205 if (!group)
206 group = RadioButtonGroup::create();
207 group->add(element);
208 }
209
updateCheckedState(HTMLInputElement * element)210 void RadioButtonGroupScope::updateCheckedState(HTMLInputElement* element)
211 {
212 ASSERT(element->isRadioButton());
213 if (element->name().isEmpty())
214 return;
215 ASSERT(m_nameToGroupMap);
216 if (!m_nameToGroupMap)
217 return;
218 RadioButtonGroup* group = m_nameToGroupMap->get(element->name());
219 ASSERT(group);
220 group->updateCheckedState(element);
221 }
222
requiredAttributeChanged(HTMLInputElement * element)223 void RadioButtonGroupScope::requiredAttributeChanged(HTMLInputElement* element)
224 {
225 ASSERT(element->isRadioButton());
226 if (element->name().isEmpty())
227 return;
228 ASSERT(m_nameToGroupMap);
229 if (!m_nameToGroupMap)
230 return;
231 RadioButtonGroup* group = m_nameToGroupMap->get(element->name());
232 ASSERT(group);
233 group->requiredAttributeChanged(element);
234 }
235
checkedButtonForGroup(const AtomicString & name) const236 HTMLInputElement* RadioButtonGroupScope::checkedButtonForGroup(const AtomicString& name) const
237 {
238 if (!m_nameToGroupMap)
239 return 0;
240 RadioButtonGroup* group = m_nameToGroupMap->get(name);
241 return group ? group->checkedButton() : 0;
242 }
243
isInRequiredGroup(HTMLInputElement * element) const244 bool RadioButtonGroupScope::isInRequiredGroup(HTMLInputElement* element) const
245 {
246 ASSERT(element->isRadioButton());
247 if (element->name().isEmpty())
248 return false;
249 if (!m_nameToGroupMap)
250 return false;
251 RadioButtonGroup* group = m_nameToGroupMap->get(element->name());
252 return group && group->isRequired() && group->contains(element);
253 }
254
removeButton(HTMLInputElement * element)255 void RadioButtonGroupScope::removeButton(HTMLInputElement* element)
256 {
257 ASSERT(element->isRadioButton());
258 if (element->name().isEmpty())
259 return;
260 if (!m_nameToGroupMap)
261 return;
262
263 RadioButtonGroup* group = m_nameToGroupMap->get(element->name());
264 if (!group)
265 return;
266 group->remove(element);
267 if (group->isEmpty()) {
268 // We don't remove an empty RadioButtonGroup from m_nameToGroupMap for
269 // better performance.
270 ASSERT(!group->isRequired());
271 ASSERT_WITH_SECURITY_IMPLICATION(!group->checkedButton());
272 }
273 }
274
trace(Visitor * visitor)275 void RadioButtonGroupScope::trace(Visitor* visitor)
276 {
277 #if ENABLE(OILPAN)
278 visitor->trace(m_nameToGroupMap);
279 #endif
280 }
281
282 } // namespace
283