1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/HTMLInputElement.h"
8 #include "mozilla/dom/RadioGroupContainer.h"
9 #include "mozilla/dom/TreeOrderedArrayInlines.h"
10 #include "mozilla/Assertions.h"
12 #include "nsIRadioVisitor.h"
13 #include "nsRadioVisitor.h"
15 namespace mozilla::dom
{
18 * A struct that holds all the information about a radio group.
20 struct nsRadioGroupStruct
{
22 : mRequiredRadioCount(0), mGroupSuffersFromValueMissing(false) {}
25 * A strong pointer to the currently selected radio button.
27 RefPtr
<HTMLInputElement
> mSelectedRadioButton
;
28 TreeOrderedArray
<RefPtr
<HTMLInputElement
>> mRadioButtons
;
29 uint32_t mRequiredRadioCount
;
30 bool mGroupSuffersFromValueMissing
;
33 RadioGroupContainer::RadioGroupContainer() = default;
35 RadioGroupContainer::~RadioGroupContainer() {
36 for (const auto& group
: mRadioGroups
) {
37 for (const auto& button
: group
.GetData()->mRadioButtons
.AsList()) {
38 // When the radio group container is being cycle-collected, any remaining
39 // connected buttons will also be in the process of being cycle-collected.
40 // Here, we unset the button's reference to the container so that when it
41 // is collected it does not attempt to remove itself from a potentially
42 // already deleted radio group.
43 button
->DisconnectRadioGroupContainer();
49 void RadioGroupContainer::Traverse(RadioGroupContainer
* tmp
,
50 nsCycleCollectionTraversalCallback
& cb
) {
51 for (const auto& entry
: tmp
->mRadioGroups
) {
52 nsRadioGroupStruct
* radioGroup
= entry
.GetWeak();
53 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
54 cb
, "mRadioGroups entry->mSelectedRadioButton");
55 cb
.NoteXPCOMChild(ToSupports(radioGroup
->mSelectedRadioButton
));
57 uint32_t i
, count
= radioGroup
->mRadioButtons
->Length();
58 for (i
= 0; i
< count
; ++i
) {
59 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
60 cb
, "mRadioGroups entry->mRadioButtons[i]");
61 cb
.NoteXPCOMChild(ToSupports(radioGroup
->mRadioButtons
->ElementAt(i
)));
66 size_t RadioGroupContainer::SizeOfIncludingThis(
67 MallocSizeOf aMallocSizeOf
) const {
68 return aMallocSizeOf(this) + mRadioGroups
.SizeOfExcludingThis(aMallocSizeOf
);
71 nsresult
RadioGroupContainer::WalkRadioGroup(const nsAString
& aName
,
72 nsIRadioVisitor
* aVisitor
) {
73 nsRadioGroupStruct
* radioGroup
= GetOrCreateRadioGroup(aName
);
75 for (HTMLInputElement
* button
: radioGroup
->mRadioButtons
.AsList()) {
76 if (!aVisitor
->Visit(button
)) {
84 void RadioGroupContainer::WalkRadioGroup(const nsAString
& aName
,
85 const VisitCallback
& aCallback
) {
86 nsRadioGroupStruct
* radioGroup
= GetOrCreateRadioGroup(aName
);
87 for (HTMLInputElement
* button
: radioGroup
->mRadioButtons
.AsList()) {
88 if (!aCallback(button
)) {
94 void RadioGroupContainer::SetCurrentRadioButton(const nsAString
& aName
,
95 HTMLInputElement
* aRadio
) {
96 nsRadioGroupStruct
* radioGroup
= GetOrCreateRadioGroup(aName
);
97 radioGroup
->mSelectedRadioButton
= aRadio
;
100 HTMLInputElement
* RadioGroupContainer::GetCurrentRadioButton(
101 const nsAString
& aName
) {
102 return GetOrCreateRadioGroup(aName
)->mSelectedRadioButton
;
105 nsresult
RadioGroupContainer::GetNextRadioButton(
106 const nsAString
& aName
, const bool aPrevious
,
107 HTMLInputElement
* aFocusedRadio
, HTMLInputElement
** aRadioOut
) {
108 *aRadioOut
= nullptr;
110 nsRadioGroupStruct
* radioGroup
= GetOrCreateRadioGroup(aName
);
112 // Return the radio button relative to the focused radio button.
113 // If no radio is focused, get the radio relative to the selected one.
114 RefPtr
<HTMLInputElement
> currentRadio
;
116 currentRadio
= aFocusedRadio
;
118 currentRadio
= radioGroup
->mSelectedRadioButton
;
120 return NS_ERROR_FAILURE
;
123 int32_t index
= radioGroup
->mRadioButtons
->IndexOf(currentRadio
);
125 return NS_ERROR_FAILURE
;
128 int32_t numRadios
= static_cast<int32_t>(radioGroup
->mRadioButtons
->Length());
129 RefPtr
<HTMLInputElement
> radio
;
133 index
= numRadios
- 1;
135 } else if (++index
>= numRadios
) {
138 radio
= radioGroup
->mRadioButtons
->ElementAt(index
);
139 } while ((radio
->Disabled() || !radio
->GetPrimaryFrame() ||
140 !radio
->GetPrimaryFrame()->IsVisibleConsideringAncestors()) &&
141 radio
!= currentRadio
);
143 radio
.forget(aRadioOut
);
147 HTMLInputElement
* RadioGroupContainer::GetFirstRadioButton(
148 const nsAString
& aName
) {
149 nsRadioGroupStruct
* radioGroup
= GetOrCreateRadioGroup(aName
);
150 for (HTMLInputElement
* radio
: radioGroup
->mRadioButtons
.AsList()) {
151 if (!radio
->Disabled() && radio
->GetPrimaryFrame() &&
152 radio
->GetPrimaryFrame()->IsVisibleConsideringAncestors()) {
159 void RadioGroupContainer::AddToRadioGroup(const nsAString
& aName
,
160 HTMLInputElement
* aRadio
,
161 nsIContent
* aAncestor
) {
162 nsRadioGroupStruct
* radioGroup
= GetOrCreateRadioGroup(aName
);
163 radioGroup
->mRadioButtons
.Insert(*aRadio
, aAncestor
);
164 if (aRadio
->IsRequired()) {
165 radioGroup
->mRequiredRadioCount
++;
169 void RadioGroupContainer::RemoveFromRadioGroup(const nsAString
& aName
,
170 HTMLInputElement
* aRadio
) {
171 nsRadioGroupStruct
* radioGroup
= GetOrCreateRadioGroup(aName
);
173 radioGroup
->mRadioButtons
->Contains(aRadio
),
174 "Attempting to remove radio button from group it is not a part of!");
176 radioGroup
->mRadioButtons
.RemoveElement(*aRadio
);
178 if (aRadio
->IsRequired()) {
179 MOZ_ASSERT(radioGroup
->mRequiredRadioCount
!= 0,
180 "mRequiredRadioCount about to wrap below 0!");
181 radioGroup
->mRequiredRadioCount
--;
185 uint32_t RadioGroupContainer::GetRequiredRadioCount(
186 const nsAString
& aName
) const {
187 nsRadioGroupStruct
* radioGroup
= GetRadioGroup(aName
);
188 return radioGroup
? radioGroup
->mRequiredRadioCount
: 0;
191 void RadioGroupContainer::RadioRequiredWillChange(const nsAString
& aName
,
192 bool aRequiredAdded
) {
193 nsRadioGroupStruct
* radioGroup
= GetOrCreateRadioGroup(aName
);
195 if (aRequiredAdded
) {
196 radioGroup
->mRequiredRadioCount
++;
198 MOZ_ASSERT(radioGroup
->mRequiredRadioCount
!= 0,
199 "mRequiredRadioCount about to wrap below 0!");
200 radioGroup
->mRequiredRadioCount
--;
204 bool RadioGroupContainer::GetValueMissingState(const nsAString
& aName
) const {
205 nsRadioGroupStruct
* radioGroup
= GetRadioGroup(aName
);
206 return radioGroup
&& radioGroup
->mGroupSuffersFromValueMissing
;
209 void RadioGroupContainer::SetValueMissingState(const nsAString
& aName
,
211 nsRadioGroupStruct
* radioGroup
= GetOrCreateRadioGroup(aName
);
212 radioGroup
->mGroupSuffersFromValueMissing
= aValue
;
215 nsRadioGroupStruct
* RadioGroupContainer::GetRadioGroup(
216 const nsAString
& aName
) const {
217 nsRadioGroupStruct
* radioGroup
= nullptr;
218 mRadioGroups
.Get(aName
, &radioGroup
);
222 nsRadioGroupStruct
* RadioGroupContainer::GetOrCreateRadioGroup(
223 const nsAString
& aName
) {
224 return mRadioGroups
.GetOrInsertNew(aName
);
227 } // namespace mozilla::dom