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(); 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 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 55 RadioButtonGroup::RadioButtonGroup() 56 : m_checkedButton(nullptr) 57 , m_requiredCount(0) 58 { 59 } 60 61 PassOwnPtrWillBeRawPtr<RadioButtonGroup> RadioButtonGroup::create() 62 { 63 return adoptPtrWillBeNoop(new RadioButtonGroup); 64 } 65 66 inline bool RadioButtonGroup::isValid() const 67 { 68 return !isRequired() || m_checkedButton; 69 } 70 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 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 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 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 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 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 171 bool RadioButtonGroup::contains(HTMLInputElement* button) const 172 { 173 return m_members.contains(button); 174 } 175 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. 187 RadioButtonGroupScope::RadioButtonGroupScope() 188 { 189 } 190 191 RadioButtonGroupScope::~RadioButtonGroupScope() 192 { 193 } 194 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 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 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 236 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 244 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 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 275 void RadioButtonGroupScope::trace(Visitor* visitor) 276 { 277 #if ENABLE(OILPAN) 278 visitor->trace(m_nameToGroupMap); 279 #endif 280 } 281 282 } // namespace 283