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 15:47:11 +00:00
parent a861bb857d
commit 3198d33ca2
21 changed files with 390 additions and 423 deletions

View file

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

View file

@ -52,6 +52,7 @@
#include "mozilla/dom/Element.h" #include "mozilla/dom/Element.h"
#include "mozilla/dom/EventTarget.h" #include "mozilla/dom/EventTarget.h"
#include "mozilla/dom/Nullable.h" #include "mozilla/dom/Nullable.h"
#include "mozilla/dom/RadioGroupContainer.h"
#include "mozilla/dom/TreeOrderedArray.h" #include "mozilla/dom/TreeOrderedArray.h"
#include "mozilla/dom/ViewportMetaData.h" #include "mozilla/dom/ViewportMetaData.h"
#include "mozilla/glean/GleanMetrics.h" #include "mozilla/glean/GleanMetrics.h"
@ -79,7 +80,6 @@
#include "nsIParser.h" #include "nsIParser.h"
#include "nsIPrincipal.h" #include "nsIPrincipal.h"
#include "nsIProgressEventSink.h" #include "nsIProgressEventSink.h"
#include "nsIRadioGroupContainer.h"
#include "nsIReferrerInfo.h" #include "nsIReferrerInfo.h"
#include "nsIRequestObserver.h" #include "nsIRequestObserver.h"
#include "nsIScriptObjectPrincipal.h" #include "nsIScriptObjectPrincipal.h"
@ -532,7 +532,6 @@ enum class SheetPreloadStatus : uint8_t {
class Document : public nsINode, class Document : public nsINode,
public DocumentOrShadowRoot, public DocumentOrShadowRoot,
public nsSupportsWeakReference, public nsSupportsWeakReference,
public nsIRadioGroupContainer,
public nsIScriptObjectPrincipal, public nsIScriptObjectPrincipal,
public DispatcherTrait, public DispatcherTrait,
public SupportsWeakPtr { public SupportsWeakPtr {
@ -584,49 +583,6 @@ class Document : public nsINode,
} \ } \
} while (0) } 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* EffectiveCookiePrincipal() const;
nsIPrincipal* EffectiveStoragePrincipal() const; nsIPrincipal* EffectiveStoragePrincipal() const;
@ -5360,6 +5316,8 @@ class Document : public nsINode,
// Registry of custom highlight definitions associated with this document. // Registry of custom highlight definitions associated with this document.
RefPtr<class HighlightRegistry> mHighlightRegistry; RefPtr<class HighlightRegistry> mHighlightRegistry;
UniquePtr<RadioGroupContainer> mRadioGroupContainer;
public: public:
// Needs to be public because the bindings code pokes at it. // Needs to be public because the bindings code pokes at it.
JS::ExpandoAndGeneration mExpandoAndGeneration; JS::ExpandoAndGeneration mExpandoAndGeneration;
@ -5375,6 +5333,8 @@ class Document : public nsINode,
} }
void LoadEventFired(); void LoadEventFired();
RadioGroupContainer& OwnedRadioGroupContainer();
}; };
NS_DEFINE_STATIC_IID_ACCESSOR(Document, NS_IDOCUMENT_IID) NS_DEFINE_STATIC_IID_ACCESSOR(Document, NS_IDOCUMENT_IID)

View file

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

View file

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

View file

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

View file

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

View file

