Bug 1685926 - Group disconnected radio buttons together. r=saschanaz,smaug

Differential Revision: https://phabricator.services.mozilla.com/D162349
This commit is contained in:
Adam Vandolder 2023-09-28 06:35:44 +00:00
Родитель 4e0db835b2
Коммит 226c8aba6a
20 изменённых файлов: 389 добавлений и 423 удалений

Просмотреть файл

@ -2429,7 +2429,6 @@ NS_INTERFACE_TABLE_HEAD(Document)
NS_INTERFACE_TABLE_ENTRY(Document, nsIScriptObjectPrincipal)
NS_INTERFACE_TABLE_ENTRY(Document, EventTarget)
NS_INTERFACE_TABLE_ENTRY(Document, nsISupportsWeakReference)
NS_INTERFACE_TABLE_ENTRY(Document, nsIRadioGroupContainer)
NS_INTERFACE_TABLE_END
NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(Document)
NS_INTERFACE_MAP_END
@ -2504,6 +2503,10 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(Document)
DocumentOrShadowRoot::Traverse(tmp, cb);
if (tmp->mRadioGroupContainer) {
RadioGroupContainer::Traverse(tmp->mRadioGroupContainer.get(), cb);
}
for (auto& sheets : tmp->mAdditionalSheets) {
tmp->TraverseStyleSheets(sheets, "mAdditionalSheets[<origin>][i]", cb);
}
@ -2703,6 +2706,8 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Document)
DocumentOrShadowRoot::Unlink(tmp);
tmp->mRadioGroupContainer = nullptr;
// Document has a pretty complex destructor, so we're going to
// assume that *most* cycles you actually want to break somewhere
// else, and not unlink an awful lot here.
@ -15819,6 +15824,12 @@ void Document::DocAddSizeOfExcludingThis(nsWindowSizes& aWindowSizes) const {
aWindowSizes.mState.mMallocSizeOf);
}
if (mRadioGroupContainer) {
aWindowSizes.mDOMSizes.mDOMOtherSize +=
mRadioGroupContainer->SizeOfIncludingThis(
aWindowSizes.mState.mMallocSizeOf);
}
aWindowSizes.mDOMSizes.mDOMOtherSize +=
mStyledLinks.ShallowSizeOfExcludingThis(
aWindowSizes.mState.mMallocSizeOf);
@ -18761,4 +18772,11 @@ HighlightRegistry& Document::HighlightRegistry() {
return *mHighlightRegistry;
}
RadioGroupContainer& Document::OwnedRadioGroupContainer() {
if (!mRadioGroupContainer) {
mRadioGroupContainer = MakeUnique<RadioGroupContainer>();
}
return *mRadioGroupContainer;
}
} // namespace mozilla::dom

Просмотреть файл

@ -52,6 +52,7 @@
#include "mozilla/dom/Element.h"
#include "mozilla/dom/EventTarget.h"
#include "mozilla/dom/Nullable.h"
#include "mozilla/dom/RadioGroupContainer.h"
#include "mozilla/dom/TreeOrderedArray.h"
#include "mozilla/dom/ViewportMetaData.h"
#include "mozilla/glean/GleanMetrics.h"
@ -79,7 +80,6 @@
#include "nsIParser.h"
#include "nsIPrincipal.h"
#include "nsIProgressEventSink.h"
#include "nsIRadioGroupContainer.h"
#include "nsIReferrerInfo.h"
#include "nsIRequestObserver.h"
#include "nsIScriptObjectPrincipal.h"
@ -532,7 +532,6 @@ enum class SheetPreloadStatus : uint8_t {
class Document : public nsINode,
public DocumentOrShadowRoot,
public nsSupportsWeakReference,
public nsIRadioGroupContainer,
public nsIScriptObjectPrincipal,
public DispatcherTrait,
public SupportsWeakPtr {
@ -584,49 +583,6 @@ class Document : public nsINode,
} \
} while (0)
// nsIRadioGroupContainer
NS_IMETHOD WalkRadioGroup(const nsAString& aName,
nsIRadioVisitor* aVisitor) final {
return DocumentOrShadowRoot::WalkRadioGroup(aName, aVisitor);
}
void SetCurrentRadioButton(const nsAString& aName,
HTMLInputElement* aRadio) final {
DocumentOrShadowRoot::SetCurrentRadioButton(aName, aRadio);
}
HTMLInputElement* GetCurrentRadioButton(const nsAString& aName) final {
return DocumentOrShadowRoot::GetCurrentRadioButton(aName);
}
NS_IMETHOD
GetNextRadioButton(const nsAString& aName, const bool aPrevious,
HTMLInputElement* aFocusedRadio,
HTMLInputElement** aRadioOut) final {
return DocumentOrShadowRoot::GetNextRadioButton(aName, aPrevious,
aFocusedRadio, aRadioOut);
}
void AddToRadioGroup(const nsAString& aName, HTMLInputElement* aRadio) final {
DocumentOrShadowRoot::AddToRadioGroup(aName, aRadio, nullptr);
}
void RemoveFromRadioGroup(const nsAString& aName,
HTMLInputElement* aRadio) final {
DocumentOrShadowRoot::RemoveFromRadioGroup(aName, aRadio);
}
uint32_t GetRequiredRadioCount(const nsAString& aName) const final {
return DocumentOrShadowRoot::GetRequiredRadioCount(aName);
}
void RadioRequiredWillChange(const nsAString& aName,
bool aRequiredAdded) final {
DocumentOrShadowRoot::RadioRequiredWillChange(aName, aRequiredAdded);
}
bool GetValueMissingState(const nsAString& aName) const final {
return DocumentOrShadowRoot::GetValueMissingState(aName);
}
void SetValueMissingState(const nsAString& aName, bool aValue) final {
return DocumentOrShadowRoot::SetValueMissingState(aName, aValue);
}
nsIPrincipal* EffectiveCookiePrincipal() const;
nsIPrincipal* EffectiveStoragePrincipal() const;
@ -5360,6 +5316,8 @@ class Document : public nsINode,
// Registry of custom highlight definitions associated with this document.
RefPtr<class HighlightRegistry> mHighlightRegistry;
UniquePtr<RadioGroupContainer> mRadioGroupContainer;
public:
// Needs to be public because the bindings code pokes at it.
JS::ExpandoAndGeneration mExpandoAndGeneration;
@ -5375,6 +5333,8 @@ class Document : public nsINode,
}
void LoadEventFired();
RadioGroupContainer& OwnedRadioGroupContainer();
};
NS_DEFINE_STATIC_IID_ACCESSOR(Document, NS_IDOCUMENT_IID)

Просмотреть файл

