• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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