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"
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
{
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");
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);
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
{
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;
82 static UniquePtr
<CustomElementCallback
> Create(
83 ElementCallbackType aType
, Element
* aCustomElement
,
84 const LifecycleCallbackArgs
& aArgs
, CustomElementDefinition
* aDefinition
);
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
{
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
);
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
);
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;
145 case ElementCallbackType::eConnected
:
146 if (aDefinition
->mCallbacks
->mConnectedCallback
.WasPassed()) {
147 func
= aDefinition
->mCallbacks
->mConnectedCallback
.Value();
151 case ElementCallbackType::eDisconnected
:
152 if (aDefinition
->mCallbacks
->mDisconnectedCallback
.WasPassed()) {
153 func
= aDefinition
->mCallbacks
->mDisconnectedCallback
.Value();
157 case ElementCallbackType::eAdopted
:
158 if (aDefinition
->mCallbacks
->mAdoptedCallback
.WasPassed()) {
159 func
= aDefinition
->mCallbacks
->mAdoptedCallback
.Value();
163 case ElementCallbackType::eAttributeChanged
:
164 if (aDefinition
->mCallbacks
->mAttributeChangedCallback
.WasPassed()) {
165 func
= aDefinition
->mCallbacks
->mAttributeChangedCallback
.Value();
169 case ElementCallbackType::eFormAssociated
:
170 if (aDefinition
->mFormAssociatedCallbacks
->mFormAssociatedCallback
172 func
= aDefinition
->mFormAssociatedCallbacks
->mFormAssociatedCallback
177 case ElementCallbackType::eFormReset
:
178 if (aDefinition
->mFormAssociatedCallbacks
->mFormResetCallback
181 aDefinition
->mFormAssociatedCallbacks
->mFormResetCallback
.Value();
185 case ElementCallbackType::eFormDisabled
:
186 if (aDefinition
->mFormAssociatedCallbacks
->mFormDisabledCallback
188 func
= aDefinition
->mFormAssociatedCallbacks
->mFormDisabledCallback
193 case ElementCallbackType::eFormStateRestore
:
194 if (aDefinition
->mFormAssociatedCallbacks
->mFormStateRestoreCallback
196 func
= aDefinition
->mFormAssociatedCallbacks
->mFormStateRestoreCallback
201 case ElementCallbackType::eGetCustomInterface
:
202 MOZ_ASSERT_UNREACHABLE("Don't call GetCustomInterface through callback");
206 // If there is no such callback, stop.
211 // Add CALLBACK to ELEMENT's callback queue.
212 return MakeUnique
<CustomElementCallback
>(aCustomElement
, aType
, func
, aArgs
);
215 void CustomElementCallback::Call() {
217 case ElementCallbackType::eConnected
:
218 static_cast<LifecycleConnectedCallback
*>(mCallback
.get())
221 case ElementCallbackType::eDisconnected
:
222 static_cast<LifecycleDisconnectedCallback
*>(mCallback
.get())
225 case ElementCallbackType::eAdopted
:
226 static_cast<LifecycleAdoptedCallback
*>(mCallback
.get())
227 ->Call(mThisObject
, mArgs
.mOldDocument
, mArgs
.mNewDocument
);
229 case ElementCallbackType::eAttributeChanged
:
230 static_cast<LifecycleAttributeChangedCallback
*>(mCallback
.get())
231 ->Call(mThisObject
, nsDependentAtomString(mArgs
.mName
),
232 mArgs
.mOldValue
, mArgs
.mNewValue
, mArgs
.mNamespaceURI
);
234 case ElementCallbackType::eFormAssociated
:
235 static_cast<LifecycleFormAssociatedCallback
*>(mCallback
.get())
236 ->Call(mThisObject
, mArgs
.mForm
);
238 case ElementCallbackType::eFormReset
:
239 static_cast<LifecycleFormResetCallback
*>(mCallback
.get())
242 case ElementCallbackType::eFormDisabled
:
243 static_cast<LifecycleFormDisabledCallback
*>(mCallback
.get())
244 ->Call(mThisObject
, mArgs
.mDisabled
);
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 "
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();
261 value
.SetValue().SetAsUSVString().ShareOrDependUpon(
262 owningValue
.GetAsUSVString());
264 static_cast<LifecycleFormStateRestoreCallback
*>(mCallback
.get())
265 ->Call(mThisObject
, value
, mArgs
.mReason
);
267 case ElementCallbackType::eGetCustomInterface
:
268 MOZ_ASSERT_UNREACHABLE("Don't call GetCustomInterface through callback");
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
);
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
),
306 //-----------------------------------------------------
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
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");
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).
388 n
+= reaction
->SizeOfIncludingThis(aMallocSizeOf
);
395 //-----------------------------------------------------
396 // CustomElementRegistry
400 class MOZ_RAII AutoConstructionStackEntry final
{
402 AutoConstructionStackEntry(nsTArray
<RefPtr
<Element
>>& aStack
,
405 MOZ_ASSERT(aElement
->IsHTMLElement() || aElement
->IsXULElement());
408 mIndex
= mStack
.Length();
410 mStack
.AppendElement(aElement
);
413 ~AutoConstructionStackEntry() {
414 MOZ_ASSERT(mIndex
== mStack
.Length() - 1,
415 "Removed element should be the last element");
416 mStack
.RemoveLastElement();
420 nsTArray
<RefPtr
<Element
>>& mStack
;
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
)
461 CustomElementRegistry::CustomElementRegistry(nsPIDOMWindowInner
* aWindow
)
462 : mWindow(aWindow
), mIsCustomDefinitionRunning(false) {
465 mozilla::HoldJSObjects(this);
468 CustomElementRegistry::~CustomElementRegistry() {
469 mozilla::DropJSObjects(this);
473 CustomElementRegistry::RunCustomElementCreationCallback::Run() {
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
,
489 MOZ_ASSERT(elements
, "There should be a list");
491 for (const auto& key
: *elements
) {
492 nsCOMPtr
<Element
> elem
= do_QueryReferent(key
);
497 CustomElementRegistry::Upgrade(elem
, definition
, er
);
498 MOZ_ASSERT(NS_SUCCEEDED(er
.StealNSResult()),
499 "chrome JavaScript error in custom element construction.");
505 CustomElementDefinition
* CustomElementRegistry::LookupCustomElementDefinition(
506 nsAtom
* aNameAtom
, int32_t aNameSpaceID
, nsAtom
* aTypeAtom
) {
507 CustomElementDefinition
* data
= mCustomDefinitions
.GetWeak(aTypeAtom
);
510 RefPtr
<CustomElementCreationCallback
> callback
;
511 mElementCreationCallbacks
.Get(aTypeAtom
, getter_AddRefs(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
) {
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
);
541 CustomElementDefinition
* definition
=
542 mCustomDefinitions
.GetWeak(ptr
->value());
543 MOZ_ASSERT(definition
, "Definition must be found in mCustomDefinitions");
548 void CustomElementRegistry::RegisterUnresolvedElement(Element
* aElement
,
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()) {
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
;
563 typeName
= info
->NameAtom();
566 if (mCustomDefinitions
.GetWeak(typeName
)) {
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
,
578 nsIWeakReference
* weak
= aElement
->GetExistingWeakReference();
585 nsWeakPtr weakPtr
= do_GetWeakReference(aElement
);
587 weak
== weakPtr
.get(),
588 "do_GetWeakReference should reuse the existing nsIWeakReference.");
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
601 void CustomElementRegistry::EnqueueLifecycleCallback(
602 ElementCallbackType aType
, Element
* aCustomElement
,
603 const LifecycleCallbackArgs
& aArgs
, CustomElementDefinition
* aDefinition
) {
604 CustomElementDefinition
* definition
= aDefinition
;
606 definition
= aCustomElement
->GetCustomElementDefinition();
608 definition
->mLocalName
!= aCustomElement
->NodeInfo()->NameAtom()) {
612 if (!definition
->mCallbacks
&& !definition
->mFormAssociatedCallbacks
) {
613 // definition has been unlinked. Don't try to mess with it.
619 CustomElementCallback::Create(aType
, aCustomElement
, aArgs
, definition
);
624 DocGroup
* docGroup
= aCustomElement
->OwnerDoc()->GetDocGroup();
629 if (aType
== ElementCallbackType::eAttributeChanged
) {
630 if (!definition
->mObservedAttributes
.Contains(aArgs
.mName
)) {
635 CustomElementReactionsStack
* reactionsStack
=
636 docGroup
->CustomElementReactionsStack();
637 reactionsStack
->EnqueueCallbackReaction(aCustomElement
, std::move(callback
));
642 class CandidateFinder
{
644 CandidateFinder(nsTHashSet
<RefPtr
<nsIWeakReference
>>& aCandidates
,
646 nsTArray
<nsCOMPtr
<Element
>> OrderedCandidates();
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()) {
657 for (const auto& candidate
: aCandidates
) {
658 nsCOMPtr
<Element
> elem
= do_QueryReferent(candidate
);
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())});
677 nsTArray
<nsCOMPtr
<Element
>> orderedElements(mCandidates
.Count());
678 for (nsINode
* node
: ShadowIncludingTreeIterator(*mDoc
)) {
679 Element
* element
= Element::FromNode(node
);
684 nsCOMPtr
<Element
> elem
;
685 if (mCandidates
.Remove(element
, getter_AddRefs(elem
))) {
686 orderedElements
.AppendElement(std::move(elem
));
687 if (mCandidates
.Count() == 0) {
693 return orderedElements
;
698 void CustomElementRegistry::UpgradeCandidates(
699 nsAtom
* aKey
, CustomElementDefinition
* aDefinition
, ErrorResult
& aRv
) {
700 DocGroup
* docGroup
= mWindow
->GetDocGroup();
702 aRv
.Throw(NS_ERROR_UNEXPECTED
);
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
);
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(),
753 aRv
.NoteJSContextException(aCx
);
757 if (!iterable
.isUndefined()) {
758 if (!iterable
.isObject()) {
759 aRv
.ThrowTypeError
<MSG_CONVERSION_ERROR
>(NS_ConvertUTF16toUTF8(aName
),
764 JS::ForOfIterator
iter(aCx
);
765 if (!iter
.init(iterable
, JS::ForOfIterator::AllowNonIterable
)) {
766 aRv
.NoteJSContextException(aCx
);
770 if (!iter
.valueIsIterable()) {
771 aRv
.ThrowTypeError
<MSG_CONVERSION_ERROR
>(NS_ConvertUTF16toUTF8(aName
),
776 JS::Rooted
<JS::Value
> attribute(aCx
);
779 if (!iter
.next(&attribute
, &done
)) {
780 aRv
.NoteJSContextException(aCx
);
787 nsAutoString attrStr
;
788 if (!ConvertJSValueToString(aCx
, attribute
, eStringify
, eStringify
,
790 aRv
.NoteJSContextException(aCx
);
794 // XXX(Bug 1631371) Check if this should use a fallible operation as it
795 // pretended earlier.
796 aArray
.AppendElement(NS_Atomize(attrStr
));
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
814 // In any case, aCx represents the global we want to be using for the unwrap
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
);
826 * 1. If IsConstructor(constructor) is false, then throw a TypeError and abort
829 if (!JS::IsConstructor(constructorUnwrapped
)) {
830 aRv
.ThrowTypeError
<MSG_NOT_CONSTRUCTOR
>("Argument 2");
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()));
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()));
861 * 4. If this CustomElementRegistry contains an entry with constructor
862 * constructor, then throw a "NotSupportedError" DOMException and abort these
865 const auto& ptr
= mConstructors
.lookup(constructorUnwrapped
);
867 MOZ_ASSERT(mCustomDefinitions
.GetWeak(ptr
->value()),
868 "Definition must be found in mCustomDefinitions");
870 ptr
->value()->ToUTF8String(name
);
871 aRv
.ThrowNotSupportedError(
872 nsPrintfCString("'%s' and '%s' have the same constructor",
873 NS_ConvertUTF16toUTF8(aName
).get(), name
.get()));
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"
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
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()));
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
);
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
);
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");
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
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
965 JS::Rooted
<JS::Value
> prototype(aCx
);
966 if (!JS_GetProperty(aCx
, constructor
, "prototype", &prototype
)) {
967 aRv
.NoteJSContextException(aCx
);
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");
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
985 * 14.4. For each of the four keys callbackName in lifecycleCallbacks:
986 * 1. Let callbackValue be Get(prototype, callbackName). Rethrow any
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
);
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
)) {
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
)) {
1029 // 14.9. Set disableInternals to true if disabledFeaturesSequence contains
1031 disableInternals
= disabledFeatures
.Contains(
1032 static_cast<nsStaticAtom
*>(nsGkAtoms::internals
));
1034 // 14.10. Set disableShadow to true if disabledFeaturesSequence contains
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
);
1048 // 14.12. Set formAssociated to the result of converting
1049 // formAssociatedValue to a boolean. Rethrow any exceptions from
1051 if (!ValueToPrimitive
<bool, eDefault
>(aCx
, formAssociatedValue
,
1052 "formAssociated", &formAssociated
)) {
1053 aRv
.NoteJSContextException(aCx
);
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
);
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
);
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
));
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
,
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
);
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
);
1168 CustomElementData
* ceData
= element
->GetCustomElementData();
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
);
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
);
1190 aRetVal
.SetUndefined();
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());
1204 aDefinition
->mType
->ToString(aResult
);
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
1215 auto createPromise
= [&](auto&& action
) -> already_AddRefed
<Promise
> {
1216 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(mWindow
);
1217 RefPtr
<Promise
> promise
= Promise::Create(global
, aRv
);
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
> {
1248 return createPromise([&entry
](const RefPtr
<Promise
>& promise
) {
1249 entry
.Insert(promise
);
1252 return do_AddRef(entry
.Data());
1259 static void DoUpgrade(Element
* aElement
, CustomElementDefinition
* aDefinition
,
1260 CustomElementConstructor
* aConstructor
,
1262 if (aDefinition
->mDisableShadow
&& aElement
->GetShadowRoot()) {
1263 aRv
.ThrowNotSupportedError(nsPrintfCString(
1264 "Custom element upgrade to '%s' is disabled because a shadow root "
1266 NS_ConvertUTF16toUTF8(aDefinition
->mType
->GetUTF16String()).get()));
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
);
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");
1293 } // anonymous namespace
1295 // https://html.spec.whatwg.org/commit-snapshots/2793ee4a461c6c39896395f1a45c269ea820c47e/#upgrades
1297 void CustomElementRegistry::Upgrade(Element
* aElement
,
1298 CustomElementDefinition
* aDefinition
,
1300 CustomElementData
* data
= aElement
->GetCustomElementData();
1301 MOZ_ASSERT(data
, "CustomElementData should exist");
1304 if (data
->mState
!= CustomElementData::State::eUndefined
) {
1309 aElement
->SetCustomElementDefinition(aDefinition
);
1312 data
->mState
= CustomElementData::State::eFailed
;
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
,
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
,
1345 if (aElement
->IsInComposedDoc()) {
1346 nsContentUtils::EnqueueLifecycleCallback(ElementCallbackType::eConnected
,
1347 aElement
, {}, aDefinition
);
1351 AutoConstructionStackEntry
acs(aDefinition
->mConstructionStack
, aElement
);
1353 // Step 7 and step 8.
1354 DoUpgrade(aElement
, aDefinition
, MOZ_KnownLive(aDefinition
->mConstructor
),
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();
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();
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())) {
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())) {
1400 LifecycleGetCustomInterfaceCallback
* func
=
1401 definition
->mCallbacks
->mGetCustomInterfaceCallback
.Value();
1403 // Initialize a AutoJSAPI to enter the compartment of the callback.
1405 JS::Rooted
<JSObject
*> funcGlobal(RootingCx(), func
->CallbackGlobalOrNull());
1406 if (!funcGlobal
|| !jsapi
.Init(funcGlobal
)) {
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
)) {
1419 JS::Rooted
<JSObject
*> customInterface(cx
);
1420 func
->Call(aElement
, jsiid
, &customInterface
);
1421 if (!customInterface
) {
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
))) {
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.
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(
1498 UniquePtr
<CustomElementCallback
> aCustomElementCallback
) {
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
);
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
) {
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);
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
;
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
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.
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
1578 auto reaction(std::move(reactions
.ElementAt(j
)));
1580 if (!aGlobal
&& reaction
->IsUpgradeReaction()) {
1581 nsIGlobalObject
* global
= element
->GetOwnerGlobal();
1583 aes
.emplace(global
, "custom elements reaction invocation");
1586 reaction
->Invoke(MOZ_KnownLive(element
), rv
);
1588 JSContext
* cx
= aes
->cx();
1589 if (rv
.MaybeSetPendingException(cx
)) {
1590 aes
->ReportException();
1592 MOZ_ASSERT(!JS_IsExceptionPending(cx
));
1593 if (!aGlobal
&& reaction
->IsUpgradeReaction()) {
1597 MOZ_ASSERT(!rv
.Failed());
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
)
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