RadioGroupContainer.cpp (7195B)
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/. */ 6 7 #include "mozilla/dom/RadioGroupContainer.h" 8 9 #include "mozilla/Assertions.h" 10 #include "mozilla/dom/HTMLInputElement.h" 11 #include "mozilla/dom/TreeOrderedArrayInlines.h" 12 #include "nsIFrame.h" 13 14 namespace mozilla::dom { 15 16 /** 17 * A struct that holds all the information about a radio group. 18 */ 19 struct nsRadioGroupStruct { 20 nsRadioGroupStruct() 21 : mRequiredRadioCount(0), mGroupSuffersFromValueMissing(false) {} 22 23 /** 24 * A strong pointer to the currently selected radio button. 25 */ 26 RefPtr<HTMLInputElement> mSelectedRadioButton; 27 TreeOrderedArray<RefPtr<HTMLInputElement>> mRadioButtons; 28 uint32_t mRequiredRadioCount; 29 bool mGroupSuffersFromValueMissing; 30 }; 31 32 RadioGroupContainer::RadioGroupContainer() = default; 33 34 RadioGroupContainer::~RadioGroupContainer() { 35 for (const auto& group : mRadioGroups) { 36 for (const auto& button : group.GetData()->mRadioButtons.AsSpan()) { 37 // When the radio group container is being cycle-collected, any remaining 38 // connected buttons will also be in the process of being cycle-collected. 39 // Here, we unset the button's reference to the container so that when it 40 // is collected it does not attempt to remove itself from a potentially 41 // already deleted radio group. 42 button->DisconnectRadioGroupContainer(); 43 } 44 } 45 } 46 47 /* static */ 48 void RadioGroupContainer::Traverse(RadioGroupContainer* tmp, 49 nsCycleCollectionTraversalCallback& cb) { 50 for (const auto& entry : tmp->mRadioGroups) { 51 nsRadioGroupStruct* radioGroup = entry.GetWeak(); 52 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME( 53 cb, "mRadioGroups entry->mSelectedRadioButton"); 54 cb.NoteXPCOMChild(ToSupports(radioGroup->mSelectedRadioButton)); 55 56 for (auto& button : radioGroup->mRadioButtons.AsSpan()) { 57 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME( 58 cb, "mRadioGroups entry->mRadioButtons[i]"); 59 cb.NoteXPCOMChild(ToSupports(button)); 60 } 61 } 62 } 63 64 size_t RadioGroupContainer::SizeOfIncludingThis( 65 MallocSizeOf aMallocSizeOf) const { 66 return aMallocSizeOf(this) + mRadioGroups.SizeOfExcludingThis(aMallocSizeOf); 67 } 68 69 void RadioGroupContainer::SetCurrentRadioButton(const nsAString& aName, 70 HTMLInputElement* aRadio) { 71 nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); 72 radioGroup->mSelectedRadioButton = aRadio; 73 } 74 75 HTMLInputElement* RadioGroupContainer::GetCurrentRadioButton( 76 const nsAString& aName) { 77 return GetOrCreateRadioGroup(aName)->mSelectedRadioButton; 78 } 79 80 nsresult RadioGroupContainer::GetNextRadioButton( 81 const nsAString& aName, const bool aPrevious, 82 HTMLInputElement* aFocusedRadio, HTMLInputElement** aRadioOut) { 83 *aRadioOut = nullptr; 84 85 nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); 86 87 // Return the radio button relative to the focused radio button. 88 // If no radio is focused, get the radio relative to the selected one. 89 RefPtr<HTMLInputElement> currentRadio; 90 if (aFocusedRadio) { 91 currentRadio = aFocusedRadio; 92 } else { 93 currentRadio = radioGroup->mSelectedRadioButton; 94 if (!currentRadio) { 95 return NS_ERROR_FAILURE; 96 } 97 } 98 int32_t index = radioGroup->mRadioButtons.IndexOf(currentRadio); 99 if (index < 0) { 100 return NS_ERROR_FAILURE; 101 } 102 103 int32_t numRadios = static_cast<int32_t>(radioGroup->mRadioButtons.Length()); 104 RefPtr<HTMLInputElement> radio; 105 do { 106 if (aPrevious) { 107 if (--index < 0) { 108 index = numRadios - 1; 109 } 110 } else if (++index >= numRadios) { 111 index = 0; 112 } 113 radio = radioGroup->mRadioButtons.ElementAt(index); 114 } while ((radio->Disabled() || !radio->GetPrimaryFrame() || 115 !radio->GetPrimaryFrame()->IsVisibleConsideringAncestors()) && 116 radio != currentRadio); 117 118 radio.forget(aRadioOut); 119 return NS_OK; 120 } 121 122 HTMLInputElement* RadioGroupContainer::GetFirstRadioButton( 123 const nsAString& aName) { 124 nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); 125 for (HTMLInputElement* radio : radioGroup->mRadioButtons.AsSpan()) { 126 if (!radio->Disabled() && radio->GetPrimaryFrame() && 127 radio->GetPrimaryFrame()->IsVisibleConsideringAncestors()) { 128 return radio; 129 } 130 } 131 return nullptr; 132 } 133 134 void RadioGroupContainer::AddToRadioGroup(const nsAString& aName, 135 HTMLInputElement* aRadio, 136 nsIContent* aAncestor) { 137 nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); 138 radioGroup->mRadioButtons.Insert(*aRadio, aAncestor); 139 if (aRadio->IsRequired()) { 140 radioGroup->mRequiredRadioCount++; 141 } 142 } 143 144 void RadioGroupContainer::RemoveFromRadioGroup(const nsAString& aName, 145 HTMLInputElement* aRadio) { 146 nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); 147 MOZ_ASSERT( 148 radioGroup->mRadioButtons.Contains(aRadio), 149 "Attempting to remove radio button from group it is not a part of!"); 150 151 radioGroup->mRadioButtons.RemoveElement(*aRadio); 152 153 if (aRadio->IsRequired()) { 154 MOZ_ASSERT(radioGroup->mRequiredRadioCount != 0, 155 "mRequiredRadioCount about to wrap below 0!"); 156 radioGroup->mRequiredRadioCount--; 157 } 158 } 159 160 uint32_t RadioGroupContainer::GetRequiredRadioCount( 161 const nsAString& aName) const { 162 nsRadioGroupStruct* radioGroup = GetRadioGroup(aName); 163 return radioGroup ? radioGroup->mRequiredRadioCount : 0; 164 } 165 166 void RadioGroupContainer::RadioRequiredWillChange(const nsAString& aName, 167 bool aRequiredAdded) { 168 nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); 169 170 if (aRequiredAdded) { 171 radioGroup->mRequiredRadioCount++; 172 } else { 173 MOZ_ASSERT(radioGroup->mRequiredRadioCount != 0, 174 "mRequiredRadioCount about to wrap below 0!"); 175 radioGroup->mRequiredRadioCount--; 176 } 177 } 178 179 bool RadioGroupContainer::GetValueMissingState(const nsAString& aName) const { 180 nsRadioGroupStruct* radioGroup = GetRadioGroup(aName); 181 return radioGroup && radioGroup->mGroupSuffersFromValueMissing; 182 } 183 184 void RadioGroupContainer::SetValueMissingState(const nsAString& aName, 185 bool aValue) { 186 nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); 187 radioGroup->mGroupSuffersFromValueMissing = aValue; 188 } 189 190 nsRadioGroupStruct* RadioGroupContainer::GetRadioGroup( 191 const nsAString& aName) const { 192 nsRadioGroupStruct* radioGroup = nullptr; 193 mRadioGroups.Get(aName, &radioGroup); 194 return radioGroup; 195 } 196 197 nsRadioGroupStruct* RadioGroupContainer::GetOrCreateRadioGroup( 198 const nsAString& aName) { 199 return mRadioGroups.GetOrInsertNew(aName); 200 } 201 202 Span<const RefPtr<HTMLInputElement>> RadioGroupContainer::GetButtonsInGroup( 203 nsRadioGroupStruct* aGroup) const { 204 return aGroup->mRadioButtons.AsSpan(); 205 } 206 207 } // namespace mozilla::dom