diff options
Diffstat (limited to 'dom/html/HTMLFieldSetElement.cpp')
-rw-r--r-- | dom/html/HTMLFieldSetElement.cpp | 324 |
1 files changed, 324 insertions, 0 deletions
diff --git a/dom/html/HTMLFieldSetElement.cpp b/dom/html/HTMLFieldSetElement.cpp new file mode 100644 index 0000000000..47c5e649d5 --- /dev/null +++ b/dom/html/HTMLFieldSetElement.cpp @@ -0,0 +1,324 @@ +/* -*- 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/. */ + +#include "mozilla/BasicEvents.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/Maybe.h" +#include "mozilla/dom/CustomElementRegistry.h" +#include "mozilla/dom/HTMLFieldSetElement.h" +#include "mozilla/dom/HTMLFieldSetElementBinding.h" +#include "nsContentList.h" +#include "nsQueryObject.h" + +NS_IMPL_NS_NEW_HTML_ELEMENT(FieldSet) + +namespace mozilla::dom { + +HTMLFieldSetElement::HTMLFieldSetElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : nsGenericHTMLFormControlElement(std::move(aNodeInfo), + FormControlType::Fieldset), + mElements(nullptr), + mFirstLegend(nullptr), + mInvalidElementsCount(0) { + // <fieldset> is always barred from constraint validation. + SetBarredFromConstraintValidation(true); + + // We start out enabled and valid. + AddStatesSilently(ElementState::ENABLED | ElementState::VALID); +} + +HTMLFieldSetElement::~HTMLFieldSetElement() { + uint32_t length = mDependentElements.Length(); + for (uint32_t i = 0; i < length; ++i) { + mDependentElements[i]->ForgetFieldSet(this); + } +} + +NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLFieldSetElement, + nsGenericHTMLFormControlElement, mValidity, + mElements) + +NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLFieldSetElement, + nsGenericHTMLFormControlElement, + nsIConstraintValidation) + +NS_IMPL_ELEMENT_CLONE(HTMLFieldSetElement) + +bool HTMLFieldSetElement::IsDisabledForEvents(WidgetEvent* aEvent) { + return IsElementDisabledForEvents(aEvent, nullptr); +} + +// nsIContent +void HTMLFieldSetElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) { + // Do not process any DOM events if the element is disabled. + aVisitor.mCanHandle = false; + if (IsDisabledForEvents(aVisitor.mEvent)) { + return; + } + + nsGenericHTMLFormControlElement::GetEventTargetParent(aVisitor); +} + +void HTMLFieldSetElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, + const nsAttrValue* aValue, + const nsAttrValue* aOldValue, + nsIPrincipal* aSubjectPrincipal, + bool aNotify) { + if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::disabled) { + // This *has* to be called *before* calling FieldSetDisabledChanged on our + // controls, as they may depend on our disabled state. + UpdateDisabledState(aNotify); + + if (nsINode::GetFirstChild()) { + if (!mElements) { + mElements = new nsContentList(this, MatchListedElements, nullptr, + nullptr, true); + } + + uint32_t length = mElements->Length(true); + for (uint32_t i = 0; i < length; ++i) { + static_cast<nsGenericHTMLFormElement*>(mElements->Item(i)) + ->FieldSetDisabledChanged(aNotify); + } + } + } + + return nsGenericHTMLFormControlElement::AfterSetAttr( + aNameSpaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify); +} + +void HTMLFieldSetElement::GetType(nsAString& aType) const { + aType.AssignLiteral("fieldset"); +} + +/* static */ +bool HTMLFieldSetElement::MatchListedElements(Element* aElement, + int32_t aNamespaceID, + nsAtom* aAtom, void* aData) { + nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(aElement); + return formControl; +} + +nsIHTMLCollection* HTMLFieldSetElement::Elements() { + if (!mElements) { + mElements = + new nsContentList(this, MatchListedElements, nullptr, nullptr, true); + } + + return mElements; +} + +// nsIFormControl + +nsresult HTMLFieldSetElement::Reset() { return NS_OK; } + +void HTMLFieldSetElement::InsertChildBefore(nsIContent* aChild, + nsIContent* aBeforeThis, + bool aNotify, ErrorResult& aRv) { + bool firstLegendHasChanged = false; + + if (aChild->IsHTMLElement(nsGkAtoms::legend)) { + if (!mFirstLegend) { + mFirstLegend = aChild; + // We do not want to notify the first time mFirstElement is set. + } else { + // If mFirstLegend is before aIndex, we do not change it. + // Otherwise, mFirstLegend is now aChild. + const Maybe<uint32_t> indexOfRef = + aBeforeThis ? ComputeIndexOf(aBeforeThis) : Some(GetChildCount()); + const Maybe<uint32_t> indexOfFirstLegend = ComputeIndexOf(mFirstLegend); + if ((indexOfRef.isSome() && indexOfFirstLegend.isSome() && + *indexOfRef <= *indexOfFirstLegend) || + // XXX Keep the odd traditional behavior for now. + indexOfRef.isNothing()) { + mFirstLegend = aChild; + firstLegendHasChanged = true; + } + } + } + + nsGenericHTMLFormControlElement::InsertChildBefore(aChild, aBeforeThis, + aNotify, aRv); + if (aRv.Failed()) { + return; + } + + if (firstLegendHasChanged) { + NotifyElementsForFirstLegendChange(aNotify); + } +} + +void HTMLFieldSetElement::RemoveChildNode(nsIContent* aKid, bool aNotify) { + bool firstLegendHasChanged = false; + + if (mFirstLegend && aKid == mFirstLegend) { + // If we are removing the first legend we have to found another one. + nsIContent* child = mFirstLegend->GetNextSibling(); + mFirstLegend = nullptr; + firstLegendHasChanged = true; + + for (; child; child = child->GetNextSibling()) { + if (child->IsHTMLElement(nsGkAtoms::legend)) { + mFirstLegend = child; + break; + } + } + } + + nsGenericHTMLFormControlElement::RemoveChildNode(aKid, aNotify); + + if (firstLegendHasChanged) { + NotifyElementsForFirstLegendChange(aNotify); + } +} + +void HTMLFieldSetElement::AddElement(nsGenericHTMLFormElement* aElement) { + mDependentElements.AppendElement(aElement); + + // If the element that we are adding aElement is a fieldset, then all the + // invalid elements in aElement are also invalid elements of this. + HTMLFieldSetElement* fieldSet = FromNode(aElement); + if (fieldSet) { + for (int32_t i = 0; i < fieldSet->mInvalidElementsCount; i++) { + UpdateValidity(false); + } + return; + } + + // If the element is a form-associated custom element, adding element might be + // caused by FACE upgrade which won't trigger mutation observer, so mark + // mElements dirty manually here. + CustomElementData* data = aElement->GetCustomElementData(); + if (data && data->IsFormAssociated() && mElements) { + mElements->SetDirty(); + } + + // We need to update the validity of the fieldset. + nsCOMPtr<nsIConstraintValidation> cvElmt = do_QueryObject(aElement); + if (cvElmt && cvElmt->IsCandidateForConstraintValidation() && + !cvElmt->IsValid()) { + UpdateValidity(false); + } + +#if DEBUG + int32_t debugInvalidElementsCount = 0; + for (uint32_t i = 0; i < mDependentElements.Length(); i++) { + HTMLFieldSetElement* fieldSet = FromNode(mDependentElements[i]); + if (fieldSet) { + debugInvalidElementsCount += fieldSet->mInvalidElementsCount; + continue; + } + nsCOMPtr<nsIConstraintValidation> cvElmt = + do_QueryObject(mDependentElements[i]); + if (cvElmt && cvElmt->IsCandidateForConstraintValidation() && + !(cvElmt->IsValid())) { + debugInvalidElementsCount += 1; + } + } + MOZ_ASSERT(debugInvalidElementsCount == mInvalidElementsCount); +#endif +} + +void HTMLFieldSetElement::RemoveElement(nsGenericHTMLFormElement* aElement) { + mDependentElements.RemoveElement(aElement); + + // If the element that we are removing aElement is a fieldset, then all the + // invalid elements in aElement are also removed from this. + HTMLFieldSetElement* fieldSet = FromNode(aElement); + if (fieldSet) { + for (int32_t i = 0; i < fieldSet->mInvalidElementsCount; i++) { + UpdateValidity(true); + } + return; + } + + // We need to update the validity of the fieldset. + nsCOMPtr<nsIConstraintValidation> cvElmt = do_QueryObject(aElement); + if (cvElmt && cvElmt->IsCandidateForConstraintValidation() && + !cvElmt->IsValid()) { + UpdateValidity(true); + } + +#if DEBUG + int32_t debugInvalidElementsCount = 0; + for (uint32_t i = 0; i < mDependentElements.Length(); i++) { + HTMLFieldSetElement* fieldSet = FromNode(mDependentElements[i]); + if (fieldSet) { + debugInvalidElementsCount += fieldSet->mInvalidElementsCount; + continue; + } + nsCOMPtr<nsIConstraintValidation> cvElmt = + do_QueryObject(mDependentElements[i]); + if (cvElmt && cvElmt->IsCandidateForConstraintValidation() && + !(cvElmt->IsValid())) { + debugInvalidElementsCount += 1; + } + } + MOZ_ASSERT(debugInvalidElementsCount == mInvalidElementsCount); +#endif +} + +void HTMLFieldSetElement::NotifyElementsForFirstLegendChange(bool aNotify) { + /** + * NOTE: this could be optimized if only call when the fieldset is currently + * disabled. + * This should also make sure that mElements is set when we happen to be here. + * However, this method shouldn't be called very often in normal use cases. + */ + if (!mElements) { + mElements = + new nsContentList(this, MatchListedElements, nullptr, nullptr, true); + } + + uint32_t length = mElements->Length(true); + for (uint32_t i = 0; i < length; ++i) { + static_cast<nsGenericHTMLFormElement*>(mElements->Item(i)) + ->FieldSetFirstLegendChanged(aNotify); + } +} + +void HTMLFieldSetElement::UpdateValidity(bool aElementValidity) { + if (aElementValidity) { + --mInvalidElementsCount; + } else { + ++mInvalidElementsCount; + } + + MOZ_ASSERT(mInvalidElementsCount >= 0); + + // The fieldset validity has just changed if: + // - there are no more invalid elements ; + // - or there is one invalid elmement and an element just became invalid. + if (!mInvalidElementsCount || + (mInvalidElementsCount == 1 && !aElementValidity)) { + UpdateState(true); + } + + // We should propagate the change to the fieldset parent chain. + if (mFieldSet) { + mFieldSet->UpdateValidity(aElementValidity); + } +} + +ElementState HTMLFieldSetElement::IntrinsicState() const { + ElementState state = nsGenericHTMLFormControlElement::IntrinsicState(); + + if (mInvalidElementsCount) { + state |= ElementState::INVALID; + } else { + state |= ElementState::VALID; + } + + return state; +} + +JSObject* HTMLFieldSetElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return HTMLFieldSetElement_Binding::Wrap(aCx, this, aGivenProto); +} + +} // namespace mozilla::dom |