tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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