summaryrefslogtreecommitdiff
path: root/Source/WebCore/dom/RadioButtonGroups.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/dom/RadioButtonGroups.cpp')
-rw-r--r--Source/WebCore/dom/RadioButtonGroups.cpp323
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