Home | History | Annotate | Download | only in forms
      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