Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / base / CustomElementRegistry.cpp
blobf7732a2aa32513c02ab9b73121ea850d9f3ff3e8
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 MOZ_ASSERT(definition, "Callback should define the definition of type.");
483 MOZ_ASSERT(!mRegistry->mElementCreationCallbacks.GetWeak(mAtom),
484 "Callback should be removed.");
486 mozilla::UniquePtr<nsTHashSet<RefPtr<nsIWeakReference>>> elements;
487 mRegistry->mElementCreationCallbacksUpgradeCandidatesMap.Remove(mAtom,
488 &elements);
489 MOZ_ASSERT(elements, "There should be a list");
491 for (const auto& key : *elements) {
492 nsCOMPtr<Element> elem = do_QueryReferent(key);
493 if (!elem) {
494 continue;
497 CustomElementRegistry::Upgrade(elem, definition, er);
498 MOZ_ASSERT(NS_SUCCEEDED(er.StealNSResult()),
499 "chrome JavaScript error in custom element construction.");
502 return NS_OK;
505 CustomElementDefinition* CustomElementRegistry::LookupCustomElementDefinition(
506 nsAtom* aNameAtom, int32_t aNameSpaceID, nsAtom* aTypeAtom) {
507 CustomElementDefinition* data = mCustomDefinitions.GetWeak(aTypeAtom);
509 if (!data) {
510 RefPtr<CustomElementCreationCallback> callback;
511 mElementCreationCallbacks.Get(aTypeAtom, getter_AddRefs(callback));
512 if (callback) {
513 mElementCreationCallbacks.Remove(aTypeAtom);
514 mElementCreationCallbacksUpgradeCandidatesMap.GetOrInsertNew(aTypeAtom);
515 RefPtr<Runnable> runnable =
516 new RunCustomElementCreationCallback(this, aTypeAtom, callback);
517 nsContentUtils::AddScriptRunner(runnable.forget());
518 data = mCustomDefinitions.GetWeak(aTypeAtom);
522 if (data && data->mLocalName == aNameAtom &&
523 data->mNamespaceID == aNameSpaceID) {
524 return data;
527 return nullptr;
530 CustomElementDefinition* CustomElementRegistry::LookupCustomElementDefinition(
531 JSContext* aCx, JSObject* aConstructor) const {
532 // We're looking up things that tested true for JS::IsConstructor,
533 // so doing a CheckedUnwrapStatic is fine here.
534 JS::Rooted<JSObject*> constructor(aCx, js::CheckedUnwrapStatic(aConstructor));
536 const auto& ptr = mConstructors.lookup(constructor);
537 if (!ptr) {
538 return nullptr;
541 CustomElementDefinition* definition =
542 mCustomDefinitions.GetWeak(ptr->value());
543 MOZ_ASSERT(definition, "Definition must be found in mCustomDefinitions");
545 return definition;
548 void CustomElementRegistry::RegisterUnresolvedElement(Element* aElement,
549 nsAtom* aTypeName) {
550 // We don't have a use-case for a Custom Element inside NAC, and continuing
551 // here causes performance issues for NAC + XBL anonymous content.
552 if (aElement->IsInNativeAnonymousSubtree()) {
553 return;
556 mozilla::dom::NodeInfo* info = aElement->NodeInfo();
558 // Candidate may be a custom element through extension,
559 // in which case the custom element type name will not
560 // match the element tag name. e.g. <button is="x-button">.
561 RefPtr<nsAtom> typeName = aTypeName;
562 if (!typeName) {
563 typeName = info->NameAtom();
566 if (mCustomDefinitions.GetWeak(typeName)) {
567 return;
570 nsTHashSet<RefPtr<nsIWeakReference>>* unresolved =
571 mCandidatesMap.GetOrInsertNew(typeName);
572 nsWeakPtr elem = do_GetWeakReference(aElement);
573 unresolved->Insert(elem);
576 void CustomElementRegistry::UnregisterUnresolvedElement(Element* aElement,
577 nsAtom* aTypeName) {
578 nsIWeakReference* weak = aElement->GetExistingWeakReference();
579 if (!weak) {
580 return;
583 #ifdef DEBUG
585 nsWeakPtr weakPtr = do_GetWeakReference(aElement);
586 MOZ_ASSERT(
587 weak == weakPtr.get(),
588 "do_GetWeakReference should reuse the existing nsIWeakReference.");
590 #endif
592 nsTHashSet<RefPtr<nsIWeakReference>>* candidates = nullptr;
593 if (mCandidatesMap.Get(aTypeName, &candidates)) {
594 MOZ_ASSERT(candidates);
595 candidates->Remove(weak);
599 // https://html.spec.whatwg.org/commit-snapshots/65f39c6fc0efa92b0b2b23b93197016af6ac0de6/#enqueue-a-custom-element-callback-reaction
600 /* static */
601 void CustomElementRegistry::EnqueueLifecycleCallback(
602 ElementCallbackType aType, Element* aCustomElement,
603 const LifecycleCallbackArgs& aArgs, CustomElementDefinition* aDefinition) {
604 CustomElementDefinition* definition = aDefinition;
605 if (!definition) {
606 definition = aCustomElement->GetCustomElementDefinition();
607 if (!definition ||
608 definition->mLocalName != aCustomElement->NodeInfo()->NameAtom()) {
609 return;
612 if (!definition->mCallbacks && !definition->mFormAssociatedCallbacks) {
613 // definition has been unlinked. Don't try to mess with it.
614 return;
618 auto callback =
619 CustomElementCallback::Create(aType, aCustomElement, aArgs, definition);
620 if (!callback) {
621 return;
624 DocGroup* docGroup = aCustomElement->OwnerDoc()->GetDocGroup();
625 if (!docGroup) {
626 return;
629 if (aType == ElementCallbackType::eAttributeChanged) {
630 if (!definition->mObservedAttributes.Contains(aArgs.mName)) {
631 return;
635 CustomElementReactionsStack* reactionsStack =
636 docGroup->CustomElementReactionsStack();
637 reactionsStack->EnqueueCallbackReaction(aCustomElement, std::move(callback));
640 namespace {
642 class CandidateFinder {
643 public:
644 CandidateFinder(nsTHashSet<RefPtr<nsIWeakReference>>& aCandidates,
645 Document* aDoc);
646 nsTArray<nsCOMPtr<Element>> OrderedCandidates();
648 private:
649 nsCOMPtr<Document> mDoc;
650 nsInterfaceHashtable<nsPtrHashKey<Element>, Element> mCandidates;
653 CandidateFinder::CandidateFinder(
654 nsTHashSet<RefPtr<nsIWeakReference>>& aCandidates, Document* aDoc)
655 : mDoc(aDoc), mCandidates(aCandidates.Count()) {
656 MOZ_ASSERT(mDoc);
657 for (const auto& candidate : aCandidates) {
658 nsCOMPtr<Element> elem = do_QueryReferent(candidate);
659 if (!elem) {
660 continue;
663 Element* key = elem.get();
664 mCandidates.InsertOrUpdate(key, elem.forget());
668 nsTArray<nsCOMPtr<Element>> CandidateFinder::OrderedCandidates() {
669 if (mCandidates.Count() == 1) {
670 // Fast path for one candidate.
671 auto iter = mCandidates.Iter();
672 nsTArray<nsCOMPtr<Element>> rval({std::move(iter.Data())});
673 iter.Remove();
674 return rval;
677 nsTArray<nsCOMPtr<Element>> orderedElements(mCandidates.Count());
678 for (nsINode* node : ShadowIncludingTreeIterator(*mDoc)) {
679 Element* element = Element::FromNode(node);
680 if (!element) {
681 continue;
684 nsCOMPtr<Element> elem;
685 if (mCandidates.Remove(element, getter_AddRefs(elem))) {
686 orderedElements.AppendElement(std::move(elem));
687 if (mCandidates.Count() == 0) {
688 break;
693 return orderedElements;
696 } // namespace
698 void CustomElementRegistry::UpgradeCandidates(
699 nsAtom* aKey, CustomElementDefinition* aDefinition, ErrorResult& aRv) {
700 DocGroup* docGroup = mWindow->GetDocGroup();
701 if (!docGroup) {
702 aRv.Throw(NS_ERROR_UNEXPECTED);
703 return;
706 mozilla::UniquePtr<nsTHashSet<RefPtr<nsIWeakReference>>> candidates;
707 if (mCandidatesMap.Remove(aKey, &candidates)) {
708 MOZ_ASSERT(candidates);
709 CustomElementReactionsStack* reactionsStack =
710 docGroup->CustomElementReactionsStack();
712 CandidateFinder finder(*candidates, mWindow->GetExtantDoc());
713 for (auto& elem : finder.OrderedCandidates()) {
714 reactionsStack->EnqueueUpgradeReaction(elem, aDefinition);
719 JSObject* CustomElementRegistry::WrapObject(JSContext* aCx,
720 JS::Handle<JSObject*> aGivenProto) {
721 return CustomElementRegistry_Binding::Wrap(aCx, this, aGivenProto);
724 nsISupports* CustomElementRegistry::GetParentObject() const { return mWindow; }
726 DocGroup* CustomElementRegistry::GetDocGroup() const {
727 return mWindow ? mWindow->GetDocGroup() : nullptr;
730 int32_t CustomElementRegistry::InferNamespace(
731 JSContext* aCx, JS::Handle<JSObject*> constructor) {
732 JS::Rooted<JSObject*> XULConstructor(
733 aCx, XULElement_Binding::GetConstructorObject(aCx));
735 JS::Rooted<JSObject*> proto(aCx, constructor);
736 while (proto) {
737 if (proto == XULConstructor) {
738 return kNameSpaceID_XUL;
741 JS_GetPrototype(aCx, proto, &proto);
744 return kNameSpaceID_XHTML;
747 bool CustomElementRegistry::JSObjectToAtomArray(
748 JSContext* aCx, JS::Handle<JSObject*> aConstructor, const nsString& aName,
749 nsTArray<RefPtr<nsAtom>>& aArray, ErrorResult& aRv) {
750 JS::Rooted<JS::Value> iterable(aCx, JS::UndefinedValue());
751 if (!JS_GetUCProperty(aCx, aConstructor, aName.get(), aName.Length(),
752 &iterable)) {
753 aRv.NoteJSContextException(aCx);
754 return false;
757 if (!iterable.isUndefined()) {
758 if (!iterable.isObject()) {
759 aRv.ThrowTypeError<MSG_CONVERSION_ERROR>(NS_ConvertUTF16toUTF8(aName),
760 "sequence");
761 return false;
764 JS::ForOfIterator iter(aCx);
765 if (!iter.init(iterable, JS::ForOfIterator::AllowNonIterable)) {
766 aRv.NoteJSContextException(aCx);
767 return false;
770 if (!iter.valueIsIterable()) {
771 aRv.ThrowTypeError<MSG_CONVERSION_ERROR>(NS_ConvertUTF16toUTF8(aName),
772 "sequence");
773 return false;
776 JS::Rooted<JS::Value> attribute(aCx);
777 while (true) {
778 bool done;
779 if (!iter.next(&attribute, &done)) {
780 aRv.NoteJSContextException(aCx);
781 return false;
783 if (done) {
784 break;
787 nsAutoString attrStr;
788 if (!ConvertJSValueToString(aCx, attribute, eStringify, eStringify,
789 attrStr)) {
790 aRv.NoteJSContextException(aCx);
791 return false;
794 // XXX(Bug 1631371) Check if this should use a fallible operation as it
795 // pretended earlier.
796 aArray.AppendElement(NS_Atomize(attrStr));
800 return true;
803 // https://html.spec.whatwg.org/commit-snapshots/b48bb2238269d90ea4f455a52cdf29505aff3df0/#dom-customelementregistry-define
804 void CustomElementRegistry::Define(
805 JSContext* aCx, const nsAString& aName,
806 CustomElementConstructor& aFunctionConstructor,
807 const ElementDefinitionOptions& aOptions, ErrorResult& aRv) {
808 JS::Rooted<JSObject*> constructor(aCx, aFunctionConstructor.CallableOrNull());
810 // We need to do a dynamic unwrap in order to throw the right exception. We
811 // could probably avoid that if we just threw MSG_NOT_CONSTRUCTOR if unwrap
812 // fails.
814 // In any case, aCx represents the global we want to be using for the unwrap
815 // here.
816 JS::Rooted<JSObject*> constructorUnwrapped(
817 aCx, js::CheckedUnwrapDynamic(constructor, aCx));
818 if (!constructorUnwrapped) {
819 // If the caller's compartment does not have permission to access the
820 // unwrapped constructor then throw.
821 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
822 return;
826 * 1. If IsConstructor(constructor) is false, then throw a TypeError and abort
827 * these steps.
829 if (!JS::IsConstructor(constructorUnwrapped)) {
830 aRv.ThrowTypeError<MSG_NOT_CONSTRUCTOR>("Argument 2");
831 return;
834 int32_t nameSpaceID = InferNamespace(aCx, constructor);
837 * 2. If name is not a valid custom element name, then throw a "SyntaxError"
838 * DOMException and abort these steps.
840 Document* doc = mWindow->GetExtantDoc();
841 RefPtr<nsAtom> nameAtom(NS_Atomize(aName));
842 if (!nsContentUtils::IsCustomElementName(nameAtom, nameSpaceID)) {
843 aRv.ThrowSyntaxError(
844 nsPrintfCString("'%s' is not a valid custom element name",
845 NS_ConvertUTF16toUTF8(aName).get()));
846 return;
850 * 3. If this CustomElementRegistry contains an entry with name name, then
851 * throw a "NotSupportedError" DOMException and abort these steps.
853 if (mCustomDefinitions.GetWeak(nameAtom)) {
854 aRv.ThrowNotSupportedError(
855 nsPrintfCString("'%s' has already been defined as a custom element",
856 NS_ConvertUTF16toUTF8(aName).get()));
857 return;
861 * 4. If this CustomElementRegistry contains an entry with constructor
862 * constructor, then throw a "NotSupportedError" DOMException and abort these
863 * steps.
865 const auto& ptr = mConstructors.lookup(constructorUnwrapped);
866 if (ptr) {
867 MOZ_ASSERT(mCustomDefinitions.GetWeak(ptr->value()),
868 "Definition must be found in mCustomDefinitions");
869 nsAutoCString name;
870 ptr->value()->ToUTF8String(name);
871 aRv.ThrowNotSupportedError(
872 nsPrintfCString("'%s' and '%s' have the same constructor",
873 NS_ConvertUTF16toUTF8(aName).get(), name.get()));
874 return;
878 * 5. Let localName be name.
879 * 6. Let extends be the value of the extends member of options, or null if
880 * no such member exists.
881 * 7. If extends is not null, then:
882 * 1. If extends is a valid custom element name, then throw a
883 * "NotSupportedError" DOMException.
884 * 2. If the element interface for extends and the HTML namespace is
885 * HTMLUnknownElement (e.g., if extends does not indicate an element
886 * definition in this specification), then throw a "NotSupportedError"
887 * DOMException.
888 * 3. Set localName to extends.
890 * Special note for XUL elements:
892 * For step 7.1, we'll subject XUL to the same rules as HTML, so that a
893 * custom built-in element will not be extending from a dashed name.
894 * Step 7.2 is disregarded. But, we do check if the name is a dashed name
895 * (i.e. step 2) given that there is no reason for a custom built-in element
896 * type to take on a non-dashed name.
897 * This also ensures the name of the built-in custom element type can never
898 * be the same as the built-in element name, so we don't break the assumption
899 * elsewhere.
901 nsAutoString localName(aName);
902 if (aOptions.mExtends.WasPassed()) {
903 doc->SetUseCounter(eUseCounter_custom_CustomizedBuiltin);
905 RefPtr<nsAtom> extendsAtom(NS_Atomize(aOptions.mExtends.Value()));
906 if (nsContentUtils::IsCustomElementName(extendsAtom, kNameSpaceID_XHTML)) {
907 aRv.ThrowNotSupportedError(
908 nsPrintfCString("'%s' cannot extend a custom element",
909 NS_ConvertUTF16toUTF8(aName).get()));
910 return;
913 if (nameSpaceID == kNameSpaceID_XHTML) {
914 // bgsound and multicol are unknown html element.
915 int32_t tag = nsHTMLTags::CaseSensitiveAtomTagToId(extendsAtom);
916 if (tag == eHTMLTag_userdefined || tag == eHTMLTag_bgsound ||
917 tag == eHTMLTag_multicol) {
918 aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
919 return;
921 } else { // kNameSpaceID_XUL
922 // As stated above, ensure the name of the customized built-in element
923 // (the one that goes to the |is| attribute) is a dashed name.
924 if (!nsContentUtils::IsNameWithDash(nameAtom)) {
925 aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
926 return;
930 localName.Assign(aOptions.mExtends.Value());
934 * 8. If this CustomElementRegistry's element definition is running flag is
935 * set, then throw a "NotSupportedError" DOMException and abort these steps.
937 if (mIsCustomDefinitionRunning) {
938 aRv.ThrowNotSupportedError(
939 "Cannot define a custom element while defining another custom element");
940 return;
943 auto callbacksHolder = MakeUnique<LifecycleCallbacks>();
944 auto formAssociatedCallbacksHolder =
945 MakeUnique<FormAssociatedLifecycleCallbacks>();
946 nsTArray<RefPtr<nsAtom>> observedAttributes;
947 AutoTArray<RefPtr<nsAtom>, 2> disabledFeatures;
948 bool formAssociated = false;
949 bool disableInternals = false;
950 bool disableShadow = false;
951 { // Set mIsCustomDefinitionRunning.
953 * 9. Set this CustomElementRegistry's element definition is running flag.
955 AutoRestore<bool> restoreRunning(mIsCustomDefinitionRunning);
956 mIsCustomDefinitionRunning = true;
959 * 14.1. Let prototype be Get(constructor, "prototype"). Rethrow any
960 * exceptions.
962 // The .prototype on the constructor passed could be an "expando" of a
963 // wrapper. So we should get it from wrapper instead of the underlying
964 // object.
965 JS::Rooted<JS::Value> prototype(aCx);
966 if (!JS_GetProperty(aCx, constructor, "prototype", &prototype)) {
967 aRv.NoteJSContextException(aCx);
968 return;
972 * 14.2. If Type(prototype) is not Object, then throw a TypeError exception.
974 if (!prototype.isObject()) {
975 aRv.ThrowTypeError<MSG_NOT_OBJECT>("constructor.prototype");
976 return;
980 * 14.3. Let lifecycleCallbacks be a map with the four keys
981 * "connectedCallback", "disconnectedCallback", "adoptedCallback", and
982 * "attributeChangedCallback", each of which belongs to an entry whose
983 * value is null. The 'getCustomInterface' callback is also included
984 * for chrome usage.
985 * 14.4. For each of the four keys callbackName in lifecycleCallbacks:
986 * 1. Let callbackValue be Get(prototype, callbackName). Rethrow any
987 * exceptions.
988 * 2. If callbackValue is not undefined, then set the value of the
989 * entry in lifecycleCallbacks with key callbackName to the result
990 * of converting callbackValue to the Web IDL Function callback
991 * type. Rethrow any exceptions from the conversion.
993 if (!callbacksHolder->Init(aCx, prototype)) {
994 aRv.NoteJSContextException(aCx);
995 return;
999 * 14.5. If the value of the entry in lifecycleCallbacks with key
1000 * "attributeChangedCallback" is not null, then:
1001 * 1. Let observedAttributesIterable be Get(constructor,
1002 * "observedAttributes"). Rethrow any exceptions.
1003 * 2. If observedAttributesIterable is not undefined, then set
1004 * observedAttributes to the result of converting
1005 * observedAttributesIterable to a sequence<DOMString>. Rethrow
1006 * any exceptions from the conversion.
1008 if (callbacksHolder->mAttributeChangedCallback.WasPassed()) {
1009 if (!JSObjectToAtomArray(aCx, constructor, u"observedAttributes"_ns,
1010 observedAttributes, aRv)) {
1011 return;
1016 * 14.6. Let disabledFeatures be an empty sequence<DOMString>.
1017 * 14.7. Let disabledFeaturesIterable be Get(constructor,
1018 * "disabledFeatures"). Rethrow any exceptions.
1019 * 14.8. If disabledFeaturesIterable is not undefined, then set
1020 * disabledFeatures to the result of converting
1021 * disabledFeaturesIterable to a sequence<DOMString>.
1022 * Rethrow any exceptions from the conversion.
1024 if (!JSObjectToAtomArray(aCx, constructor, u"disabledFeatures"_ns,
1025 disabledFeatures, aRv)) {
1026 return;
1029 // 14.9. Set disableInternals to true if disabledFeaturesSequence contains
1030 // "internals".
1031 disableInternals = disabledFeatures.Contains(
1032 static_cast<nsStaticAtom*>(nsGkAtoms::internals));
1034 // 14.10. Set disableShadow to true if disabledFeaturesSequence contains
1035 // "shadow".
1036 disableShadow = disabledFeatures.Contains(
1037 static_cast<nsStaticAtom*>(nsGkAtoms::shadow));
1039 // 14.11. Let formAssociatedValue be Get(constructor, "formAssociated").
1040 // Rethrow any exceptions.
1041 JS::Rooted<JS::Value> formAssociatedValue(aCx);
1042 if (!JS_GetProperty(aCx, constructor, "formAssociated",
1043 &formAssociatedValue)) {
1044 aRv.NoteJSContextException(aCx);
1045 return;
1048 // 14.12. Set formAssociated to the result of converting
1049 // formAssociatedValue to a boolean. Rethrow any exceptions from
1050 // the conversion.
1051 if (!ValueToPrimitive<bool, eDefault>(aCx, formAssociatedValue,
1052 "formAssociated", &formAssociated)) {
1053 aRv.NoteJSContextException(aCx);
1054 return;
1058 * 14.13. If formAssociated is true, for each of "formAssociatedCallback",
1059 * "formResetCallback", "formDisabledCallback", and
1060 * "formStateRestoreCallback" callbackName:
1061 * 1. Let callbackValue be ? Get(prototype, callbackName).
1062 * 2. If callbackValue is not undefined, then set the value of the
1063 * entry in lifecycleCallbacks with key callbackName to the result
1064 * of converting callbackValue to the Web IDL Function callback
1065 * type. Rethrow any exceptions from the conversion.
1067 if (formAssociated &&
1068 !formAssociatedCallbacksHolder->Init(aCx, prototype)) {
1069 aRv.NoteJSContextException(aCx);
1070 return;
1072 } // Unset mIsCustomDefinitionRunning
1075 * 15. Let definition be a new custom element definition with name name,
1076 * local name localName, constructor constructor, prototype prototype,
1077 * observed attributes observedAttributes, and lifecycle callbacks
1078 * lifecycleCallbacks.
1080 // Associate the definition with the custom element.
1081 RefPtr<nsAtom> localNameAtom(NS_Atomize(localName));
1084 * 16. Add definition to this CustomElementRegistry.
1086 if (!mConstructors.put(constructorUnwrapped, nameAtom)) {
1087 aRv.Throw(NS_ERROR_FAILURE);
1088 return;
1091 RefPtr<CustomElementDefinition> definition = new CustomElementDefinition(
1092 nameAtom, localNameAtom, nameSpaceID, &aFunctionConstructor,
1093 std::move(observedAttributes), std::move(callbacksHolder),
1094 std::move(formAssociatedCallbacksHolder), formAssociated,
1095 disableInternals, disableShadow);
1097 CustomElementDefinition* def = definition.get();
1098 mCustomDefinitions.InsertOrUpdate(nameAtom, std::move(definition));
1100 MOZ_ASSERT(mCustomDefinitions.Count() == mConstructors.count(),
1101 "Number of entries should be the same");
1104 * 17. 18. 19. Upgrade candidates
1106 UpgradeCandidates(nameAtom, def, aRv);
1109 * 20. If this CustomElementRegistry's when-defined promise map contains an
1110 * entry with key name:
1111 * 1. Let promise be the value of that entry.
1112 * 2. Resolve promise with undefined.
1113 * 3. Delete the entry with key name from this CustomElementRegistry's
1114 * when-defined promise map.
1116 RefPtr<Promise> promise;
1117 mWhenDefinedPromiseMap.Remove(nameAtom, getter_AddRefs(promise));
1118 if (promise) {
1119 promise->MaybeResolve(def->mConstructor);
1122 // Dispatch a "customelementdefined" event for DevTools.
1124 JSString* nameJsStr =
1125 JS_NewUCStringCopyN(aCx, aName.BeginReading(), aName.Length());
1127 JS::Rooted<JS::Value> detail(aCx, JS::StringValue(nameJsStr));
1128 RefPtr<CustomEvent> event = NS_NewDOMCustomEvent(doc, nullptr, nullptr);
1129 event->InitCustomEvent(aCx, u"customelementdefined"_ns,
1130 /* CanBubble */ true,
1131 /* Cancelable */ true, detail);
1132 event->SetTrusted(true);
1134 AsyncEventDispatcher* dispatcher =
1135 new AsyncEventDispatcher(doc, event.forget());
1136 dispatcher->mOnlyChromeDispatch = ChromeOnlyDispatch::eYes;
1138 dispatcher->PostDOMEvent();
1142 * Clean-up mElementCreationCallbacks (if it exists)
1144 mElementCreationCallbacks.Remove(nameAtom);
1147 void CustomElementRegistry::SetElementCreationCallback(
1148 const nsAString& aName, CustomElementCreationCallback& aCallback,
1149 ErrorResult& aRv) {
1150 RefPtr<nsAtom> nameAtom(NS_Atomize(aName));
1151 if (mElementCreationCallbacks.GetWeak(nameAtom) ||
1152 mCustomDefinitions.GetWeak(nameAtom)) {
1153 aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
1154 return;
1157 RefPtr<CustomElementCreationCallback> callback = &aCallback;
1158 mElementCreationCallbacks.InsertOrUpdate(nameAtom, std::move(callback));
1161 void CustomElementRegistry::Upgrade(nsINode& aRoot) {
1162 for (nsINode* node : ShadowIncludingTreeIterator(aRoot)) {
1163 Element* element = Element::FromNode(node);
1164 if (!element) {
1165 continue;
1168 CustomElementData* ceData = element->GetCustomElementData();
1169 if (ceData) {
1170 NodeInfo* nodeInfo = element->NodeInfo();
1171 nsAtom* typeAtom = ceData->GetCustomElementType();
1172 CustomElementDefinition* definition =
1173 nsContentUtils::LookupCustomElementDefinition(
1174 nodeInfo->GetDocument(), nodeInfo->NameAtom(),
1175 nodeInfo->NamespaceID(), typeAtom);
1176 if (definition) {
1177 nsContentUtils::EnqueueUpgradeReaction(element, definition);
1183 void CustomElementRegistry::Get(
1184 const nsAString& aName,
1185 OwningCustomElementConstructorOrUndefined& aRetVal) {
1186 RefPtr<nsAtom> nameAtom(NS_Atomize(aName));
1187 CustomElementDefinition* data = mCustomDefinitions.GetWeak(nameAtom);
1189 if (!data) {
1190 aRetVal.SetUndefined();
1191 return;
1194 aRetVal.SetAsCustomElementConstructor() = data->mConstructor;
1197 void CustomElementRegistry::GetName(JSContext* aCx,
1198 CustomElementConstructor& aConstructor,
1199 nsAString& aResult) {
1200 CustomElementDefinition* aDefinition =
1201 LookupCustomElementDefinition(aCx, aConstructor.CallableOrNull());
1203 if (aDefinition) {
1204 aDefinition->mType->ToString(aResult);
1205 } else {
1206 aResult.SetIsVoid(true);
1210 already_AddRefed<Promise> CustomElementRegistry::WhenDefined(
1211 const nsAString& aName, ErrorResult& aRv) {
1212 // Define a function that lazily creates a Promise and perform some action on
1213 // it when creation succeeded. It's needed in multiple cases below, but not in
1214 // all of them.
1215 auto createPromise = [&](auto&& action) -> already_AddRefed<Promise> {
1216 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
1217 RefPtr<Promise> promise = Promise::Create(global, aRv);
1219 if (aRv.Failed()) {
1220 return nullptr;
1223 action(promise);
1225 return promise.forget();
1228 RefPtr<nsAtom> nameAtom(NS_Atomize(aName));
1229 Document* doc = mWindow->GetExtantDoc();
1230 uint32_t nameSpaceID =
1231 doc ? doc->GetDefaultNamespaceID() : kNameSpaceID_XHTML;
1232 if (!nsContentUtils::IsCustomElementName(nameAtom, nameSpaceID)) {
1233 return createPromise([](const RefPtr<Promise>& promise) {
1234 promise->MaybeReject(NS_ERROR_DOM_SYNTAX_ERR);
1238 if (CustomElementDefinition* definition =
1239 mCustomDefinitions.GetWeak(nameAtom)) {
1240 return createPromise([&](const RefPtr<Promise>& promise) {
1241 promise->MaybeResolve(definition->mConstructor);
1245 return mWhenDefinedPromiseMap.WithEntryHandle(
1246 nameAtom, [&](auto&& entry) -> already_AddRefed<Promise> {
1247 if (!entry) {
1248 return createPromise([&entry](const RefPtr<Promise>& promise) {
1249 entry.Insert(promise);
1252 return do_AddRef(entry.Data());
1256 namespace {
1258 MOZ_CAN_RUN_SCRIPT
1259 static void DoUpgrade(Element* aElement, CustomElementDefinition* aDefinition,
1260 CustomElementConstructor* aConstructor,
1261 ErrorResult& aRv) {
1262 if (aDefinition->mDisableShadow && aElement->GetShadowRoot()) {
1263 aRv.ThrowNotSupportedError(nsPrintfCString(
1264 "Custom element upgrade to '%s' is disabled because a shadow root "
1265 "already exists",
1266 NS_ConvertUTF16toUTF8(aDefinition->mType->GetUTF16String()).get()));
1267 return;
1270 CustomElementData* data = aElement->GetCustomElementData();
1271 MOZ_ASSERT(data, "CustomElementData should exist");
1272 data->mState = CustomElementData::State::ePrecustomized;
1274 JS::Rooted<JS::Value> constructResult(RootingCx());
1275 // Rethrow the exception since it might actually throw the exception from the
1276 // upgrade steps back out to the caller of document.createElement.
1277 aConstructor->Construct(&constructResult, aRv, "Custom Element Upgrade",
1278 CallbackFunction::eRethrowExceptions);
1279 if (aRv.Failed()) {
1280 return;
1283 Element* element;
1284 // constructResult is an ObjectValue because construction with a callback
1285 // always forms the return value from a JSObject.
1286 if (NS_FAILED(UNWRAP_OBJECT(Element, &constructResult, element)) ||
1287 element != aElement) {
1288 aRv.ThrowTypeError("Custom element constructor returned a wrong element");
1289 return;
1293 } // anonymous namespace
1295 // https://html.spec.whatwg.org/commit-snapshots/2793ee4a461c6c39896395f1a45c269ea820c47e/#upgrades
1296 /* static */
1297 void CustomElementRegistry::Upgrade(Element* aElement,
1298 CustomElementDefinition* aDefinition,
1299 ErrorResult& aRv) {
1300 CustomElementData* data = aElement->GetCustomElementData();
1301 MOZ_ASSERT(data, "CustomElementData should exist");
1303 // Step 1.
1304 if (data->mState != CustomElementData::State::eUndefined) {
1305 return;
1308 // Step 2.
1309 aElement->SetCustomElementDefinition(aDefinition);
1311 // Step 3.
1312 data->mState = CustomElementData::State::eFailed;
1314 // Step 4.
1315 if (!aDefinition->mObservedAttributes.IsEmpty()) {
1316 uint32_t count = aElement->GetAttrCount();
1317 for (uint32_t i = 0; i < count; i++) {
1318 mozilla::dom::BorrowedAttrInfo info = aElement->GetAttrInfoAt(i);
1320 const nsAttrName* name = info.mName;
1321 nsAtom* attrName = name->LocalName();
1323 if (aDefinition->IsInObservedAttributeList(attrName)) {
1324 int32_t namespaceID = name->NamespaceID();
1325 nsAutoString attrValue, namespaceURI;
1326 info.mValue->ToString(attrValue);
1327 nsNameSpaceManager::GetInstance()->GetNameSpaceURI(namespaceID,
1328 namespaceURI);
1330 LifecycleCallbackArgs args;
1331 args.mName = attrName;
1332 args.mOldValue = VoidString();
1333 args.mNewValue = attrValue;
1334 args.mNamespaceURI =
1335 (namespaceURI.IsEmpty() ? VoidString() : namespaceURI);
1337 nsContentUtils::EnqueueLifecycleCallback(
1338 ElementCallbackType::eAttributeChanged, aElement, args,
1339 aDefinition);
1344 // Step 5.
1345 if (aElement->IsInComposedDoc()) {
1346 nsContentUtils::EnqueueLifecycleCallback(ElementCallbackType::eConnected,
1347 aElement, {}, aDefinition);
1350 // Step 6.
1351 AutoConstructionStackEntry acs(aDefinition->mConstructionStack, aElement);
1353 // Step 7 and step 8.
1354 DoUpgrade(aElement, aDefinition, MOZ_KnownLive(aDefinition->mConstructor),
1355 aRv);
1356 if (aRv.Failed()) {
1357 MOZ_ASSERT(data->mState == CustomElementData::State::eFailed ||
1358 data->mState == CustomElementData::State::ePrecustomized);
1359 // Spec doesn't set custom element state to failed here, but without this we
1360 // would have inconsistent state on a custom elemet that is failed to
1361 // upgrade, see https://github.com/whatwg/html/issues/6929, and
1362 // https://github.com/web-platform-tests/wpt/pull/29911 for the test.
1363 data->mState = CustomElementData::State::eFailed;
1364 aElement->SetCustomElementDefinition(nullptr);
1365 // Empty element's custom element reaction queue.
1366 data->mReactionQueue.Clear();
1367 return;
1370 // Step 9.
1371 if (data->IsFormAssociated()) {
1372 ElementInternals* internals = data->GetElementInternals();
1373 MOZ_ASSERT(internals);
1374 MOZ_ASSERT(aElement->IsHTMLElement());
1375 MOZ_ASSERT(!aDefinition->IsCustomBuiltIn());
1377 internals->UpdateFormOwner();
1380 // Step 10.
1381 data->mState = CustomElementData::State::eCustom;
1382 aElement->SetDefined(true);
1385 already_AddRefed<nsISupports> CustomElementRegistry::CallGetCustomInterface(
1386 Element* aElement, const nsIID& aIID) {
1387 MOZ_ASSERT(aElement);
1389 if (!nsContentUtils::IsChromeDoc(aElement->OwnerDoc())) {
1390 return nullptr;
1393 // Try to get our GetCustomInterfaceCallback callback.
1394 CustomElementDefinition* definition = aElement->GetCustomElementDefinition();
1395 if (!definition || !definition->mCallbacks ||
1396 !definition->mCallbacks->mGetCustomInterfaceCallback.WasPassed() ||
1397 (definition->mLocalName != aElement->NodeInfo()->NameAtom())) {
1398 return nullptr;
1400 LifecycleGetCustomInterfaceCallback* func =
1401 definition->mCallbacks->mGetCustomInterfaceCallback.Value();
1403 // Initialize a AutoJSAPI to enter the compartment of the callback.
1404 AutoJSAPI jsapi;
1405 JS::Rooted<JSObject*> funcGlobal(RootingCx(), func->CallbackGlobalOrNull());
1406 if (!funcGlobal || !jsapi.Init(funcGlobal)) {
1407 return nullptr;
1410 // Grab our JSContext.
1411 JSContext* cx = jsapi.cx();
1413 // Convert our IID to a JSValue to call our callback.
1414 JS::Rooted<JS::Value> jsiid(cx);
1415 if (!xpc::ID2JSValue(cx, aIID, &jsiid)) {
1416 return nullptr;
1419 JS::Rooted<JSObject*> customInterface(cx);
1420 func->Call(aElement, jsiid, &customInterface);
1421 if (!customInterface) {
1422 return nullptr;
1425 // Wrap our JSObject into a nsISupports through XPConnect
1426 nsCOMPtr<nsISupports> wrapper;
1427 nsresult rv = nsContentUtils::XPConnect()->WrapJSAggregatedToNative(
1428 aElement, cx, customInterface, aIID, getter_AddRefs(wrapper));
1429 if (NS_WARN_IF(NS_FAILED(rv))) {
1430 return nullptr;
1433 return wrapper.forget();
1436 void CustomElementRegistry::TraceDefinitions(JSTracer* aTrc) {
1437 for (const RefPtr<CustomElementDefinition>& definition :
1438 mCustomDefinitions.Values()) {
1439 if (definition && definition->mConstructor) {
1440 mozilla::TraceScriptHolder(definition->mConstructor, aTrc);
1445 //-----------------------------------------------------
1446 // CustomElementReactionsStack
1448 void CustomElementReactionsStack::CreateAndPushElementQueue() {
1449 MOZ_ASSERT(mRecursionDepth);
1450 MOZ_ASSERT(!mIsElementQueuePushedForCurrentRecursionDepth);
1452 // Push a new element queue onto the custom element reactions stack.
1453 mReactionsStack.AppendElement(MakeUnique<ElementQueue>());
1454 mIsElementQueuePushedForCurrentRecursionDepth = true;
1457 void CustomElementReactionsStack::PopAndInvokeElementQueue() {
1458 MOZ_ASSERT(mRecursionDepth);
1459 MOZ_ASSERT(mIsElementQueuePushedForCurrentRecursionDepth);
1460 MOZ_ASSERT(!mReactionsStack.IsEmpty(), "Reaction stack shouldn't be empty");
1462 // Pop the element queue from the custom element reactions stack,
1463 // and invoke custom element reactions in that queue.
1464 const uint32_t lastIndex = mReactionsStack.Length() - 1;
1465 ElementQueue* elementQueue = mReactionsStack.ElementAt(lastIndex).get();
1466 // Check element queue size in order to reduce function call overhead.
1467 if (!elementQueue->IsEmpty()) {
1468 // It is still not clear what error reporting will look like in custom
1469 // element, see https://github.com/w3c/webcomponents/issues/635.
1470 // We usually report the error to entry global in gecko, so just follow the
1471 // same behavior here.
1472 // This may be null if it's called from parser, see the case of
1473 // attributeChangedCallback in
1474 // https://html.spec.whatwg.org/multipage/parsing.html#create-an-element-for-the-token
1475 // In that case, the exception of callback reactions will be automatically
1476 // reported in CallSetup.
1477 nsIGlobalObject* global = GetEntryGlobal();
1478 InvokeReactions(elementQueue, MOZ_KnownLive(global));
1481 // InvokeReactions() might create other custom element reactions, but those
1482 // new reactions should be already consumed and removed at this point.
1483 MOZ_ASSERT(
1484 lastIndex == mReactionsStack.Length() - 1,
1485 "reactions created by InvokeReactions() should be consumed and removed");
1487 mReactionsStack.RemoveLastElement();
1488 mIsElementQueuePushedForCurrentRecursionDepth = false;
1491 void CustomElementReactionsStack::EnqueueUpgradeReaction(
1492 Element* aElement, CustomElementDefinition* aDefinition) {
1493 Enqueue(aElement, new CustomElementUpgradeReaction(aDefinition));
1496 void CustomElementReactionsStack::EnqueueCallbackReaction(
1497 Element* aElement,
1498 UniquePtr<CustomElementCallback> aCustomElementCallback) {
1499 Enqueue(aElement,
1500 new CustomElementCallbackReaction(std::move(aCustomElementCallback)));
1503 void CustomElementReactionsStack::Enqueue(Element* aElement,
1504 CustomElementReaction* aReaction) {
1505 CustomElementData* elementData = aElement->GetCustomElementData();
1506 MOZ_ASSERT(elementData, "CustomElementData should exist");
1508 if (mRecursionDepth) {
1509 // If the element queue is not created for current recursion depth, create
1510 // and push an element queue to reactions stack first.
1511 if (!mIsElementQueuePushedForCurrentRecursionDepth) {
1512 CreateAndPushElementQueue();
1515 MOZ_ASSERT(!mReactionsStack.IsEmpty());
1516 // Add element to the current element queue.
1517 mReactionsStack.LastElement()->AppendElement(aElement);
1518 elementData->mReactionQueue.AppendElement(aReaction);
1519 return;
1522 // If the custom element reactions stack is empty, then:
1523 // Add element to the backup element queue.
1524 MOZ_ASSERT(mReactionsStack.IsEmpty(),
1525 "custom element reactions stack should be empty");
1526 mBackupQueue.AppendElement(aElement);
1527 elementData->mReactionQueue.AppendElement(aReaction);
1529 if (mIsBackupQueueProcessing) {
1530 return;
1533 CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
1534 RefPtr<BackupQueueMicroTask> bqmt = new BackupQueueMicroTask(this);
1535 context->DispatchToMicroTask(bqmt.forget());
1538 void CustomElementReactionsStack::InvokeBackupQueue() {
1539 // Check backup queue size in order to reduce function call overhead.
1540 if (!mBackupQueue.IsEmpty()) {
1541 // Upgrade reactions won't be scheduled in backup queue and the exception of
1542 // callback reactions will be automatically reported in CallSetup.
1543 // If the reactions are invoked from backup queue (in microtask check
1544 // point), we don't need to pass global object for error reporting.
1545 InvokeReactions(&mBackupQueue, nullptr);
1547 MOZ_ASSERT(
1548 mBackupQueue.IsEmpty(),
1549 "There are still some reactions in BackupQueue not being consumed!?!");
1552 void CustomElementReactionsStack::InvokeReactions(ElementQueue* aElementQueue,
1553 nsIGlobalObject* aGlobal) {
1554 // This is used for error reporting.
1555 Maybe<AutoEntryScript> aes;
1556 if (aGlobal) {
1557 aes.emplace(aGlobal, "custom elements reaction invocation");
1560 // Note: It's possible to re-enter this method.
1561 for (uint32_t i = 0; i < aElementQueue->Length(); ++i) {
1562 Element* element = aElementQueue->ElementAt(i);
1563 // ElementQueue hold a element's strong reference, it should not be a
1564 // nullptr.
1565 MOZ_ASSERT(element);
1567 CustomElementData* elementData = element->GetCustomElementData();
1568 if (!elementData || !element->GetOwnerGlobal()) {
1569 // This happens when the document is destroyed and the element is already
1570 // unlinked, no need to fire the callbacks in this case.
1571 continue;
1574 auto& reactions = elementData->mReactionQueue;
1575 for (uint32_t j = 0; j < reactions.Length(); ++j) {
1576 // Transfer the ownership of the entry due to reentrant invocation of
1577 // this function.
1578 auto reaction(std::move(reactions.ElementAt(j)));
1579 if (reaction) {
1580 if (!aGlobal && reaction->IsUpgradeReaction()) {
1581 nsIGlobalObject* global = element->GetOwnerGlobal();
1582 MOZ_ASSERT(!aes);
1583 aes.emplace(global, "custom elements reaction invocation");
1585 ErrorResult rv;
1586 reaction->Invoke(MOZ_KnownLive(element), rv);
1587 if (aes) {
1588 JSContext* cx = aes->cx();
1589 if (rv.MaybeSetPendingException(cx)) {
1590 aes->ReportException();
1592 MOZ_ASSERT(!JS_IsExceptionPending(cx));
1593 if (!aGlobal && reaction->IsUpgradeReaction()) {
1594 aes.reset();
1597 MOZ_ASSERT(!rv.Failed());
1600 reactions.Clear();
1602 aElementQueue->Clear();
1605 //-----------------------------------------------------
1606 // CustomElementDefinition
1608 NS_IMPL_CYCLE_COLLECTION(CustomElementDefinition, mConstructor, mCallbacks,
1609 mFormAssociatedCallbacks, mConstructionStack)
1611 CustomElementDefinition::CustomElementDefinition(
1612 nsAtom* aType, nsAtom* aLocalName, int32_t aNamespaceID,
1613 CustomElementConstructor* aConstructor,
1614 nsTArray<RefPtr<nsAtom>>&& aObservedAttributes,
1615 UniquePtr<LifecycleCallbacks>&& aCallbacks,
1616 UniquePtr<FormAssociatedLifecycleCallbacks>&& aFormAssociatedCallbacks,
1617 bool aFormAssociated, bool aDisableInternals, bool aDisableShadow)
1618 : mType(aType),
1619 mLocalName(aLocalName),
1620 mNamespaceID(aNamespaceID),
1621 mConstructor(aConstructor),
1622 mObservedAttributes(std::move(aObservedAttributes)),
1623 mCallbacks(std::move(aCallbacks)),
1624 mFormAssociatedCallbacks(std::move(aFormAssociatedCallbacks)),
1625 mFormAssociated(aFormAssociated),
1626 mDisableInternals(aDisableInternals),
1627 mDisableShadow(aDisableShadow) {}
1629 } // namespace mozilla::dom