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