Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/HTMLInputElement.h"
#include "mozilla/dom/RadioGroupContainer.h"
#include "mozilla/dom/TreeOrderedArrayInlines.h"
#include "mozilla/Assertions.h"
#include "nsIFrame.h"
#include "nsIRadioVisitor.h"
#include "nsRadioVisitor.h"
namespace mozilla::dom {
/**
* A struct that holds all the information about a radio group.
*/
struct nsRadioGroupStruct {
nsRadioGroupStruct()
: mRequiredRadioCount(0), mGroupSuffersFromValueMissing(false) {}
/**
* A strong pointer to the currently selected radio button.
*/
RefPtr<HTMLInputElement> mSelectedRadioButton;
TreeOrderedArray<RefPtr<HTMLInputElement>> mRadioButtons;
uint32_t mRequiredRadioCount;
bool mGroupSuffersFromValueMissing;
};
RadioGroupContainer::RadioGroupContainer() = default;
RadioGroupContainer::~RadioGroupContainer() {
for (const auto& group : mRadioGroups) {
for (const auto& button : group.GetData()->mRadioButtons.AsList()) {
// When the radio group container is being cycle-collected, any remaining
// connected buttons will also be in the process of being cycle-collected.
// Here, we unset the button's reference to the container so that when it
// is collected it does not attempt to remove itself from a potentially
// already deleted radio group.
button->DisconnectRadioGroupContainer();
}
}
}
/* static */
void RadioGroupContainer::Traverse(RadioGroupContainer* tmp,
nsCycleCollectionTraversalCallback& cb) {
for (const auto& entry : tmp->mRadioGroups) {
nsRadioGroupStruct* radioGroup = entry.GetWeak();
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
cb, "mRadioGroups entry->mSelectedRadioButton");
cb.NoteXPCOMChild(ToSupports(radioGroup->mSelectedRadioButton));
uint32_t i, count = radioGroup->mRadioButtons->Length();
for (i = 0; i < count; ++i) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
cb, "mRadioGroups entry->mRadioButtons[i]");
cb.NoteXPCOMChild(ToSupports(radioGroup->mRadioButtons->ElementAt(i)));
}
}
}
size_t RadioGroupContainer::SizeOfIncludingThis(
MallocSizeOf aMallocSizeOf) const {
return aMallocSizeOf(this) + mRadioGroups.SizeOfExcludingThis(aMallocSizeOf);
}
nsresult RadioGroupContainer::WalkRadioGroup(const nsAString& aName,
nsIRadioVisitor* aVisitor) {
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
for (HTMLInputElement* button : radioGroup->mRadioButtons.AsList()) {
if (!aVisitor->Visit(button)) {
return NS_OK;
}
}
return NS_OK;
}
void RadioGroupContainer::SetCurrentRadioButton(const nsAString& aName,
HTMLInputElement* aRadio) {
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
radioGroup->mSelectedRadioButton = aRadio;
}
HTMLInputElement* RadioGroupContainer::GetCurrentRadioButton(
const nsAString& aName) {
return GetOrCreateRadioGroup(aName)->mSelectedRadioButton;
}
nsresult RadioGroupContainer::GetNextRadioButton(
const nsAString& aName, const bool aPrevious,
HTMLInputElement* aFocusedRadio, HTMLInputElement** aRadioOut) {
*aRadioOut = nullptr;
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
// Return the radio button relative to the focused radio button.
// If no radio is focused, get the radio relative to the selected one.
RefPtr<HTMLInputElement> currentRadio;
if (aFocusedRadio) {
currentRadio = aFocusedRadio;
} else {
currentRadio = radioGroup->mSelectedRadioButton;
if (!currentRadio) {
return NS_ERROR_FAILURE;
}
}
int32_t index = radioGroup->mRadioButtons->IndexOf(currentRadio);
if (index < 0) {
return NS_ERROR_FAILURE;
}
int32_t numRadios = static_cast<int32_t>(radioGroup->mRadioButtons->Length());
RefPtr<HTMLInputElement> radio;
do {
if (aPrevious) {
if (--index < 0) {
index = numRadios - 1;
}
} else if (++index >= numRadios) {
index = 0;
}
radio = radioGroup->mRadioButtons->ElementAt(index);
} while ((radio->Disabled() || !radio->GetPrimaryFrame() ||
!radio->GetPrimaryFrame()->IsVisibleConsideringAncestors()) &&
radio != currentRadio);
radio.forget(aRadioOut);
return NS_OK;
}
HTMLInputElement* RadioGroupContainer::GetFirstRadioButton(
const nsAString& aName) {
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
for (HTMLInputElement* radio : radioGroup->mRadioButtons.AsList()) {
if (!radio->Disabled() && radio->GetPrimaryFrame() &&
radio->GetPrimaryFrame()->IsVisibleConsideringAncestors()) {
return radio;
}
}
return nullptr;
}
void RadioGroupContainer::AddToRadioGroup(const nsAString& aName,
HTMLInputElement* aRadio,
nsIContent* aAncestor) {
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
radioGroup->mRadioButtons.Insert(*aRadio, aAncestor);
if (aRadio->IsRequired()) {
radioGroup->mRequiredRadioCount++;
}
}
void RadioGroupContainer::RemoveFromRadioGroup(const nsAString& aName,
HTMLInputElement* aRadio) {
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
MOZ_ASSERT(
radioGroup->mRadioButtons->Contains(aRadio),
"Attempting to remove radio button from group it is not a part of!");
radioGroup->mRadioButtons.RemoveElement(*aRadio);
if (aRadio->IsRequired()) {
MOZ_ASSERT(radioGroup->mRequiredRadioCount != 0,
"mRequiredRadioCount about to wrap below 0!");
radioGroup->mRequiredRadioCount--;
}
}
uint32_t RadioGroupContainer::GetRequiredRadioCount(
const nsAString& aName) const {
nsRadioGroupStruct* radioGroup = GetRadioGroup(aName);
return radioGroup ? radioGroup->mRequiredRadioCount : 0;
}
void RadioGroupContainer::RadioRequiredWillChange(const nsAString& aName,
bool aRequiredAdded) {
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
if (aRequiredAdded) {
radioGroup->mRequiredRadioCount++;
} else {
MOZ_ASSERT(radioGroup->mRequiredRadioCount != 0,
"mRequiredRadioCount about to wrap below 0!");
radioGroup->mRequiredRadioCount--;
}
}
bool RadioGroupContainer::GetValueMissingState(const nsAString& aName) const {
nsRadioGroupStruct* radioGroup = GetRadioGroup(aName);
return radioGroup && radioGroup->mGroupSuffersFromValueMissing;
}
void RadioGroupContainer::SetValueMissingState(const nsAString& aName,
bool aValue) {
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
radioGroup->mGroupSuffersFromValueMissing = aValue;
}
nsRadioGroupStruct* RadioGroupContainer::GetRadioGroup(
const nsAString& aName) const {
nsRadioGroupStruct* radioGroup = nullptr;
mRadioGroups.Get(aName, &radioGroup);
return radioGroup;
}
nsRadioGroupStruct* RadioGroupContainer::GetOrCreateRadioGroup(
const nsAString& aName) {
return mRadioGroups.GetOrInsertNew(aName);
}
} // namespace mozilla::dom