@ -18,7 +18,6 @@
#include "nsTHashtable.h"
#include "nsContentUtils.h"
#include "nsFocusManager.h"
#include "nsIRadioVisitor.h"
#include "nsIFormControl.h"
#include "nsLayoutUtils.h"
#include "nsNameSpaceManager.h"
@ -668,8 +667,6 @@ void DocumentOrShadowRoot::Traverse(DocumentOrShadowRoot* tmp,
for (auto iter = tmp->mIdentifierMap.Iter(); !iter.Done(); iter.Next()) {
iter.Get()->Traverse(&cb);
}
RadioGroupManager::Traverse(tmp, cb);
}
void DocumentOrShadowRoot::UnlinkStyleSheets(
@ -691,7 +688,6 @@ void DocumentOrShadowRoot::Unlink(DocumentOrShadowRoot* tmp) {
});
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAdoptedStyleSheets);
tmp->mIdentifierMap.Clear();
RadioGroupManager::Unlink(tmp);
}
} // namespace mozilla::dom

Просмотреть файл

@ -15,13 +15,11 @@
#include "nsContentListDeclarations.h"
#include "nsTArray.h"
#include "nsTHashSet.h"
#include "RadioGroupManager.h"
class nsContentList;
class nsCycleCollectionTraversalCallback;
class nsINode;
class nsINodeList;
class nsIRadioVisitor;
class nsWindowSizes;
namespace mozilla {
@ -48,7 +46,7 @@ class Sequence;
* TODO(emilio, bug 1418159): In the future this should hold most of the
* relevant style state, this should allow us to fix bug 548397.
*/
class DocumentOrShadowRoot : public RadioGroupManager {
class DocumentOrShadowRoot {
enum class Kind {
Document,
ShadowRoot,

Просмотреть файл

@ -29,6 +29,7 @@
#include "mozilla/TouchEvents.h"
#include "mozilla/URLExtraData.h"
#include "mozilla/dom/Attr.h"
#include "mozilla/dom/RadioGroupContainer.h"
#include "nsDOMAttributeMap.h"
#include "nsAtom.h"
#include "mozilla/dom/NodeInfo.h"
@ -659,6 +660,7 @@ void FragmentOrElement::nsExtendedDOMSlots::UnlinkExtendedSlots(
aContent.ClearMayHaveAnimations();
}
mExplicitlySetAttrElements.Clear();
mRadioGroupContainer = nullptr;
}
void FragmentOrElement::nsExtendedDOMSlots::TraverseExtendedSlots(
@ -683,6 +685,9 @@ void FragmentOrElement::nsExtendedDOMSlots::TraverseExtendedSlots(
if (mAnimations) {
mAnimations->Traverse(aCb);
}
if (mRadioGroupContainer) {
RadioGroupContainer::Traverse(mRadioGroupContainer.get(), aCb);
}
}
size_t FragmentOrElement::nsExtendedDOMSlots::SizeOfExcludingThis(
@ -717,6 +722,10 @@ size_t FragmentOrElement::nsExtendedDOMSlots::SizeOfExcludingThis(
n += mCustomElementData->SizeOfIncludingThis(aMallocSizeOf);
}
if (mRadioGroupContainer) {
n += mRadioGroupContainer->SizeOfIncludingThis(aMallocSizeOf);
}
return n;
}

Просмотреть файл

@ -17,6 +17,7 @@
#include "mozilla/EnumSet.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/dom/RadioGroupContainer.h"
#include "nsCycleCollectionParticipant.h" // NS_DECL_CYCLE_*
#include "nsIContent.h" // base class
#include "nsAtomHashKeys.h"
@ -114,6 +115,14 @@ class FragmentOrElement : public nsIContent {
return Children()->Length();
}
RadioGroupContainer& OwnedRadioGroupContainer() {
auto* slots = ExtendedDOMSlots();
if (!slots->mRadioGroupContainer) {
slots->mRadioGroupContainer = MakeUnique<RadioGroupContainer>();
}
return *slots->mRadioGroupContainer;
}
public:
/**
* If there are listeners for DOMNodeInserted event, fires the event on all
@ -208,6 +217,12 @@ class FragmentOrElement : public nsIContent {
*/
UniquePtr<PopoverData> mPopoverData;
/**
* RadioGroupContainer for radio buttons grouped under this disconnected
* element.
*/
UniquePtr<RadioGroupContainer> mRadioGroupContainer;
/**
* Last remembered size (in CSS pixels) for the element.
* @see {@link https://drafts.csswg.org/css-sizing-4/#last-remembered}

Просмотреть файл

@ -4,9 +4,11 @@
* 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 "RadioGroupManager.h"
#include "nsIRadioVisitor.h"
#include "mozilla/dom/HTMLInputElement.h"
#include "mozilla/dom/RadioGroupContainer.h"
#include "mozilla/Assertions.h"
#include "nsIRadioVisitor.h"
#include "nsRadioVisitor.h"
namespace mozilla::dom {
@ -26,10 +28,24 @@ struct nsRadioGroupStruct {
bool mGroupSuffersFromValueMissing;
};
RadioGroupManager::RadioGroupManager() = default;
RadioGroupContainer::RadioGroupContainer() = default;
void RadioGroupManager::Traverse(RadioGroupManager* tmp,
nsCycleCollectionTraversalCallback& cb) {
RadioGroupContainer::~RadioGroupContainer() {
for (const auto& group : mRadioGroups) {
for (const auto& button : group.GetData()->mRadioButtons) {
// 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(
@ -45,12 +61,13 @@ void RadioGroupManager::Traverse(RadioGroupManager* tmp,
}
}
void RadioGroupManager::Unlink(RadioGroupManager* tmp) {
tmp->mRadioGroups.Clear();
size_t RadioGroupContainer::SizeOfIncludingThis(
MallocSizeOf aMallocSizeOf) const {
return mRadioGroups.SizeOfIncludingThis(aMallocSizeOf);
}
nsresult RadioGroupManager::WalkRadioGroup(const nsAString& aName,
nsIRadioVisitor* aVisitor) {
nsresult RadioGroupContainer::WalkRadioGroup(const nsAString& aName,
nsIRadioVisitor* aVisitor) {
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
for (size_t i = 0; i < radioGroup->mRadioButtons.Length(); i++) {
@ -62,21 +79,20 @@ nsresult RadioGroupManager::WalkRadioGroup(const nsAString& aName,
return NS_OK;
}
void RadioGroupManager::SetCurrentRadioButton(const nsAString& aName,
HTMLInputElement* aRadio) {
void RadioGroupContainer::SetCurrentRadioButton(const nsAString& aName,
HTMLInputElement* aRadio) {
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
radioGroup->mSelectedRadioButton = aRadio;
}
HTMLInputElement* RadioGroupManager::GetCurrentRadioButton(
HTMLInputElement* RadioGroupContainer::GetCurrentRadioButton(
const nsAString& aName) {
return GetOrCreateRadioGroup(aName)->mSelectedRadioButton;
}
nsresult RadioGroupManager::GetNextRadioButton(const nsAString& aName,
const bool aPrevious,
HTMLInputElement* aFocusedRadio,
HTMLInputElement** aRadioOut) {
nsresult RadioGroupContainer::GetNextRadioButton(
const nsAString& aName, const bool aPrevious,
HTMLInputElement* aFocusedRadio, HTMLInputElement** aRadioOut) {
*aRadioOut = nullptr;
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
@ -114,9 +130,9 @@ nsresult RadioGroupManager::GetNextRadioButton(const nsAString& aName,
return NS_OK;
}
void RadioGroupManager::AddToRadioGroup(const nsAString& aName,
HTMLInputElement* aRadio,
nsIContent* aAncestor) {
void RadioGroupContainer::AddToRadioGroup(const nsAString& aName,
HTMLInputElement* aRadio,
nsIContent* aAncestor) {
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
nsContentUtils::AddElementToListByTreeOrder(radioGroup->mRadioButtons, aRadio,
aAncestor);
@ -126,9 +142,13 @@ void RadioGroupManager::AddToRadioGroup(const nsAString& aName,
}
}
void RadioGroupManager::RemoveFromRadioGroup(const nsAString& aName,
HTMLInputElement* aRadio) {
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()) {
@ -138,14 +158,14 @@ void RadioGroupManager::RemoveFromRadioGroup(const nsAString& aName,
}
}
uint32_t RadioGroupManager::GetRequiredRadioCount(
uint32_t RadioGroupContainer::GetRequiredRadioCount(
const nsAString& aName) const {
nsRadioGroupStruct* radioGroup = GetRadioGroup(aName);
return radioGroup ? radioGroup->mRequiredRadioCount : 0;
}
void RadioGroupManager::RadioRequiredWillChange(const nsAString& aName,
bool aRequiredAdded) {
void RadioGroupContainer::RadioRequiredWillChange(const nsAString& aName,
bool aRequiredAdded) {
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
if (aRequiredAdded) {
@ -157,25 +177,25 @@ void RadioGroupManager::RadioRequiredWillChange(const nsAString& aName,
}
}
bool RadioGroupManager::GetValueMissingState(const nsAString& aName) const {
bool RadioGroupContainer::GetValueMissingState(const nsAString& aName) const {
nsRadioGroupStruct* radioGroup = GetRadioGroup(aName);
return radioGroup && radioGroup->mGroupSuffersFromValueMissing;
}
void RadioGroupManager::SetValueMissingState(const nsAString& aName,
bool aValue) {
void RadioGroupContainer::SetValueMissingState(const nsAString& aName,
bool aValue) {
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
radioGroup->mGroupSuffersFromValueMissing = aValue;
}
nsRadioGroupStruct* RadioGroupManager::GetRadioGroup(
nsRadioGroupStruct* RadioGroupContainer::GetRadioGroup(
const nsAString& aName) const {
nsRadioGroupStruct* radioGroup = nullptr;
mRadioGroups.Get(aName, &radioGroup);
return radioGroup;
}
nsRadioGroupStruct* RadioGroupManager::GetOrCreateRadioGroup(
nsRadioGroupStruct* RadioGroupContainer::GetOrCreateRadioGroup(
const nsAString& aName) {
return mRadioGroups.GetOrInsertNew(aName);
}

Просмотреть файл

@ -7,32 +7,25 @@
#ifndef mozilla_dom_nsRadioGroupStruct_h
#define mozilla_dom_nsRadioGroupStruct_h
#include "nsCOMArray.h"
#include "nsIFormControl.h"
#include "nsIRadioGroupContainer.h"
#include "nsClassHashtable.h"
class nsIContent;
namespace mozilla {
namespace html {
class nsIRadioVisitor;
}
namespace dom {
namespace mozilla::dom {
class HTMLInputElement;
struct nsRadioGroupStruct;
class RadioGroupManager {
class RadioGroupContainer final {
public:
RadioGroupManager();
RadioGroupContainer();
~RadioGroupContainer();
static void Traverse(RadioGroupManager* tmp,
static void Traverse(RadioGroupContainer* tmp,
nsCycleCollectionTraversalCallback& cb);
static void Unlink(RadioGroupManager* tmp);
size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
// nsIRadioGroupContainer
NS_IMETHOD WalkRadioGroup(const nsAString& aName, nsIRadioVisitor* aVisitor);
void SetCurrentRadioButton(const nsAString& aName, HTMLInputElement* aRadio);
HTMLInputElement* GetCurrentRadioButton(const nsAString& aName);
@ -55,7 +48,6 @@ class RadioGroupManager {
nsClassHashtable<nsStringHashKey, nsRadioGroupStruct> mRadioGroups;
};
} // namespace dom
} // namespace mozilla
} // namespace mozilla::dom
#endif // mozilla_dom_nsRadioGroupStruct_h

Просмотреть файл

@ -44,7 +44,6 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(DocumentFragment)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ShadowRoot)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContent)
NS_INTERFACE_MAP_ENTRY(nsIRadioGroupContainer)
NS_INTERFACE_MAP_END_INHERITING(DocumentFragment)
NS_IMPL_ADDREF_INHERITED(ShadowRoot, DocumentFragment)

Просмотреть файл

@ -16,7 +16,6 @@
#include "mozilla/ServoBindings.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIRadioGroupContainer.h"
#include "nsStubMutationObserver.h"
#include "nsTHashtable.h"
@ -40,9 +39,7 @@ class CSSImportRule;
class Element;
class HTMLInputElement;
class ShadowRoot final : public DocumentFragment,
public DocumentOrShadowRoot,
public nsIRadioGroupContainer {
class ShadowRoot final : public DocumentFragment, public DocumentOrShadowRoot {
friend class DocumentOrShadowRoot;
public:
@ -234,58 +231,16 @@ class ShadowRoot final : public DocumentFragment,
void GetEventTargetParent(EventChainPreVisitor& aVisitor) override;
// nsIRadioGroupContainer
NS_IMETHOD WalkRadioGroup(const nsAString& aName,
nsIRadioVisitor* aVisitor) override {
return DocumentOrShadowRoot::WalkRadioGroup(aName, aVisitor);
}
void SetCurrentRadioButton(const nsAString& aName,
HTMLInputElement* aRadio) override {
DocumentOrShadowRoot::SetCurrentRadioButton(aName, aRadio);
}
HTMLInputElement* GetCurrentRadioButton(const nsAString& aName) override {
return DocumentOrShadowRoot::GetCurrentRadioButton(aName);
}
NS_IMETHOD
GetNextRadioButton(const nsAString& aName, const bool aPrevious,
HTMLInputElement* aFocusedRadio,
HTMLInputElement** aRadioOut) override {
return DocumentOrShadowRoot::GetNextRadioButton(aName, aPrevious,
aFocusedRadio, aRadioOut);
}
void AddToRadioGroup(const nsAString& aName,
HTMLInputElement* aRadio) override {
DocumentOrShadowRoot::AddToRadioGroup(aName, aRadio, this);
}
void RemoveFromRadioGroup(const nsAString& aName,
HTMLInputElement* aRadio) override {
DocumentOrShadowRoot::RemoveFromRadioGroup(aName, aRadio);
}
uint32_t GetRequiredRadioCount(const nsAString& aName) const override {
return DocumentOrShadowRoot::GetRequiredRadioCount(aName);
}
void RadioRequiredWillChange(const nsAString& aName,
bool aRequiredAdded) override {
DocumentOrShadowRoot::RadioRequiredWillChange(aName, aRequiredAdded);
}
bool GetValueMissingState(const nsAString& aName) const override {
return DocumentOrShadowRoot::GetValueMissingState(aName);
}
void SetValueMissingState(const nsAString& aName, bool aValue) override {
return DocumentOrShadowRoot::SetValueMissingState(aName, aValue);
}
protected:
// FIXME(emilio): This will need to become more fine-grained.
void ApplicableRulesChanged();
virtual ~ShadowRoot();
const ShadowRootMode mMode;
Element::DelegatesFocus mDelegatesFocus;
const SlotAssignmentMode mSlotAssignment;
// Make sure that the first field is pointer-aligned so it doesn't get packed
// in the base class' padding, since otherwise rust-bindgen can't generate
// correct bindings for it, see
// https://github.com/rust-lang/rust-bindgen/issues/380
// The computed data from the style sheets.
UniquePtr<StyleAuthorStyles> mServoStyles;
@ -301,6 +256,12 @@ class ShadowRoot final : public DocumentFragment,
// tree.
nsTArray<const Element*> mParts;
const ShadowRootMode mMode;
Element::DelegatesFocus mDelegatesFocus;
const SlotAssignmentMode mSlotAssignment;
// Whether this is the <details> internal shadow tree.
bool mIsDetailsShadowTree : 1;

Просмотреть файл

@ -255,7 +255,7 @@ EXPORTS.mozilla.dom += [
"Pose.h",
"PostMessageEvent.h",
"ProcessMessageManager.h",
"RadioGroupManager.h",
"RadioGroupContainer.h",
"ResizeObserver.h",
"ResizeObserverController.h",
"ResponsiveImageSelector.h",
@ -438,7 +438,7 @@ UNIFIED_SOURCES += [
"Pose.cpp",
"PostMessageEvent.cpp",
"ProcessMessageManager.cpp",
"RadioGroupManager.cpp",
"RadioGroupContainer.cpp",
"RangeUtils.cpp",
"RemoteOuterWindowProxy.cpp",
"ResizeObserver.cpp",

Просмотреть файл

@ -145,21 +145,18 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLFormElement,
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPastNameLookupTable)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelList)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTargetContext)
RadioGroupManager::Traverse(tmp, cb);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLFormElement,
nsGenericHTMLElement)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mRelList)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mTargetContext)
RadioGroupManager::Unlink(tmp);
tmp->Clear();
tmp->mExpandoAndGeneration.OwnerUnlinked();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLFormElement,
nsGenericHTMLElement,
nsIRadioGroupContainer)
NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(HTMLFormElement,
nsGenericHTMLElement)
// EventTarget
void HTMLFormElement::AsyncEventRunning(AsyncEventDispatcher* aEvent) {
@ -1251,7 +1248,7 @@ nsresult HTMLFormElement::AddElement(nsGenericHTMLFormElement* aChild,
// being count twice.
if (type == FormControlType::InputRadio) {
RefPtr<HTMLInputElement> radio = static_cast<HTMLInputElement*>(aChild);
radio->AddedToRadioGroup();
radio->AddToRadioGroup();
}
return NS_OK;
@ -1287,7 +1284,7 @@ nsresult HTMLFormElement::RemoveElement(nsGenericHTMLFormElement* aChild,
MOZ_ASSERT(fc);
if (fc->ControlType() == FormControlType::InputRadio) {
RefPtr<HTMLInputElement> radio = static_cast<HTMLInputElement*>(aChild);
radio->WillRemoveFromRadioGroup();
radio->RemoveFromRadioGroup();
}
// Determine whether to remove the child from the elements list
@ -1860,59 +1857,6 @@ int32_t HTMLFormElement::IndexOfContent(nsIContent* aContent) {
return mControls->IndexOfContent(aContent, &index) == NS_OK ? index : 0;
}
void HTMLFormElement::SetCurrentRadioButton(const nsAString& aName,
HTMLInputElement* aRadio) {
RadioGroupManager::SetCurrentRadioButton(aName, aRadio);
}
HTMLInputElement* HTMLFormElement::GetCurrentRadioButton(
const nsAString& aName) {
return RadioGroupManager::GetCurrentRadioButton(aName);
}
NS_IMETHODIMP
HTMLFormElement::GetNextRadioButton(const nsAString& aName,
const bool aPrevious,
HTMLInputElement* aFocusedRadio,
HTMLInputElement** aRadioOut) {
return RadioGroupManager::GetNextRadioButton(aName, aPrevious, aFocusedRadio,
aRadioOut);
}
NS_IMETHODIMP
HTMLFormElement::WalkRadioGroup(const nsAString& aName,
nsIRadioVisitor* aVisitor) {
return RadioGroupManager::WalkRadioGroup(aName, aVisitor);
}
void HTMLFormElement::AddToRadioGroup(const nsAString& aName,
HTMLInputElement* aRadio) {
RadioGroupManager::AddToRadioGroup(aName, aRadio, this);
}
void HTMLFormElement::RemoveFromRadioGroup(const nsAString& aName,
HTMLInputElement* aRadio) {
RadioGroupManager::RemoveFromRadioGroup(aName, aRadio);
}
uint32_t HTMLFormElement::GetRequiredRadioCount(const nsAString& aName) const {
return RadioGroupManager::GetRequiredRadioCount(aName);
}
void HTMLFormElement::RadioRequiredWillChange(const nsAString& aName,
bool aRequiredAdded) {
RadioGroupManager::RadioRequiredWillChange(aName, aRequiredAdded);
}
bool HTMLFormElement::GetValueMissingState(const nsAString& aName) const {
return RadioGroupManager::GetValueMissingState(aName);
}
void HTMLFormElement::SetValueMissingState(const nsAString& aName,
bool aValue) {
RadioGroupManager::SetValueMissingState(aName, aValue);
}
void HTMLFormElement::Clear() {
for (int32_t i = mImageElements.Length() - 1; i >= 0; i--) {
mImageElements[i]->ClearForm(false);

Просмотреть файл

@ -13,11 +13,10 @@
#include "mozilla/dom/AnchorAreaFormRelValues.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/PopupBlocker.h"
#include "mozilla/dom/RadioGroupManager.h"
#include "mozilla/dom/RadioGroupContainer.h"
#include "nsCOMPtr.h"
#include "nsIFormControl.h"
#include "nsGenericHTMLElement.h"
#include "nsIRadioGroupContainer.h"
#include "nsIWeakReferenceUtils.h"
#include "nsThreadUtils.h"
#include "nsInterfaceHashtable.h"
@ -39,9 +38,7 @@ class HTMLImageElement;
class FormData;
class HTMLFormElement final : public nsGenericHTMLElement,
public nsIRadioGroupContainer,
public AnchorAreaFormRelValues,
RadioGroupManager {
public AnchorAreaFormRelValues {
friend class HTMLFormControlsCollection;
public:
@ -61,25 +58,6 @@ class HTMLFormElement final : public nsGenericHTMLElement,
return aElement == mDefaultSubmitElement;
}
// nsIRadioGroupContainer
void SetCurrentRadioButton(const nsAString& aName,
HTMLInputElement* aRadio) override;
HTMLInputElement* GetCurrentRadioButton(const nsAString& aName) override;
NS_IMETHOD GetNextRadioButton(const nsAString& aName, const bool aPrevious,
HTMLInputElement* aFocusedRadio,
HTMLInputElement** aRadioOut) override;
NS_IMETHOD WalkRadioGroup(const nsAString& aName,
nsIRadioVisitor* aVisitor) override;
void AddToRadioGroup(const nsAString& aName,
HTMLInputElement* aRadio) override;
void RemoveFromRadioGroup(const nsAString& aName,
HTMLInputElement* aRadio) override;
uint32_t GetRequiredRadioCount(const nsAString& aName) const override;
void RadioRequiredWillChange(const nsAString& aName,
bool aRequiredAdded) override;
bool GetValueMissingState(const nsAString& aName) const override;
void SetValueMissingState(const nsAString& aName, bool aValue) override;
// EventTarget
void AsyncEventRunning(AsyncEventDispatcher* aEvent) override;

Просмотреть файл

@ -36,8 +36,6 @@
#include "nsNetUtil.h"
#include "nsQueryObject.h"
#include "nsIRadioVisitor.h"
#include "HTMLDataListElement.h"
#include "HTMLFormSubmissionConstants.h"
#include "mozilla/Telemetry.h"
@ -85,7 +83,9 @@
#include <algorithm>
// input type=radio
#include "nsIRadioGroupContainer.h"
#include "mozilla/dom/RadioGroupContainer.h"
#include "nsIRadioVisitor.h"
#include "nsRadioVisitor.h"
// input type=file
#include "mozilla/dom/FileSystemEntry.h"
@ -106,7 +106,6 @@
#include "nsContentCreatorFunctions.h"
#include "nsContentUtils.h"
#include "mozilla/dom/DirectionalityUtils.h"
#include "nsRadioVisitor.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/Preferences.h"
@ -1014,7 +1013,8 @@ HTMLInputElement::HTMLInputElement(already_AddRefed<dom::NodeInfo>&& aNodeInfo,
mPickerRunning(false),
mIsPreviewEnabled(false),
mHasBeenTypePassword(false),
mHasPatternAttribute(false) {
mHasPatternAttribute(false),
mRadioGroupContainer(nullptr) {
// If size is above 512, mozjemalloc allocates 1kB, see
// memory/build/mozjemalloc.cpp
static_assert(sizeof(HTMLInputElement) <= 512,
@ -1186,9 +1186,9 @@ void HTMLInputElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName,
if (mType == FormControlType::InputRadio) {
if ((aName == nsGkAtoms::name || (aName == nsGkAtoms::type && !mForm)) &&
(mForm || mDoneCreating)) {
WillRemoveFromRadioGroup();
RemoveFromRadioGroup();
} else if (aName == nsGkAtoms::required) {
nsCOMPtr<nsIRadioGroupContainer> container = GetRadioGroupContainer();
auto* container = GetCurrentRadioGroupContainer();
if (container && ((aValue && !HasAttr(aNameSpaceID, aName)) ||
(!aValue && HasAttr(aNameSpaceID, aName)))) {
@ -1284,7 +1284,7 @@ void HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
// If we are not done creating the radio, we also should not do it.
if ((aName == nsGkAtoms::name || (aName == nsGkAtoms::type && !mForm)) &&
mType == FormControlType::InputRadio && (mForm || mDoneCreating)) {
AddedToRadioGroup();
AddToRadioGroup();
UpdateValueMissingValidityStateForRadio(false);
needValidityUpdate = true;
}
@ -1416,7 +1416,7 @@ void HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
void HTMLInputElement::BeforeSetForm(bool aBindToTree) {
// No need to remove from radio group if we are just binding to tree.
if (mType == FormControlType::InputRadio && !aBindToTree) {
WillRemoveFromRadioGroup();
RemoveFromRadioGroup();
}
}
@ -1424,8 +1424,9 @@ void HTMLInputElement::AfterClearForm(bool aUnbindOrDelete) {
MOZ_ASSERT(!mForm);
// Do not add back to radio group if we are releasing or unbinding from tree.
if (mType == FormControlType::InputRadio && !aUnbindOrDelete) {
AddedToRadioGroup();
if (mType == FormControlType::InputRadio && !aUnbindOrDelete &&
!GetCurrentRadioGroupContainer()) {
AddToRadioGroup();
UpdateValueMissingValidityStateForRadio(false);
}
}
@ -2861,8 +2862,7 @@ void HTMLInputElement::DoSetChecked(bool aChecked, bool aNotify,
return;
}
nsIRadioGroupContainer* container = GetRadioGroupContainer();
if (container) {
if (auto* container = GetCurrentRadioGroupContainer()) {
nsAutoString name;
GetAttr(nsGkAtoms::name, name);
container->SetCurrentRadioButton(name, nullptr);
@ -2885,8 +2885,7 @@ void HTMLInputElement::RadioSetChecked(bool aNotify) {
}
// Let the group know that we are now the One True Radio Button
nsIRadioGroupContainer* container = GetRadioGroupContainer();
if (container) {
if (auto* container = GetCurrentRadioGroupContainer()) {
nsAutoString name;
GetAttr(nsGkAtoms::name, name);
container->SetCurrentRadioButton(name, this);
@ -2897,38 +2896,39 @@ void HTMLInputElement::RadioSetChecked(bool aNotify) {
SetCheckedInternal(true, aNotify);
}
nsIRadioGroupContainer* HTMLInputElement::GetRadioGroupContainer() const {
RadioGroupContainer* HTMLInputElement::GetCurrentRadioGroupContainer() const {
NS_ASSERTION(
mType == FormControlType::InputRadio,
"GetRadioGroupContainer should only be called when type='radio'");
return mRadioGroupContainer;
}
RadioGroupContainer* HTMLInputElement::FindTreeRadioGroupContainer() const {
nsAutoString name;
GetAttr(nsGkAtoms::name, name);
if (name.IsEmpty()) {
return nullptr;
}
if (mForm) {
return mForm;
return &mForm->OwnedRadioGroupContainer();
}
if (IsInNativeAnonymousSubtree()) {
return nullptr;
}
DocumentOrShadowRoot* docOrShadow = GetUncomposedDocOrConnectedShadowRoot();
if (!docOrShadow) {
return nullptr;
if (Document* doc = GetUncomposedDoc()) {
return &doc->OwnedRadioGroupContainer();
}
return &static_cast<FragmentOrElement*>(SubtreeRoot())
->OwnedRadioGroupContainer();
}
nsCOMPtr<nsIRadioGroupContainer> container =
do_QueryInterface(&(docOrShadow->AsNode()));
return container;
void HTMLInputElement::DisconnectRadioGroupContainer() {
mRadioGroupContainer = nullptr;
}
HTMLInputElement* HTMLInputElement::GetSelectedRadioButton() const {
nsIRadioGroupContainer* container = GetRadioGroupContainer();
auto* container = GetCurrentRadioGroupContainer();
if (!container) {
return nullptr;
}
@ -4140,7 +4140,7 @@ nsresult HTMLInputElement::MaybeHandleRadioButtonNavigation(
}
// Arrow key pressed, focus+select prev/next radio button
RefPtr<HTMLInputElement> selectedRadioButton;
if (nsIRadioGroupContainer* container = GetRadioGroupContainer()) {
if (auto* container = GetCurrentRadioGroupContainer()) {
nsAutoString name;
GetAttr(nsGkAtoms::name, name);
container->GetNextRadioButton(name, move == RadioButtonMove::Back, this,
@ -4270,6 +4270,12 @@ void HTMLInputElement::MaybeLoadImage() {
}
nsresult HTMLInputElement::BindToTree(BindContext& aContext, nsINode& aParent) {
// If we are currently bound to a disconnected subtree root, remove
// ourselves from it first.
if (!mForm && mType == FormControlType::InputRadio) {
RemoveFromRadioGroup();
}
nsresult rv =
nsGenericHTMLFormControlElementWithState::BindToTree(aContext, aParent);
NS_ENSURE_SUCCESS(rv, rv);
@ -4292,9 +4298,8 @@ nsresult HTMLInputElement::BindToTree(BindContext& aContext, nsINode& aParent) {
// Add radio to document if we don't have a form already (if we do it's
// already been added into that group)
if (!mForm && mType == FormControlType::InputRadio &&
GetUncomposedDocOrConnectedShadowRoot()) {
AddedToRadioGroup();
if (!mForm && mType == FormControlType::InputRadio) {
AddToRadioGroup();
}
// Set direction based on value if dir=auto
@ -4346,7 +4351,7 @@ void HTMLInputElement::UnbindFromTree(bool aNullParent) {
// of the case where we're removing from the document and we don't
// have a form
if (!mForm && mType == FormControlType::InputRadio) {
WillRemoveFromRadioGroup();
RemoveFromRadioGroup();
}
if (CreatesDateTimeWidget() && IsInComposedDoc()) {
@ -4356,6 +4361,12 @@ void HTMLInputElement::UnbindFromTree(bool aNullParent) {
nsImageLoadingContent::UnbindFromTree(aNullParent);
nsGenericHTMLFormControlElementWithState::UnbindFromTree(aNullParent);
// If we are contained within a disconnected subtree, attempt to add
// ourselves to the subtree root's radio group.
if (!mForm && mType == FormControlType::InputRadio) {
AddToRadioGroup();
}
// GetCurrentDoc is returning nullptr so we can update the value
// missing validity state to reflect we are no longer into a doc.
UpdateValueMissingValidityState();
@ -6220,16 +6231,27 @@ bool HTMLInputElement::RestoreState(PresState* aState) {
* Radio group stuff
*/
void HTMLInputElement::AddedToRadioGroup() {
// If the element is neither in a form nor a document, there is no group so we
// should just stop here.
if (!mForm && (!GetUncomposedDocOrConnectedShadowRoot() ||
IsInNativeAnonymousSubtree())) {
void HTMLInputElement::AddToRadioGroup() {
MOZ_ASSERT(!mRadioGroupContainer,
"Radio button must be removed from previous radio group container "
"before being added to another!");
// If the element has no radio group container we can stop here.
auto* container = FindTreeRadioGroupContainer();
if (!container) {
return;
}
// Make sure not to notify if we're still being created
bool notify = mDoneCreating;
nsAutoString name;
GetAttr(nsGkAtoms::name, name);
// If we are part of a radio group, the element must have a name.
MOZ_ASSERT(!name.IsEmpty());
//
// Add the radio to the radio group container.
//
container->AddToRadioGroup(name, this, mForm);
mRadioGroupContainer = container;
//
// If the input element is checked, and we add it to the group, it will
@ -6242,8 +6264,9 @@ void HTMLInputElement::AddedToRadioGroup() {
// radio button, but as adding a checked radio button into the group
// should not be that common an occurrence, I think we can live with
// that.
// Make sure not to notify if we're still being created.
//
RadioSetChecked(notify);
RadioSetChecked(mDoneCreating);
}
//
@ -6258,24 +6281,14 @@ void HTMLInputElement::AddedToRadioGroup() {
SetCheckedChangedInternal(checkedChanged);
//
// Add the radio to the radio group container.
//
nsCOMPtr<nsIRadioGroupContainer> container = GetRadioGroupContainer();
if (container) {
nsAutoString name;
GetAttr(nsGkAtoms::name, name);
container->AddToRadioGroup(name, this);
// We initialize the validity of the element to the validity of the group
// because we assume UpdateValueMissingState() will be called after.
SetValidityState(VALIDITY_STATE_VALUE_MISSING,
container->GetValueMissingState(name));
}
// We initialize the validity of the element to the validity of the group
// because we assume UpdateValueMissingState() will be called after.
SetValidityState(VALIDITY_STATE_VALUE_MISSING,
container->GetValueMissingState(name));
}
void HTMLInputElement::WillRemoveFromRadioGroup() {
nsIRadioGroupContainer* container = GetRadioGroupContainer();
void HTMLInputElement::RemoveFromRadioGroup() {
auto* container = GetCurrentRadioGroupContainer();
if (!container) {
return;
}
@ -6296,6 +6309,7 @@ void HTMLInputElement::WillRemoveFromRadioGroup() {
// the group validity is updated (with this element being ignored).
UpdateValueMissingValidityStateForRadio(true);
container->RemoveFromRadioGroup(name, this);
mRadioGroupContainer = nullptr;
}
bool HTMLInputElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
@ -6352,7 +6366,7 @@ bool HTMLInputElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
// Current radio button is not selected.
// But make it tabbable if nothing in group is selected.
nsIRadioGroupContainer* container = GetRadioGroupContainer();
auto* container = GetCurrentRadioGroupContainer();
if (!container) {
*aIsFocusable = defaultFocusable;
return false;
@ -6369,8 +6383,7 @@ bool HTMLInputElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
}
nsresult HTMLInputElement::VisitGroup(nsIRadioVisitor* aVisitor) {
nsIRadioGroupContainer* container = GetRadioGroupContainer();
if (container) {
if (auto* container = GetCurrentRadioGroupContainer()) {
nsAutoString name;
GetAttr(nsGkAtoms::name, name);
return container->WalkRadioGroup(name, aVisitor);
@ -6661,21 +6674,15 @@ void HTMLInputElement::UpdateValueMissingValidityStateForRadio(
bool selected = selection || (!aIgnoreSelf && mChecked);
bool required = !aIgnoreSelf && IsRequired();
nsCOMPtr<nsIRadioGroupContainer> container = GetRadioGroupContainer();
auto* container = GetCurrentRadioGroupContainer();
if (!container) {
SetValidityState(VALIDITY_STATE_VALUE_MISSING, false);
return;
}
nsAutoString name;
GetAttr(nsGkAtoms::name, name);
if (!container) {
// As per the spec, a radio button not within a radio button group cannot
// suffer from being missing; however, we currently are failing to get a
// radio group in the case of a single, named radio button that has no
// form owner, forcing us to check for validity in that case here.
SetValidityState(VALIDITY_STATE_VALUE_MISSING,
required && !selected && !name.IsEmpty());
return;
}
// If the current radio is required and not ignored, we can assume the entire
// group is required.
if (!required) {

Просмотреть файл

@ -28,6 +28,7 @@
#include "mozilla/dom/ConstraintValidation.h"
#include "mozilla/dom/FileInputType.h"
#include "mozilla/dom/HiddenInputType.h"
#include "mozilla/dom/RadioGroupContainer.h"
#include "nsGenericHTMLElement.h"
#include "nsImageLoadingContent.h"
#include "nsCOMPtr.h"
@ -35,7 +36,6 @@
#include "nsIContentPrefService2.h"
#include "nsContentUtils.h"
class nsIRadioGroupContainer;
class nsIRadioVisitor;
namespace mozilla {
@ -277,8 +277,9 @@ class HTMLInputElement final : public TextControlElement,
void SetCheckedChangedInternal(bool aCheckedChanged);
bool GetCheckedChanged() const { return mCheckedChanged; }
void AddedToRadioGroup();
void WillRemoveFromRadioGroup();
void AddToRadioGroup();
void RemoveFromRadioGroup();
void DisconnectRadioGroupContainer();
/**
* Helper function returning the currently selected button in the radio group.
@ -1144,12 +1145,15 @@ class HTMLInputElement final : public TextControlElement,
}
/**
* Returns the radio group container if the element has one, null otherwise.
* The radio group container will be the form owner if there is one.
* The current document otherwise.
* @return the radio group container if the element has one, null otherwise.
* Returns the radio group container within the DOM tree that the element
* is currently a member of, if one exists.
*/
nsIRadioGroupContainer* GetRadioGroupContainer() const;
RadioGroupContainer* GetCurrentRadioGroupContainer() const;
/**
* Returns the radio group container within the DOM tree that the element
* should be added into, if one exists.
*/
RadioGroupContainer* FindTreeRadioGroupContainer() const;
/**
* Parse a color string of the form #XXXXXX where X should be hexa characters
@ -1633,6 +1637,12 @@ class HTMLInputElement final : public TextControlElement,
*/
static bool IsDateTimeTypeSupported(FormControlType);
/**
* The radio group container containing the group the element is a part of.
* This allows the element to only access a container it has been added to.
*/
RadioGroupContainer* mRadioGroupContainer;
struct nsFilePickerFilter {
nsFilePickerFilter() : mFilterMask(0) {}

Просмотреть файл

@ -30,7 +30,6 @@ EXPORTS += [
"nsIConstraintValidation.h",
"nsIFormControl.h",
"nsIHTMLCollection.h",
"nsIRadioGroupContainer.h",
"nsIRadioVisitor.h",
]

Просмотреть файл

@ -1,101 +0,0 @@
/* -*- 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/. */
#ifndef nsIRadioGroupContainer_h___
#define nsIRadioGroupContainer_h___
#include "nsISupports.h"
class nsIRadioVisitor;
class nsIFormControl;
namespace mozilla::dom {
class HTMLInputElement;
} // namespace mozilla::dom
#define NS_IRADIOGROUPCONTAINER_IID \
{ \
0x800320a0, 0x733f, 0x11e4, { \
0x82, 0xf8, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 \
} \
}
/**
* A container that has multiple radio groups in it, defined by name.
*/
class nsIRadioGroupContainer : public nsISupports {
public:
NS_DECLARE_STATIC_IID_ACCESSOR(NS_IRADIOGROUPCONTAINER_IID)
/**
* Walk through the radio group, visiting each note with avisitor->Visit()
* @param aName the group name
* @param aVisitor the visitor to visit with
*/
NS_IMETHOD WalkRadioGroup(const nsAString& aName,
nsIRadioVisitor* aVisitor) = 0;
/**
* Set the current radio button in a group
* @param aName the group name
* @param aRadio the currently selected radio button
*/
virtual void SetCurrentRadioButton(
const nsAString& aName, mozilla::dom::HTMLInputElement* aRadio) = 0;
/**
* Get the current radio button in a group
* @param aName the group name
* @return the currently selected radio button
*/
virtual mozilla::dom::HTMLInputElement* GetCurrentRadioButton(
const nsAString& aName) = 0;
/**
* Get the next/prev radio button in a group
* @param aName the group name
* @param aPrevious, true gets previous radio button, false gets next
* @param aFocusedRadio the currently focused radio button
* @param aRadio the currently selected radio button [OUT]
*/
NS_IMETHOD GetNextRadioButton(const nsAString& aName, const bool aPrevious,
mozilla::dom::HTMLInputElement* aFocusedRadio,
mozilla::dom::HTMLInputElement** aRadio) = 0;
/**
* Add radio button to radio group
*
* Note that forms do not do anything for this method since they already
* store radio groups on their own.
*
* @param aName radio group's name
* @param aRadio radio button's pointer
*/
virtual void AddToRadioGroup(const nsAString& aName,
mozilla::dom::HTMLInputElement* aRadio) = 0;
/**
* Remove radio button from radio group
*
* Note that forms do not do anything for this method since they already
* store radio groups on their own.
*
* @param aName radio group's name
* @param aRadio radio button's pointer
*/
virtual void RemoveFromRadioGroup(const nsAString& aName,
mozilla::dom::HTMLInputElement* aRadio) = 0;
virtual uint32_t GetRequiredRadioCount(const nsAString& aName) const = 0;
virtual void RadioRequiredWillChange(const nsAString& aName,
bool aRequiredAdded) = 0;
virtual bool GetValueMissingState(const nsAString& aName) const = 0;
virtual void SetValueMissingState(const nsAString& aName, bool aValue) = 0;
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsIRadioGroupContainer,
NS_IRADIOGROUPCONTAINER_IID)
#endif /* nsIRadioGroupContainer_h__ */

Просмотреть файл

@ -6,6 +6,7 @@
#include "PerformanceEventTiming.h"
#include "PerformanceMainThread.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/dom/PerformanceEventTimingBinding.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/Performance.h"

Просмотреть файл

@ -1,8 +0,0 @@
[radio.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[Radio buttons in an orphan tree should make a group]
expected: FAIL
[Radio buttons in different groups (because they have different form owners or no form owner) do not affect each other's checkedness]
expected: FAIL

Просмотреть файл

@ -0,0 +1,168 @@
<!DOCTYPE html>
<html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<meta charset="utf-8">
<title>Test for validity of disconnected radio buttons</title>
</head>
<body>
<input type="radio" name="group" id="radio1" required />
<input type="radio" name="group" id="radio2" checked />
<form>
<input type="radio" name="group" id="radio3" required />
<input type="radio" name="group" id="radio4" checked />
</form>
<script>
test(() => {
const radio1 = document.getElementById("radio1");
const radio2 = document.getElementById("radio2");
assert_false(radio1.validity.valueMissing, "Element should not suffer from value missing");
radio2.remove();
assert_true(radio1.validity.valueMissing, "Element should suffer from value missing");
radio1.checked = true;
radio2.required = true;
assert_false(radio2.validity.valueMissing, "Element should not suffer from value missing");
const radio3 = document.getElementById("radio3");
const radio4 = document.getElementById("radio4");
assert_false(radio3.validity.valueMissing, "Element should not suffer from value missing");
radio4.remove();
assert_true(radio3.validity.valueMissing, "Element should suffer from value missing");
radio3.checked = true;
assert_true(radio4.checked, "Element should remain checked");
assert_false(radio3.validity.valueMissing, "Element should not suffer from value missing");
document.querySelector("form").appendChild(radio2);
assert_false(radio3.checked, "Element should be unchecked");
assert_false(radio1.validity.valueMissing, "Element should not suffer from value missing");
}, "Removed elements are moved into separate radio groups.");
test(() => {
const container = document.createElement("div");
const radio = document.createElement("input");
radio.type = "radio";
radio.name = "group";
radio.id = "radio5";
radio.required = true;
container.appendChild(radio);
assert_true(radio.validity.valueMissing, "Element should suffer from value missing");
const outerContainer = document.createElement("div");
const outerRadio = document.createElement("input");
outerRadio.type = "radio";
outerRadio.name = "group";
outerRadio.id = "radio6";
outerRadio.checked = true;
outerContainer.appendChild(outerRadio);
outerContainer.appendChild(container);
assert_false(radio.validity.valueMissing, "Element should not suffer from value missing");
container.remove();
assert_true(radio.validity.valueMissing, "Element should suffer from value missing");
}, "Disconnected radio buttons should be contained by their tree root.");
test(() => {
const radioParent = document.createElement("input");
radioParent.type = "radio";
radioParent.name = "group";
radioParent.id = "radio-parent";
radioParent.required = true;
assert_true(radioParent.validity.valueMissing, "Element should suffer from value missing");
const radioChild = document.createElement("input");
radioChild.type = "radio";
radioChild.name = "group";
radioChild.id = "radio-child";
radioChild.checked = true;
assert_false(radioChild.validity.valueMissing, "Element should not suffer from value missing");
radioParent.appendChild(radioChild);
assert_false(radioChild.validity.valueMissing, "Element should not suffer from value missing");
assert_false(radioParent.validity.valueMissing, "Element should not suffer from value missing");
radioParent.checked = true;
assert_false(radioChild.checked, "Element should no longer be checked");
}, "Disconnected radio buttons can serve as radio group containers.");
test(() => {
const shadowHost = document.createElement("div");
const root = shadowHost.attachShadow({mode: "open"});
const container = document.createElement("div");
container.appendChild(shadowHost);
const radio1 = document.createElement("input");
radio1.type = "radio";
radio1.name = "group";
radio1.required = true;
const radio2 = document.createElement("input");
radio2.type = "radio";
radio2.name = "group";
radio2.checked = true;
const radio3 = document.createElement("input");
radio3.type = "radio";
radio3.name = "group";
radio3.required = true;
root.appendChild(radio1);
container.appendChild(radio3);
assert_true(radio1.validity.valueMissing, "Element should suffer from value missing");
assert_true(radio3.validity.valueMissing, "Element should suffer from value missing");
root.appendChild(radio2);
assert_false(radio1.validity.valueMissing, "Element should not suffer from value missing");
assert_true(radio3.validity.valueMissing, "Element should suffer from value missing");
}, "Shadow roots in disconnected trees can serve as radio group containers.");
test(() => {
const svgRoot = document.createElementNS("http://www.w3.org/2000/svg", "g")
const htmlContainer = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
svgRoot.appendChild(htmlContainer);
const radio1 = document.createElement("input");
radio1.type = "radio";
radio1.name = "group";
radio1.required = true;
const radio2 = document.createElement("input");
radio2.type = "radio";
radio2.name = "group";
radio2.checked = true;
htmlContainer.appendChild(radio1);
assert_true(radio1.validity.valueMissing, "Element should suffer from value missing");
htmlContainer.appendChild(radio2);
assert_false(radio1.validity.valueMissing, "Element should not suffer from value missing");
radio1.checked = true;
assert_false(radio2.checked, "Element should no longer be checked");
}, "Non-HTML elements in disconnected trees can serve as radio group containers.");
test(() => {
const fragment = document.createDocumentFragment();
const radio1 = document.createElement("input");
radio1.type = "radio";
radio1.name = "group";
radio1.required = true;
const radio2 = document.createElement("input");
radio2.type = "radio";
radio2.name = "group";
radio2.checked = true;
fragment.appendChild(radio1);
assert_true(radio1.validity.valueMissing, "Element should suffer from value missing");
fragment.appendChild(radio2);
assert_false(radio1.validity.valueMissing, "Element should not suffer from value missing");
radio1.checked = true;
assert_false(radio2.checked, "Element should no longer be checked");
}, "Disconnected document fragments can serve as radio group containers.");
</script>
</body>
</html>