227 lines
7.7 KiB
C++
227 lines
7.7 KiB
C++
/* -*- 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::WalkRadioGroup(const nsAString& aName,
|
|
const VisitCallback& aCallback) {
|
|
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
|
|
for (HTMLInputElement* button : radioGroup->mRadioButtons.AsList()) {
|
|
if (!aCallback(button)) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|