diff options
Diffstat (limited to 'Source/WebCore/dom/RadioButtonGroups.cpp')
-rw-r--r-- | Source/WebCore/dom/RadioButtonGroups.cpp | 323 |
1 files changed, 323 insertions, 0 deletions
diff --git a/Source/WebCore/dom/RadioButtonGroups.cpp b/Source/WebCore/dom/RadioButtonGroups.cpp new file mode 100644 index 000000000..4d337279c --- /dev/null +++ b/Source/WebCore/dom/RadioButtonGroups.cpp @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2007, 2008, 2009, 2016 Apple Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" +#include "RadioButtonGroups.h" + +#include "HTMLInputElement.h" +#include "Range.h" +#include <wtf/HashSet.h> + +namespace WebCore { + +class RadioButtonGroup { + WTF_MAKE_FAST_ALLOCATED; +public: + RadioButtonGroup(); + bool isEmpty() const { return m_members.isEmpty(); } + bool isRequired() const { return m_requiredCount; } + HTMLInputElement* checkedButton() const { return m_checkedButton; } + void add(HTMLInputElement*); + void updateCheckedState(HTMLInputElement*); + void requiredAttributeChanged(HTMLInputElement*); + void remove(HTMLInputElement*); + bool contains(HTMLInputElement*) const; + Vector<HTMLInputElement*> members() const; + +private: + void setNeedsStyleRecalcForAllButtons(); + void updateValidityForAllButtons(); + bool isValid() const; + void changeCheckedButton(HTMLInputElement*); + void setCheckedButton(HTMLInputElement*); + + HashSet<HTMLInputElement*> m_members; + HTMLInputElement* m_checkedButton; + size_t m_requiredCount; +}; + +RadioButtonGroup::RadioButtonGroup() + : m_checkedButton(nullptr) + , m_requiredCount(0) +{ +} + +inline bool RadioButtonGroup::isValid() const +{ + return !isRequired() || m_checkedButton; +} + +Vector<HTMLInputElement*> RadioButtonGroup::members() const +{ + Vector<HTMLInputElement*> members; + copyToVector(m_members, members); + std::sort(members.begin(), members.end(), documentOrderComparator); + return members; +} + +void RadioButtonGroup::setCheckedButton(HTMLInputElement* button) +{ + HTMLInputElement* oldCheckedButton = m_checkedButton; + if (oldCheckedButton == button) + return; + + bool hadCheckedButton = m_checkedButton; + bool willHaveCheckedButton = button; + if (hadCheckedButton != willHaveCheckedButton) + setNeedsStyleRecalcForAllButtons(); + + m_checkedButton = button; + if (oldCheckedButton) + oldCheckedButton->setChecked(false); +} + +void RadioButtonGroup::add(HTMLInputElement* button) +{ + ASSERT(button->isRadioButton()); + if (!m_members.add(button).isNewEntry) + return; + bool groupWasValid = isValid(); + if (button->isRequired()) + ++m_requiredCount; + if (button->checked()) + setCheckedButton(button); + + bool groupIsValid = isValid(); + if (groupWasValid != groupIsValid) + updateValidityForAllButtons(); + else if (!groupIsValid) { + // A radio button not in a group is always valid. We need to make it + // invalid only if the group is invalid. + button->updateValidity(); + } +} + +void RadioButtonGroup::updateCheckedState(HTMLInputElement* button) +{ + ASSERT(button->isRadioButton()); + ASSERT(m_members.contains(button)); + bool wasValid = isValid(); + if (button->checked()) + setCheckedButton(button); + else { + if (m_checkedButton == button) + setCheckedButton(nullptr); + } + if (wasValid != isValid()) + updateValidityForAllButtons(); +} + +void RadioButtonGroup::requiredAttributeChanged(HTMLInputElement* button) +{ + ASSERT(button->isRadioButton()); + ASSERT(m_members.contains(button)); + bool wasValid = isValid(); + if (button->isRequired()) + ++m_requiredCount; + else { + ASSERT(m_requiredCount); + --m_requiredCount; + } + if (wasValid != isValid()) + updateValidityForAllButtons(); +} + +void RadioButtonGroup::remove(HTMLInputElement* button) +{ + ASSERT(button->isRadioButton()); + HashSet<HTMLInputElement*>::iterator it = m_members.find(button); + if (it == m_members.end()) + return; + + bool wasValid = isValid(); + m_members.remove(it); + if (button->isRequired()) { + ASSERT(m_requiredCount); + --m_requiredCount; + } + if (m_checkedButton) { + button->invalidateStyleForSubtree(); + if (m_checkedButton == button) { + m_checkedButton = nullptr; + setNeedsStyleRecalcForAllButtons(); + } + } + + if (m_members.isEmpty()) { + ASSERT(!m_requiredCount); + ASSERT(!m_checkedButton); + } else if (wasValid != isValid()) + updateValidityForAllButtons(); + if (!wasValid) { + // A radio button not in a group is always valid. We need to make it + // valid only if the group was invalid. + button->updateValidity(); + } +} + +void RadioButtonGroup::setNeedsStyleRecalcForAllButtons() +{ + for (auto& button : m_members) { + ASSERT(button->isRadioButton()); + button->invalidateStyleForSubtree(); + } +} + +void RadioButtonGroup::updateValidityForAllButtons() +{ + for (auto& button : m_members) { + ASSERT(button->isRadioButton()); + button->updateValidity(); + } +} + +bool RadioButtonGroup::contains(HTMLInputElement* button) const +{ + return m_members.contains(button); +} + +// ---------------------------------------------------------------- + +// Explicity define empty constructor and destructor in order to prevent the +// compiler from generating them as inlines. So we don't need to to define +// RadioButtonGroup in the header. +RadioButtonGroups::RadioButtonGroups() +{ +} + +RadioButtonGroups::~RadioButtonGroups() +{ +} + +void RadioButtonGroups::addButton(HTMLInputElement* element) +{ + ASSERT(element->isRadioButton()); + if (element->name().isEmpty()) + return; + + if (!m_nameToGroupMap) + m_nameToGroupMap = std::make_unique<NameToGroupMap>(); + + auto& group = m_nameToGroupMap->add(element->name().impl(), nullptr).iterator->value; + if (!group) + group = std::make_unique<RadioButtonGroup>(); + group->add(element); +} + +Vector<HTMLInputElement*> RadioButtonGroups::groupMembers(const HTMLInputElement& element) const +{ + ASSERT(element.isRadioButton()); + if (!element.isRadioButton()) + return { }; + + auto* name = element.name().impl(); + if (!name) + return { }; + + if (!m_nameToGroupMap) + return { }; + + auto* group = m_nameToGroupMap->get(name); + if (!group) + return { }; + return group->members(); +} + +void RadioButtonGroups::updateCheckedState(HTMLInputElement* element) +{ + ASSERT(element->isRadioButton()); + if (element->name().isEmpty()) + return; + ASSERT(m_nameToGroupMap); + if (!m_nameToGroupMap) + return; + RadioButtonGroup* group = m_nameToGroupMap->get(element->name().impl()); + ASSERT(group); + group->updateCheckedState(element); +} + +void RadioButtonGroups::requiredAttributeChanged(HTMLInputElement* element) +{ + ASSERT(element->isRadioButton()); + if (element->name().isEmpty()) + return; + ASSERT(m_nameToGroupMap); + if (!m_nameToGroupMap) + return; + RadioButtonGroup* group = m_nameToGroupMap->get(element->name().impl()); + ASSERT(group); + group->requiredAttributeChanged(element); +} + +HTMLInputElement* RadioButtonGroups::checkedButtonForGroup(const AtomicString& name) const +{ + if (!m_nameToGroupMap) + return 0; + m_nameToGroupMap->checkConsistency(); + RadioButtonGroup* group = m_nameToGroupMap->get(name.impl()); + return group ? group->checkedButton() : nullptr; +} + +bool RadioButtonGroups::hasCheckedButton(const HTMLInputElement* element) const +{ + ASSERT(element->isRadioButton()); + const AtomicString& name = element->name(); + if (name.isEmpty() || !m_nameToGroupMap) + return element->checked(); + + const RadioButtonGroup* group = m_nameToGroupMap->get(name.impl()); + return group->checkedButton(); +} + +bool RadioButtonGroups::isInRequiredGroup(HTMLInputElement* element) const +{ + ASSERT(element->isRadioButton()); + if (element->name().isEmpty()) + return false; + if (!m_nameToGroupMap) + return false; + RadioButtonGroup* group = m_nameToGroupMap->get(element->name().impl()); + return group && group->isRequired() && group->contains(element); +} + +void RadioButtonGroups::removeButton(HTMLInputElement* element) +{ + ASSERT(element->isRadioButton()); + if (element->name().isEmpty()) + return; + if (!m_nameToGroupMap) + return; + + m_nameToGroupMap->checkConsistency(); + NameToGroupMap::iterator it = m_nameToGroupMap->find(element->name().impl()); + if (it == m_nameToGroupMap->end()) + return; + it->value->remove(element); + if (it->value->isEmpty()) { + // FIXME: We may skip deallocating the empty RadioButtonGroup for + // performance improvement. If we do so, we need to change the key type + // of m_nameToGroupMap from AtomicStringImpl* to RefPtr<AtomicStringImpl>. + m_nameToGroupMap->remove(it); + if (m_nameToGroupMap->isEmpty()) + m_nameToGroupMap = nullptr; + } +} + +} // namespace |