Backed out changeset b462e7b742d8 (bug 1908261) for causing multiple reftest failures...
[gecko.git] / dom / base / CustomElementRegistry.cpp
blobdf0c2fdc38714bd62ad7279c4ec7324a987abcfc
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/. */
7 #include "mozilla/dom/CustomElementRegistry.h"
9 #include "mozilla/AsyncEventDispatcher.h"
10 #include "mozilla/CycleCollectedJSContext.h"
11 #include "mozilla/dom/AutoEntryScript.h"
12 #include "mozilla/dom/CustomElementRegistryBinding.h"
13 #include "mozilla/dom/ElementBinding.h"
14 #include "mozilla/dom/HTMLElement.h"
15 #include "mozilla/dom/HTMLElementBinding.h"
16 #include "mozilla/dom/PrimitiveConversions.h"
17 #include "mozilla/dom/ShadowIncludingTreeIterator.h"
18 #include "mozilla/dom/XULElementBinding.h"
19 #include "mozilla/dom/Promise.h"
20 #include "mozilla/dom/DocGroup.h"
21 #include "mozilla/dom/CustomEvent.h"
22 #include "mozilla/dom/ShadowRoot.h"
23 #include "mozilla/dom/UnionTypes.h"
24 #include "mozilla/AutoRestore.h"
25 #include "mozilla/HoldDropJSObjects.h"
26 #include "mozilla/UseCounter.h"
27 #include "nsContentUtils.h"
28 #include "nsHTMLTags.h"
29 #include "nsInterfaceHashtable.h"
30 #include "nsPIDOMWindow.h"
31 #include "jsapi.h"
32 #include "js/ForOfIterator.h" // JS::ForOfIterator
33 #include "js/PropertyAndElement.h" // JS_GetProperty, JS_GetUCProperty
34 #include "xpcprivate.h"
35 #include "nsNameSpaceManager.h"
37 namespace mozilla::dom {
39 //-----------------------------------------------------
40 // CustomElementUpgradeReaction
42 class CustomElementUpgradeReaction final : public CustomElementReaction {
43 public:
44 explicit CustomElementUpgradeReaction(CustomElementDefinition* aDefinition)
45 : mDefinition(aDefinition) {
46 mIsUpgradeReaction = true;
49 virtual void Traverse(
50 nsCycleCollectionTraversalCallback& aCb) const override {
51 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mDefinition");
52 aCb.NoteNativeChild(
53 mDefinition, NS_CYCLE_COLLECTION_PARTICIPANT(CustomElementDefinition));
56 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
57 // We don't really own mDefinition.
58 return aMallocSizeOf(this);
61 private:
62 MOZ_CAN_RUN_SCRIPT
63 virtual void Invoke(Element* aElement, ErrorResult& aRv) override {
64 CustomElementRegistry::Upgrade(aElement, mDefinition, aRv);
67 const RefPtr<CustomElementDefinition> mDefinition;
70 //-----------------------------------------------------
71 // CustomElementCallbackReaction
73 class CustomElementCallback {
74 public:
75 CustomElementCallback(Element* aThisObject, ElementCallbackType aCallbackType,
76 CallbackFunction* aCallback,
77 const LifecycleCallbackArgs& aArgs);
78 void Traverse(nsCycleCollectionTraversalCallback& aCb) const;
79 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
80 void Call();
82 static UniquePtr<CustomElementCallback> Create(
83 ElementCallbackType aType, Element* aCustomElement,
84 const LifecycleCallbackArgs& aArgs, CustomElementDefinition* aDefinition);
86 private:
87 // The this value to use for invocation of the callback.
88 RefPtr<Element> mThisObject;
89 RefPtr<CallbackFunction> mCallback;
90 // The type of callback (eCreated, eAttached, etc.)
91 ElementCallbackType mType;
92 // Arguments to be passed to the callback,
93 LifecycleCallbackArgs mArgs;
96 class CustomElementCallbackReaction final : public CustomElementReaction {
97 public:
98 explicit CustomElementCallbackReaction(
99 UniquePtr<CustomElementCallback> aCustomElementCallback)
100 : mCustomElementCallback(std::move(aCustomElementCallback)) {}
102 virtual void Traverse(
103 nsCycleCollectionTraversalCallback& aCb) const override {
104 mCustomElementCallback->Traverse(aCb);
107 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
108 size_t n = aMallocSizeOf(this);
110 n += mCustomElementCallback->SizeOfIncludingThis(aMallocSizeOf);
112 return n;
115 private:
116 virtual void Invoke(Element* aElement, ErrorResult& aRv) override {
117 mCustomElementCallback->Call();
120 UniquePtr<CustomElementCallback> mCustomElementCallback;
123 //-----------------------------------------------------
124 // CustomElementCallback
126 size_t LifecycleCallbackArgs::SizeOfExcludingThis(
127 MallocSizeOf aMallocSizeOf) const {
128 size_t n = mOldValue.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
129 n += mNewValue.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
130 n += mNamespaceURI.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
131 return n;
134 /* static */
135 UniquePtr<CustomElementCallback> CustomElementCallback::Create(
136 ElementCallbackType aType, Element* aCustomElement,
137 const LifecycleCallbackArgs& aArgs, CustomElementDefinition* aDefinition) {
138 MOZ_ASSERT(aDefinition, "CustomElementDefinition should not be null");
139 MOZ_ASSERT(aCustomElement->GetCustomElementData(),
140 "CustomElementData should exist");
142 // Let CALLBACK be the callback associated with the key NAME in CALLBACKS.
143 CallbackFunction* func = nullptr;
144 switch (aType) {
145 case ElementCallbackType::eConnected:
146 if (aDefinition->mCallbacks->mConnectedCallback.WasPassed()) {
147 func = aDefinition->mCallbacks->mConnectedCallback.Value();
149 break;
151 case ElementCallbackType::eDisconnected:
152 if (aDefinition->mCallbacks->mDisconnectedCallback.WasPassed()) {
153 func = aDefinition->mCallbacks->mDisconnectedCallback.Value();
155 break;
157 case ElementCallbackType::eAdopted:
158 if (aDefinition->mCallbacks->mAdoptedCallback.WasPassed()) {
159 func = aDefinition->mCallbacks->mAdoptedCallback.Value();
161 break;
163 case ElementCallbackType::eAttributeChanged:
164 if (aDefinition->mCallbacks->mAttributeChangedCallback.WasPassed()) {
165 func = aDefinition->mCallbacks->mAttributeChangedCallback.Value();
167 break;
169 case ElementCallbackType::eFormAssociated:
170 if (aDefinition->mFormAssociatedCallbacks->mFormAssociatedCallback
171 .WasPassed()) {
172 func = aDefinition->mFormAssociatedCallbacks->mFormAssociatedCallback
173 .Value();
175 break;
177 case ElementCallbackType::eFormReset:
178 if (aDefinition->mFormAssociatedCallbacks->mFormResetCallback
179 .WasPassed()) {
180 func =
181 aDefinition->mFormAssociatedCallbacks->mFormResetCallback.Value();
183 break;
185 case ElementCallbackType::eFormDisabled:
186 if (aDefinition->mFormAssociatedCallbacks->mFormDisabledCallback
187 .WasPassed()) {
188 func = aDefinition->mFormAssociatedCallbacks->mFormDisabledCallback
189 .Value();
191 break;
193 case ElementCallbackType::eFormStateRestore:
194 if (aDefinition->mFormAssociatedCallbacks->mFormStateRestoreCallback
195 .WasPassed()) {
196 func = aDefinition->mFormAssociatedCallbacks->mFormStateRestoreCallback
197 .Value();
199 break;
201 case ElementCallbackType::eGetCustomInterface:
202 MOZ_ASSERT_UNREACHABLE("Don't call GetCustomInterface through callback");
203 break;
206 // If there is no such callback, stop.
207 if (!func) {
208 return nullptr;
211 // Add CALLBACK to ELEMENT's callback queue.
212 return MakeUnique<CustomElementCallback>(aCustomElement, aType, func, aArgs);
215 void CustomElementCallback::Call() {
216 switch (mType) {
217 case ElementCallbackType::eConnected:
218 static_cast<LifecycleConnectedCallback*>(mCallback.get())
219 ->Call(mThisObject);
220 break;
221 case ElementCallbackType::eDisconnected:
222 static_cast<LifecycleDisconnectedCallback*>(mCallback.get())
223 ->Call(mThisObject);
224 break;
225 case ElementCallbackType::eAdopted:
226 static_cast<LifecycleAdoptedCallback*>(mCallback.get())
227 ->Call(mThisObject, mArgs.mOldDocument, mArgs.mNewDocument);
228 break;
229 case ElementCallbackType::eAttributeChanged:
230 static_cast<LifecycleAttributeChangedCallback*>(mCallback.get())
231 ->Call(mThisObject, nsDependentAtomString(mArgs.mName),
232 mArgs.mOldValue, mArgs.mNewValue, mArgs.mNamespaceURI);
233 break;
234 case ElementCallbackType::eFormAssociated:
235 static_cast<LifecycleFormAssociatedCallback*>(mCallback.get())
236 ->Call(mThisObject, mArgs.mForm);
237 break;
238 case ElementCallbackType::eFormReset:
239 static_cast<LifecycleFormResetCallback*>(mCallback.get())
240 ->Call(mThisObject);
241 break;
242 case ElementCallbackType::eFormDisabled:
243 static_cast<LifecycleFormDisabledCallback*>(mCallback.get())
244 ->Call(mThisObject, mArgs.mDisabled);
245 break;
246 case ElementCallbackType::eFormStateRestore: {
247 if (mArgs.mState.IsNull()) {
248 MOZ_ASSERT_UNREACHABLE(
249 "A null state should never be restored to a form-associated "
250 "custom element");
251 return;
254 const OwningFileOrUSVStringOrFormData& owningValue = mArgs.mState.Value();
255 Nullable<FileOrUSVStringOrFormData> value;
256 if (owningValue.IsFormData()) {
257 value.SetValue().SetAsFormData() = owningValue.GetAsFormData();
258 } else if (owningValue.IsFile()) {
259 value.SetValue().SetAsFile() = owningValue.GetAsFile();
260 } else {
261 value.SetValue().SetAsUSVString().ShareOrDependUpon(
262 owningValue.GetAsUSVString());
264 static_cast<LifecycleFormStateRestoreCallback*>(mCallback.get())
265 ->Call(mThisObject, value, mArgs.mReason);
266 } break;
267 case ElementCallbackType::eGetCustomInterface:
268 MOZ_ASSERT_UNREACHABLE("Don't call GetCustomInterface through callback");
269 break;
273 void CustomElementCallback::Traverse(
274 nsCycleCollectionTraversalCallback& aCb) const {
275 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mThisObject");
276 aCb.NoteXPCOMChild(mThisObject);
278 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mCallback");
279 aCb.NoteXPCOMChild(mCallback);
282 size_t CustomElementCallback::SizeOfIncludingThis(
283 MallocSizeOf aMallocSizeOf) const {
284 size_t n = aMallocSizeOf(this);
286 // We don't uniquely own mThisObject.
288 // We own mCallback but it doesn't have any special memory reporting we can do
289 // for it other than report its own size.
290 n += aMallocSizeOf(mCallback);
292 n += mArgs.SizeOfExcludingThis(aMallocSizeOf);
294 return n;
297 CustomElementCallback::CustomElementCallback(
298 Element* aThisObject, ElementCallbackType aCallbackType,
299 mozilla::dom::CallbackFunction* aCallback,
300 const LifecycleCallbackArgs& aArgs)
301 : mThisObject(aThisObject),
302 mCallback(aCallback),
303 mType(aCallbackType),
304 mArgs(aArgs) {}
306 //-----------------------------------------------------
307 // CustomElementData
309 CustomElementData::CustomElementData(nsAtom* aType)
310 : CustomElementData(aType, CustomElementData::State::eUndefined) {}
312 CustomElementData::CustomElementData(nsAtom* aType, State aState)
313 : mState(aState), mType(aType) {}
315 void CustomElementData::SetCustomElementDefinition(
316 CustomElementDefinition* aDefinition) {
317 // Only allow reset definition to nullptr if the custom element state is
318 // "failed".
319 MOZ_ASSERT(aDefinition ? !mCustomElementDefinition
320 : mState == State::eFailed);
321 MOZ_ASSERT_IF(aDefinition, aDefinition->mType == mType);
323 mCustomElementDefinition = aDefinition;
326 void CustomElementData::AttachedInternals() {
327 MOZ_ASSERT(!mIsAttachedInternals);
329 mIsAttachedInternals = true;
332 CustomElementDefinition* CustomElementData::GetCustomElementDefinition() const {
333 // Per spec, if there is a definition, the custom element state should be
334 // either "failed" (during upgrade) or "customized".
335 MOZ_ASSERT_IF(mCustomElementDefinition, mState != State::eUndefined);
337 return mCustomElementDefinition;
340 bool CustomElementData::IsFormAssociated() const {
341 // https://html.spec.whatwg.org/#form-associated-custom-element
342 return mCustomElementDefinition &&
343 !mCustomElementDefinition->IsCustomBuiltIn() &&
344 mCustomElementDefinition->mFormAssociated;
347 void CustomElementData::Traverse(
348 nsCycleCollectionTraversalCallback& aCb) const {
349 for (uint32_t i = 0; i < mReactionQueue.Length(); i++) {
350 if (mReactionQueue[i]) {
351 mReactionQueue[i]->Traverse(aCb);
355 if (mCustomElementDefinition) {
356 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mCustomElementDefinition");
357 aCb.NoteNativeChild(
358 mCustomElementDefinition,
359 NS_CYCLE_COLLECTION_PARTICIPANT(CustomElementDefinition));
362 if (mElementInternals) {
363 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mElementInternals");
364 aCb.NoteXPCOMChild(ToSupports(mElementInternals.get()));
368 void CustomElementData::Unlink() {
369 mReactionQueue.Clear();
370 if (mElementInternals) {
371 mElementInternals->Unlink();
372 mElementInternals = nullptr;
374 mCustomElementDefinition = nullptr;
377 size_t CustomElementData::SizeOfIncludingThis(
378 MallocSizeOf aMallocSizeOf) const {
379 size_t n = aMallocSizeOf(this);
381 n += mReactionQueue.ShallowSizeOfExcludingThis(aMallocSizeOf);
383 for (auto& reaction : mReactionQueue) {
384 // "reaction" can be null if we're being called indirectly from
385 // InvokeReactions (e.g. due to a reaction causing a memory report to be
386 // captured somehow).
387 if (reaction) {
388 n += reaction->SizeOfIncludingThis(aMallocSizeOf);
392 return n;
395 //-----------------------------------------------------
396 // CustomElementRegistry
398 namespace {
400 class MOZ_RAII AutoConstructionStackEntry final {
401 public:
402 AutoConstructionStackEntry(nsTArray<RefPtr<Element>>& aStack,
403 Element* aElement)
404 : mStack(aStack) {
405 MOZ_ASSERT(aElement->IsHTMLElement() || aElement->IsXULElement());
407 #ifdef DEBUG
408 mIndex = mStack.Length();
409 #endif
410 mStack.AppendElement(aElement);
413 ~AutoConstructionStackEntry() {
414 MOZ_ASSERT(mIndex == mStack.Length() - 1,
415 "Removed element should be the last element");
416 mStack.RemoveLastElement();
419 private:
420 nsTArray<RefPtr<Element>>& mStack;
421 #ifdef DEBUG
422 uint32_t mIndex;
423 #endif
426 } // namespace
428 NS_IMPL_CYCLE_COLLECTION_CLASS(CustomElementRegistry)
430 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CustomElementRegistry)
431 tmp->mConstructors.clear();
432 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCustomDefinitions)
433 NS_IMPL_CYCLE_COLLECTION_UNLINK(mWhenDefinedPromiseMap)
434 NS_IMPL_CYCLE_COLLECTION_UNLINK(mElementCreationCallbacks)
435 NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
436 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
437 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
439 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CustomElementRegistry)
440 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCustomDefinitions)
441 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWhenDefinedPromiseMap)
442 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElementCreationCallbacks)
443 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
444 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
446 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CustomElementRegistry)
447 for (auto iter = tmp->mConstructors.iter(); !iter.done(); iter.next()) {
448 aCallbacks.Trace(&iter.get().mutableKey(), "mConstructors key", aClosure);
450 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
451 NS_IMPL_CYCLE_COLLECTION_TRACE_END
453 NS_IMPL_CYCLE_COLLECTING_ADDREF(CustomElementRegistry)
454 NS_IMPL_CYCLE_COLLECTING_RELEASE(CustomElementRegistry)
456 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CustomElementRegistry)
457 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
458 NS_INTERFACE_MAP_ENTRY(nsISupports)
459 NS_INTERFACE_MAP_END
461 CustomElementRegistry::CustomElementRegistry(nsPIDOMWindowInner* aWindow)
462 : mWindow(aWindow), mIsCustomDefinitionRunning(false) {
463 MOZ_ASSERT(aWindow);
465 mozilla::HoldJSObjects(this);
468 CustomElementRegistry::~CustomElementRegistry() {
469 mozilla::DropJSObjects(this);
472 NS_IMETHODIMP
473 CustomElementRegistry::RunCustomElementCreationCallback::Run() {
474 ErrorResult er;
475 nsDependentAtomString value(mAtom);
476 mCallback->Call(value, er);
477 MOZ_ASSERT(NS_SUCCEEDED(er.StealNSResult()),
478 "chrome JavaScript error in the callback.");
480 RefPtr<CustomElementDefinition> definition =
481 mRegistry->mCustomDefinitions.Get(mAtom);
482 if (!definition) {
483 // Callback should set the definition of the type.
484 MOZ_DIAGNOSTIC_ASSERT(false,
485 "Callback should set the definition of the type.");
486 return NS_ERROR_FAILURE;
489 MOZ_ASSERT(!mRegistry->mElementCreationCallbacks.GetWeak(mAtom),
490 "Callback should be removed.");
492 mozilla::UniquePtr<nsTHashSet<RefPtr<nsIWeakReference>>> elements;
493 mRegistry->mElementCreationCallbacksUpgradeCandidatesMap.Remove(mAtom,
494 &elements);
495 MOZ_ASSERT(elements, "There should be a list");
497 for (const auto& key : *elements) {
498 nsCOMPtr<Element> elem = do_QueryReferent(key);
499 if (!elem) {
500 continue;
503 CustomElementRegistry::Upgrade(elem, definition, er);
504 MOZ_ASSERT(NS_SUCCEEDED(er.StealNSResult()),
505 "chrome JavaScript error in custom element construction.");
508 return NS_OK;
511 CustomElementDefinition* CustomElementRegistry::LookupCustomElementDefinition(
512 nsAtom* aNameAtom, int32_t aNameSpaceID, nsAtom* aTypeAtom) {
513 CustomElementDefinition* data = mCustomDefinitions.GetWeak(aTypeAtom);
515 if (!data) {
516 RefPtr<CustomElementCreationCallback> callback;
517 mElementCreationCallbacks.Get(aTypeAtom, getter_AddRefs(callback));
518 if (callback) {
519 mElementCreationCallbacks.Remove(aTypeAtom);
520 mElementCreationCallbacksUpgradeCandidatesMap.GetOrInsertNew(aTypeAtom);
521 RefPtr<Runnable> runnable =
522 new RunCustomElementCreationCallback(this, aTypeAtom, callback);
523 nsContentUtils::AddScriptRunner(runnable.forget());
524 data = mCustomDefinitions.GetWeak(aTypeAtom);
528 if (data && data->mLocalName == aNameAtom &&
529 data->mNamespaceID == aNameSpaceID) {
530 return data;
533 return nullptr;
536 CustomElementDefinition* CustomElementRegistry::LookupCustomElementDefinition(
537 JSContext* aCx, JSObject* aConstructor) const {
538 // We're looking up things that tested true for JS::IsConstructor,
539 // so doing a CheckedUnwrapStatic is fine here.
540 JS::Rooted<JSObject*> constructor(aCx, js::CheckedUnwrapStatic(aConstructor));
542 const auto& ptr = mConstructors.lookup(constructor);
543 if (!ptr) {
544 return nullptr;
547 CustomElementDefinition* definition =
548 mCustomDefinitions.GetWeak(ptr->value());
549 MOZ_ASSERT(definition, "Definition must be found in mCustomDefinitions");
551 return definition;
554 void CustomElementRegistry::RegisterUnresolvedElement(Element* aElement,
555 nsAtom* aTypeName) {
556 // We don't have a use-case for a Custom Element inside NAC, and continuing
557 // here causes performance issues for NAC + XBL anonymous content.
558 if (aElement->IsInNativeAnonymousSubtree()) {
559 return;
562 mozilla::dom::NodeInfo* info = aElement->NodeInfo();
564 // Candidate may be a custom element through extension,
565 // in which case the custom element type name will not
566 // match the element tag name. e.g. <button is="x-button">.
567 RefPtr<nsAtom> typeName = aTypeName;
568 if (!typeName) {
569 typeName = info->NameAtom();
572 if (mCustomDefinitions.GetWeak(typeName)) {
573 return;
576 nsTHashSet<RefPtr<nsIWeakReference>>* unresolved =
577 mCandidatesMap.GetOrInsertNew(typeName);
578 nsWeakPtr elem = do_GetWeakReference(aElement);
579 unresolved->Insert(elem);
582 void CustomElementRegistry::UnregisterUnresolvedElement(Element* aElement,
583 nsAtom* aTypeName) {
584 nsIWeakReference* weak = aElement->GetExistingWeakReference();
585 if (!weak) {
586 return;
589 #ifdef DEBUG
591 nsWeakPtr weakPtr = do_GetWeakReference(aElement);
592 MOZ_ASSERT(
593 weak == weakPtr.get(),
594 "do_GetWeakReference should reuse the existing nsIWeakReference.");
596 #endif
598 nsTHashSet<RefPtr<nsIWeakReference>>* candidates = nullptr;
599 if (mCandidatesMap.Get(aTypeName, &candidates)) {
600 MOZ_ASSERT(candidates);
601 candidates->Remove(weak);
605 // https://html.spec.whatwg.org/commit-snapshots/65f39c6fc0efa92b0b2b23b93197016af6ac0de6/#enqueue-a-custom-element-callback-reaction
606 /* static */
607 void CustomElementRegistry::EnqueueLifecycleCallback(
608 ElementCallbackType aType, Element* aCustomElement,
609 const LifecycleCallbackArgs& aArgs, CustomElementDefinition* aDefinition) {
610 CustomElementDefinition* definition = aDefinition;
611 if (!definition) {
612 definition = aCustomElement->GetCustomElementDefinition();
613 if (!definition ||
614 definition->mLocalName != aCustomElement->NodeInfo()->NameAtom()) {
615 return;
618 if (!definition->mCallbacks && !definition->mFormAssociatedCallbacks) {
619 // definition has been unlinked. Don't try to mess with it.
620 return;
624 auto callback =
625 CustomElementCallback::Create(aType, aCustomElement, aArgs, definition);
626 if (!callback) {
627 return;
630 DocGroup* docGroup = aCustomElement->OwnerDoc()->GetDocGroup();
631 if (!docGroup) {
632 return;
635 if (aType == ElementCallbackType::eAttributeChanged) {
636 if (!definition->mObservedAttributes.Contains(aArgs.mName)) {
637 return;
641 CustomElementReactionsStack* reactionsStack =
642 docGroup->CustomElementReactionsStack();
643 reactionsStack->EnqueueCallbackReaction(aCustomElement, std::move(callback));
646 namespace {
648 class CandidateFinder {
649 public:
650 CandidateFinder(nsTHashSet<RefPtr<nsIWeakReference>>& aCandidates,
651 Document* aDoc);
652 nsTArray<nsCOMPtr<Element>> OrderedCandidates();
654 private:
655 nsCOMPtr<Document> mDoc;
656 nsInterfaceHashtable<nsPtrHashKey<Element>, Element> mCandidates;
659 CandidateFinder::CandidateFinder(
660 nsTHashSet<RefPtr<nsIWeakReference>>& aCandidates, Document* aDoc)
661 : mDoc(aDoc), mCandidates(aCandidates.Count()) {
662 MOZ_ASSERT(mDoc);
663 for (const auto& candidate : aCandidates) {
664 nsCOMPtr<Element> elem = do_QueryReferent(candidate);
665 if (!elem) {
666 continue;
669 Element* key = elem.get();
670 mCandidates.InsertOrUpdate(key, elem.forget());
674 nsTArray<nsCOMPtr<Element>> CandidateFinder::OrderedCandidates() {
675 if (mCandidates.Count() == 1) {
676 // Fast path for one candidate.
677 auto iter = mCandidates.Iter();
678 nsTArray<nsCOMPtr<Element>> rval({std::move(iter.Data())});
679 iter.Remove();
680 return rval;
683 nsTArray<nsCOMPtr<Element>> orderedElements(mCandidates.Count());
684 for (nsINode* node : ShadowIncludingTreeIterator(*mDoc)) {
685 Element* element = Element::FromNode(node);
686 if (!element) {
687 continue;
690 nsCOMPtr<Element> elem;
691 if (mCandidates.Remove(element, getter_AddRefs(elem))) {
692 orderedElements.AppendElement(std::move(elem));
693 if (mCandidates.Count() == 0) {
694 break;
699 return orderedElements;
702 } // namespace
704 void CustomElementRegistry::UpgradeCandidates(
705 nsAtom* aKey, CustomElementDefinition* aDefinition, ErrorResult& aRv) {
706 DocGroup* docGroup = mWindow->GetDocGroup();
707 if (!docGroup) {
708 aRv.Throw(NS_ERROR_UNEXPECTED);
709 return;
712 mozilla::UniquePtr<nsTHashSet<RefPtr<nsIWeakReference>>> candidates;
713 if (mCandidatesMap.Remove(aKey, &candidates)) {
714 MOZ_ASSERT(candidates);
715 CustomElementReactionsStack* reactionsStack =
716 docGroup->CustomElementReactionsStack();
718 CandidateFinder finder(*candidates, mWindow->GetExtantDoc());
719 for (auto& elem : finder.OrderedCandidates()) {
720 reactionsStack->EnqueueUpgradeReaction(elem, aDefinition);
725 JSObject* CustomElementRegistry::WrapObject(JSContext* aCx,
726 JS::Handle<JSObject*> aGivenProto) {
727 return CustomElementRegistry_Binding::Wrap(aCx, this, aGivenProto);
730 nsISupports* CustomElementRegistry::GetParentObject() const { return mWindow; }
732 DocGroup* CustomElementRegistry::GetDocGroup() const {
733 return mWindow ? mWindow->GetDocGroup() : nullptr;
736 int32_t CustomElementRegistry::InferNamespace(
737 JSContext* aCx, JS::Handle<JSObject*> constructor) {
738 JS::Rooted<JSObject*> XULConstructor(
739 aCx, XULElement_Binding::GetConstructorObjectHandle(aCx));
741 JS::Rooted<JSObject*> proto(aCx, constructor);
742 while (proto) {
743 if (proto == XULConstructor) {
744 return kNameSpaceID_XUL;
747 JS_GetPrototype(aCx, proto, &proto);
750 return kNameSpaceID_XHTML;
753 bool CustomElementRegistry::JSObjectToAtomArray(
754 JSContext* aCx, JS::Handle<JSObject*> aConstructor, const nsString& aName,
755 nsTArray<RefPtr<nsAtom>>& aArray, ErrorResult& aRv) {
756 JS::Rooted<JS::Value> iterable(aCx, JS::UndefinedValue());
757 if (!JS_GetUCProperty(aCx, aConstructor, aName.get(), aName.Length(),
758 &iterable)) {
759 aRv.NoteJSContextException(aCx);
760 return false;
763 if (!iterable.isUndefined()) {
764 if (!iterable.isObject()) {
765 aRv.ThrowTypeError<MSG_CONVERSION_ERROR>(NS_ConvertUTF16toUTF8(aName),
766 "sequence");
767 return false;
770 JS::ForOfIterator iter(aCx);
771 if (!iter.init(iterable, JS::ForOfIterator::AllowNonIterable)) {
772 aRv.NoteJSContextException(aCx);
773 return false;
776 if (!iter.valueIsIterable()) {
777 aRv.ThrowTypeError<MSG_CONVERSION_ERROR>(NS_ConvertUTF16toUTF8(aName),
778 "sequence");
779 return false;
782 JS::Rooted<JS::Value> attribute(aCx);
783 while (true) {
784 bool done;
785 if (!iter.next(&attribute, &done)) {
786 aRv.NoteJSContextException(aCx);
787 return false;
789 if (done) {
790 break;
793 nsAutoString attrStr;
794 if (!ConvertJSValueToString(aCx, attribute, eStringify, eStringify,
795 attrStr)) {
796 aRv.NoteJSContextException(aCx);
797 return false;
800 // XXX(Bug 1631371) Check if this should use a fallible operation as it
801 // pretended earlier.
802 aArray.AppendElement(NS_Atomize(attrStr));
806 return true;
809 // https://html.spec.whatwg.org/commit-snapshots/b48bb2238269d90ea4f455a52cdf29505aff3df0/#dom-customelementregistry-define
810 void CustomElementRegistry::Define(
811 JSContext* aCx, const nsAString& aName,
812 CustomElementConstructor& aFunctionConstructor,
813 const ElementDefinitionOptions& aOptions, ErrorResult& aRv) {
814 JS::Rooted<JSObject*> constructor(aCx, aFunctionConstructor.CallableOrNull());
816 // We need to do a dynamic unwrap in order to throw the right exception. We
817 // could probably avoid that if we just threw MSG_NOT_CONSTRUCTOR if unwrap
818 // fails.
820 // In any case, aCx represents the global we want to be using for the unwrap
821 // here.
822 JS::Rooted<JSObject*> constructorUnwrapped(
823 aCx, js::CheckedUnwrapDynamic(constructor, aCx));
824 if (!constructorUnwrapped) {
825 // If the caller's compartment does not have permission to access the
826 // unwrapped constructor then throw.
827 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
828 return;
832 * 1. If IsConstructor(constructor) is false, then throw a TypeError and abort
833 * these steps.
835 if (!JS::IsConstructor(constructorUnwrapped)) {
836 aRv.ThrowTypeError<MSG_NOT_CONSTRUCTOR>("Argument 2");
837 return;
840 int32_t nameSpaceID = InferNamespace(aCx, constructor);
843 * 2. If name is not a valid custom element name, then throw a "SyntaxError"
844 * DOMException and abort these steps.
846 Document* doc = mWindow->GetExtantDoc();
847 RefPtr<nsAtom> nameAtom(NS_Atomize(aName));
848 if (!nsContentUtils::IsCustomElementName(nameAtom, nameSpaceID)) {
849 aRv.ThrowSyntaxError(
850 nsPrintfCString("'%s' is not a valid custom element name",
851 NS_ConvertUTF16toUTF8(aName).get()));
852 return;
856 * 3. If this CustomElementRegistry contains an entry with name name, then
857 * throw a "NotSupportedError" DOMException and abort these steps.
859 if (mCustomDefinitions.GetWeak(nameAtom)) {
860 aRv.ThrowNotSupportedError(
861 nsPrintfCString("'%s' has already been defined as a custom element",
862 NS_ConvertUTF16toUTF8(aName).get()));
863 return;
867 * 4. If this CustomElementRegistry contains an entry with constructor
868 * constructor, then throw a "NotSupportedError" DOMException and abort these
869 * steps.
871 const auto& ptr = mConstructors.lookup(constructorUnwrapped);
872 if (ptr) {
873 MOZ_ASSERT(mCustomDefinitions.GetWeak(ptr->value()),
874 "Definition must be found in mCustomDefinitions");
875 nsAutoCString name;
876 ptr->value()->ToUTF8String(name);
877 aRv.ThrowNotSupportedError(
878 nsPrintfCString("'%s' and '%s' have the same constructor",
879 NS_ConvertUTF16toUTF8(aName).get(), name.get()));
880 return;
884 * 5. Let localName be name.
885 * 6. Let extends be the value of the extends member of options, or null if
886 * no such member exists.
887 * 7. If extends is not null, then:
888 * 1. If extends is a valid custom element name, then throw a
889 * "NotSupportedError" DOMException.
890 * 2. If the element interface for extends and the HTML namespace is
891 * HTMLUnknownElement (e.g., if extends does not indicate an element
892 * definition in this specification), then throw a "NotSupportedError"
893 * DOMException.
894 * 3. Set localName to extends.
896 * Special note for XUL elements:
898 * For step 7.1, we'll subject XUL to the same rules as HTML, so that a
899 * custom built-in element will not be extending from a dashed name.
900 * Step 7.2 is disregarded. But, we do check if the name is a dashed name
901 * (i.e. step 2) given that there is no reason for a custom built-in element
902 * type to take on a non-dashed name.
903 * This also ensures the name of the built-in custom element type can never
904 * be the same as the built-in element name, so we don't break the assumption
905 * elsewhere.
907 nsAutoString localName(aName);
908 if (aOptions.mExtends.WasPassed()) {
909 doc->SetUseCounter(eUseCounter_custom_CustomizedBuiltin);
911 RefPtr<nsAtom> extendsAtom(NS_Atomize(aOptions.mExtends.Value()));
912 if (nsContentUtils::IsCustomElementName(extendsAtom, kNameSpaceID_XHTML)) {
913 aRv.ThrowNotSupportedError(
914 nsPrintfCString("'%s' cannot extend a custom element",
915 NS_ConvertUTF16toUTF8(aName).get()));
916 return;
919 if (nameSpaceID == kNameSpaceID_XHTML) {
920 // bgsound and multicol are unknown html element.
921 int32_t tag = nsHTMLTags::CaseSensitiveAtomTagToId(extendsAtom);
922 if (tag == eHTMLTag_userdefined || tag == eHTMLTag_bgsound ||
923 tag == eHTMLTag_multicol) {
924 aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
925 return;
927 } else { // kNameSpaceID_XUL
928 // As stated above, ensure the name of the customized built-in element
929 // (the one that goes to the |is| attribute) is a dashed name.
930 if (!nsContentUtils::IsNameWithDash(nameAtom)) {
931 aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
932 return;
936 localName.Assign(aOptions.mExtends.Value());
940 * 8. If this CustomElementRegistry's element definition is running flag is
941 * set, then throw a "NotSupportedError" DOMException and abort these steps.
943 if (mIsCustomDefinitionRunning) {
944 aRv.ThrowNotSupportedError(
945 "Cannot define a custom element while defining another custom element");
946 return;
949 auto callbacksHolder = MakeUnique<LifecycleCallbacks>();
950 auto formAssociatedCallbacksHolder =
951 MakeUnique<FormAssociatedLifecycleCallbacks>();
952 nsTArray<RefPtr<nsAtom>> observedAttributes;
953 AutoTArray<RefPtr<nsAtom>, 2> disabledFeatures;
954 bool formAssociated = false;
955 bool disableInternals = false;
956 bool disableShadow = false;
957 { // Set mIsCustomDefinitionRunning.
959 * 9. Set this CustomElementRegistry's element definition is running flag.
961 AutoRestore<bool> restoreRunning(mIsCustomDefinitionRunning);
962 mIsCustomDefinitionRunning = true;
965 * 14.1. Let prototype be Get(constructor, "prototype"). Rethrow any
966 * exceptions.
968 // The .prototype on the constructor passed could be an "expando" of a
969 // wrapper. So we should get it from wrapper instead of the underlying
970 // object.
971 JS::Rooted<JS::Value> prototype(aCx);
972 if (!JS_GetProperty(aCx, constructor, "prototype", &prototype)) {
973 aRv.NoteJSContextException(aCx);
974 return;
978 * 14.2. If Type(prototype) is not Object, then throw a TypeError exception.
980 if (!prototype.isObject()) {
981 aRv.ThrowTypeError<MSG_NOT_OBJECT>("constructor.prototype");
982 return;
986 * 14.3. Let lifecycleCallbacks be a map with the four keys
987 * "connectedCallback", "disconnectedCallback", "adoptedCallback", and
988 * "attributeChangedCallback", each of which belongs to an entry whose
989 * value is null. The 'getCustomInterface' callback is also included
990 * for chrome usage.
991 * 14.4. For each of the four keys callbackName in lifecycleCallbacks:
992 * 1. Let callbackValue be Get(prototype, callbackName). Rethrow any
993 * exceptions.
994 * 2. If callbackValue is not undefined, then set the value of the
995 * entry in lifecycleCallbacks with key callbackName to the result
996 * of converting callbackValue to the Web IDL Function callback
997 * type. Rethrow any exceptions from the conversion.
999 if (!callbacksHolder->Init(aCx, prototype)) {
1000 aRv.NoteJSContextException(aCx);
1001 return;
1005 * 14.5. If the value of the entry in lifecycleCallbacks with key
1006 * "attributeChangedCallback" is not null, then:
1007 * 1. Let observedAttributesIterable be Get(constructor,
1008 * "observedAttributes"). Rethrow any exceptions.
1009 * 2. If observedAttributesIterable is not undefined, then set
1010 * observedAttributes to the result of converting
1011 * observedAttributesIterable to a sequence<DOMString>. Rethrow
1012 * any exceptions from the conversion.
1014 if (callbacksHolder->mAttributeChangedCallback.WasPassed()) {
1015 if (!JSObjectToAtomArray(aCx, constructor, u"observedAttributes"_ns,
1016 observedAttributes, aRv)) {
1017 return;
1022 * 14.6. Let disabledFeatures be an empty sequence<DOMString>.
1023 * 14.7. Let disabledFeaturesIterable be Get(constructor,
1024 * "disabledFeatures"). Rethrow any exceptions.
1025 * 14.8. If disabledFeaturesIterable is not undefined, then set
1026 * disabledFeatures to the result of converting
1027 * disabledFeaturesIterable to a sequence<DOMString>.
1028 * Rethrow any exceptions from the conversion.
1030 if (!JSObjectToAtomArray(aCx, constructor, u"disabledFeatures"_ns,
1031 disabledFeatures, aRv)) {
1032 return;
1035 // 14.9. Set disableInternals to true if disabledFeaturesSequence contains
1036 // "internals".
1037 disableInternals = disabledFeatures.Contains(
1038 static_cast<nsStaticAtom*>(nsGkAtoms::internals));
1040 // 14.10. Set disableShadow to true if disabledFeaturesSequence contains
1041 // "shadow".
1042 disableShadow = disabledFeatures.Contains(
1043 static_cast<nsStaticAtom*>(nsGkAtoms::shadow));
1045 // 14.11. Let formAssociatedValue be Get(constructor, "formAssociated").
1046 // Rethrow any exceptions.
1047 JS::Rooted<JS::Value> formAssociatedValue(aCx);
1048 if (!JS_GetProperty(aCx, constructor, "formAssociated",
1049 &formAssociatedValue)) {
1050 aRv.NoteJSContextException(aCx);
1051 return;
1054 // 14.12. Set formAssociated to the result of converting
1055 // formAssociatedValue to a boolean. Rethrow any exceptions from
1056 // the conversion.
1057 if (!ValueToPrimitive<bool, eDefault>(aCx, formAssociatedValue,
1058 "formAssociated", &formAssociated)) {
1059 aRv.NoteJSContextException(aCx);
1060 return;
1064 * 14.13. If formAssociated is true, for each of "formAssociatedCallback",
1065 * "formResetCallback", "formDisabledCallback", and
1066 * "formStateRestoreCallback" callbackName:
1067 * 1. Let callbackValue be ? Get(prototype, callbackName).
1068 * 2. If callbackValue is not undefined, then set the value of the
1069 * entry in lifecycleCallbacks with key callbackName to the result
1070 * of converting callbackValue to the Web IDL Function callback
1071 * type. Rethrow any exceptions from the conversion.
1073 if (formAssociated &&
1074 !formAssociatedCallbacksHolder->Init(aCx, prototype)) {
1075 aRv.NoteJSContextException(aCx);
1076 return;
1078 } // Unset mIsCustomDefinitionRunning
1081 * 15. Let definition be a new custom element definition with name name,
1082 * local name localName, constructor constructor, prototype prototype,
1083 * observed attributes observedAttributes, and lifecycle callbacks
1084 * lifecycleCallbacks.
1086 // Associate the definition with the custom element.
1087 RefPtr<nsAtom> localNameAtom(NS_Atomize(localName));
1090 * 16. Add definition to this CustomElementRegistry.
1092 if (!mConstructors.put(constructorUnwrapped, nameAtom)) {
1093 aRv.Throw(NS_ERROR_FAILURE);
1094 return;
1097 RefPtr<CustomElementDefinition> definition = new CustomElementDefinition(
1098 nameAtom, localNameAtom, nameSpaceID, &aFunctionConstructor,
1099 std::move(observedAttributes), std::move(callbacksHolder),
1100 std::move(formAssociatedCallbacksHolder), formAssociated,
1101 disableInternals, disableShadow);
1103 CustomElementDefinition* def = definition.get();
1104 mCustomDefinitions.InsertOrUpdate(nameAtom, std::move(definition));
1106 MOZ_ASSERT(mCustomDefinitions.Count() == mConstructors.count(),
1107 "Number of entries should be the same");
1110 * 17. 18. 19. Upgrade candidates
1112 UpgradeCandidates(nameAtom, def, aRv);
1115 * 20. If this CustomElementRegistry's when-defined promise map contains an
1116 * entry with key name:
1117 * 1. Let promise be the value of that entry.
1118 * 2. Resolve promise with undefined.
1119 * 3. Delete the entry with key name from this CustomElementRegistry's
1120 * when-defined promise map.
1122 RefPtr<Promise> promise;
1123 mWhenDefinedPromiseMap.Remove(nameAtom, getter_AddRefs(promise));
1124 if (promise) {
1125 promise->MaybeResolve(def->mConstructor);
1128 // Dispatch a "customelementdefined" event for DevTools.
1130 JSString* nameJsStr =
1131 JS_NewUCStringCopyN(aCx, aName.BeginReading(), aName.Length());
1133 JS::Rooted<JS::Value> detail(aCx, JS::StringValue(nameJsStr));
1134 RefPtr<CustomEvent> event = NS_NewDOMCustomEvent(doc, nullptr, nullptr);
1135 event->InitCustomEvent(aCx, u"customelementdefined"_ns,
1136 /* CanBubble */ true,
1137 /* Cancelable */ true, detail);
1138 event->SetTrusted(true);
1140 AsyncEventDispatcher* dispatcher =
1141 new AsyncEventDispatcher(doc, event.forget());
1142 dispatcher->mOnlyChromeDispatch = ChromeOnlyDispatch::eYes;
1144 dispatcher->PostDOMEvent();
1148 * Clean-up mElementCreationCallbacks (if it exists)
1150 mElementCreationCallbacks.Remove(nameAtom);
1153 void CustomElementRegistry::SetElementCreationCallback(
1154 const nsAString& aName, CustomElementCreationCallback& aCallback,
1155 ErrorResult& aRv) {
1156 RefPtr<nsAtom> nameAtom(NS_Atomize(aName));
1157 if (mElementCreationCallbacks.GetWeak(nameAtom) ||
1158 mCustomDefinitions.GetWeak(nameAtom)) {
1159 aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
1160 return;
1163 RefPtr<CustomElementCreationCallback> callback = &aCallback;
1165 if (mCandidatesMap.Contains(nameAtom)) {
1166 mElementCreationCallbacksUpgradeCandidatesMap.GetOrInsertNew(nameAtom);
1167 RefPtr<Runnable> runnable =
1168 new RunCustomElementCreationCallback(this, nameAtom, callback);
1169 nsContentUtils::AddScriptRunner(runnable.forget());
1170 } else {
1171 mElementCreationCallbacks.InsertOrUpdate(nameAtom, std::move(callback));
1175 void CustomElementRegistry::Upgrade(nsINode& aRoot) {
1176 for (nsINode* node : ShadowIncludingTreeIterator(aRoot)) {
1177 Element* element = Element::FromNode(node);
1178 if (!element) {
1179 continue;
1182 CustomElementData* ceData = element->GetCustomElementData();
1183 if (ceData) {
1184 NodeInfo* nodeInfo = element->NodeInfo();
1185 nsAtom* typeAtom = ceData->GetCustomElementType();
1186 CustomElementDefinition* definition =
1187 nsContentUtils::LookupCustomElementDefinition(
1188 nodeInfo->GetDocument(), nodeInfo->NameAtom(),
1189 nodeInfo->NamespaceID(), typeAtom);
1190 if (definition) {
1191 nsContentUtils::EnqueueUpgradeReaction(element, definition);
1197 void CustomElementRegistry::Get(
1198 const nsAString& aName,
1199 OwningCustomElementConstructorOrUndefined& aRetVal) {
1200 RefPtr<nsAtom> nameAtom(NS_Atomize(aName));
1201 CustomElementDefinition* data = mCustomDefinitions.GetWeak(nameAtom);
1203 if (!data) {
1204 aRetVal.SetUndefined();
1205 return;
1208 aRetVal.SetAsCustomElementConstructor() = data->mConstructor;
1211 void CustomElementRegistry::GetName(JSContext* aCx,
1212 CustomElementConstructor& aConstructor,
1213 nsAString& aResult) {
1214 CustomElementDefinition* aDefinition =
1215 LookupCustomElementDefinition(aCx, aConstructor.CallableOrNull());
1217 if (aDefinition) {
1218 aDefinition->mType->ToString(aResult);
1219 } else {
1220 aResult.SetIsVoid(true);
1224 already_AddRefed<Promise> CustomElementRegistry::WhenDefined(
1225 const nsAString& aName, ErrorResult& aRv) {
1226 // Define a function that lazily creates a Promise and perform some action on
1227 // it when creation succeeded. It's needed in multiple cases below, but not in
1228 // all of them.
1229 auto createPromise = [&](auto&& action) -> already_AddRefed<Promise> {
1230 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
1231 RefPtr<Promise> promise = Promise::Create(global, aRv);
1233 if (aRv.Failed()) {
1234 return nullptr;
1237 action(promise);
1239 return promise.forget();
1242 RefPtr<nsAtom> nameAtom(NS_Atomize(aName));
1243 Document* doc = mWindow->GetExtantDoc();
1244 uint32_t nameSpaceID =
1245 doc ? doc->GetDefaultNamespaceID() : kNameSpaceID_XHTML;
1246 if (!nsContentUtils::IsCustomElementName(nameAtom, nameSpaceID)) {
1247 aRv.ThrowSyntaxError(
1248 nsPrintfCString("'%s' is not a valid custom element name",
1249 NS_ConvertUTF16toUTF8(aName).get()));
1250 return nullptr;
1253 if (CustomElementDefinition* definition =
1254 mCustomDefinitions.GetWeak(nameAtom)) {
1255 return createPromise([&](const RefPtr<Promise>& promise) {
1256 promise->MaybeResolve(definition->mConstructor);
1260 return mWhenDefinedPromiseMap.WithEntryHandle(
1261 nameAtom, [&](auto&& entry) -> already_AddRefed<Promise> {
1262 if (!entry) {
1263 return createPromise([&entry](const RefPtr<Promise>& promise) {
1264 entry.Insert(promise);
1267 return do_AddRef(entry.Data());
1271 namespace {
1273 MOZ_CAN_RUN_SCRIPT
1274 static void DoUpgrade(Element* aElement, CustomElementDefinition* aDefinition,
1275 CustomElementConstructor* aConstructor,
1276 ErrorResult& aRv) {
1277 if (aDefinition->mDisableShadow && aElement->GetShadowRoot()) {
1278 aRv.ThrowNotSupportedError(nsPrintfCString(
1279 "Custom element upgrade to '%s' is disabled because a shadow root "
1280 "already exists",
1281 NS_ConvertUTF16toUTF8(aDefinition->mType->GetUTF16String()).get()));
1282 return;
1285 CustomElementData* data = aElement->GetCustomElementData();
1286 MOZ_ASSERT(data, "CustomElementData should exist");
1287 data->mState = CustomElementData::State::ePrecustomized;
1289 JS::Rooted<JS::Value> constructResult(RootingCx());
1290 // Rethrow the exception since it might actually throw the exception from the
1291 // upgrade steps back out to the caller of document.createElement.
1292 aConstructor->Construct(&constructResult, aRv, "Custom Element Upgrade",
1293 CallbackFunction::eRethrowExceptions);
1294 if (aRv.Failed()) {
1295 return;
1298 Element* element;
1299 // constructResult is an ObjectValue because construction with a callback
1300 // always forms the return value from a JSObject.
1301 if (NS_FAILED(UNWRAP_OBJECT(Element, &constructResult, element)) ||
1302 element != aElement) {
1303 aRv.ThrowTypeError("Custom element constructor returned a wrong element");
1304 return;
1308 } // anonymous namespace
1310 // https://html.spec.whatwg.org/commit-snapshots/2793ee4a461c6c39896395f1a45c269ea820c47e/#upgrades
1311 /* static */
1312 void CustomElementRegistry::Upgrade(Element* aElement,
1313 CustomElementDefinition* aDefinition,
1314 ErrorResult& aRv) {
1315 CustomElementData* data = aElement->GetCustomElementData();
1316 MOZ_ASSERT(data, "CustomElementData should exist");
1318 // Step 1.
1319 if (data->mState != CustomElementData::State::eUndefined) {
1320 return;
1323 // Step 2.
1324 aElement->SetCustomElementDefinition(aDefinition);
1326 // Step 3.
1327 data->mState = CustomElementData::State::eFailed;
1329 // Step 4.
1330 if (!aDefinition->mObservedAttributes.IsEmpty()) {
1331 uint32_t count = aElement->GetAttrCount();
1332 for (uint32_t i = 0; i < count; i++) {
1333 mozilla::dom::BorrowedAttrInfo info = aElement->GetAttrInfoAt(i);
1335 const nsAttrName* name = info.mName;
1336 nsAtom* attrName = name->LocalName();
1338 if (aDefinition->IsInObservedAttributeList(attrName)) {
1339 int32_t namespaceID = name->NamespaceID();
1340 nsAutoString attrValue, namespaceURI;
1341 info.mValue->ToString(attrValue);
1342 nsNameSpaceManager::GetInstance()->GetNameSpaceURI(namespaceID,
1343 namespaceURI);
1345 LifecycleCallbackArgs args;
1346 args.mName = attrName;
1347 args.mOldValue = VoidString();
1348 args.mNewValue = attrValue;
1349 args.mNamespaceURI =
1350 (namespaceURI.IsEmpty() ? VoidString() : namespaceURI);
1352 nsContentUtils::EnqueueLifecycleCallback(
1353 ElementCallbackType::eAttributeChanged, aElement, args,
1354 aDefinition);
1359 // Step 5.
1360 if (aElement->IsInComposedDoc()) {
1361 nsContentUtils::EnqueueLifecycleCallback(ElementCallbackType::eConnected,
1362 aElement, {}, aDefinition);
1365 // Step 6.
1366 AutoConstructionStackEntry acs(aDefinition->mConstructionStack, aElement);
1368 // Step 7 and step 8.
1369 DoUpgrade(aElement, aDefinition, MOZ_KnownLive(aDefinition->mConstructor),
1370 aRv);
1371 if (aRv.Failed()) {
1372 MOZ_ASSERT(data->mState == CustomElementData::State::eFailed ||
1373 data->mState == CustomElementData::State::ePrecustomized);
1374 // Spec doesn't set custom element state to failed here, but without this we
1375 // would have inconsistent state on a custom elemet that is failed to
1376 // upgrade, see https://github.com/whatwg/html/issues/6929, and
1377 // https://github.com/web-platform-tests/wpt/pull/29911 for the test.
1378 data->mState = CustomElementData::State::eFailed;
1379 aElement->SetCustomElementDefinition(nullptr);
1380 // Empty element's custom element reaction queue.
1381 data->mReactionQueue.Clear();
1382 return;
1385 // Step 9.
1386 if (data->IsFormAssociated()) {
1387 ElementInternals* internals = data->GetElementInternals();
1388 MOZ_ASSERT(internals);
1389 MOZ_ASSERT(aElement->IsHTMLElement());
1390 MOZ_ASSERT(!aDefinition->IsCustomBuiltIn());
1392 internals->UpdateFormOwner();
1395 // Step 10.
1396 data->mState = CustomElementData::State::eCustom;
1397 aElement->SetDefined(true);
1400 already_AddRefed<nsISupports> CustomElementRegistry::CallGetCustomInterface(
1401 Element* aElement, const nsIID& aIID) {
1402 MOZ_ASSERT(aElement);
1404 if (!nsContentUtils::IsChromeDoc(aElement->OwnerDoc())) {
1405 return nullptr;
1408 // Try to get our GetCustomInterfaceCallback callback.
1409 CustomElementDefinition* definition = aElement->GetCustomElementDefinition();
1410 if (!definition || !definition->mCallbacks ||
1411 !definition->mCallbacks->mGetCustomInterfaceCallback.WasPassed() ||
1412 (definition->mLocalName != aElement->NodeInfo()->NameAtom())) {
1413 return nullptr;
1415 LifecycleGetCustomInterfaceCallback* func =
1416 definition->mCallbacks->mGetCustomInterfaceCallback.Value();
1418 // Initialize a AutoJSAPI to enter the compartment of the callback.
1419 AutoJSAPI jsapi;
1420 JS::Rooted<JSObject*> funcGlobal(RootingCx(), func->CallbackGlobalOrNull());
1421 if (!funcGlobal || !jsapi.Init(funcGlobal)) {
1422 return nullptr;
1425 // Grab our JSContext.
1426 JSContext* cx = jsapi.cx();
1428 // Convert our IID to a JSValue to call our callback.
1429 JS::Rooted<JS::Value> jsiid(cx);
1430 if (!xpc::ID2JSValue(cx, aIID, &jsiid)) {
1431 return nullptr;
1434 JS::Rooted<JSObject*> customInterface(cx);
1435 func->Call(aElement, jsiid, &customInterface);
1436 if (!customInterface) {
1437 return nullptr;
1440 // Wrap our JSObject into a nsISupports through XPConnect
1441 nsCOMPtr<nsISupports> wrapper;
1442 nsresult rv = nsContentUtils::XPConnect()->WrapJSAggregatedToNative(
1443 aElement, cx, customInterface, aIID, getter_AddRefs(wrapper));
1444 if (NS_WARN_IF(NS_FAILED(rv))) {
1445 return nullptr;
1448 return wrapper.forget();
1451 void CustomElementRegistry::TraceDefinitions(JSTracer* aTrc) {
1452 for (const RefPtr<CustomElementDefinition>& definition :
1453 mCustomDefinitions.Values()) {
1454 if (definition && definition->mConstructor) {
1455 mozilla::TraceScriptHolder(definition->mConstructor, aTrc);
1460 //-----------------------------------------------------
1461 // CustomElementReactionsStack
1463 void CustomElementReactionsStack::CreateAndPushElementQueue() {
1464 MOZ_ASSERT(mRecursionDepth);
1465 MOZ_ASSERT(!mIsElementQueuePushedForCurrentRecursionDepth);
1467 // Push a new element queue onto the custom element reactions stack.
1468 mReactionsStack.AppendElement(MakeUnique<ElementQueue>());
1469 mIsElementQueuePushedForCurrentRecursionDepth = true;
1472 void CustomElementReactionsStack::PopAndInvokeElementQueue() {
1473 MOZ_ASSERT(mRecursionDepth);
1474 MOZ_ASSERT(mIsElementQueuePushedForCurrentRecursionDepth);
1475 MOZ_ASSERT(!mReactionsStack.IsEmpty(), "Reaction stack shouldn't be empty");
1477 // Pop the element queue from the custom element reactions stack,
1478 // and invoke custom element reactions in that queue.
1479 const uint32_t lastIndex = mReactionsStack.Length() - 1;
1480 ElementQueue* elementQueue = mReactionsStack.ElementAt(lastIndex).get();
1481 // Check element queue size in order to reduce function call overhead.
1482 if (!elementQueue->IsEmpty()) {
1483 // It is still not clear what error reporting will look like in custom
1484 // element, see https://github.com/w3c/webcomponents/issues/635.
1485 // We usually report the error to entry global in gecko, so just follow the
1486 // same behavior here.
1487 // This may be null if it's called from parser, see the case of
1488 // attributeChangedCallback in
1489 // https://html.spec.whatwg.org/multipage/parsing.html#create-an-element-for-the-token
1490 // In that case, the exception of callback reactions will be automatically
1491 // reported in CallSetup.
1492 nsIGlobalObject* global = GetEntryGlobal();
1493 InvokeReactions(elementQueue, MOZ_KnownLive(global));
1496 // InvokeReactions() might create other custom element reactions, but those
1497 // new reactions should be already consumed and removed at this point.
1498 MOZ_ASSERT(
1499 lastIndex == mReactionsStack.Length() - 1,
1500 "reactions created by InvokeReactions() should be consumed and removed");
1502 mReactionsStack.RemoveLastElement();
1503 mIsElementQueuePushedForCurrentRecursionDepth = false;
1506 void CustomElementReactionsStack::EnqueueUpgradeReaction(
1507 Element* aElement, CustomElementDefinition* aDefinition) {
1508 Enqueue(aElement, new CustomElementUpgradeReaction(aDefinition));
1511 void CustomElementReactionsStack::EnqueueCallbackReaction(
1512 Element* aElement,
1513 UniquePtr<CustomElementCallback> aCustomElementCallback) {
1514 Enqueue(aElement,
1515 new CustomElementCallbackReaction(std::move(aCustomElementCallback)));
1518 void CustomElementReactionsStack::Enqueue(Element* aElement,
1519 CustomElementReaction* aReaction) {
1520 CustomElementData* elementData = aElement->GetCustomElementData();
1521 MOZ_ASSERT(elementData, "CustomElementData should exist");
1523 if (mRecursionDepth) {
1524 // If the element queue is not created for current recursion depth, create
1525 // and push an element queue to reactions stack first.
1526 if (!mIsElementQueuePushedForCurrentRecursionDepth) {
1527 CreateAndPushElementQueue();
1530 MOZ_ASSERT(!mReactionsStack.IsEmpty());
1531 // Add element to the current element queue.
1532 mReactionsStack.LastElement()->AppendElement(aElement);
1533 elementData->mReactionQueue.AppendElement(aReaction);
1534 return;
1537 // If the custom element reactions stack is empty, then:
1538 // Add element to the backup element queue.
1539 MOZ_ASSERT(mReactionsStack.IsEmpty(),
1540 "custom element reactions stack should be empty");
1541 mBackupQueue.AppendElement(aElement);
1542 elementData->mReactionQueue.AppendElement(aReaction);
1544 if (mIsBackupQueueProcessing) {
1545 return;
1548 CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
1549 RefPtr<BackupQueueMicroTask> bqmt = new BackupQueueMicroTask(this);
1550 context->DispatchToMicroTask(bqmt.forget());
1553 void CustomElementReactionsStack::InvokeBackupQueue() {
1554 // Check backup queue size in order to reduce function call overhead.
1555 if (!mBackupQueue.IsEmpty()) {
1556 // Upgrade reactions won't be scheduled in backup queue and the exception of
1557 // callback reactions will be automatically reported in CallSetup.
1558 // If the reactions are invoked from backup queue (in microtask check
1559 // point), we don't need to pass global object for error reporting.
1560 InvokeReactions(&mBackupQueue, nullptr);
1562 MOZ_ASSERT(
1563 mBackupQueue.IsEmpty(),
1564 "There are still some reactions in BackupQueue not being consumed!?!");
1567 void CustomElementReactionsStack::InvokeReactions(ElementQueue* aElementQueue,
1568 nsIGlobalObject* aGlobal) {
1569 // This is used for error reporting.
1570 Maybe<AutoEntryScript> aes;
1571 if (aGlobal) {
1572 aes.emplace(aGlobal, "custom elements reaction invocation");
1575 // Note: It's possible to re-enter this method.
1576 for (uint32_t i = 0; i < aElementQueue->Length(); ++i) {
1577 Element* element = aElementQueue->ElementAt(i);
1578 // ElementQueue hold a element's strong reference, it should not be a
1579 // nullptr.
1580 MOZ_ASSERT(element);
1582 CustomElementData* elementData = element->GetCustomElementData();
1583 if (!elementData || !element->GetOwnerGlobal()) {
1584 // This happens when the document is destroyed and the element is already
1585 // unlinked, no need to fire the callbacks in this case.
1586 continue;
1589 auto& reactions = elementData->mReactionQueue;
1590 for (uint32_t j = 0; j < reactions.Length(); ++j) {
1591 // Transfer the ownership of the entry due to reentrant invocation of
1592 // this function.
1593 auto reaction(std::move(reactions.ElementAt(j)));
1594 if (reaction) {
1595 if (!aGlobal && reaction->IsUpgradeReaction()) {
1596 nsIGlobalObject* global = element->GetOwnerGlobal();
1597 MOZ_ASSERT(!aes);
1598 aes.emplace(global, "custom elements reaction invocation");
1600 ErrorResult rv;
1601 reaction->Invoke(MOZ_KnownLive(element), rv);
1602 if (aes) {
1603 JSContext* cx = aes->cx();
1604 if (rv.MaybeSetPendingException(cx)) {
1605 aes->ReportException();
1607 MOZ_ASSERT(!JS_IsExceptionPending(cx));
1608 if (!aGlobal && reaction->IsUpgradeReaction()) {
1609 aes.reset();
1612 MOZ_ASSERT(!rv.Failed());
1615 reactions.Clear();
1617 aElementQueue->Clear();
1620 //-----------------------------------------------------
1621 // CustomElementDefinition
1623 NS_IMPL_CYCLE_COLLECTION(CustomElementDefinition, mConstructor, mCallbacks,
1624 mFormAssociatedCallbacks, mConstructionStack)
1626 CustomElementDefinition::CustomElementDefinition(
1627 nsAtom* aType, nsAtom* aLocalName, int32_t aNamespaceID,
1628 CustomElementConstructor* aConstructor,
1629 nsTArray<RefPtr<nsAtom>>&& aObservedAttributes,
1630 UniquePtr<LifecycleCallbacks>&& aCallbacks,
1631 UniquePtr<FormAssociatedLifecycleCallbacks>&& aFormAssociatedCallbacks,
1632 bool aFormAssociated, bool aDisableInternals, bool aDisableShadow)
1633 : mType(aType),
1634 mLocalName(aLocalName),
1635 mNamespaceID(aNamespaceID),
1636 mConstructor(aConstructor),
1637 mObservedAttributes(std::move(aObservedAttributes)),
1638 mCallbacks(std::move(aCallbacks)),
1639 mFormAssociatedCallbacks(std::move(aFormAssociatedCallbacks)),
1640 mFormAssociated(aFormAssociated),
1641 mDisableInternals(aDisableInternals),
1642 mDisableShadow(aDisableShadow) {}
1644 } // namespace mozilla::dom