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