tor-browser

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

ElementInternals.cpp (22465B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
      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/ElementInternals.h"
      8 
      9 #include "mozAutoDocUpdate.h"
     10 #include "mozilla/dom/CustomElementRegistry.h"
     11 #include "mozilla/dom/CustomEvent.h"
     12 #include "mozilla/dom/CustomStateSet.h"
     13 #include "mozilla/dom/ElementInternalsBinding.h"
     14 #include "mozilla/dom/FormData.h"
     15 #include "mozilla/dom/HTMLElement.h"
     16 #include "mozilla/dom/HTMLFieldSetElement.h"
     17 #include "mozilla/dom/MutationObservers.h"
     18 #include "mozilla/dom/ShadowRoot.h"
     19 #include "mozilla/dom/ValidityState.h"
     20 #include "nsContentUtils.h"
     21 #include "nsDebug.h"
     22 #include "nsGenericHTMLElement.h"
     23 #include "nsIMutationObserver.h"
     24 
     25 #ifdef ACCESSIBILITY
     26 #  include "nsAccessibilityService.h"
     27 #endif
     28 
     29 namespace mozilla::dom {
     30 
     31 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ElementInternals)
     32 
     33 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ElementInternals)
     34  tmp->Unlink();
     35  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTarget, mSubmissionValue, mState, mValidity,
     36                                  mValidationAnchor, mCustomStateSet);
     37  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
     38 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
     39 
     40 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ElementInternals)
     41  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTarget, mSubmissionValue, mState,
     42                                    mValidity, mValidationAnchor,
     43                                    mCustomStateSet);
     44 
     45  for (auto& tableEntry : tmp->mAttrElementsMap) {
     46    auto& [explicitlySetElements, cachedAttrElements] =
     47        *tableEntry.GetModifiableData();
     48    ImplCycleCollectionTraverse(cb, cachedAttrElements,
     49                                "cached attribute elements entry", 0);
     50  }
     51 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
     52 
     53 NS_IMPL_CYCLE_COLLECTING_ADDREF(ElementInternals)
     54 NS_IMPL_CYCLE_COLLECTING_RELEASE(ElementInternals)
     55 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ElementInternals)
     56  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
     57  NS_INTERFACE_MAP_ENTRY(nsIFormControl)
     58  NS_INTERFACE_MAP_ENTRY(nsIConstraintValidation)
     59 NS_INTERFACE_MAP_END
     60 
     61 ElementInternals::ElementInternals(HTMLElement* aTarget)
     62    : nsIFormControl(FormControlType::FormAssociatedCustomElement),
     63      mTarget(aTarget),
     64      mForm(nullptr),
     65      mFieldSet(nullptr),
     66      mControlNumber(-1) {}
     67 
     68 nsISupports* ElementInternals::GetParentObject() { return ToSupports(mTarget); }
     69 
     70 JSObject* ElementInternals::WrapObject(JSContext* aCx,
     71                                       JS::Handle<JSObject*> aGivenProto) {
     72  return ElementInternals_Binding::Wrap(aCx, this, aGivenProto);
     73 }
     74 
     75 // https://html.spec.whatwg.org/#dom-elementinternals-shadowroot
     76 ShadowRoot* ElementInternals::GetShadowRoot() const {
     77  MOZ_ASSERT(mTarget);
     78 
     79  ShadowRoot* shadowRoot = mTarget->GetShadowRoot();
     80  if (shadowRoot && !shadowRoot->IsAvailableToElementInternals()) {
     81    return nullptr;
     82  }
     83 
     84  return shadowRoot;
     85 }
     86 
     87 // https://html.spec.whatwg.org/commit-snapshots/912a3fe1f29649ccf8229de56f604b3c07ffd242/#dom-elementinternals-setformvalue
     88 void ElementInternals::SetFormValue(
     89    const Nullable<FileOrUSVStringOrFormData>& aValue,
     90    const Optional<Nullable<FileOrUSVStringOrFormData>>& aState,
     91    ErrorResult& aRv) {
     92  MOZ_ASSERT(mTarget);
     93 
     94  /**
     95   * 1. Let element be this's target element.
     96   * 2. If element is not a form-associated custom element, then throw a
     97   *    "NotSupportedError" DOMException.
     98   */
     99  if (!mTarget->IsFormAssociatedElement()) {
    100    aRv.ThrowNotSupportedError(
    101        "Target element is not a form-associated custom element");
    102    return;
    103  }
    104 
    105  /**
    106   * 3. Set target element's submission value to value if value is not a
    107   *    FormData object, or to a clone of the entry list associated with value
    108   *    otherwise.
    109   */
    110  mSubmissionValue.SetNull();
    111  if (!aValue.IsNull()) {
    112    const FileOrUSVStringOrFormData& value = aValue.Value();
    113    OwningFileOrUSVStringOrFormData& owningValue = mSubmissionValue.SetValue();
    114    if (value.IsFormData()) {
    115      owningValue.SetAsFormData() = value.GetAsFormData().Clone();
    116    } else if (value.IsFile()) {
    117      owningValue.SetAsFile() = &value.GetAsFile();
    118    } else {
    119      owningValue.SetAsUSVString() = value.GetAsUSVString();
    120    }
    121  }
    122 
    123  /**
    124   * 4. If the state argument of the function is omitted, set element's state to
    125   *    its submission value.
    126   */
    127  if (!aState.WasPassed()) {
    128    mState = mSubmissionValue;
    129    return;
    130  }
    131 
    132  /**
    133   * 5. Otherwise, if state is a FormData object, set element's state to clone
    134   *    of the entry list associated with state.
    135   * 6. Otherwise, set element's state to state.
    136   */
    137  mState.SetNull();
    138  if (!aState.Value().IsNull()) {
    139    const FileOrUSVStringOrFormData& state = aState.Value().Value();
    140    OwningFileOrUSVStringOrFormData& owningState = mState.SetValue();
    141    if (state.IsFormData()) {
    142      owningState.SetAsFormData() = state.GetAsFormData().Clone();
    143    } else if (state.IsFile()) {
    144      owningState.SetAsFile() = &state.GetAsFile();
    145    } else {
    146      owningState.SetAsUSVString() = state.GetAsUSVString();
    147    }
    148  }
    149 }
    150 
    151 // https://html.spec.whatwg.org/#dom-elementinternals-form
    152 HTMLFormElement* ElementInternals::GetForm(ErrorResult& aRv) const {
    153  MOZ_ASSERT(mTarget);
    154 
    155  if (!mTarget->IsFormAssociatedElement()) {
    156    aRv.ThrowNotSupportedError(
    157        "Target element is not a form-associated custom element");
    158    return nullptr;
    159  }
    160  return GetForm();
    161 }
    162 
    163 // https://html.spec.whatwg.org/commit-snapshots/3ad5159be8f27e110a70cefadcb50fc45ec21b05/#dom-elementinternals-setvalidity
    164 void ElementInternals::SetValidity(
    165    const ValidityStateFlags& aFlags, const Optional<nsAString>& aMessage,
    166    const Optional<NonNull<nsGenericHTMLElement>>& aAnchor, ErrorResult& aRv) {
    167  MOZ_ASSERT(mTarget);
    168 
    169  /**
    170   * 1. Let element be this's target element.
    171   * 2. If element is not a form-associated custom element, then throw a
    172   *    "NotSupportedError" DOMException.
    173   */
    174  if (!mTarget->IsFormAssociatedElement()) {
    175    aRv.ThrowNotSupportedError(
    176        "Target element is not a form-associated custom element");
    177    return;
    178  }
    179 
    180  /**
    181   * 3. If flags contains one or more true values and message is not given or is
    182   *    the empty string, then throw a TypeError.
    183   */
    184  if ((aFlags.mBadInput || aFlags.mCustomError || aFlags.mPatternMismatch ||
    185       aFlags.mRangeOverflow || aFlags.mRangeUnderflow ||
    186       aFlags.mStepMismatch || aFlags.mTooLong || aFlags.mTooShort ||
    187       aFlags.mTypeMismatch || aFlags.mValueMissing) &&
    188      (!aMessage.WasPassed() || aMessage.Value().IsEmpty())) {
    189    aRv.ThrowTypeError("Need to provide validation message");
    190    return;
    191  }
    192 
    193  /**
    194   * 4. For each entry flag → value of flags, set element's validity flag with
    195   *    the name flag to value.
    196   */
    197  SetValidityState(VALIDITY_STATE_VALUE_MISSING, aFlags.mValueMissing);
    198  SetValidityState(VALIDITY_STATE_TYPE_MISMATCH, aFlags.mTypeMismatch);
    199  SetValidityState(VALIDITY_STATE_PATTERN_MISMATCH, aFlags.mPatternMismatch);
    200  SetValidityState(VALIDITY_STATE_TOO_LONG, aFlags.mTooLong);
    201  SetValidityState(VALIDITY_STATE_TOO_SHORT, aFlags.mTooShort);
    202  SetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW, aFlags.mRangeUnderflow);
    203  SetValidityState(VALIDITY_STATE_RANGE_OVERFLOW, aFlags.mRangeOverflow);
    204  SetValidityState(VALIDITY_STATE_STEP_MISMATCH, aFlags.mStepMismatch);
    205  SetValidityState(VALIDITY_STATE_BAD_INPUT, aFlags.mBadInput);
    206  SetValidityState(VALIDITY_STATE_CUSTOM_ERROR, aFlags.mCustomError);
    207  mTarget->UpdateValidityElementStates(true);
    208 
    209  /**
    210   * 5. Set element's validation message to the empty string if message is not
    211   *    given or all of element's validity flags are false, or to message
    212   *    otherwise.
    213   * 6. If element's customError validity flag is true, then set element's
    214   *    custom validity error message to element's validation message.
    215   *    Otherwise, set element's custom validity error message to the empty
    216   *    string.
    217   */
    218  mValidationMessage =
    219      (!aMessage.WasPassed() || IsValid()) ? EmptyString() : aMessage.Value();
    220 
    221  /**
    222   * 7. Set element's validation anchor to null if anchor is not given.
    223   *    Otherwise, if anchor is not a shadow-including descendant of element,
    224   *    then throw a "NotFoundError" DOMException. Otherwise, set element's
    225   *    validation anchor to anchor.
    226   */
    227  nsGenericHTMLElement* anchor =
    228      aAnchor.WasPassed() ? &aAnchor.Value() : nullptr;
    229  // TODO: maybe create something like IsShadowIncludingDescendantOf if there
    230  //       are other places also need such check.
    231  if (anchor && (anchor == mTarget ||
    232                 !anchor->IsShadowIncludingInclusiveDescendantOf(mTarget))) {
    233    aRv.ThrowNotFoundError(
    234        "Validation anchor is not a shadow-including descendant of target"
    235        "element");
    236    return;
    237  }
    238  mValidationAnchor = anchor;
    239 }
    240 
    241 // https://html.spec.whatwg.org/#dom-elementinternals-willvalidate
    242 bool ElementInternals::GetWillValidate(ErrorResult& aRv) const {
    243  MOZ_ASSERT(mTarget);
    244 
    245  if (!mTarget->IsFormAssociatedElement()) {
    246    aRv.ThrowNotSupportedError(
    247        "Target element is not a form-associated custom element");
    248    return false;
    249  }
    250  return WillValidate();
    251 }
    252 
    253 // https://html.spec.whatwg.org/#dom-elementinternals-validity
    254 ValidityState* ElementInternals::GetValidity(ErrorResult& aRv) {
    255  MOZ_ASSERT(mTarget);
    256 
    257  if (!mTarget->IsFormAssociatedElement()) {
    258    aRv.ThrowNotSupportedError(
    259        "Target element is not a form-associated custom element");
    260    return nullptr;
    261  }
    262  return Validity();
    263 }
    264 
    265 // https://html.spec.whatwg.org/#dom-elementinternals-validationmessage
    266 void ElementInternals::GetValidationMessage(nsAString& aValidationMessage,
    267                                            ErrorResult& aRv) const {
    268  MOZ_ASSERT(mTarget);
    269 
    270  if (!mTarget->IsFormAssociatedElement()) {
    271    aRv.ThrowNotSupportedError(
    272        "Target element is not a form-associated custom element");
    273    return;
    274  }
    275  aValidationMessage = mValidationMessage;
    276 }
    277 
    278 // https://html.spec.whatwg.org/#dom-elementinternals-checkvalidity
    279 bool ElementInternals::CheckValidity(ErrorResult& aRv) {
    280  MOZ_ASSERT(mTarget);
    281 
    282  if (!mTarget->IsFormAssociatedElement()) {
    283    aRv.ThrowNotSupportedError(
    284        "Target element is not a form-associated custom element");
    285    return false;
    286  }
    287  return nsIConstraintValidation::CheckValidity(*mTarget);
    288 }
    289 
    290 // https://html.spec.whatwg.org/#dom-elementinternals-reportvalidity
    291 bool ElementInternals::ReportValidity(ErrorResult& aRv) {
    292  MOZ_ASSERT(mTarget);
    293 
    294  if (!mTarget->IsFormAssociatedElement()) {
    295    aRv.ThrowNotSupportedError(
    296        "Target element is not a form-associated custom element");
    297    return false;
    298  }
    299 
    300  bool defaultAction = true;
    301  if (nsIConstraintValidation::CheckValidity(*mTarget, &defaultAction)) {
    302    return true;
    303  }
    304 
    305  if (!defaultAction) {
    306    return false;
    307  }
    308 
    309  AutoTArray<RefPtr<Element>, 1> invalidElements;
    310  invalidElements.AppendElement(mTarget);
    311 
    312  AutoJSAPI jsapi;
    313  if (!jsapi.Init(mTarget->GetOwnerGlobal())) {
    314    return false;
    315  }
    316  JS::Rooted<JS::Value> detail(jsapi.cx());
    317  if (!ToJSValue(jsapi.cx(), invalidElements, &detail)) {
    318    return false;
    319  }
    320 
    321  RefPtr<CustomEvent> event =
    322      NS_NewDOMCustomEvent(mTarget->OwnerDoc(), nullptr, nullptr);
    323  event->InitCustomEvent(jsapi.cx(), u"MozInvalidForm"_ns,
    324                         /* CanBubble */ true,
    325                         /* Cancelable */ true, detail);
    326  event->SetTrusted(true);
    327  event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
    328  mTarget->DispatchEvent(*event);
    329 
    330  return false;
    331 }
    332 
    333 // https://html.spec.whatwg.org/#dom-elementinternals-labels
    334 already_AddRefed<nsINodeList> ElementInternals::GetLabels(
    335    ErrorResult& aRv) const {
    336  MOZ_ASSERT(mTarget);
    337 
    338  if (!mTarget->IsFormAssociatedElement()) {
    339    aRv.ThrowNotSupportedError(
    340        "Target element is not a form-associated custom element");
    341    return nullptr;
    342  }
    343  return mTarget->Labels();
    344 }
    345 
    346 nsGenericHTMLElement* ElementInternals::GetValidationAnchor(
    347    ErrorResult& aRv) const {
    348  MOZ_ASSERT(mTarget);
    349 
    350  if (!mTarget->IsFormAssociatedElement()) {
    351    aRv.ThrowNotSupportedError(
    352        "Target element is not a form-associated custom element");
    353    return nullptr;
    354  }
    355  return mValidationAnchor;
    356 }
    357 
    358 CustomStateSet* ElementInternals::States() {
    359  if (!mCustomStateSet) {
    360    mCustomStateSet = new CustomStateSet(mTarget);
    361  }
    362  return mCustomStateSet;
    363 }
    364 
    365 void ElementInternals::SetForm(HTMLFormElement* aForm) { mForm = aForm; }
    366 
    367 void ElementInternals::ClearForm(bool aRemoveFromForm, bool aUnbindOrDelete) {
    368  if (mTarget) {
    369    mTarget->ClearForm(aRemoveFromForm, aUnbindOrDelete);
    370  }
    371 }
    372 
    373 NS_IMETHODIMP ElementInternals::Reset() {
    374  if (mTarget) {
    375    MOZ_ASSERT(mTarget->IsFormAssociatedElement());
    376    nsContentUtils::EnqueueLifecycleCallback(ElementCallbackType::eFormReset,
    377                                             mTarget, {});
    378  }
    379  return NS_OK;
    380 }
    381 
    382 NS_IMETHODIMP ElementInternals::SubmitNamesValues(FormData* aFormData) {
    383  if (!mTarget) {
    384    return NS_ERROR_UNEXPECTED;
    385  }
    386 
    387  MOZ_ASSERT(mTarget->IsFormAssociatedElement());
    388 
    389  // https://html.spec.whatwg.org/#face-entry-construction
    390  if (!mSubmissionValue.IsNull()) {
    391    if (mSubmissionValue.Value().IsFormData()) {
    392      aFormData->Append(mSubmissionValue.Value().GetAsFormData());
    393      return NS_OK;
    394    }
    395 
    396    // Get the name
    397    nsAutoString name;
    398    if (!mTarget->GetAttr(nsGkAtoms::name, name) || name.IsEmpty()) {
    399      return NS_OK;
    400    }
    401 
    402    if (mSubmissionValue.Value().IsUSVString()) {
    403      return aFormData->AddNameValuePair(
    404          name, mSubmissionValue.Value().GetAsUSVString());
    405    }
    406 
    407    return aFormData->AddNameBlobPair(name,
    408                                      mSubmissionValue.Value().GetAsFile());
    409  }
    410  return NS_OK;
    411 }
    412 
    413 void ElementInternals::UpdateFormOwner() {
    414  if (mTarget) {
    415    mTarget->UpdateFormOwner();
    416  }
    417 }
    418 
    419 void ElementInternals::UpdateBarredFromConstraintValidation() {
    420  if (mTarget) {
    421    MOZ_ASSERT(mTarget->IsFormAssociatedElement());
    422    SetBarredFromConstraintValidation(
    423        mTarget->IsDisabled() || mTarget->HasAttr(nsGkAtoms::readonly) ||
    424        mTarget->HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR));
    425  }
    426 }
    427 
    428 void ElementInternals::Unlink() {
    429  if (mForm) {
    430    // Don't notify, since we're being destroyed in any case.
    431    ClearForm(true, true);
    432    MOZ_DIAGNOSTIC_ASSERT(!mForm);
    433  }
    434  if (mFieldSet) {
    435    mFieldSet->RemoveElement(mTarget);
    436    mFieldSet = nullptr;
    437  }
    438  mAttrElementsMap.Clear();
    439 }
    440 
    441 void ElementInternals::GetAttr(const nsAtom* aName, nsAString& aResult) const {
    442  MOZ_ASSERT(aResult.IsEmpty(), "Should have empty string coming in");
    443 
    444  const nsAttrValue* val = mAttrs.GetAttr(aName);
    445  if (val) {
    446    val->ToString(aResult);
    447    return;
    448  }
    449  SetDOMStringToNull(aResult);
    450 }
    451 
    452 nsresult ElementInternals::SetAttr(nsAtom* aName, const nsAString& aValue) {
    453  Document* document = mTarget->GetComposedDoc();
    454  mozAutoDocUpdate updateBatch(document, true);
    455 
    456  const AttrModType modType =
    457      mAttrs.HasAttr(aName) ? AttrModType::Modification : AttrModType::Addition;
    458  MutationObservers::NotifyARIAAttributeDefaultWillChange(mTarget, aName,
    459                                                          modType);
    460 
    461  nsAttrValue attrValue(aValue);
    462  nsresult rs = NS_OK;
    463  if (DOMStringIsNull(aValue)) {
    464    auto attrPos = mAttrs.IndexOfAttr(aName);
    465    if (attrPos >= 0) {
    466      rs = mAttrs.RemoveAttrAt(attrPos, attrValue);
    467    }
    468  } else {
    469    bool attrHadValue = false;
    470    rs = mAttrs.SetAndSwapAttr(aName, attrValue, &attrHadValue);
    471  }
    472  nsMutationGuard::DidMutate();
    473 
    474  MutationObservers::NotifyARIAAttributeDefaultChanged(mTarget, aName, modType);
    475 
    476  return rs;
    477 }
    478 
    479 nsresult ElementInternals::SetAttrInternal(nsAtom* aName,
    480                                           const nsAString& aValue) {
    481  bool attrHadValue;
    482  nsAttrValue attrValue(aValue);
    483  return mAttrs.SetAndSwapAttr(aName, attrValue, &attrHadValue);
    484 }
    485 
    486 nsresult ElementInternals::UnsetAttrInternal(nsAtom* aName) {
    487  nsAttrValue attrValue;
    488  auto attrPos = mAttrs.IndexOfAttr(aName);
    489  if (attrPos >= 0) {
    490    return mAttrs.RemoveAttrAt(attrPos, attrValue);
    491  }
    492 
    493  return NS_OK;
    494 }
    495 
    496 DocGroup* ElementInternals::GetDocGroup() {
    497  return mTarget->OwnerDoc()->GetDocGroup();
    498 }
    499 
    500 void ElementInternals::RestoreFormValue(
    501    Nullable<OwningFileOrUSVStringOrFormData>&& aValue,
    502    Nullable<OwningFileOrUSVStringOrFormData>&& aState) {
    503  mSubmissionValue = aValue;
    504  mState = aState;
    505 
    506  if (!mState.IsNull()) {
    507    LifecycleCallbackArgs args;
    508    args.mState = mState;
    509    args.mReason = RestoreReason::Restore;
    510    nsContentUtils::EnqueueLifecycleCallback(
    511        ElementCallbackType::eFormStateRestore, mTarget, args);
    512  }
    513 }
    514 
    515 void ElementInternals::InitializeControlNumber() {
    516  MOZ_ASSERT(mControlNumber == -1,
    517             "FACE control number should only be initialized once!");
    518  mControlNumber = mTarget->OwnerDoc()->GetNextControlNumber();
    519 }
    520 
    521 void ElementInternals::SetAttrElement(nsAtom* aAttr, Element* aElement) {
    522  // Accessibility requires that no other attribute changes occur between
    523  // AttrElementWillChange and AttrElementChanged. Scripts could cause
    524  // this, so don't let them run here. We do this even if accessibility isn't
    525  // running so that the JS behavior is consistent regardless of accessibility.
    526  // Otherwise, JS might be able to use this difference to determine whether
    527  // accessibility is running, which would be a privacy concern.
    528  nsAutoScriptBlocker scriptBlocker;
    529 
    530 #ifdef ACCESSIBILITY
    531  // If the target has this attribute defined then it overrides the defaults
    532  // defined here in the Internals instance. In that case we don't need to
    533  // notify the change to a11y since the attribute hasn't changed, just the
    534  // underlying default. We can set accService to null and not notify.
    535  nsAccessibilityService* accService =
    536      !mTarget->HasAttr(aAttr) ? GetAccService() : nullptr;
    537  if (accService) {
    538    accService->NotifyAttrElementWillChange(mTarget, aAttr);
    539  }
    540 #endif
    541 
    542  if (aElement) {
    543    mAttrElementMap.InsertOrUpdate(aAttr, do_GetWeakReference(aElement));
    544    SetAttrInternal(aAttr, EmptyString());
    545  } else {
    546    mAttrElementMap.Remove(aAttr);
    547    UnsetAttrInternal(aAttr);
    548  }
    549 
    550 #ifdef ACCESSIBILITY
    551  if (accService) {
    552    accService->NotifyAttrElementChanged(mTarget, aAttr);
    553  }
    554 #endif
    555 }
    556 
    557 Element* ElementInternals::GetAttrElement(nsAtom* aAttr) const {
    558  nsWeakPtr weakAttrEl = mAttrElementMap.Get(aAttr);
    559  nsCOMPtr<Element> attrEl = do_QueryReferent(weakAttrEl);
    560  return attrEl;
    561 }
    562 
    563 void ElementInternals::SetAttrElements(
    564    nsAtom* aAttr,
    565    const Nullable<Sequence<OwningNonNull<Element>>>& aElements) {
    566 #ifdef ACCESSIBILITY
    567  nsAccessibilityService* accService = GetAccService();
    568 #endif
    569  // Accessibility requires that no other attribute changes occur between
    570  // AttrElementWillChange and AttrElementChanged. Scripts could cause
    571  // this, so don't let them run here. We do this even if accessibility isn't
    572  // running so that the JS behavior is consistent regardless of accessibility.
    573  // Otherwise, JS might be able to use this difference to determine whether
    574  // accessibility is running, which would be a privacy concern.
    575  nsAutoScriptBlocker scriptBlocker;
    576 #ifdef ACCESSIBILITY
    577  if (accService) {
    578    accService->NotifyAttrElementWillChange(mTarget, aAttr);
    579  }
    580 #endif
    581 
    582  nsAttrValue emptyAttr;
    583  if (aElements.IsNull()) {
    584    mAttrElementsMap.Remove(aAttr);
    585    UnsetAttrInternal(aAttr);
    586  } else {
    587    auto& [attrElements, cachedAttrElements] =
    588        mAttrElementsMap.LookupOrInsert(aAttr);
    589    attrElements.Clear();
    590    for (Element* el : aElements.Value()) {
    591      attrElements.AppendElement(do_GetWeakReference(el));
    592    }
    593    SetAttrInternal(aAttr, EmptyString());
    594  }
    595 
    596 #ifdef ACCESSIBILITY
    597  if (accService) {
    598    accService->NotifyAttrElementChanged(mTarget, aAttr);
    599  }
    600 #endif
    601 }
    602 
    603 void ElementInternals::GetAttrElements(
    604    nsAtom* aAttr, bool* aUseCachedValue,
    605    Nullable<nsTArray<RefPtr<Element>>>& aElements) {
    606  MOZ_ASSERT(aElements.IsNull());
    607 
    608  auto attrElementsMaybeEntry = mAttrElementsMap.Lookup(aAttr);
    609  if (!attrElementsMaybeEntry) {
    610    return;
    611  }
    612 
    613  aElements.SetValue(nsTArray<RefPtr<Element>>());
    614  auto& [attrElements, cachedAttrElements] = attrElementsMaybeEntry.Data();
    615 
    616  auto getAttrAssociatedElements = [&, &attrElements = attrElements]() {
    617    CopyableTArray<RefPtr<Element>> elements;
    618 
    619    for (const nsWeakPtr& weakEl : attrElements) {
    620      // For each attrElement in reflectedTarget's explicitly set attr-elements:
    621      if (nsCOMPtr<Element> attrEl = do_QueryReferent(weakEl)) {
    622        // Append attrElement to elements.
    623        elements.AppendElement(attrEl);
    624      }
    625    }
    626 
    627    return elements;
    628  };
    629 
    630  // https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#attr-associated-elements
    631  // Getter steps:
    632  // 1. Let elements be the result of running this's get the attr-associated
    633  // elements.
    634  auto elements = getAttrAssociatedElements();
    635 
    636  if (elements == cachedAttrElements) {
    637    // 2. If the contents of elements is equal to the contents of this's cached
    638    // attr-associated elements, then return this's cached attr-associated
    639    // elements object.
    640    MOZ_ASSERT(!*aUseCachedValue);
    641    *aUseCachedValue = true;
    642    return;
    643  }
    644 
    645  // 3. Let elementsAsFrozenArray be elements, converted to a FrozenArray<T>?.
    646  //    (the binding code takes aElements and returns it as a FrozenArray)
    647  // 5. Set this's cached attr-associated elements object to
    648  // elementsAsFrozenArray.
    649  //    (the binding code stores the attr-associated elements object in a slot)
    650  // 6. Return elementsAsFrozenArray.
    651  aElements.SetValue(elements.Clone());
    652 
    653  // 4. Set this's cached attr-associated elements to elements.
    654  cachedAttrElements = std::move(elements);
    655 }
    656 
    657 bool ElementInternals::GetAttrElements(nsAtom* aAttr,
    658                                       nsTArray<Element*>& aElements) {
    659  aElements.Clear();
    660  auto attrElementsMaybeEntry = mAttrElementsMap.Lookup(aAttr);
    661  if (!attrElementsMaybeEntry) {
    662    return false;
    663  }
    664 
    665  auto& [attrElements, cachedAttrElements] = attrElementsMaybeEntry.Data();
    666  for (const nsWeakPtr& weakEl : attrElements) {
    667    if (nsCOMPtr<Element> attrEl = do_QueryReferent(weakEl)) {
    668      aElements.AppendElement(attrEl);
    669    }
    670  }
    671 
    672  return true;
    673 }
    674 
    675 }  // namespace mozilla::dom