Bug 1685926 - Group disconnected radio buttons together. r=saschanaz,smaug
Differential Revision: https://phabricator.services.mozilla.com/D162349
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user