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 28namespace blink { 29 30class RadioButtonGroup : public NoBaseWillBeGarbageCollected<RadioButtonGroup> { 31 WTF_MAKE_FAST_ALLOCATED_WILL_BE_REMOVED; 32public: 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 45private: 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 56RadioButtonGroup::RadioButtonGroup() 57 : m_checkedButton(nullptr) 58 , m_requiredCount(0) 59{ 60} 61 62PassOwnPtrWillBeRawPtr<RadioButtonGroup> RadioButtonGroup::create() 63{ 64 return adoptPtrWillBeNoop(new RadioButtonGroup); 65} 66 67inline bool RadioButtonGroup::isValid() const 68{ 69 return !isRequired() || m_checkedButton; 70} 71 72void 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 82void 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 103void 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 typedef WillBeHeapHashSet<RawPtrWillBeMember<HTMLInputElement> >::const_iterator Iterator; 117 Iterator end = m_members.end(); 118 for (Iterator it = m_members.begin(); it != end; ++it) { 119 (*it)->pseudoStateChanged(CSSSelector::PseudoIndeterminate); 120 } 121} 122 123void RadioButtonGroup::requiredAttributeChanged(HTMLInputElement* button) 124{ 125 ASSERT(button->type() == InputTypeNames::radio); 126 ASSERT(m_members.contains(button)); 127 bool wasValid = isValid(); 128 if (button->isRequired()) { 129 ++m_requiredCount; 130 } else { 131 ASSERT(m_requiredCount); 132 --m_requiredCount; 133 } 134 if (wasValid != isValid()) 135 setNeedsValidityCheckForAllButtons(); 136} 137 138void RadioButtonGroup::remove(HTMLInputElement* button) 139{ 140 ASSERT(button->type() == InputTypeNames::radio); 141 WillBeHeapHashSet<RawPtrWillBeMember<HTMLInputElement> >::iterator it = m_members.find(button); 142 if (it == m_members.end()) 143 return; 144 bool wasValid = isValid(); 145 m_members.remove(it); 146 if (button->isRequired()) { 147 ASSERT(m_requiredCount); 148 --m_requiredCount; 149 } 150 if (m_checkedButton == button) 151 m_checkedButton = nullptr; 152 153 if (m_members.isEmpty()) { 154 ASSERT(!m_requiredCount); 155 ASSERT(!m_checkedButton); 156 } else if (wasValid != isValid()) { 157 setNeedsValidityCheckForAllButtons(); 158 } 159 if (!wasValid) { 160 // A radio button not in a group is always valid. We need to make it 161 // valid only if the group was invalid. 162 button->setNeedsValidityCheck(); 163 } 164} 165 166void RadioButtonGroup::setNeedsValidityCheckForAllButtons() 167{ 168 typedef WillBeHeapHashSet<RawPtrWillBeMember<HTMLInputElement> >::const_iterator Iterator; 169 Iterator end = m_members.end(); 170 for (Iterator it = m_members.begin(); it != end; ++it) { 171 HTMLInputElement* button = *it; 172 ASSERT(button->type() == InputTypeNames::radio); 173 button->setNeedsValidityCheck(); 174 } 175} 176 177bool RadioButtonGroup::contains(HTMLInputElement* button) const 178{ 179 return m_members.contains(button); 180} 181 182void RadioButtonGroup::trace(Visitor* visitor) 183{ 184#if ENABLE(OILPAN) 185 visitor->trace(m_members); 186 visitor->trace(m_checkedButton); 187#endif 188} 189 190// ---------------------------------------------------------------- 191 192// Explicity define empty constructor and destructor in order to prevent the 193// compiler from generating them as inlines. So we don't need to to define 194// RadioButtonGroup in the header. 195RadioButtonGroupScope::RadioButtonGroupScope() 196{ 197} 198 199RadioButtonGroupScope::~RadioButtonGroupScope() 200{ 201} 202 203void RadioButtonGroupScope::addButton(HTMLInputElement* element) 204{ 205 ASSERT(element->type() == InputTypeNames::radio); 206 if (element->name().isEmpty()) 207 return; 208 209 if (!m_nameToGroupMap) 210 m_nameToGroupMap = adoptPtrWillBeNoop(new NameToGroupMap); 211 212 OwnPtrWillBeMember<RadioButtonGroup>& group = m_nameToGroupMap->add(element->name(), nullptr).storedValue->value; 213 if (!group) 214 group = RadioButtonGroup::create(); 215 group->add(element); 216} 217 218void RadioButtonGroupScope::updateCheckedState(HTMLInputElement* element) 219{ 220 ASSERT(element->type() == InputTypeNames::radio); 221 if (element->name().isEmpty()) 222 return; 223 ASSERT(m_nameToGroupMap); 224 if (!m_nameToGroupMap) 225 return; 226 RadioButtonGroup* group = m_nameToGroupMap->get(element->name()); 227 ASSERT(group); 228 group->updateCheckedState(element); 229} 230 231void RadioButtonGroupScope::requiredAttributeChanged(HTMLInputElement* element) 232{ 233 ASSERT(element->type() == InputTypeNames::radio); 234 if (element->name().isEmpty()) 235 return; 236 ASSERT(m_nameToGroupMap); 237 if (!m_nameToGroupMap) 238 return; 239 RadioButtonGroup* group = m_nameToGroupMap->get(element->name()); 240 ASSERT(group); 241 group->requiredAttributeChanged(element); 242} 243 244HTMLInputElement* RadioButtonGroupScope::checkedButtonForGroup(const AtomicString& name) const 245{ 246 if (!m_nameToGroupMap) 247 return 0; 248 RadioButtonGroup* group = m_nameToGroupMap->get(name); 249 return group ? group->checkedButton() : 0; 250} 251 252bool RadioButtonGroupScope::isInRequiredGroup(HTMLInputElement* element) const 253{ 254 ASSERT(element->type() == InputTypeNames::radio); 255 if (element->name().isEmpty()) 256 return false; 257 if (!m_nameToGroupMap) 258 return false; 259 RadioButtonGroup* group = m_nameToGroupMap->get(element->name()); 260 return group && group->isRequired() && group->contains(element); 261} 262 263void RadioButtonGroupScope::removeButton(HTMLInputElement* element) 264{ 265 ASSERT(element->type() == InputTypeNames::radio); 266 if (element->name().isEmpty()) 267 return; 268 if (!m_nameToGroupMap) 269 return; 270 271 RadioButtonGroup* group = m_nameToGroupMap->get(element->name()); 272 if (!group) 273 return; 274 group->remove(element); 275 if (group->isEmpty()) { 276 // We don't remove an empty RadioButtonGroup from m_nameToGroupMap for 277 // better performance. 278 ASSERT(!group->isRequired()); 279 ASSERT_WITH_SECURITY_IMPLICATION(!group->checkedButton()); 280 } 281} 282 283void RadioButtonGroupScope::trace(Visitor* visitor) 284{ 285#if ENABLE(OILPAN) 286 visitor->trace(m_nameToGroupMap); 287#endif 288} 289 290} // namespace 291