HTMLFieldSetElement.cpp (10933B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/dom/HTMLFieldSetElement.h" 8 9 #include "mozilla/BasicEvents.h" 10 #include "mozilla/EventDispatcher.h" 11 #include "mozilla/Maybe.h" 12 #include "mozilla/StaticPrefs_dom.h" 13 #include "mozilla/dom/CustomElementRegistry.h" 14 #include "mozilla/dom/HTMLFieldSetElementBinding.h" 15 #include "nsContentList.h" 16 #include "nsQueryObject.h" 17 18 NS_IMPL_NS_NEW_HTML_ELEMENT(FieldSet) 19 20 namespace mozilla::dom { 21 22 HTMLFieldSetElement::HTMLFieldSetElement( 23 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) 24 : nsGenericHTMLFormControlElement(std::move(aNodeInfo), 25 FormControlType::Fieldset), 26 mElements(nullptr), 27 mFirstLegend(nullptr), 28 mInvalidElementsCount(0) { 29 // <fieldset> is always barred from constraint validation. 30 SetBarredFromConstraintValidation(true); 31 32 // We start out enabled and valid. 33 AddStatesSilently(ElementState::ENABLED | ElementState::VALID); 34 } 35 36 HTMLFieldSetElement::~HTMLFieldSetElement() { 37 uint32_t length = mDependentElements.Length(); 38 for (uint32_t i = 0; i < length; ++i) { 39 mDependentElements[i]->ForgetFieldSet(this); 40 } 41 } 42 43 NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLFieldSetElement, 44 nsGenericHTMLFormControlElement, mValidity, 45 mElements) 46 47 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLFieldSetElement, 48 nsGenericHTMLFormControlElement, 49 nsIConstraintValidation) 50 51 NS_IMPL_ELEMENT_CLONE(HTMLFieldSetElement) 52 53 bool HTMLFieldSetElement::IsDisabledForEvents(WidgetEvent* aEvent) { 54 if (StaticPrefs::dom_forms_fieldset_disable_only_descendants_enabled()) { 55 return false; 56 } 57 return IsElementDisabledForEvents(aEvent, nullptr); 58 } 59 60 // nsIContent 61 void HTMLFieldSetElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) { 62 // Do not process any DOM events if the element is disabled. 63 aVisitor.mCanHandle = false; 64 if (IsDisabledForEvents(aVisitor.mEvent)) { 65 return; 66 } 67 68 nsGenericHTMLFormControlElement::GetEventTargetParent(aVisitor); 69 } 70 71 void HTMLFieldSetElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, 72 const nsAttrValue* aValue, 73 const nsAttrValue* aOldValue, 74 nsIPrincipal* aSubjectPrincipal, 75 bool aNotify) { 76 if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::disabled) { 77 // This *has* to be called *before* calling FieldSetDisabledChanged on our 78 // controls, as they may depend on our disabled state. 79 UpdateDisabledState(aNotify); 80 } 81 82 return nsGenericHTMLFormControlElement::AfterSetAttr( 83 aNameSpaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify); 84 } 85 86 void HTMLFieldSetElement::GetType(nsAString& aType) const { 87 aType.AssignLiteral("fieldset"); 88 } 89 90 /* static */ 91 bool HTMLFieldSetElement::MatchListedElements(Element* aElement, 92 int32_t aNamespaceID, 93 nsAtom* aAtom, void* aData) { 94 return nsIFormControl::FromNodeOrNull(aElement) != nullptr; 95 } 96 97 nsIHTMLCollection* HTMLFieldSetElement::Elements() { 98 if (!mElements) { 99 mElements = 100 new nsContentList(this, MatchListedElements, nullptr, nullptr, true); 101 } 102 103 return mElements; 104 } 105 106 // nsIFormControl 107 108 nsresult HTMLFieldSetElement::Reset() { return NS_OK; } 109 110 void HTMLFieldSetElement::InsertChildBefore( 111 nsIContent* aChild, nsIContent* aBeforeThis, bool aNotify, ErrorResult& aRv, 112 nsINode* aOldParent, MutationEffectOnScript aMutationEffectOnScript) { 113 bool firstLegendHasChanged = false; 114 115 if (aChild->IsHTMLElement(nsGkAtoms::legend)) { 116 if (!mFirstLegend) { 117 mFirstLegend = aChild; 118 // We do not want to notify the first time mFirstElement is set. 119 } else { 120 // If mFirstLegend is before aIndex, we do not change it. 121 // Otherwise, mFirstLegend is now aChild. 122 const Maybe<uint32_t> indexOfRef = 123 aBeforeThis ? ComputeIndexOf(aBeforeThis) : Some(GetChildCount()); 124 const Maybe<uint32_t> indexOfFirstLegend = ComputeIndexOf(mFirstLegend); 125 if ((indexOfRef.isSome() && indexOfFirstLegend.isSome() && 126 *indexOfRef <= *indexOfFirstLegend) || 127 // XXX Keep the odd traditional behavior for now. 128 indexOfRef.isNothing()) { 129 mFirstLegend = aChild; 130 firstLegendHasChanged = true; 131 } 132 } 133 } 134 135 nsGenericHTMLFormControlElement::InsertChildBefore( 136 aChild, aBeforeThis, aNotify, aRv, aOldParent, aMutationEffectOnScript); 137 if (aRv.Failed()) { 138 return; 139 } 140 141 if (firstLegendHasChanged) { 142 NotifyElementsForFirstLegendChange(aNotify); 143 } 144 } 145 146 void HTMLFieldSetElement::RemoveChildNode( 147 nsIContent* aKid, bool aNotify, const BatchRemovalState* aState, 148 nsINode* aNewParent, MutationEffectOnScript aMutationEffectOnScript) { 149 bool firstLegendHasChanged = false; 150 151 if (mFirstLegend && aKid == mFirstLegend) { 152 // If we are removing the first legend we have to found another one. 153 nsIContent* child = mFirstLegend->GetNextSibling(); 154 mFirstLegend = nullptr; 155 firstLegendHasChanged = true; 156 157 for (; child; child = child->GetNextSibling()) { 158 if (child->IsHTMLElement(nsGkAtoms::legend)) { 159 mFirstLegend = child; 160 break; 161 } 162 } 163 } 164 165 nsGenericHTMLFormControlElement::RemoveChildNode( 166 aKid, aNotify, aState, aNewParent, aMutationEffectOnScript); 167 168 if (firstLegendHasChanged) { 169 NotifyElementsForFirstLegendChange(aNotify); 170 } 171 } 172 173 void HTMLFieldSetElement::AddElement(nsGenericHTMLFormElement* aElement) { 174 mDependentElements.AppendElement(aElement); 175 176 // If the element that we are adding aElement is a fieldset, then all the 177 // invalid elements in aElement are also invalid elements of this. 178 HTMLFieldSetElement* fieldSet = FromNode(aElement); 179 if (fieldSet) { 180 for (int32_t i = 0; i < fieldSet->mInvalidElementsCount; i++) { 181 UpdateValidity(false); 182 } 183 return; 184 } 185 186 // If the element is a form-associated custom element, adding element might be 187 // caused by FACE upgrade which won't trigger mutation observer, so mark 188 // mElements dirty manually here. 189 CustomElementData* data = aElement->GetCustomElementData(); 190 if (data && data->IsFormAssociated() && mElements) { 191 mElements->SetDirty(); 192 } 193 194 // We need to update the validity of the fieldset. 195 nsCOMPtr<nsIConstraintValidation> cvElmt = do_QueryObject(aElement); 196 if (cvElmt && cvElmt->IsCandidateForConstraintValidation() && 197 !cvElmt->IsValid()) { 198 UpdateValidity(false); 199 } 200 201 #if DEBUG 202 int32_t debugInvalidElementsCount = 0; 203 for (uint32_t i = 0; i < mDependentElements.Length(); i++) { 204 HTMLFieldSetElement* fieldSet = FromNode(mDependentElements[i]); 205 if (fieldSet) { 206 debugInvalidElementsCount += fieldSet->mInvalidElementsCount; 207 continue; 208 } 209 nsCOMPtr<nsIConstraintValidation> cvElmt = 210 do_QueryObject(mDependentElements[i]); 211 if (cvElmt && cvElmt->IsCandidateForConstraintValidation() && 212 !(cvElmt->IsValid())) { 213 debugInvalidElementsCount += 1; 214 } 215 } 216 MOZ_ASSERT(debugInvalidElementsCount == mInvalidElementsCount); 217 #endif 218 } 219 220 void HTMLFieldSetElement::RemoveElement(nsGenericHTMLFormElement* aElement) { 221 mDependentElements.RemoveElement(aElement); 222 223 // If the element that we are removing aElement is a fieldset, then all the 224 // invalid elements in aElement are also removed from this. 225 HTMLFieldSetElement* fieldSet = FromNode(aElement); 226 if (fieldSet) { 227 for (int32_t i = 0; i < fieldSet->mInvalidElementsCount; i++) { 228 UpdateValidity(true); 229 } 230 return; 231 } 232 233 // We need to update the validity of the fieldset. 234 nsCOMPtr<nsIConstraintValidation> cvElmt = do_QueryObject(aElement); 235 if (cvElmt && cvElmt->IsCandidateForConstraintValidation() && 236 !cvElmt->IsValid()) { 237 UpdateValidity(true); 238 } 239 240 #if DEBUG 241 int32_t debugInvalidElementsCount = 0; 242 for (uint32_t i = 0; i < mDependentElements.Length(); i++) { 243 HTMLFieldSetElement* fieldSet = FromNode(mDependentElements[i]); 244 if (fieldSet) { 245 debugInvalidElementsCount += fieldSet->mInvalidElementsCount; 246 continue; 247 } 248 nsCOMPtr<nsIConstraintValidation> cvElmt = 249 do_QueryObject(mDependentElements[i]); 250 if (cvElmt && cvElmt->IsCandidateForConstraintValidation() && 251 !(cvElmt->IsValid())) { 252 debugInvalidElementsCount += 1; 253 } 254 } 255 MOZ_ASSERT(debugInvalidElementsCount == mInvalidElementsCount); 256 #endif 257 } 258 259 void HTMLFieldSetElement::UpdateDisabledState(bool aNotify) { 260 nsGenericHTMLFormControlElement::UpdateDisabledState(aNotify); 261 262 for (nsGenericHTMLFormElement* element : mDependentElements) { 263 element->FieldSetDisabledChanged(aNotify); 264 } 265 } 266 267 void HTMLFieldSetElement::NotifyElementsForFirstLegendChange(bool aNotify) { 268 /** 269 * NOTE: this could be optimized if only call when the fieldset is currently 270 * disabled. 271 * This should also make sure that mElements is set when we happen to be here. 272 * However, this method shouldn't be called very often in normal use cases. 273 */ 274 if (!mElements) { 275 mElements = 276 new nsContentList(this, MatchListedElements, nullptr, nullptr, true); 277 } 278 279 uint32_t length = mElements->Length(true); 280 for (uint32_t i = 0; i < length; ++i) { 281 static_cast<nsGenericHTMLFormElement*>(mElements->Item(i)) 282 ->FieldSetFirstLegendChanged(aNotify); 283 } 284 } 285 286 void HTMLFieldSetElement::UpdateValidity(bool aElementValidity) { 287 if (aElementValidity) { 288 --mInvalidElementsCount; 289 } else { 290 ++mInvalidElementsCount; 291 } 292 293 MOZ_ASSERT(mInvalidElementsCount >= 0); 294 295 // The fieldset validity has just changed if: 296 // - there are no more invalid elements ; 297 // - or there is one invalid elmement and an element just became invalid. 298 if (!mInvalidElementsCount || 299 (mInvalidElementsCount == 1 && !aElementValidity)) { 300 AutoStateChangeNotifier notifier(*this, true); 301 RemoveStatesSilently(ElementState::VALID | ElementState::INVALID); 302 AddStatesSilently(mInvalidElementsCount ? ElementState::INVALID 303 : ElementState::VALID); 304 } 305 306 // We should propagate the change to the fieldset parent chain. 307 if (mFieldSet) { 308 mFieldSet->UpdateValidity(aElementValidity); 309 } 310 } 311 312 JSObject* HTMLFieldSetElement::WrapNode(JSContext* aCx, 313 JS::Handle<JSObject*> aGivenProto) { 314 return HTMLFieldSetElement_Binding::Wrap(aCx, this, aGivenProto); 315 } 316 317 } // namespace mozilla::dom