2 * Copyright (C) 2007, 2008, 2009 Apple Inc. All rights reserved.
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.
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.
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.
22 #include "core/html/forms/RadioButtonGroupScope.h"
24 #include "core/InputTypeNames.h"
25 #include "core/html/HTMLInputElement.h"
26 #include "wtf/HashSet.h"
30 class RadioButtonGroup : public NoBaseWillBeGarbageCollected<RadioButtonGroup> {
31 WTF_MAKE_FAST_ALLOCATED_WILL_BE_REMOVED;
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;
47 void setNeedsValidityCheckForAllButtons();
49 void setCheckedButton(HTMLInputElement*);
51 WillBeHeapHashSet<RawPtrWillBeMember<HTMLInputElement>> m_members;
52 RawPtrWillBeMember<HTMLInputElement> m_checkedButton;
53 size_t m_requiredCount;
56 RadioButtonGroup::RadioButtonGroup()
57 : m_checkedButton(nullptr)
62 PassOwnPtrWillBeRawPtr<RadioButtonGroup> RadioButtonGroup::create()
64 return adoptPtrWillBeNoop(new RadioButtonGroup);
67 inline bool RadioButtonGroup::isValid() const
69 return !isRequired() || m_checkedButton;
72 void RadioButtonGroup::setCheckedButton(HTMLInputElement* button)
74 HTMLInputElement* oldCheckedButton = m_checkedButton;
75 if (oldCheckedButton == button)
77 m_checkedButton = button;
79 oldCheckedButton->setChecked(false);
82 void RadioButtonGroup::add(HTMLInputElement* button)
84 ASSERT(button->type() == InputTypeNames::radio);
85 if (!m_members.add(button).isNewEntry)
87 bool groupWasValid = isValid();
88 if (button->isRequired())
90 if (button->checked())
91 setCheckedButton(button);
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();
103 void RadioButtonGroup::updateCheckedState(HTMLInputElement* button)
105 ASSERT(button->type() == InputTypeNames::radio);
106 ASSERT(m_members.contains(button));
107 bool wasValid = isValid();
108 if (button->checked()) {
109 setCheckedButton(button);
111 if (m_checkedButton == button)
112 m_checkedButton = nullptr;
114 if (wasValid != isValid())
115 setNeedsValidityCheckForAllButtons();
116 for (HTMLInputElement* const inputElement : m_members) {
117 inputElement->pseudoStateChanged(CSSSelector::PseudoIndeterminate);
121 void RadioButtonGroup::requiredAttributeChanged(HTMLInputElement* button)
123 ASSERT(button->type() == InputTypeNames::radio);
124 ASSERT(m_members.contains(button));
125 bool wasValid = isValid();
126 if (button->isRequired()) {
129 ASSERT(m_requiredCount);
132 if (wasValid != isValid())
133 setNeedsValidityCheckForAllButtons();
136 void RadioButtonGroup::remove(HTMLInputElement* button)
138 ASSERT(button->type() == InputTypeNames::radio);
139 WillBeHeapHashSet<RawPtrWillBeMember<HTMLInputElement>>::iterator it = m_members.find(button);
140 if (it == m_members.end())
142 bool wasValid = isValid();
143 m_members.remove(it);
144 if (button->isRequired()) {
145 ASSERT(m_requiredCount);
148 if (m_checkedButton == button)
149 m_checkedButton = nullptr;
151 if (m_members.isEmpty()) {
152 ASSERT(!m_requiredCount);
153 ASSERT(!m_checkedButton);
154 } else if (wasValid != isValid()) {
155 setNeedsValidityCheckForAllButtons();
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();
164 void RadioButtonGroup::setNeedsValidityCheckForAllButtons()
166 for (HTMLInputElement* const button : m_members) {
167 ASSERT(button->type() == InputTypeNames::radio);
168 button->setNeedsValidityCheck();
172 bool RadioButtonGroup::contains(HTMLInputElement* button) const
174 return m_members.contains(button);
177 void RadioButtonGroup::trace(Visitor* visitor)
180 visitor->trace(m_members);
181 visitor->trace(m_checkedButton);
185 // ----------------------------------------------------------------
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()
194 RadioButtonGroupScope::~RadioButtonGroupScope()
198 void RadioButtonGroupScope::addButton(HTMLInputElement* element)
200 ASSERT(element->type() == InputTypeNames::radio);
201 if (element->name().isEmpty())
204 if (!m_nameToGroupMap)
205 m_nameToGroupMap = adoptPtrWillBeNoop(new NameToGroupMap);
207 OwnPtrWillBeMember<RadioButtonGroup>& group = m_nameToGroupMap->add(element->name(), nullptr).storedValue->value;
209 group = RadioButtonGroup::create();
213 void RadioButtonGroupScope::updateCheckedState(HTMLInputElement* element)
215 ASSERT(element->type() == InputTypeNames::radio);
216 if (element->name().isEmpty())
218 ASSERT(m_nameToGroupMap);
219 if (!m_nameToGroupMap)
221 RadioButtonGroup* group = m_nameToGroupMap->get(element->name());
223 group->updateCheckedState(element);
226 void RadioButtonGroupScope::requiredAttributeChanged(HTMLInputElement* element)
228 ASSERT(element->type() == InputTypeNames::radio);
229 if (element->name().isEmpty())
231 ASSERT(m_nameToGroupMap);
232 if (!m_nameToGroupMap)
234 RadioButtonGroup* group = m_nameToGroupMap->get(element->name());
236 group->requiredAttributeChanged(element);
239 HTMLInputElement* RadioButtonGroupScope::checkedButtonForGroup(const AtomicString& name) const
241 if (!m_nameToGroupMap)
243 RadioButtonGroup* group = m_nameToGroupMap->get(name);
244 return group ? group->checkedButton() : nullptr;
247 bool RadioButtonGroupScope::isInRequiredGroup(HTMLInputElement* element) const
249 ASSERT(element->type() == InputTypeNames::radio);
250 if (element->name().isEmpty())
252 if (!m_nameToGroupMap)
254 RadioButtonGroup* group = m_nameToGroupMap->get(element->name());
255 return group && group->isRequired() && group->contains(element);
258 void RadioButtonGroupScope::removeButton(HTMLInputElement* element)
260 ASSERT(element->type() == InputTypeNames::radio);
261 if (element->name().isEmpty())
263 if (!m_nameToGroupMap)
266 RadioButtonGroup* group = m_nameToGroupMap->get(element->name());
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());
278 void RadioButtonGroupScope::trace(Visitor* visitor)
281 visitor->trace(m_nameToGroupMap);