зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1490406, radio groups should work in shadow DOM, r=ehsan
--HG-- extra : rebase_source : 952ffd47acea3d99d2209e7f05039767b73faa0d
This commit is contained in:
Родитель
cee95ec8de
Коммит
697d41350d
|
@ -6,10 +6,13 @@
|
|||
|
||||
#include "DocumentOrShadowRoot.h"
|
||||
#include "mozilla/EventStateManager.h"
|
||||
#include "mozilla/dom/HTMLInputElement.h"
|
||||
#include "mozilla/dom/ShadowRoot.h"
|
||||
#include "mozilla/dom/StyleSheetList.h"
|
||||
#include "nsDocument.h"
|
||||
#include "nsFocusManager.h"
|
||||
#include "nsIRadioVisitor.h"
|
||||
#include "nsIFormControl.h"
|
||||
#include "nsLayoutUtils.h"
|
||||
#include "nsSVGUtils.h"
|
||||
#include "nsWindowSizes.h"
|
||||
|
@ -398,5 +401,205 @@ DocumentOrShadowRoot::ReportEmptyGetElementByIdArg()
|
|||
nsContentUtils::ReportEmptyGetElementByIdArg(AsNode().OwnerDoc());
|
||||
}
|
||||
|
||||
/**
|
||||
* A struct that holds all the information about a radio group.
|
||||
*/
|
||||
struct nsRadioGroupStruct
|
||||
{
|
||||
nsRadioGroupStruct()
|
||||
: mRequiredRadioCount(0)
|
||||
, mGroupSuffersFromValueMissing(false)
|
||||
{}
|
||||
|
||||
/**
|
||||
* A strong pointer to the currently selected radio button.
|
||||
*/
|
||||
RefPtr<HTMLInputElement> mSelectedRadioButton;
|
||||
nsCOMArray<nsIFormControl> mRadioButtons;
|
||||
uint32_t mRequiredRadioCount;
|
||||
bool mGroupSuffersFromValueMissing;
|
||||
};
|
||||
|
||||
nsresult
|
||||
DocumentOrShadowRoot::WalkRadioGroup(const nsAString& aName,
|
||||
nsIRadioVisitor* aVisitor,
|
||||
bool aFlushContent)
|
||||
{
|
||||
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
|
||||
|
||||
for (int i = 0; i < radioGroup->mRadioButtons.Count(); i++) {
|
||||
if (!aVisitor->Visit(radioGroup->mRadioButtons[i])) {
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
DocumentOrShadowRoot::SetCurrentRadioButton(const nsAString& aName,
|
||||
HTMLInputElement* aRadio)
|
||||
{
|
||||
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
|
||||
radioGroup->mSelectedRadioButton = aRadio;
|
||||
}
|
||||
|
||||
HTMLInputElement*
|
||||
DocumentOrShadowRoot::GetCurrentRadioButton(const nsAString& aName)
|
||||
{
|
||||
return GetOrCreateRadioGroup(aName)->mSelectedRadioButton;
|
||||
}
|
||||
|
||||
nsresult
|
||||
DocumentOrShadowRoot::GetNextRadioButton(const nsAString& aName,
|
||||
const bool aPrevious,
|
||||
HTMLInputElement* aFocusedRadio,
|
||||
HTMLInputElement** aRadioOut)
|
||||
{
|
||||
// XXX Can we combine the HTML radio button method impls of
|
||||
// nsDocument and nsHTMLFormControl?
|
||||
// XXX Why is HTML radio button stuff in nsDocument, as
|
||||
// opposed to nsHTMLDocument?
|
||||
*aRadioOut = nullptr;
|
||||
|
||||
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
|
||||
|
||||
// Return the radio button relative to the focused radio button.
|
||||
// If no radio is focused, get the radio relative to the selected one.
|
||||
RefPtr<HTMLInputElement> currentRadio;
|
||||
if (aFocusedRadio) {
|
||||
currentRadio = aFocusedRadio;
|
||||
} else {
|
||||
currentRadio = radioGroup->mSelectedRadioButton;
|
||||
if (!currentRadio) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
int32_t index = radioGroup->mRadioButtons.IndexOf(currentRadio);
|
||||
if (index < 0) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
int32_t numRadios = radioGroup->mRadioButtons.Count();
|
||||
RefPtr<HTMLInputElement> radio;
|
||||
do {
|
||||
if (aPrevious) {
|
||||
if (--index < 0) {
|
||||
index = numRadios -1;
|
||||
}
|
||||
} else if (++index >= numRadios) {
|
||||
index = 0;
|
||||
}
|
||||
NS_ASSERTION(static_cast<nsGenericHTMLFormElement*>(radioGroup->mRadioButtons[index])->IsHTMLElement(nsGkAtoms::input),
|
||||
"mRadioButtons holding a non-radio button");
|
||||
radio = static_cast<HTMLInputElement*>(radioGroup->mRadioButtons[index]);
|
||||
} while (radio->Disabled() && radio != currentRadio);
|
||||
|
||||
radio.forget(aRadioOut);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
DocumentOrShadowRoot::AddToRadioGroup(const nsAString& aName,
|
||||
HTMLInputElement* aRadio)
|
||||
{
|
||||
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
|
||||
radioGroup->mRadioButtons.AppendObject(aRadio);
|
||||
|
||||
if (aRadio->IsRequired()) {
|
||||
radioGroup->mRequiredRadioCount++;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DocumentOrShadowRoot::RemoveFromRadioGroup(const nsAString& aName,
|
||||
HTMLInputElement* aRadio)
|
||||
{
|
||||
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
|
||||
radioGroup->mRadioButtons.RemoveObject(aRadio);
|
||||
|
||||
if (aRadio->IsRequired()) {
|
||||
NS_ASSERTION(radioGroup->mRequiredRadioCount != 0,
|
||||
"mRequiredRadioCount about to wrap below 0!");
|
||||
radioGroup->mRequiredRadioCount--;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t
|
||||
DocumentOrShadowRoot::GetRequiredRadioCount(const nsAString& aName) const
|
||||
{
|
||||
nsRadioGroupStruct* radioGroup = GetRadioGroup(aName);
|
||||
return radioGroup ? radioGroup->mRequiredRadioCount : 0;
|
||||
}
|
||||
|
||||
void
|
||||
DocumentOrShadowRoot::RadioRequiredWillChange(const nsAString& aName,
|
||||
bool aRequiredAdded)
|
||||
{
|
||||
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
|
||||
|
||||
if (aRequiredAdded) {
|
||||
radioGroup->mRequiredRadioCount++;
|
||||
} else {
|
||||
NS_ASSERTION(radioGroup->mRequiredRadioCount != 0,
|
||||
"mRequiredRadioCount about to wrap below 0!");
|
||||
radioGroup->mRequiredRadioCount--;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
DocumentOrShadowRoot::GetValueMissingState(const nsAString& aName) const
|
||||
{
|
||||
nsRadioGroupStruct* radioGroup = GetRadioGroup(aName);
|
||||
return radioGroup && radioGroup->mGroupSuffersFromValueMissing;
|
||||
}
|
||||
|
||||
void
|
||||
DocumentOrShadowRoot::SetValueMissingState(const nsAString& aName, bool aValue)
|
||||
{
|
||||
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
|
||||
radioGroup->mGroupSuffersFromValueMissing = aValue;
|
||||
}
|
||||
|
||||
nsRadioGroupStruct*
|
||||
DocumentOrShadowRoot::GetRadioGroup(const nsAString& aName) const
|
||||
{
|
||||
nsRadioGroupStruct* radioGroup = nullptr;
|
||||
mRadioGroups.Get(aName, &radioGroup);
|
||||
return radioGroup;
|
||||
}
|
||||
|
||||
nsRadioGroupStruct*
|
||||
DocumentOrShadowRoot::GetOrCreateRadioGroup(const nsAString& aName)
|
||||
{
|
||||
return mRadioGroups.LookupForAdd(aName).OrInsert(
|
||||
[] () { return new nsRadioGroupStruct(); });
|
||||
}
|
||||
|
||||
void
|
||||
DocumentOrShadowRoot::Traverse(DocumentOrShadowRoot* tmp,
|
||||
nsCycleCollectionTraversalCallback &cb)
|
||||
{
|
||||
for (auto iter = tmp->mRadioGroups.Iter(); !iter.Done(); iter.Next()) {
|
||||
nsRadioGroupStruct* radioGroup = iter.UserData();
|
||||
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
|
||||
cb, "mRadioGroups entry->mSelectedRadioButton");
|
||||
cb.NoteXPCOMChild(ToSupports(radioGroup->mSelectedRadioButton));
|
||||
|
||||
uint32_t i, count = radioGroup->mRadioButtons.Count();
|
||||
for (i = 0; i < count; ++i) {
|
||||
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
|
||||
cb, "mRadioGroups entry->mRadioButtons[i]");
|
||||
cb.NoteXPCOMChild(radioGroup->mRadioButtons[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DocumentOrShadowRoot::Unlink(DocumentOrShadowRoot* tmp)
|
||||
{
|
||||
tmp->mRadioGroups.Clear();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,13 +8,16 @@
|
|||
#define mozilla_dom_DocumentOrShadowRoot_h__
|
||||
|
||||
#include "mozilla/dom/NameSpaceConstants.h"
|
||||
#include "nsClassHashtable.h"
|
||||
#include "nsContentListDeclarations.h"
|
||||
#include "nsTArray.h"
|
||||
#include "nsIdentifierMapEntry.h"
|
||||
|
||||
class nsContentList;
|
||||
class nsCycleCollectionTraversalCallback;
|
||||
class nsIDocument;
|
||||
class nsINode;
|
||||
class nsIRadioVisitor;
|
||||
class nsWindowSizes;
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -23,6 +26,9 @@ class StyleSheet;
|
|||
namespace dom {
|
||||
|
||||
class Element;
|
||||
class DocumentOrShadowRoot;
|
||||
class HTMLInputElement;
|
||||
struct nsRadioGroupStruct;
|
||||
class StyleSheetList;
|
||||
class ShadowRoot;
|
||||
|
||||
|
@ -45,6 +51,11 @@ public:
|
|||
explicit DocumentOrShadowRoot(nsIDocument&);
|
||||
explicit DocumentOrShadowRoot(mozilla::dom::ShadowRoot&);
|
||||
|
||||
// Unusual argument naming is because of cycle collection macros.
|
||||
static void Traverse(DocumentOrShadowRoot* tmp,
|
||||
nsCycleCollectionTraversalCallback &cb);
|
||||
static void Unlink(DocumentOrShadowRoot* tmp);
|
||||
|
||||
nsINode& AsNode()
|
||||
{
|
||||
return mAsNode;
|
||||
|
@ -186,6 +197,31 @@ public:
|
|||
|
||||
void ReportEmptyGetElementByIdArg();
|
||||
|
||||
// nsIRadioGroupContainer
|
||||
NS_IMETHOD WalkRadioGroup(const nsAString& aName,
|
||||
nsIRadioVisitor* aVisitor,
|
||||
bool aFlushContent);
|
||||
void SetCurrentRadioButton(const nsAString& aName,
|
||||
HTMLInputElement* aRadio);
|
||||
HTMLInputElement* GetCurrentRadioButton(const nsAString& aName);
|
||||
nsresult GetNextRadioButton(const nsAString& aName,
|
||||
const bool aPrevious,
|
||||
HTMLInputElement* aFocusedRadio,
|
||||
HTMLInputElement** aRadioOut);
|
||||
void AddToRadioGroup(const nsAString& aName,
|
||||
HTMLInputElement* aRadio);
|
||||
void RemoveFromRadioGroup(const nsAString& aName,
|
||||
HTMLInputElement* aRadio);
|
||||
uint32_t GetRequiredRadioCount(const nsAString& aName) const;
|
||||
void RadioRequiredWillChange(const nsAString& aName,
|
||||
bool aRequiredAdded);
|
||||
bool GetValueMissingState(const nsAString& aName) const;
|
||||
void SetValueMissingState(const nsAString& aName, bool aValue);
|
||||
|
||||
// for radio group
|
||||
nsRadioGroupStruct* GetRadioGroup(const nsAString& aName) const;
|
||||
nsRadioGroupStruct* GetOrCreateRadioGroup(const nsAString& aName);
|
||||
|
||||
protected:
|
||||
// Returns the reference to the sheet, if found in mStyleSheets.
|
||||
already_AddRefed<StyleSheet> RemoveSheet(StyleSheet& aSheet);
|
||||
|
@ -218,6 +254,8 @@ protected:
|
|||
*/
|
||||
nsTHashtable<nsIdentifierMapEntry> mIdentifierMap;
|
||||
|
||||
nsClassHashtable<nsStringHashKey, nsRadioGroupStruct> mRadioGroups;
|
||||
|
||||
nsINode& mAsNode;
|
||||
const Kind mKind;
|
||||
};
|
||||
|
|
|
@ -41,6 +41,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ShadowRoot, DocumentFragment)
|
|||
iter.Next()) {
|
||||
iter.Get()->Traverse(&cb);
|
||||
}
|
||||
DocumentOrShadowRoot::Traverse(tmp, cb);
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ShadowRoot)
|
||||
|
@ -49,11 +50,13 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ShadowRoot)
|
|||
}
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMStyleSheets)
|
||||
tmp->mIdentifierMap.Clear();
|
||||
DocumentOrShadowRoot::Unlink(tmp);
|
||||
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(nsIMutationObserver)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIRadioGroupContainer)
|
||||
NS_INTERFACE_MAP_END_INHERITING(DocumentFragment)
|
||||
|
||||
NS_IMPL_ADDREF_INHERITED(ShadowRoot, DocumentFragment)
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "nsCOMPtr.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsIdentifierMapEntry.h"
|
||||
#include "nsIRadioGroupContainer.h"
|
||||
#include "nsStubMutationObserver.h"
|
||||
#include "nsTHashtable.h"
|
||||
|
||||
|
@ -35,10 +36,12 @@ class Rule;
|
|||
namespace dom {
|
||||
|
||||
class Element;
|
||||
class HTMLInputElement;
|
||||
|
||||
class ShadowRoot final : public DocumentFragment,
|
||||
public DocumentOrShadowRoot,
|
||||
public nsStubMutationObserver
|
||||
public nsStubMutationObserver,
|
||||
public nsIRadioGroupContainer
|
||||
{
|
||||
public:
|
||||
NS_IMPL_FROMNODE_HELPER(ShadowRoot, IsShadowRoot());
|
||||
|
@ -207,6 +210,61 @@ public:
|
|||
|
||||
void GetEventTargetParent(EventChainPreVisitor& aVisitor) override;
|
||||
|
||||
// nsIRadioGroupContainer
|
||||
NS_IMETHOD WalkRadioGroup(const nsAString& aName,
|
||||
nsIRadioVisitor* aVisitor,
|
||||
bool aFlushContent) override
|
||||
{
|
||||
return DocumentOrShadowRoot::WalkRadioGroup(aName, aVisitor, aFlushContent);
|
||||
}
|
||||
virtual void
|
||||
SetCurrentRadioButton(const nsAString& aName,
|
||||
HTMLInputElement* aRadio) override
|
||||
{
|
||||
DocumentOrShadowRoot::SetCurrentRadioButton(aName, aRadio);
|
||||
}
|
||||
virtual 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);
|
||||
}
|
||||
virtual void AddToRadioGroup(const nsAString& aName,
|
||||
HTMLInputElement* aRadio) override
|
||||
{
|
||||
DocumentOrShadowRoot::AddToRadioGroup(aName, aRadio);
|
||||
}
|
||||
virtual void RemoveFromRadioGroup(const nsAString& aName,
|
||||
HTMLInputElement* aRadio) override
|
||||
{
|
||||
DocumentOrShadowRoot::RemoveFromRadioGroup(aName, aRadio);
|
||||
}
|
||||
virtual uint32_t GetRequiredRadioCount(const nsAString& aName) const override
|
||||
{
|
||||
return DocumentOrShadowRoot::GetRequiredRadioCount(aName);
|
||||
}
|
||||
virtual void RadioRequiredWillChange(const nsAString& aName,
|
||||
bool aRequiredAdded) override
|
||||
{
|
||||
DocumentOrShadowRoot::RadioRequiredWillChange(aName, aRequiredAdded);
|
||||
}
|
||||
virtual bool GetValueMissingState(const nsAString& aName) const override
|
||||
{
|
||||
return DocumentOrShadowRoot::GetValueMissingState(aName);
|
||||
}
|
||||
virtual 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();
|
||||
|
|
|
@ -119,10 +119,6 @@
|
|||
#include "nsFocusManager.h"
|
||||
#include "nsICookieService.h"
|
||||
|
||||
// for radio group stuff
|
||||
#include "nsIRadioVisitor.h"
|
||||
#include "nsIFormControl.h"
|
||||
|
||||
#include "nsBidiUtils.h"
|
||||
|
||||
#include "nsContentCreatorFunctions.h"
|
||||
|
@ -724,26 +720,6 @@ public:
|
|||
nsIDocument *mSubDocument;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A struct that holds all the information about a radio group.
|
||||
*/
|
||||
struct nsRadioGroupStruct
|
||||
{
|
||||
nsRadioGroupStruct()
|
||||
: mRequiredRadioCount(0)
|
||||
, mGroupSuffersFromValueMissing(false)
|
||||
{}
|
||||
|
||||
/**
|
||||
* A strong pointer to the currently selected radio button.
|
||||
*/
|
||||
RefPtr<HTMLInputElement> mSelectedRadioButton;
|
||||
nsCOMArray<nsIFormControl> mRadioButtons;
|
||||
uint32_t mRequiredRadioCount;
|
||||
bool mGroupSuffersFromValueMissing;
|
||||
};
|
||||
|
||||
// nsOnloadBlocker implementation
|
||||
NS_IMPL_ISUPPORTS(nsOnloadBlocker, nsIRequest)
|
||||
|
||||
|
@ -1924,19 +1900,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsDocument)
|
|||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheetSetList)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader)
|
||||
|
||||
for (auto iter = tmp->mRadioGroups.Iter(); !iter.Done(); iter.Next()) {
|
||||
nsRadioGroupStruct* radioGroup = iter.UserData();
|
||||
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
|
||||
cb, "mRadioGroups entry->mSelectedRadioButton");
|
||||
cb.NoteXPCOMChild(ToSupports(radioGroup->mSelectedRadioButton));
|
||||
|
||||
uint32_t i, count = radioGroup->mRadioButtons.Count();
|
||||
for (i = 0; i < count; ++i) {
|
||||
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
|
||||
cb, "mRadioGroups entry->mRadioButtons[i]");
|
||||
cb.NoteXPCOMChild(radioGroup->mRadioButtons[i]);
|
||||
}
|
||||
}
|
||||
DocumentOrShadowRoot::Traverse(tmp, cb);
|
||||
|
||||
// The boxobject for an element will only exist as long as it's in the
|
||||
// document, so we'll traverse the table here instead of from the element.
|
||||
|
@ -2092,7 +2056,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDocument)
|
|||
"How did we get here without our presshell going away "
|
||||
"first?");
|
||||
|
||||
tmp->mRadioGroups.Clear();
|
||||
DocumentOrShadowRoot::Unlink(tmp);
|
||||
|
||||
// nsDocument has a pretty complex destructor, so we're going to
|
||||
// assume that *most* cycles you actually want to break somewhere
|
||||
|
@ -7713,163 +7677,6 @@ nsIDocument::IsScriptEnabled()
|
|||
return xpc::Scriptability::Get(globalObject->GetGlobalJSObject()).Allowed();
|
||||
}
|
||||
|
||||
nsRadioGroupStruct*
|
||||
nsDocument::GetRadioGroup(const nsAString& aName) const
|
||||
{
|
||||
nsRadioGroupStruct* radioGroup = nullptr;
|
||||
mRadioGroups.Get(aName, &radioGroup);
|
||||
return radioGroup;
|
||||
}
|
||||
|
||||
nsRadioGroupStruct*
|
||||
nsDocument::GetOrCreateRadioGroup(const nsAString& aName)
|
||||
{
|
||||
return mRadioGroups.LookupForAdd(aName).OrInsert(
|
||||
[] () { return new nsRadioGroupStruct(); });
|
||||
}
|
||||
|
||||
void
|
||||
nsDocument::SetCurrentRadioButton(const nsAString& aName,
|
||||
HTMLInputElement* aRadio)
|
||||
{
|
||||
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
|
||||
radioGroup->mSelectedRadioButton = aRadio;
|
||||
}
|
||||
|
||||
HTMLInputElement*
|
||||
nsDocument::GetCurrentRadioButton(const nsAString& aName)
|
||||
{
|
||||
return GetOrCreateRadioGroup(aName)->mSelectedRadioButton;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDocument::GetNextRadioButton(const nsAString& aName,
|
||||
const bool aPrevious,
|
||||
HTMLInputElement* aFocusedRadio,
|
||||
HTMLInputElement** aRadioOut)
|
||||
{
|
||||
// XXX Can we combine the HTML radio button method impls of
|
||||
// nsDocument and nsHTMLFormControl?
|
||||
// XXX Why is HTML radio button stuff in nsDocument, as
|
||||
// opposed to nsHTMLDocument?
|
||||
*aRadioOut = nullptr;
|
||||
|
||||
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
|
||||
|
||||
// Return the radio button relative to the focused radio button.
|
||||
// If no radio is focused, get the radio relative to the selected one.
|
||||
RefPtr<HTMLInputElement> currentRadio;
|
||||
if (aFocusedRadio) {
|
||||
currentRadio = aFocusedRadio;
|
||||
}
|
||||
else {
|
||||
currentRadio = radioGroup->mSelectedRadioButton;
|
||||
if (!currentRadio) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
int32_t index = radioGroup->mRadioButtons.IndexOf(currentRadio);
|
||||
if (index < 0) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
int32_t numRadios = radioGroup->mRadioButtons.Count();
|
||||
RefPtr<HTMLInputElement> radio;
|
||||
do {
|
||||
if (aPrevious) {
|
||||
if (--index < 0) {
|
||||
index = numRadios -1;
|
||||
}
|
||||
}
|
||||
else if (++index >= numRadios) {
|
||||
index = 0;
|
||||
}
|
||||
NS_ASSERTION(static_cast<nsGenericHTMLFormElement*>(radioGroup->mRadioButtons[index])->IsHTMLElement(nsGkAtoms::input),
|
||||
"mRadioButtons holding a non-radio button");
|
||||
radio = static_cast<HTMLInputElement*>(radioGroup->mRadioButtons[index]);
|
||||
} while (radio->Disabled() && radio != currentRadio);
|
||||
|
||||
radio.forget(aRadioOut);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
nsDocument::AddToRadioGroup(const nsAString& aName,
|
||||
HTMLInputElement* aRadio)
|
||||
{
|
||||
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
|
||||
radioGroup->mRadioButtons.AppendObject(aRadio);
|
||||
|
||||
if (aRadio->IsRequired()) {
|
||||
radioGroup->mRequiredRadioCount++;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsDocument::RemoveFromRadioGroup(const nsAString& aName,
|
||||
HTMLInputElement* aRadio)
|
||||
{
|
||||
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
|
||||
radioGroup->mRadioButtons.RemoveObject(aRadio);
|
||||
|
||||
if (aRadio->IsRequired()) {
|
||||
NS_ASSERTION(radioGroup->mRequiredRadioCount != 0,
|
||||
"mRequiredRadioCount about to wrap below 0!");
|
||||
radioGroup->mRequiredRadioCount--;
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDocument::WalkRadioGroup(const nsAString& aName,
|
||||
nsIRadioVisitor* aVisitor,
|
||||
bool aFlushContent)
|
||||
{
|
||||
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
|
||||
|
||||
for (int i = 0; i < radioGroup->mRadioButtons.Count(); i++) {
|
||||
if (!aVisitor->Visit(radioGroup->mRadioButtons[i])) {
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
nsDocument::GetRequiredRadioCount(const nsAString& aName) const
|
||||
{
|
||||
nsRadioGroupStruct* radioGroup = GetRadioGroup(aName);
|
||||
return radioGroup ? radioGroup->mRequiredRadioCount : 0;
|
||||
}
|
||||
|
||||
void
|
||||
nsDocument::RadioRequiredWillChange(const nsAString& aName, bool aRequiredAdded)
|
||||
{
|
||||
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
|
||||
|
||||
if (aRequiredAdded) {
|
||||
radioGroup->mRequiredRadioCount++;
|
||||
} else {
|
||||
NS_ASSERTION(radioGroup->mRequiredRadioCount != 0,
|
||||
"mRequiredRadioCount about to wrap below 0!");
|
||||
radioGroup->mRequiredRadioCount--;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
nsDocument::GetValueMissingState(const nsAString& aName) const
|
||||
{
|
||||
nsRadioGroupStruct* radioGroup = GetRadioGroup(aName);
|
||||
return radioGroup && radioGroup->mGroupSuffersFromValueMissing;
|
||||
}
|
||||
|
||||
void
|
||||
nsDocument::SetValueMissingState(const nsAString& aName, bool aValue)
|
||||
{
|
||||
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
|
||||
radioGroup->mGroupSuffersFromValueMissing = aValue;
|
||||
}
|
||||
|
||||
void
|
||||
nsDocument::RetrieveRelevantHeaders(nsIChannel *aChannel)
|
||||
{
|
||||
|
|
|
@ -66,9 +66,7 @@
|
|||
|
||||
class nsDOMStyleSheetSetList;
|
||||
class nsDocument;
|
||||
class nsIRadioVisitor;
|
||||
class nsIFormControl;
|
||||
struct nsRadioGroupStruct;
|
||||
class nsOnloadBlocker;
|
||||
class nsDOMNavigationTiming;
|
||||
class nsWindowSizes;
|
||||
|
@ -147,30 +145,57 @@ public:
|
|||
// nsIRadioGroupContainer
|
||||
NS_IMETHOD WalkRadioGroup(const nsAString& aName,
|
||||
nsIRadioVisitor* aVisitor,
|
||||
bool aFlushContent) override;
|
||||
bool aFlushContent) override
|
||||
{
|
||||
return DocumentOrShadowRoot::WalkRadioGroup(aName, aVisitor, aFlushContent);
|
||||
}
|
||||
virtual void
|
||||
SetCurrentRadioButton(const nsAString& aName,
|
||||
mozilla::dom::HTMLInputElement* aRadio) override;
|
||||
mozilla::dom::HTMLInputElement* aRadio) override
|
||||
{
|
||||
DocumentOrShadowRoot::SetCurrentRadioButton(aName, aRadio);
|
||||
}
|
||||
virtual mozilla::dom::HTMLInputElement*
|
||||
GetCurrentRadioButton(const nsAString& aName) override;
|
||||
GetCurrentRadioButton(const nsAString& aName) override
|
||||
{
|
||||
return DocumentOrShadowRoot::GetCurrentRadioButton(aName);
|
||||
}
|
||||
NS_IMETHOD
|
||||
GetNextRadioButton(const nsAString& aName,
|
||||
const bool aPrevious,
|
||||
mozilla::dom::HTMLInputElement* aFocusedRadio,
|
||||
mozilla::dom::HTMLInputElement** aRadioOut) override;
|
||||
mozilla::dom::HTMLInputElement** aRadioOut) override
|
||||
{
|
||||
return DocumentOrShadowRoot::GetNextRadioButton(aName, aPrevious,
|
||||
aFocusedRadio, aRadioOut);
|
||||
}
|
||||
virtual void AddToRadioGroup(const nsAString& aName,
|
||||
mozilla::dom::HTMLInputElement* aRadio) override;
|
||||
mozilla::dom::HTMLInputElement* aRadio) override
|
||||
{
|
||||
DocumentOrShadowRoot::AddToRadioGroup(aName, aRadio);
|
||||
}
|
||||
virtual void RemoveFromRadioGroup(const nsAString& aName,
|
||||
mozilla::dom::HTMLInputElement* aRadio) override;
|
||||
virtual uint32_t GetRequiredRadioCount(const nsAString& aName) const override;
|
||||
mozilla::dom::HTMLInputElement* aRadio) override
|
||||
{
|
||||
DocumentOrShadowRoot::RemoveFromRadioGroup(aName, aRadio);
|
||||
}
|
||||
virtual uint32_t GetRequiredRadioCount(const nsAString& aName) const override
|
||||
{
|
||||
return DocumentOrShadowRoot::GetRequiredRadioCount(aName);
|
||||
}
|
||||
virtual void RadioRequiredWillChange(const nsAString& aName,
|
||||
bool aRequiredAdded) override;
|
||||
virtual bool GetValueMissingState(const nsAString& aName) const override;
|
||||
virtual void SetValueMissingState(const nsAString& aName, bool aValue) override;
|
||||
|
||||
// for radio group
|
||||
nsRadioGroupStruct* GetRadioGroup(const nsAString& aName) const;
|
||||
nsRadioGroupStruct* GetOrCreateRadioGroup(const nsAString& aName);
|
||||
bool aRequiredAdded) override
|
||||
{
|
||||
DocumentOrShadowRoot::RadioRequiredWillChange(aName, aRequiredAdded);
|
||||
}
|
||||
virtual bool GetValueMissingState(const nsAString& aName) const override
|
||||
{
|
||||
return DocumentOrShadowRoot::GetValueMissingState(aName);
|
||||
}
|
||||
virtual void SetValueMissingState(const nsAString& aName, bool aValue) override
|
||||
{
|
||||
return DocumentOrShadowRoot::SetValueMissingState(aName, aValue);
|
||||
}
|
||||
|
||||
// Check whether shadow DOM is enabled for aGlobal.
|
||||
static bool IsShadowDOMEnabled(JSContext* aCx, JSObject* aGlobal);
|
||||
|
@ -262,8 +287,6 @@ public:
|
|||
// include https://github.com/rust-lang-nursery/rust-bindgen/pull/1271.
|
||||
js::ExpandoAndGeneration mExpandoAndGeneration;
|
||||
|
||||
nsClassHashtable<nsStringHashKey, nsRadioGroupStruct> mRadioGroups;
|
||||
|
||||
friend class nsCallRequestFullscreen;
|
||||
|
||||
// The application cache that this document is associated with, if
|
||||
|
|
|
@ -3052,8 +3052,14 @@ HTMLInputElement::GetRadioGroupContainer() const
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
//XXXsmaug It isn't clear how this should work in Shadow DOM.
|
||||
return static_cast<nsDocument*>(GetUncomposedDoc());
|
||||
DocumentOrShadowRoot* docOrShadow = GetUncomposedDocOrConnectedShadowRoot();
|
||||
if (!docOrShadow) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIRadioGroupContainer> container =
|
||||
do_QueryInterface(&(docOrShadow->AsNode()));
|
||||
return container;
|
||||
}
|
||||
|
||||
HTMLInputElement*
|
||||
|
@ -4632,7 +4638,8 @@ HTMLInputElement::BindToTree(nsIDocument* aDocument, nsIContent* 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 (aDocument && !mForm && mType == NS_FORM_INPUT_RADIO) {
|
||||
if (!mForm && mType == NS_FORM_INPUT_RADIO &&
|
||||
GetUncomposedDocOrConnectedShadowRoot()) {
|
||||
AddedToRadioGroup();
|
||||
}
|
||||
|
||||
|
@ -6567,7 +6574,7 @@ 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 && (!IsInUncomposedDoc() || IsInAnonymousSubtree())) {
|
||||
if (!mForm && (!GetUncomposedDocOrConnectedShadowRoot() || IsInAnonymousSubtree())) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -397866,6 +397866,12 @@
|
|||
{}
|
||||
]
|
||||
],
|
||||
"shadow-dom/input-type-radio.html": [
|
||||
[
|
||||
"/shadow-dom/input-type-radio.html",
|
||||
{}
|
||||
]
|
||||
],
|
||||
"shadow-dom/leaktests/get-elements.html": [
|
||||
[
|
||||
"/shadow-dom/leaktests/get-elements.html",
|
||||
|
@ -646387,6 +646393,10 @@
|
|||
"b571534eb0d6f3f57cfbec3e706648b19848b6d6",
|
||||
"testharness"
|
||||
],
|
||||
"shadow-dom/input-type-radio.html": [
|
||||
"bd5d8e43b0fd9d0c9f1e078ed97a1bbd18b7b0be",
|
||||
"testharness"
|
||||
],
|
||||
"shadow-dom/layout-slot-no-longer-assigned.html": [
|
||||
"dfcac99da023ec2bbd94835f71efaef952a62341",
|
||||
"reftest"
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title></title>
|
||||
<script src=/resources/testharness.js></script>
|
||||
<script src=/resources/testharnessreport.js></script>
|
||||
<input type="radio" name="group" id="lightRadio1">
|
||||
<input type="radio" name="group" id="lightRadio2">
|
||||
<div id="host"></div>
|
||||
<script>
|
||||
|
||||
test(() => {
|
||||
var lightRadio1 = document.getElementById("lightRadio1");
|
||||
var lightRadio2 = document.getElementById("lightRadio2");
|
||||
|
||||
var host = document.getElementById("host");
|
||||
var sr = host.attachShadow({mode: "closed"});
|
||||
var shadowRadio1 = document.createElement("input");
|
||||
shadowRadio1.name = "group";
|
||||
shadowRadio1.id = "shadowRadio1";
|
||||
shadowRadio1.type = "radio";
|
||||
sr.appendChild(shadowRadio1);
|
||||
var shadowRadio2 = document.createElement("input");
|
||||
shadowRadio2.name = "group";
|
||||
shadowRadio2.id = "shadowRadio2";
|
||||
shadowRadio2.type = "radio";
|
||||
sr.appendChild(shadowRadio2);
|
||||
|
||||
assert_false(lightRadio1.checked);
|
||||
assert_false(lightRadio2.checked);
|
||||
assert_false(shadowRadio1.checked);
|
||||
assert_false(shadowRadio2.checked);
|
||||
|
||||
lightRadio1.click();
|
||||
assert_true(lightRadio1.checked);
|
||||
assert_false(lightRadio2.checked);
|
||||
assert_false(shadowRadio1.checked);
|
||||
assert_false(shadowRadio2.checked);
|
||||
|
||||
lightRadio2.click();
|
||||
assert_false(lightRadio1.checked);
|
||||
assert_true(lightRadio2.checked);
|
||||
assert_false(shadowRadio1.checked);
|
||||
assert_false(shadowRadio2.checked);
|
||||
|
||||
shadowRadio1.click();
|
||||
assert_false(lightRadio1.checked);
|
||||
assert_true(lightRadio2.checked);
|
||||
assert_true(shadowRadio1.checked);
|
||||
assert_false(shadowRadio2.checked);
|
||||
|
||||
shadowRadio2.click();
|
||||
assert_false(lightRadio1.checked);
|
||||
assert_true(lightRadio2.checked);
|
||||
assert_false(shadowRadio1.checked);
|
||||
assert_true(shadowRadio2.checked);
|
||||
|
||||
// Ensure radio groups work even when modifying shadow DOM.
|
||||
shadowRadio2.remove();
|
||||
sr.appendChild(shadowRadio2);
|
||||
shadowRadio2.click();
|
||||
assert_false(lightRadio1.checked);
|
||||
assert_true(lightRadio2.checked);
|
||||
assert_false(shadowRadio1.checked);
|
||||
assert_true(shadowRadio2.checked);
|
||||
|
||||
shadowRadio1.click();
|
||||
assert_false(lightRadio1.checked);
|
||||
assert_true(lightRadio2.checked);
|
||||
assert_true(shadowRadio1.checked);
|
||||
assert_false(shadowRadio2.checked);
|
||||
}, "input type=radio elements should form a group inside shadow DOM.");
|
||||
|
||||
</script>
|
Загрузка…
Ссылка в новой задаче