@ -4,9 +4,11 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * 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/HTMLInputElement.h"
#include "mozilla/dom/RadioGroupContainer.h"
#include "mozilla/Assertions.h"
#include "nsIRadioVisitor.h"
#include "nsRadioVisitor.h"
namespace mozilla::dom { namespace mozilla::dom {
@ -26,10 +28,24 @@ struct nsRadioGroupStruct {
bool mGroupSuffersFromValueMissing; bool mGroupSuffersFromValueMissing;
}; };
RadioGroupManager::RadioGroupManager() = default; RadioGroupContainer::RadioGroupContainer() = default;
void RadioGroupManager::Traverse(RadioGroupManager* tmp, RadioGroupContainer::~RadioGroupContainer() {
nsCycleCollectionTraversalCallback& cb) { 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) { for (const auto& entry : tmp->mRadioGroups) {
nsRadioGroupStruct* radioGroup = entry.GetWeak(); nsRadioGroupStruct* radioGroup = entry.GetWeak();
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME( NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
@ -45,12 +61,13 @@ void RadioGroupManager::Traverse(RadioGroupManager* tmp,
} }
} }
void RadioGroupManager::Unlink(RadioGroupManager* tmp) { size_t RadioGroupContainer::SizeOfIncludingThis(
tmp->mRadioGroups.Clear(); MallocSizeOf aMallocSizeOf) const {
return mRadioGroups.SizeOfIncludingThis(aMallocSizeOf);
} }
nsresult RadioGroupManager::WalkRadioGroup(const nsAString& aName, nsresult RadioGroupContainer::WalkRadioGroup(const nsAString& aName,
nsIRadioVisitor* aVisitor) { nsIRadioVisitor* aVisitor) {
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
for (size_t i = 0; i < radioGroup->mRadioButtons.Length(); i++) { for (size_t i = 0; i < radioGroup->mRadioButtons.Length(); i++) {
@ -62,21 +79,20 @@ nsresult RadioGroupManager::WalkRadioGroup(const nsAString& aName,
return NS_OK; return NS_OK;
} }
void RadioGroupManager::SetCurrentRadioButton(const nsAString& aName, void RadioGroupContainer::SetCurrentRadioButton(const nsAString& aName,
HTMLInputElement* aRadio) { HTMLInputElement* aRadio) {
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
radioGroup->mSelectedRadioButton = aRadio; radioGroup->mSelectedRadioButton = aRadio;
} }
HTMLInputElement* RadioGroupManager::GetCurrentRadioButton( HTMLInputElement* RadioGroupContainer::GetCurrentRadioButton(
const nsAString& aName) { const nsAString& aName) {
return GetOrCreateRadioGroup(aName)->mSelectedRadioButton; return GetOrCreateRadioGroup(aName)->mSelectedRadioButton;
} }
nsresult RadioGroupManager::GetNextRadioButton(const nsAString& aName, nsresult RadioGroupContainer::GetNextRadioButton(
const bool aPrevious, const nsAString& aName, const bool aPrevious,
HTMLInputElement* aFocusedRadio, HTMLInputElement* aFocusedRadio, HTMLInputElement** aRadioOut) {
HTMLInputElement** aRadioOut) {
*aRadioOut = nullptr; *aRadioOut = nullptr;
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
@ -114,9 +130,9 @@ nsresult RadioGroupManager::GetNextRadioButton(const nsAString& aName,
return NS_OK; return NS_OK;
} }
void RadioGroupManager::AddToRadioGroup(const nsAString& aName, void RadioGroupContainer::AddToRadioGroup(const nsAString& aName,
HTMLInputElement* aRadio, HTMLInputElement* aRadio,
nsIContent* aAncestor) { nsIContent* aAncestor) {
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
nsContentUtils::AddElementToListByTreeOrder(radioGroup->mRadioButtons, aRadio, nsContentUtils::AddElementToListByTreeOrder(radioGroup->mRadioButtons, aRadio,
aAncestor); aAncestor);
@ -126,9 +142,13 @@ void RadioGroupManager::AddToRadioGroup(const nsAString& aName,
} }
} }
void RadioGroupManager::RemoveFromRadioGroup(const nsAString& aName, void RadioGroupContainer::RemoveFromRadioGroup(const nsAString& aName,
HTMLInputElement* aRadio) { HTMLInputElement* aRadio) {
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); 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); radioGroup->mRadioButtons.RemoveElement(aRadio);
if (aRadio->IsRequired()) { 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 { const nsAString& aName) const {
nsRadioGroupStruct* radioGroup = GetRadioGroup(aName); nsRadioGroupStruct* radioGroup = GetRadioGroup(aName);
return radioGroup ? radioGroup->mRequiredRadioCount : 0; return radioGroup ? radioGroup->mRequiredRadioCount : 0;
} }
void RadioGroupManager::RadioRequiredWillChange(const nsAString& aName, void RadioGroupContainer::RadioRequiredWillChange(const nsAString& aName,
bool aRequiredAdded) { bool aRequiredAdded) {
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
if (aRequiredAdded) { 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); nsRadioGroupStruct* radioGroup = GetRadioGroup(aName);
return radioGroup && radioGroup->mGroupSuffersFromValueMissing; return radioGroup && radioGroup->mGroupSuffersFromValueMissing;
} }
void RadioGroupManager::SetValueMissingState(const nsAString& aName, void RadioGroupContainer::SetValueMissingState(const nsAString& aName,
bool aValue) { bool aValue) {
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
radioGroup->mGroupSuffersFromValueMissing = aValue; radioGroup->mGroupSuffersFromValueMissing = aValue;
} }
nsRadioGroupStruct* RadioGroupManager::GetRadioGroup( nsRadioGroupStruct* RadioGroupContainer::GetRadioGroup(
const nsAString& aName) const { const nsAString& aName) const {
nsRadioGroupStruct* radioGroup = nullptr; nsRadioGroupStruct* radioGroup = nullptr;
mRadioGroups.Get(aName, &radioGroup); mRadioGroups.Get(aName, &radioGroup);
return radioGroup; return radioGroup;
} }
nsRadioGroupStruct* RadioGroupManager::GetOrCreateRadioGroup( nsRadioGroupStruct* RadioGroupContainer::GetOrCreateRadioGroup(
const nsAString& aName) { const nsAString& aName) {
return mRadioGroups.GetOrInsertNew(aName); return mRadioGroups.GetOrInsertNew(aName);
} }

View file

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

View file

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

View file

@ -16,7 +16,6 @@
#include "mozilla/ServoBindings.h" #include "mozilla/ServoBindings.h"
#include "nsCOMPtr.h" #include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h" #include "nsCycleCollectionParticipant.h"
#include "nsIRadioGroupContainer.h"
#include "nsStubMutationObserver.h" #include "nsStubMutationObserver.h"
#include "nsTHashtable.h" #include "nsTHashtable.h"
@ -40,9 +39,7 @@ class CSSImportRule;
class Element; class Element;
class HTMLInputElement; class HTMLInputElement;
class ShadowRoot final : public DocumentFragment, class ShadowRoot final : public DocumentFragment, public DocumentOrShadowRoot {
public DocumentOrShadowRoot,
public nsIRadioGroupContainer {
friend class DocumentOrShadowRoot; friend class DocumentOrShadowRoot;
public: public:
@ -234,58 +231,16 @@ class ShadowRoot final : public DocumentFragment,
void GetEventTargetParent(EventChainPreVisitor& aVisitor) override; 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: protected:
// FIXME(emilio): This will need to become more fine-grained. // FIXME(emilio): This will need to become more fine-grained.
void ApplicableRulesChanged(); void ApplicableRulesChanged();
virtual ~ShadowRoot(); virtual ~ShadowRoot();
const ShadowRootMode mMode; // 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
Element::DelegatesFocus mDelegatesFocus; // correct bindings for it, see
// https://github.com/rust-lang/rust-bindgen/issues/380
const SlotAssignmentMode mSlotAssignment;
// The computed data from the style sheets. // The computed data from the style sheets.
UniquePtr<StyleAuthorStyles> mServoStyles; UniquePtr<StyleAuthorStyles> mServoStyles;
@ -301,6 +256,12 @@ class ShadowRoot final : public DocumentFragment,
// tree. // tree.
nsTArray<const Element*> mParts; nsTArray<const Element*> mParts;
const ShadowRootMode mMode;
Element::DelegatesFocus mDelegatesFocus;
const SlotAssignmentMode mSlotAssignment;
// Whether this is the <details> internal shadow tree. // Whether this is the <details> internal shadow tree.
bool mIsDetailsShadowTree : 1; bool mIsDetailsShadowTree : 1;

View file

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

View file

@ -145,21 +145,18 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLFormElement,
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPastNameLookupTable) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPastNameLookupTable)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelList) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelList)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTargetContext) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTargetContext)
RadioGroupManager::Traverse(tmp, cb);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLFormElement, NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLFormElement,
nsGenericHTMLElement) nsGenericHTMLElement)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mRelList) NS_IMPL_CYCLE_COLLECTION_UNLINK(mRelList)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mTargetContext) NS_IMPL_CYCLE_COLLECTION_UNLINK(mTargetContext)
RadioGroupManager::Unlink(tmp);
tmp->Clear(); tmp->Clear();
tmp->mExpandoAndGeneration.OwnerUnlinked(); tmp->mExpandoAndGeneration.OwnerUnlinked();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLFormElement, NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(HTMLFormElement,
nsGenericHTMLElement, nsGenericHTMLElement)
nsIRadioGroupContainer)
// EventTarget // EventTarget
void HTMLFormElement::AsyncEventRunning(AsyncEventDispatcher* aEvent) { void HTMLFormElement::AsyncEventRunning(AsyncEventDispatcher* aEvent) {
@ -1251,7 +1248,7 @@ nsresult HTMLFormElement::AddElement(nsGenericHTMLFormElement* aChild,
// being count twice. // being count twice.
if (type == FormControlType::InputRadio) { if (type == FormControlType::InputRadio) {
RefPtr<HTMLInputElement> radio = static_cast<HTMLInputElement*>(aChild); RefPtr<HTMLInputElement> radio = static_cast<HTMLInputElement*>(aChild);
radio->AddedToRadioGroup(); radio->AddToRadioGroup();
} }
return NS_OK; return NS_OK;
@ -1287,7 +1284,7 @@ nsresult HTMLFormElement::RemoveElement(nsGenericHTMLFormElement* aChild,
MOZ_ASSERT(fc); MOZ_ASSERT(fc);
if (fc->ControlType() == FormControlType::InputRadio) { if (fc->ControlType() == FormControlType::InputRadio) {
RefPtr<HTMLInputElement> radio = static_cast<HTMLInputElement*>(aChild); RefPtr<HTMLInputElement> radio = static_cast<HTMLInputElement*>(aChild);
radio->WillRemoveFromRadioGroup(); radio->RemoveFromRadioGroup();
} }
// Determine whether to remove the child from the elements list // 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; 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() { void HTMLFormElement::Clear() {
for (int32_t i = mImageElements.Length() - 1; i >= 0; i--) { for (int32_t i = mImageElements.Length() - 1; i >= 0; i--) {
mImageElements[i]->ClearForm(false); mImageElements[i]->ClearForm(false);

View file

@ -13,11 +13,10 @@
#include "mozilla/dom/AnchorAreaFormRelValues.h" #include "mozilla/dom/AnchorAreaFormRelValues.h"
#include "mozilla/dom/BrowsingContext.h" #include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/PopupBlocker.h" #include "mozilla/dom/PopupBlocker.h"
#include "mozilla/dom/RadioGroupManager.h" #include "mozilla/dom/RadioGroupContainer.h"
#include "nsCOMPtr.h" #include "nsCOMPtr.h"
#include "nsIFormControl.h" #include "nsIFormControl.h"
#include "nsGenericHTMLElement.h" #include "nsGenericHTMLElement.h"
#include "nsIRadioGroupContainer.h"
#include "nsIWeakReferenceUtils.h" #include "nsIWeakReferenceUtils.h"
#include "nsThreadUtils.h" #include "nsThreadUtils.h"
#include "nsInterfaceHashtable.h" #include "nsInterfaceHashtable.h"
@ -39,9 +38,7 @@ class HTMLImageElement;
class FormData; class FormData;
class HTMLFormElement final : public nsGenericHTMLElement, class HTMLFormElement final : public nsGenericHTMLElement,
public nsIRadioGroupContainer, public AnchorAreaFormRelValues {
public AnchorAreaFormRelValues,
RadioGroupManager {
friend class HTMLFormControlsCollection; friend class HTMLFormControlsCollection;
public: public:
@ -61,25 +58,6 @@ class HTMLFormElement final : public nsGenericHTMLElement,
return aElement == mDefaultSubmitElement; 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 // EventTarget
void AsyncEventRunning(AsyncEventDispatcher* aEvent) override; void AsyncEventRunning(AsyncEventDispatcher* aEvent) override;

View file

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

View file

@ -28,6 +28,7 @@
#include "mozilla/dom/ConstraintValidation.h" #include "mozilla/dom/ConstraintValidation.h"
#include "mozilla/dom/FileInputType.h" #include "mozilla/dom/FileInputType.h"
#include "mozilla/dom/HiddenInputType.h" #include "mozilla/dom/HiddenInputType.h"
#include "mozilla/dom/RadioGroupContainer.h"
#include "nsGenericHTMLElement.h" #include "nsGenericHTMLElement.h"
#include "nsImageLoadingContent.h" #include "nsImageLoadingContent.h"
#include "nsCOMPtr.h" #include "nsCOMPtr.h"
@ -35,7 +36,6 @@
#include "nsIContentPrefService2.h" #include "nsIContentPrefService2.h"
#include "nsContentUtils.h" #include "nsContentUtils.h"
class nsIRadioGroupContainer;
class nsIRadioVisitor; class nsIRadioVisitor;
namespace mozilla { namespace mozilla {
@ -277,8 +277,9 @@ class HTMLInputElement final : public TextControlElement,
void SetCheckedChangedInternal(bool aCheckedChanged); void SetCheckedChangedInternal(bool aCheckedChanged);
bool GetCheckedChanged() const { return mCheckedChanged; } bool GetCheckedChanged() const { return mCheckedChanged; }
void AddedToRadioGroup(); void AddToRadioGroup();
void WillRemoveFromRadioGroup(); void RemoveFromRadioGroup();
void DisconnectRadioGroupContainer();
/** /**
* Helper function returning the currently selected button in the radio group. * 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. * Returns the radio group container within the DOM tree that the element
* The radio group container will be the form owner if there is one. * is currently a member of, if one exists.
* The current document otherwise.
* @return the radio group container if the element has one, null otherwise.
*/ */
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 * 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); 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 { struct nsFilePickerFilter {
nsFilePickerFilter() : mFilterMask(0) {} nsFilePickerFilter() : mFilterMask(0) {}

View file

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

View file

@ -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__ */

View file

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

View file

@ -11,6 +11,7 @@
#include "DOMSVGPathSegList.h" #include "DOMSVGPathSegList.h"
#include "SVGPathSegListSMILType.h" #include "SVGPathSegListSMILType.h"
#include "mozilla/SMILValue.h" #include "mozilla/SMILValue.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/dom/SVGElement.h" #include "mozilla/dom/SVGElement.h"
using namespace mozilla::dom; using namespace mozilla::dom;

View file

@ -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

View file

@ -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>