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 27namespace WebCore { 28 29class RadioButtonGroup { 30 WTF_MAKE_FAST_ALLOCATED; 31public: 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 42private: 43 void setNeedsValidityCheckForAllButtons(); 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 52RadioButtonGroup::RadioButtonGroup() 53 : m_checkedButton(nullptr) 54 , m_requiredCount(0) 55{ 56} 57 58inline bool RadioButtonGroup::isValid() const 59{ 60 return !isRequired() || m_checkedButton; 61} 62 63void 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 73void 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 setNeedsValidityCheckForAllButtons(); 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->setNeedsValidityCheck(); 91 } 92} 93 94void 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 = 0; 104 } 105 if (wasValid != isValid()) 106 setNeedsValidityCheckForAllButtons(); 107} 108 109void 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 setNeedsValidityCheckForAllButtons(); 122} 123 124void 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 setNeedsValidityCheckForAllButtons(); 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->setNeedsValidityCheck(); 148 } 149} 150 151void RadioButtonGroup::setNeedsValidityCheckForAllButtons() 152{ 153 typedef HashSet<HTMLInputElement*>::const_iterator Iterator; 154 Iterator end = m_members.end(); 155 for (Iterator it = m_members.begin(); it != end; ++it) { 156 HTMLInputElement* button = *it; 157 ASSERT(button->isRadioButton()); 158 button->setNeedsValidityCheck(); 159 } 160} 161 162bool RadioButtonGroup::contains(HTMLInputElement* button) const 163{ 164 return m_members.contains(button); 165} 166 167// ---------------------------------------------------------------- 168 169// Explicity define empty constructor and destructor in order to prevent the 170// compiler from generating them as inlines. So we don't need to to define 171// RadioButtonGroup in the header. 172CheckedRadioButtons::CheckedRadioButtons() 173{ 174} 175 176CheckedRadioButtons::~CheckedRadioButtons() 177{ 178} 179 180void CheckedRadioButtons::addButton(HTMLInputElement* element) 181{ 182 ASSERT(element->isRadioButton()); 183 if (element->name().isEmpty()) 184 return; 185 186 if (!m_nameToGroupMap) 187 m_nameToGroupMap = std::make_unique<NameToGroupMap>(); 188 189 auto& group = m_nameToGroupMap->add(element->name().impl(), nullptr).iterator->value; 190 if (!group) 191 group = std::make_unique<RadioButtonGroup>(); 192 group->add(element); 193} 194 195void CheckedRadioButtons::updateCheckedState(HTMLInputElement* element) 196{ 197 ASSERT(element->isRadioButton()); 198 if (element->name().isEmpty()) 199 return; 200 ASSERT(m_nameToGroupMap); 201 if (!m_nameToGroupMap) 202 return; 203 RadioButtonGroup* group = m_nameToGroupMap->get(element->name().impl()); 204 ASSERT(group); 205 group->updateCheckedState(element); 206} 207 208void CheckedRadioButtons::requiredAttributeChanged(HTMLInputElement* element) 209{ 210 ASSERT(element->isRadioButton()); 211 if (element->name().isEmpty()) 212 return; 213 ASSERT(m_nameToGroupMap); 214 if (!m_nameToGroupMap) 215 return; 216 RadioButtonGroup* group = m_nameToGroupMap->get(element->name().impl()); 217 ASSERT(group); 218 group->requiredAttributeChanged(element); 219} 220 221HTMLInputElement* CheckedRadioButtons::checkedButtonForGroup(const AtomicString& name) const 222{ 223 if (!m_nameToGroupMap) 224 return 0; 225 m_nameToGroupMap->checkConsistency(); 226 RadioButtonGroup* group = m_nameToGroupMap->get(name.impl()); 227 return group ? group->checkedButton() : 0; 228} 229 230bool CheckedRadioButtons::isInRequiredGroup(HTMLInputElement* element) const 231{ 232 ASSERT(element->isRadioButton()); 233 if (element->name().isEmpty()) 234 return false; 235 if (!m_nameToGroupMap) 236 return false; 237 RadioButtonGroup* group = m_nameToGroupMap->get(element->name().impl()); 238 return group && group->isRequired() && group->contains(element); 239} 240 241void CheckedRadioButtons::removeButton(HTMLInputElement* element) 242{ 243 ASSERT(element->isRadioButton()); 244 if (element->name().isEmpty()) 245 return; 246 if (!m_nameToGroupMap) 247 return; 248 249 m_nameToGroupMap->checkConsistency(); 250 NameToGroupMap::iterator it = m_nameToGroupMap->find(element->name().impl()); 251 if (it == m_nameToGroupMap->end()) 252 return; 253 it->value->remove(element); 254 if (it->value->isEmpty()) { 255 // FIXME: We may skip deallocating the empty RadioButtonGroup for 256 // performance improvement. If we do so, we need to change the key type 257 // of m_nameToGroupMap from AtomicStringImpl* to RefPtr<AtomicStringImpl>. 258 m_nameToGroupMap->remove(it); 259 if (m_nameToGroupMap->isEmpty()) 260 m_nameToGroupMap = nullptr; 261 } 262} 263 264} // namespace 265