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
);
483 // Callback should set the definition of the type.
484 MOZ_DIAGNOSTIC_ASSERT(false,
485 "Callback should set the definition of the type.");
486 return NS_ERROR_FAILURE
;
489 MOZ_ASSERT(!mRegistry
->mElementCreationCallbacks
.GetWeak(mAtom
),
490 "Callback should be removed.");
492 mozilla::UniquePtr
<nsTHashSet
<RefPtr
<nsIWeakReference
>>> elements
;
493 mRegistry
->mElementCreationCallbacksUpgradeCandidatesMap
.Remove(mAtom
,
495 MOZ_ASSERT(elements
, "There should be a list");
497 for (const auto& key
: *elements
) {
498 nsCOMPtr
<Element
> elem
= do_QueryReferent(key
);
503 CustomElementRegistry::Upgrade(elem
, definition
, er
);
504 MOZ_ASSERT(NS_SUCCEEDED(er
.StealNSResult()),
505 "chrome JavaScript error in custom element construction.");
511 CustomElementDefinition
* CustomElementRegistry::LookupCustomElementDefinition(
512 nsAtom
* aNameAtom
, int32_t aNameSpaceID
, nsAtom
* aTypeAtom
) {
513 CustomElementDefinition
* data
= mCustomDefinitions
.GetWeak(aTypeAtom
);
516 RefPtr
<CustomElementCreationCallback
> callback
;
517 mElementCreationCallbacks
.Get(aTypeAtom
, getter_AddRefs(callback
));
519 mElementCreationCallbacks
.Remove(aTypeAtom
);
520 mElementCreationCallbacksUpgradeCandidatesMap
.GetOrInsertNew(aTypeAtom
);
521 RefPtr
<Runnable
> runnable
=
522 new RunCustomElementCreationCallback(this, aTypeAtom
, callback
);
523 nsContentUtils::AddScriptRunner(runnable
.forget());
524 data
= mCustomDefinitions
.GetWeak(aTypeAtom
);
528 if (data
&& data
->mLocalName
== aNameAtom
&&
529 data
->mNamespaceID
== aNameSpaceID
) {
536 CustomElementDefinition
* CustomElementRegistry::LookupCustomElementDefinition(
537 JSContext
* aCx
, JSObject
* aConstructor
) const {
538 // We're looking up things that tested true for JS::IsConstructor,
539 // so doing a CheckedUnwrapStatic is fine here.
540 JS::Rooted
<JSObject
*> constructor(aCx
, js::CheckedUnwrapStatic(aConstructor
));
542 const auto& ptr
= mConstructors
.lookup(constructor
);
547 CustomElementDefinition
* definition
=
548 mCustomDefinitions
.GetWeak(ptr
->value());
549 MOZ_ASSERT(definition
, "Definition must be found in mCustomDefinitions");
554 void CustomElementRegistry::RegisterUnresolvedElement(Element
* aElement
,
556 // We don't have a use-case for a Custom Element inside NAC, and continuing
557 // here causes performance issues for NAC + XBL anonymous content.
558 if (aElement
->IsInNativeAnonymousSubtree()) {
562 mozilla::dom::NodeInfo
* info
= aElement
->NodeInfo();
564 // Candidate may be a custom element through extension,
565 // in which case the custom element type name will not
566 // match the element tag name. e.g. <button is="x-button">.
567 RefPtr
<nsAtom
> typeName
= aTypeName
;
569 typeName
= info
->NameAtom();
572 if (mCustomDefinitions
.GetWeak(typeName
)) {
576 nsTHashSet
<RefPtr
<nsIWeakReference
>>* unresolved
=
577 mCandidatesMap
.GetOrInsertNew(typeName
);
578 nsWeakPtr elem
= do_GetWeakReference(aElement
);
579 unresolved
->Insert(elem
);
582 void CustomElementRegistry::UnregisterUnresolvedElement(Element
* aElement
,
584 nsIWeakReference
* weak
= aElement
->GetExistingWeakReference();
591 nsWeakPtr weakPtr
= do_GetWeakReference(aElement
);
593 weak
== weakPtr
.get(),
594 "do_GetWeakReference should reuse the existing nsIWeakReference.");
598 nsTHashSet
<RefPtr
<nsIWeakReference
>>* candidates
= nullptr;
599 if (mCandidatesMap
.Get(aTypeName
, &candidates
)) {
600 MOZ_ASSERT(candidates
);
601 candidates
->Remove(weak
);
605 // https://html.spec.whatwg.org/commit-snapshots/65f39c6fc0efa92b0b2b23b93197016af6ac0de6/#enqueue-a-custom-element-callback-reaction
607 void CustomElementRegistry::EnqueueLifecycleCallback(
608 ElementCallbackType aType
, Element
* aCustomElement
,
609 const LifecycleCallbackArgs
& aArgs
, CustomElementDefinition
* aDefinition
) {
610 CustomElementDefinition
* definition
= aDefinition
;
612 definition
= aCustomElement
->GetCustomElementDefinition();
614 definition
->mLocalName
!= aCustomElement
->NodeInfo()->NameAtom()) {
618 if (!definition
->mCallbacks
&& !definition
->mFormAssociatedCallbacks
) {
619 // definition has been unlinked. Don't try to mess with it.
625 CustomElementCallback::Create(aType
, aCustomElement
, aArgs
, definition
);
630 DocGroup
* docGroup
= aCustomElement
->OwnerDoc()->GetDocGroup();
635 if (aType
== ElementCallbackType::eAttributeChanged
) {
636 if (!definition
->mObservedAttributes
.Contains(aArgs
.mName
)) {
641 CustomElementReactionsStack
* reactionsStack
=
642 docGroup
->CustomElementReactionsStack();
643 reactionsStack
->EnqueueCallbackReaction(aCustomElement
, std::move(callback
));
648 class CandidateFinder
{
650 CandidateFinder(nsTHashSet
<RefPtr
<nsIWeakReference
>>& aCandidates
,
652 nsTArray
<nsCOMPtr
<Element
>> OrderedCandidates();
655 nsCOMPtr
<Document
> mDoc
;
656 nsInterfaceHashtable
<nsPtrHashKey
<Element
>, Element
> mCandidates
;
659 CandidateFinder::CandidateFinder(
660 nsTHashSet
<RefPtr
<nsIWeakReference
>>& aCandidates
, Document
* aDoc
)
661 : mDoc(aDoc
), mCandidates(aCandidates
.Count()) {
663 for (const auto& candidate
: aCandidates
) {
664 nsCOMPtr
<Element
> elem
= do_QueryReferent(candidate
);
669 Element
* key
= elem
.get();
670 mCandidates
.InsertOrUpdate(key
, elem
.forget());
674 nsTArray
<nsCOMPtr
<Element
>> CandidateFinder::OrderedCandidates() {
675 if (mCandidates
.Count() == 1) {
676 // Fast path for one candidate.
677 auto iter
= mCandidates
.Iter();
678 nsTArray
<nsCOMPtr
<Element
>> rval({std::move(iter
.Data())});
683 nsTArray
<nsCOMPtr
<Element
>> orderedElements(mCandidates
.Count());
684 for (nsINode
* node
: ShadowIncludingTreeIterator(*mDoc
)) {
685 Element
* element
= Element::FromNode(node
);
690 nsCOMPtr
<Element
> elem
;
691 if (mCandidates
.Remove(element
, getter_AddRefs(elem
))) {
692 orderedElements
.AppendElement(std::move(elem
));
693 if (mCandidates
.Count() == 0) {
699 return orderedElements
;
704 void CustomElementRegistry::UpgradeCandidates(
705 nsAtom
* aKey
, CustomElementDefinition
* aDefinition
, ErrorResult
& aRv
) {
706 DocGroup
* docGroup
= mWindow
->GetDocGroup();
708 aRv
.Throw(NS_ERROR_UNEXPECTED
);
712 mozilla::UniquePtr
<nsTHashSet
<RefPtr
<nsIWeakReference
>>> candidates
;
713 if (mCandidatesMap
.Remove(aKey
, &candidates
)) {
714 MOZ_ASSERT(candidates
);
715 CustomElementReactionsStack
* reactionsStack
=
716 docGroup
->CustomElementReactionsStack();
718 CandidateFinder
finder(*candidates
, mWindow
->GetExtantDoc());
719 for (auto& elem
: finder
.OrderedCandidates()) {
720 reactionsStack
->EnqueueUpgradeReaction(elem
, aDefinition
);
725 JSObject
* CustomElementRegistry::WrapObject(JSContext
* aCx
,
726 JS::Handle
<JSObject
*> aGivenProto
) {
727 return CustomElementRegistry_Binding::Wrap(aCx
, this, aGivenProto
);
730 nsISupports
* CustomElementRegistry::GetParentObject() const { return mWindow
; }
732 DocGroup
* CustomElementRegistry::GetDocGroup() const {
733 return mWindow
? mWindow
->GetDocGroup() : nullptr;
736 int32_t CustomElementRegistry::InferNamespace(
737 JSContext
* aCx
, JS::Handle
<JSObject
*> constructor
) {
738 JS::Rooted
<JSObject
*> XULConstructor(
739 aCx
, XULElement_Binding::GetConstructorObjectHandle(aCx
));
741 JS::Rooted
<JSObject
*> proto(aCx
, constructor
);
743 if (proto
== XULConstructor
) {
744 return kNameSpaceID_XUL
;
747 JS_GetPrototype(aCx
, proto
, &proto
);
750 return kNameSpaceID_XHTML
;
753 bool CustomElementRegistry::JSObjectToAtomArray(
754 JSContext
* aCx
, JS::Handle
<JSObject
*> aConstructor
, const nsString
& aName
,
755 nsTArray
<RefPtr
<nsAtom
>>& aArray
, ErrorResult
& aRv
) {
756 JS::Rooted
<JS::Value
> iterable(aCx
, JS::UndefinedValue());
757 if (!JS_GetUCProperty(aCx
, aConstructor
, aName
.get(), aName
.Length(),
759 aRv
.NoteJSContextException(aCx
);
763 if (!iterable
.isUndefined()) {
764 if (!iterable
.isObject()) {
765 aRv
.ThrowTypeError
<MSG_CONVERSION_ERROR
>(NS_ConvertUTF16toUTF8(aName
),
770 JS::ForOfIterator
iter(aCx
);
771 if (!iter
.init(iterable
, JS::ForOfIterator::AllowNonIterable
)) {
772 aRv
.NoteJSContextException(aCx
);
776 if (!iter
.valueIsIterable()) {
777 aRv
.ThrowTypeError
<MSG_CONVERSION_ERROR
>(NS_ConvertUTF16toUTF8(aName
),
782 JS::Rooted
<JS::Value
> attribute(aCx
);
785 if (!iter
.next(&attribute
, &done
)) {
786 aRv
.NoteJSContextException(aCx
);
793 nsAutoString attrStr
;
794 if (!ConvertJSValueToString(aCx
, attribute
, eStringify
, eStringify
,
796 aRv
.NoteJSContextException(aCx
);
800 // XXX(Bug 1631371) Check if this should use a fallible operation as it
801 // pretended earlier.
802 aArray
.AppendElement(NS_Atomize(attrStr
));
809 // https://html.spec.whatwg.org/commit-snapshots/b48bb2238269d90ea4f455a52cdf29505aff3df0/#dom-customelementregistry-define
810 void CustomElementRegistry::Define(
811 JSContext
* aCx
, const nsAString
& aName
,
812 CustomElementConstructor
& aFunctionConstructor
,
813 const ElementDefinitionOptions
& aOptions
, ErrorResult
& aRv
) {
814 JS::Rooted
<JSObject
*> constructor(aCx
, aFunctionConstructor
.CallableOrNull());
816 // We need to do a dynamic unwrap in order to throw the right exception. We
817 // could probably avoid that if we just threw MSG_NOT_CONSTRUCTOR if unwrap
820 // In any case, aCx represents the global we want to be using for the unwrap
822 JS::Rooted
<JSObject
*> constructorUnwrapped(
823 aCx
, js::CheckedUnwrapDynamic(constructor
, aCx
));
824 if (!constructorUnwrapped
) {
825 // If the caller's compartment does not have permission to access the
826 // unwrapped constructor then throw.
827 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
832 * 1. If IsConstructor(constructor) is false, then throw a TypeError and abort
835 if (!JS::IsConstructor(constructorUnwrapped
)) {
836 aRv
.ThrowTypeError
<MSG_NOT_CONSTRUCTOR
>("Argument 2");
840 int32_t nameSpaceID
= InferNamespace(aCx
, constructor
);
843 * 2. If name is not a valid custom element name, then throw a "SyntaxError"
844 * DOMException and abort these steps.
846 Document
* doc
= mWindow
->GetExtantDoc();
847 RefPtr
<nsAtom
> nameAtom(NS_Atomize(aName
));
848 if (!nsContentUtils::IsCustomElementName(nameAtom
, nameSpaceID
)) {
849 aRv
.ThrowSyntaxError(
850 nsPrintfCString("'%s' is not a valid custom element name",
851 NS_ConvertUTF16toUTF8(aName
).get()));
856 * 3. If this CustomElementRegistry contains an entry with name name, then
857 * throw a "NotSupportedError" DOMException and abort these steps.
859 if (mCustomDefinitions
.GetWeak(nameAtom
)) {
860 aRv
.ThrowNotSupportedError(
861 nsPrintfCString("'%s' has already been defined as a custom element",
862 NS_ConvertUTF16toUTF8(aName
).get()));
867 * 4. If this CustomElementRegistry contains an entry with constructor
868 * constructor, then throw a "NotSupportedError" DOMException and abort these
871 const auto& ptr
= mConstructors
.lookup(constructorUnwrapped
);
873 MOZ_ASSERT(mCustomDefinitions
.GetWeak(ptr
->value()),
874 "Definition must be found in mCustomDefinitions");
876 ptr
->value()->ToUTF8String(name
);
877 aRv
.ThrowNotSupportedError(
878 nsPrintfCString("'%s' and '%s' have the same constructor",
879 NS_ConvertUTF16toUTF8(aName
).get(), name
.get()));
884 * 5. Let localName be name.
885 * 6. Let extends be the value of the extends member of options, or null if
886 * no such member exists.
887 * 7. If extends is not null, then:
888 * 1. If extends is a valid custom element name, then throw a
889 * "NotSupportedError" DOMException.
890 * 2. If the element interface for extends and the HTML namespace is
891 * HTMLUnknownElement (e.g., if extends does not indicate an element
892 * definition in this specification), then throw a "NotSupportedError"
894 * 3. Set localName to extends.
896 * Special note for XUL elements:
898 * For step 7.1, we'll subject XUL to the same rules as HTML, so that a
899 * custom built-in element will not be extending from a dashed name.
900 * Step 7.2 is disregarded. But, we do check if the name is a dashed name
901 * (i.e. step 2) given that there is no reason for a custom built-in element
902 * type to take on a non-dashed name.
903 * This also ensures the name of the built-in custom element type can never
904 * be the same as the built-in element name, so we don't break the assumption
907 nsAutoString
localName(aName
);
908 if (aOptions
.mExtends
.WasPassed()) {
909 doc
->SetUseCounter(eUseCounter_custom_CustomizedBuiltin
);
911 RefPtr
<nsAtom
> extendsAtom(NS_Atomize(aOptions
.mExtends
.Value()));
912 if (nsContentUtils::IsCustomElementName(extendsAtom
, kNameSpaceID_XHTML
)) {
913 aRv
.ThrowNotSupportedError(
914 nsPrintfCString("'%s' cannot extend a custom element",
915 NS_ConvertUTF16toUTF8(aName
).get()));
919 if (nameSpaceID
== kNameSpaceID_XHTML
) {
920 // bgsound and multicol are unknown html element.
921 int32_t tag
= nsHTMLTags::CaseSensitiveAtomTagToId(extendsAtom
);
922 if (tag
== eHTMLTag_userdefined
|| tag
== eHTMLTag_bgsound
||
923 tag
== eHTMLTag_multicol
) {
924 aRv
.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR
);
927 } else { // kNameSpaceID_XUL
928 // As stated above, ensure the name of the customized built-in element
929 // (the one that goes to the |is| attribute) is a dashed name.
930 if (!nsContentUtils::IsNameWithDash(nameAtom
)) {
931 aRv
.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR
);
936 localName
.Assign(aOptions
.mExtends
.Value());
940 * 8. If this CustomElementRegistry's element definition is running flag is
941 * set, then throw a "NotSupportedError" DOMException and abort these steps.
943 if (mIsCustomDefinitionRunning
) {
944 aRv
.ThrowNotSupportedError(
945 "Cannot define a custom element while defining another custom element");
949 auto callbacksHolder
= MakeUnique
<LifecycleCallbacks
>();
950 auto formAssociatedCallbacksHolder
=
951 MakeUnique
<FormAssociatedLifecycleCallbacks
>();
952 nsTArray
<RefPtr
<nsAtom
>> observedAttributes
;
953 AutoTArray
<RefPtr
<nsAtom
>, 2> disabledFeatures
;
954 bool formAssociated
= false;
955 bool disableInternals
= false;
956 bool disableShadow
= false;
957 { // Set mIsCustomDefinitionRunning.
959 * 9. Set this CustomElementRegistry's element definition is running flag.
961 AutoRestore
<bool> restoreRunning(mIsCustomDefinitionRunning
);
962 mIsCustomDefinitionRunning
= true;
965 * 14.1. Let prototype be Get(constructor, "prototype"). Rethrow any
968 // The .prototype on the constructor passed could be an "expando" of a
969 // wrapper. So we should get it from wrapper instead of the underlying
971 JS::Rooted
<JS::Value
> prototype(aCx
);
972 if (!JS_GetProperty(aCx
, constructor
, "prototype", &prototype
)) {
973 aRv
.NoteJSContextException(aCx
);
978 * 14.2. If Type(prototype) is not Object, then throw a TypeError exception.
980 if (!prototype
.isObject()) {
981 aRv
.ThrowTypeError
<MSG_NOT_OBJECT
>("constructor.prototype");
986 * 14.3. Let lifecycleCallbacks be a map with the four keys
987 * "connectedCallback", "disconnectedCallback", "adoptedCallback", and
988 * "attributeChangedCallback", each of which belongs to an entry whose
989 * value is null. The 'getCustomInterface' callback is also included
991 * 14.4. For each of the four keys callbackName in lifecycleCallbacks:
992 * 1. Let callbackValue be Get(prototype, callbackName). Rethrow any
994 * 2. If callbackValue is not undefined, then set the value of the
995 * entry in lifecycleCallbacks with key callbackName to the result
996 * of converting callbackValue to the Web IDL Function callback
997 * type. Rethrow any exceptions from the conversion.
999 if (!callbacksHolder
->Init(aCx
, prototype
)) {
1000 aRv
.NoteJSContextException(aCx
);
1005 * 14.5. If the value of the entry in lifecycleCallbacks with key
1006 * "attributeChangedCallback" is not null, then:
1007 * 1. Let observedAttributesIterable be Get(constructor,
1008 * "observedAttributes"). Rethrow any exceptions.
1009 * 2. If observedAttributesIterable is not undefined, then set
1010 * observedAttributes to the result of converting
1011 * observedAttributesIterable to a sequence<DOMString>. Rethrow
1012 * any exceptions from the conversion.
1014 if (callbacksHolder
->mAttributeChangedCallback
.WasPassed()) {
1015 if (!JSObjectToAtomArray(aCx
, constructor
, u
"observedAttributes"_ns
,
1016 observedAttributes
, aRv
)) {
1022 * 14.6. Let disabledFeatures be an empty sequence<DOMString>.
1023 * 14.7. Let disabledFeaturesIterable be Get(constructor,
1024 * "disabledFeatures"). Rethrow any exceptions.
1025 * 14.8. If disabledFeaturesIterable is not undefined, then set
1026 * disabledFeatures to the result of converting
1027 * disabledFeaturesIterable to a sequence<DOMString>.
1028 * Rethrow any exceptions from the conversion.
1030 if (!JSObjectToAtomArray(aCx
, constructor
, u
"disabledFeatures"_ns
,
1031 disabledFeatures
, aRv
)) {
1035 // 14.9. Set disableInternals to true if disabledFeaturesSequence contains
1037 disableInternals
= disabledFeatures
.Contains(
1038 static_cast<nsStaticAtom
*>(nsGkAtoms::internals
));
1040 // 14.10. Set disableShadow to true if disabledFeaturesSequence contains
1042 disableShadow
= disabledFeatures
.Contains(
1043 static_cast<nsStaticAtom
*>(nsGkAtoms::shadow
));
1045 // 14.11. Let formAssociatedValue be Get(constructor, "formAssociated").
1046 // Rethrow any exceptions.
1047 JS::Rooted
<JS::Value
> formAssociatedValue(aCx
);
1048 if (!JS_GetProperty(aCx
, constructor
, "formAssociated",
1049 &formAssociatedValue
)) {
1050 aRv
.NoteJSContextException(aCx
);
1054 // 14.12. Set formAssociated to the result of converting
1055 // formAssociatedValue to a boolean. Rethrow any exceptions from
1057 if (!ValueToPrimitive
<bool, eDefault
>(aCx
, formAssociatedValue
,
1058 "formAssociated", &formAssociated
)) {
1059 aRv
.NoteJSContextException(aCx
);
1064 * 14.13. If formAssociated is true, for each of "formAssociatedCallback",
1065 * "formResetCallback", "formDisabledCallback", and
1066 * "formStateRestoreCallback" callbackName:
1067 * 1. Let callbackValue be ? Get(prototype, callbackName).
1068 * 2. If callbackValue is not undefined, then set the value of the
1069 * entry in lifecycleCallbacks with key callbackName to the result
1070 * of converting callbackValue to the Web IDL Function callback
1071 * type. Rethrow any exceptions from the conversion.
1073 if (formAssociated
&&
1074 !formAssociatedCallbacksHolder
->Init(aCx
, prototype
)) {
1075 aRv
.NoteJSContextException(aCx
);
1078 } // Unset mIsCustomDefinitionRunning
1081 * 15. Let definition be a new custom element definition with name name,
1082 * local name localName, constructor constructor, prototype prototype,
1083 * observed attributes observedAttributes, and lifecycle callbacks
1084 * lifecycleCallbacks.
1086 // Associate the definition with the custom element.
1087 RefPtr
<nsAtom
> localNameAtom(NS_Atomize(localName
));
1090 * 16. Add definition to this CustomElementRegistry.
1092 if (!mConstructors
.put(constructorUnwrapped
, nameAtom
)) {
1093 aRv
.Throw(NS_ERROR_FAILURE
);
1097 RefPtr
<CustomElementDefinition
> definition
= new CustomElementDefinition(
1098 nameAtom
, localNameAtom
, nameSpaceID
, &aFunctionConstructor
,
1099 std::move(observedAttributes
), std::move(callbacksHolder
),
1100 std::move(formAssociatedCallbacksHolder
), formAssociated
,
1101 disableInternals
, disableShadow
);
1103 CustomElementDefinition
* def
= definition
.get();
1104 mCustomDefinitions
.InsertOrUpdate(nameAtom
, std::move(definition
));
1106 MOZ_ASSERT(mCustomDefinitions
.Count() == mConstructors
.count(),
1107 "Number of entries should be the same");
1110 * 17. 18. 19. Upgrade candidates
1112 UpgradeCandidates(nameAtom
, def
, aRv
);
1115 * 20. If this CustomElementRegistry's when-defined promise map contains an
1116 * entry with key name:
1117 * 1. Let promise be the value of that entry.
1118 * 2. Resolve promise with undefined.
1119 * 3. Delete the entry with key name from this CustomElementRegistry's
1120 * when-defined promise map.
1122 RefPtr
<Promise
> promise
;
1123 mWhenDefinedPromiseMap
.Remove(nameAtom
, getter_AddRefs(promise
));
1125 promise
->MaybeResolve(def
->mConstructor
);
1128 // Dispatch a "customelementdefined" event for DevTools.
1130 JSString
* nameJsStr
=
1131 JS_NewUCStringCopyN(aCx
, aName
.BeginReading(), aName
.Length());
1133 JS::Rooted
<JS::Value
> detail(aCx
, JS::StringValue(nameJsStr
));
1134 RefPtr
<CustomEvent
> event
= NS_NewDOMCustomEvent(doc
, nullptr, nullptr);
1135 event
->InitCustomEvent(aCx
, u
"customelementdefined"_ns
,
1136 /* CanBubble */ true,
1137 /* Cancelable */ true, detail
);
1138 event
->SetTrusted(true);
1140 AsyncEventDispatcher
* dispatcher
=
1141 new AsyncEventDispatcher(doc
, event
.forget());
1142 dispatcher
->mOnlyChromeDispatch
= ChromeOnlyDispatch::eYes
;
1144 dispatcher
->PostDOMEvent();
1148 * Clean-up mElementCreationCallbacks (if it exists)
1150 mElementCreationCallbacks
.Remove(nameAtom
);
1153 void CustomElementRegistry::SetElementCreationCallback(
1154 const nsAString
& aName
, CustomElementCreationCallback
& aCallback
,
1156 RefPtr
<nsAtom
> nameAtom(NS_Atomize(aName
));
1157 if (mElementCreationCallbacks
.GetWeak(nameAtom
) ||
1158 mCustomDefinitions
.GetWeak(nameAtom
)) {
1159 aRv
.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR
);
1163 RefPtr
<CustomElementCreationCallback
> callback
= &aCallback
;
1165 if (mCandidatesMap
.Contains(nameAtom
)) {
1166 mElementCreationCallbacksUpgradeCandidatesMap
.GetOrInsertNew(nameAtom
);
1167 RefPtr
<Runnable
> runnable
=
1168 new RunCustomElementCreationCallback(this, nameAtom
, callback
);
1169 nsContentUtils::AddScriptRunner(runnable
.forget());
1171 mElementCreationCallbacks
.InsertOrUpdate(nameAtom
, std::move(callback
));
1175 void CustomElementRegistry::Upgrade(nsINode
& aRoot
) {
1176 for (nsINode
* node
: ShadowIncludingTreeIterator(aRoot
)) {
1177 Element
* element
= Element::FromNode(node
);
1182 CustomElementData
* ceData
= element
->GetCustomElementData();
1184 NodeInfo
* nodeInfo
= element
->NodeInfo();
1185 nsAtom
* typeAtom
= ceData
->GetCustomElementType();
1186 CustomElementDefinition
* definition
=
1187 nsContentUtils::LookupCustomElementDefinition(
1188 nodeInfo
->GetDocument(), nodeInfo
->NameAtom(),
1189 nodeInfo
->NamespaceID(), typeAtom
);
1191 nsContentUtils::EnqueueUpgradeReaction(element
, definition
);
1197 void CustomElementRegistry::Get(
1198 const nsAString
& aName
,
1199 OwningCustomElementConstructorOrUndefined
& aRetVal
) {
1200 RefPtr
<nsAtom
> nameAtom(NS_Atomize(aName
));
1201 CustomElementDefinition
* data
= mCustomDefinitions
.GetWeak(nameAtom
);
1204 aRetVal
.SetUndefined();
1208 aRetVal
.SetAsCustomElementConstructor() = data
->mConstructor
;
1211 void CustomElementRegistry::GetName(JSContext
* aCx
,
1212 CustomElementConstructor
& aConstructor
,
1213 nsAString
& aResult
) {
1214 CustomElementDefinition
* aDefinition
=
1215 LookupCustomElementDefinition(aCx
, aConstructor
.CallableOrNull());
1218 aDefinition
->mType
->ToString(aResult
);
1220 aResult
.SetIsVoid(true);
1224 already_AddRefed
<Promise
> CustomElementRegistry::WhenDefined(
1225 const nsAString
& aName
, ErrorResult
& aRv
) {
1226 // Define a function that lazily creates a Promise and perform some action on
1227 // it when creation succeeded. It's needed in multiple cases below, but not in
1229 auto createPromise
= [&](auto&& action
) -> already_AddRefed
<Promise
> {
1230 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(mWindow
);
1231 RefPtr
<Promise
> promise
= Promise::Create(global
, aRv
);
1239 return promise
.forget();
1242 RefPtr
<nsAtom
> nameAtom(NS_Atomize(aName
));
1243 Document
* doc
= mWindow
->GetExtantDoc();
1244 uint32_t nameSpaceID
=
1245 doc
? doc
->GetDefaultNamespaceID() : kNameSpaceID_XHTML
;
1246 if (!nsContentUtils::IsCustomElementName(nameAtom
, nameSpaceID
)) {
1247 aRv
.ThrowSyntaxError(
1248 nsPrintfCString("'%s' is not a valid custom element name",
1249 NS_ConvertUTF16toUTF8(aName
).get()));
1253 if (CustomElementDefinition
* definition
=
1254 mCustomDefinitions
.GetWeak(nameAtom
)) {
1255 return createPromise([&](const RefPtr
<Promise
>& promise
) {
1256 promise
->MaybeResolve(definition
->mConstructor
);
1260 return mWhenDefinedPromiseMap
.WithEntryHandle(
1261 nameAtom
, [&](auto&& entry
) -> already_AddRefed
<Promise
> {
1263 return createPromise([&entry
](const RefPtr
<Promise
>& promise
) {
1264 entry
.Insert(promise
);
1267 return do_AddRef(entry
.Data());
1274 static void DoUpgrade(Element
* aElement
, CustomElementDefinition
* aDefinition
,
1275 CustomElementConstructor
* aConstructor
,
1277 if (aDefinition
->mDisableShadow
&& aElement
->GetShadowRoot()) {
1278 aRv
.ThrowNotSupportedError(nsPrintfCString(
1279 "Custom element upgrade to '%s' is disabled because a shadow root "
1281 NS_ConvertUTF16toUTF8(aDefinition
->mType
->GetUTF16String()).get()));
1285 CustomElementData
* data
= aElement
->GetCustomElementData();
1286 MOZ_ASSERT(data
, "CustomElementData should exist");
1287 data
->mState
= CustomElementData::State::ePrecustomized
;
1289 JS::Rooted
<JS::Value
> constructResult(RootingCx());
1290 // Rethrow the exception since it might actually throw the exception from the
1291 // upgrade steps back out to the caller of document.createElement.
1292 aConstructor
->Construct(&constructResult
, aRv
, "Custom Element Upgrade",
1293 CallbackFunction::eRethrowExceptions
);
1299 // constructResult is an ObjectValue because construction with a callback
1300 // always forms the return value from a JSObject.
1301 if (NS_FAILED(UNWRAP_OBJECT(Element
, &constructResult
, element
)) ||
1302 element
!= aElement
) {
1303 aRv
.ThrowTypeError("Custom element constructor returned a wrong element");
1308 } // anonymous namespace
1310 // https://html.spec.whatwg.org/commit-snapshots/2793ee4a461c6c39896395f1a45c269ea820c47e/#upgrades
1312 void CustomElementRegistry::Upgrade(Element
* aElement
,
1313 CustomElementDefinition
* aDefinition
,
1315 CustomElementData
* data
= aElement
->GetCustomElementData();
1316 MOZ_ASSERT(data
, "CustomElementData should exist");
1319 if (data
->mState
!= CustomElementData::State::eUndefined
) {
1324 aElement
->SetCustomElementDefinition(aDefinition
);
1327 data
->mState
= CustomElementData::State::eFailed
;
1330 if (!aDefinition
->mObservedAttributes
.IsEmpty()) {
1331 uint32_t count
= aElement
->GetAttrCount();
1332 for (uint32_t i
= 0; i
< count
; i
++) {
1333 mozilla::dom::BorrowedAttrInfo info
= aElement
->GetAttrInfoAt(i
);
1335 const nsAttrName
* name
= info
.mName
;
1336 nsAtom
* attrName
= name
->LocalName();
1338 if (aDefinition
->IsInObservedAttributeList(attrName
)) {
1339 int32_t namespaceID
= name
->NamespaceID();
1340 nsAutoString attrValue
, namespaceURI
;
1341 info
.mValue
->ToString(attrValue
);
1342 nsNameSpaceManager::GetInstance()->GetNameSpaceURI(namespaceID
,
1345 LifecycleCallbackArgs args
;
1346 args
.mName
= attrName
;
1347 args
.mOldValue
= VoidString();
1348 args
.mNewValue
= attrValue
;
1349 args
.mNamespaceURI
=
1350 (namespaceURI
.IsEmpty() ? VoidString() : namespaceURI
);
1352 nsContentUtils::EnqueueLifecycleCallback(
1353 ElementCallbackType::eAttributeChanged
, aElement
, args
,
1360 if (aElement
->IsInComposedDoc()) {
1361 nsContentUtils::EnqueueLifecycleCallback(ElementCallbackType::eConnected
,
1362 aElement
, {}, aDefinition
);
1366 AutoConstructionStackEntry
acs(aDefinition
->mConstructionStack
, aElement
);
1368 // Step 7 and step 8.
1369 DoUpgrade(aElement
, aDefinition
, MOZ_KnownLive(aDefinition
->mConstructor
),
1372 MOZ_ASSERT(data
->mState
== CustomElementData::State::eFailed
||
1373 data
->mState
== CustomElementData::State::ePrecustomized
);
1374 // Spec doesn't set custom element state to failed here, but without this we
1375 // would have inconsistent state on a custom elemet that is failed to
1376 // upgrade, see https://github.com/whatwg/html/issues/6929, and
1377 // https://github.com/web-platform-tests/wpt/pull/29911 for the test.
1378 data
->mState
= CustomElementData::State::eFailed
;
1379 aElement
->SetCustomElementDefinition(nullptr);
1380 // Empty element's custom element reaction queue.
1381 data
->mReactionQueue
.Clear();
1386 if (data
->IsFormAssociated()) {
1387 ElementInternals
* internals
= data
->GetElementInternals();
1388 MOZ_ASSERT(internals
);
1389 MOZ_ASSERT(aElement
->IsHTMLElement());
1390 MOZ_ASSERT(!aDefinition
->IsCustomBuiltIn());
1392 internals
->UpdateFormOwner();
1396 data
->mState
= CustomElementData::State::eCustom
;
1397 aElement
->SetDefined(true);
1400 already_AddRefed
<nsISupports
> CustomElementRegistry::CallGetCustomInterface(
1401 Element
* aElement
, const nsIID
& aIID
) {
1402 MOZ_ASSERT(aElement
);
1404 if (!nsContentUtils::IsChromeDoc(aElement
->OwnerDoc())) {
1408 // Try to get our GetCustomInterfaceCallback callback.
1409 CustomElementDefinition
* definition
= aElement
->GetCustomElementDefinition();
1410 if (!definition
|| !definition
->mCallbacks
||
1411 !definition
->mCallbacks
->mGetCustomInterfaceCallback
.WasPassed() ||
1412 (definition
->mLocalName
!= aElement
->NodeInfo()->NameAtom())) {
1415 LifecycleGetCustomInterfaceCallback
* func
=
1416 definition
->mCallbacks
->mGetCustomInterfaceCallback
.Value();
1418 // Initialize a AutoJSAPI to enter the compartment of the callback.
1420 JS::Rooted
<JSObject
*> funcGlobal(RootingCx(), func
->CallbackGlobalOrNull());
1421 if (!funcGlobal
|| !jsapi
.Init(funcGlobal
)) {
1425 // Grab our JSContext.
1426 JSContext
* cx
= jsapi
.cx();
1428 // Convert our IID to a JSValue to call our callback.
1429 JS::Rooted
<JS::Value
> jsiid(cx
);
1430 if (!xpc::ID2JSValue(cx
, aIID
, &jsiid
)) {
1434 JS::Rooted
<JSObject
*> customInterface(cx
);
1435 func
->Call(aElement
, jsiid
, &customInterface
);
1436 if (!customInterface
) {
1440 // Wrap our JSObject into a nsISupports through XPConnect
1441 nsCOMPtr
<nsISupports
> wrapper
;
1442 nsresult rv
= nsContentUtils::XPConnect()->WrapJSAggregatedToNative(
1443 aElement
, cx
, customInterface
, aIID
, getter_AddRefs(wrapper
));
1444 if (NS_WARN_IF(NS_FAILED(rv
))) {
1448 return wrapper
.forget();
1451 void CustomElementRegistry::TraceDefinitions(JSTracer
* aTrc
) {
1452 for (const RefPtr
<CustomElementDefinition
>& definition
:
1453 mCustomDefinitions
.Values()) {
1454 if (definition
&& definition
->mConstructor
) {
1455 mozilla::TraceScriptHolder(definition
->mConstructor
, aTrc
);
1460 //-----------------------------------------------------
1461 // CustomElementReactionsStack
1463 void CustomElementReactionsStack::CreateAndPushElementQueue() {
1464 MOZ_ASSERT(mRecursionDepth
);
1465 MOZ_ASSERT(!mIsElementQueuePushedForCurrentRecursionDepth
);
1467 // Push a new element queue onto the custom element reactions stack.
1468 mReactionsStack
.AppendElement(MakeUnique
<ElementQueue
>());
1469 mIsElementQueuePushedForCurrentRecursionDepth
= true;
1472 void CustomElementReactionsStack::PopAndInvokeElementQueue() {
1473 MOZ_ASSERT(mRecursionDepth
);
1474 MOZ_ASSERT(mIsElementQueuePushedForCurrentRecursionDepth
);
1475 MOZ_ASSERT(!mReactionsStack
.IsEmpty(), "Reaction stack shouldn't be empty");
1477 // Pop the element queue from the custom element reactions stack,
1478 // and invoke custom element reactions in that queue.
1479 const uint32_t lastIndex
= mReactionsStack
.Length() - 1;
1480 ElementQueue
* elementQueue
= mReactionsStack
.ElementAt(lastIndex
).get();
1481 // Check element queue size in order to reduce function call overhead.
1482 if (!elementQueue
->IsEmpty()) {
1483 // It is still not clear what error reporting will look like in custom
1484 // element, see https://github.com/w3c/webcomponents/issues/635.
1485 // We usually report the error to entry global in gecko, so just follow the
1486 // same behavior here.
1487 // This may be null if it's called from parser, see the case of
1488 // attributeChangedCallback in
1489 // https://html.spec.whatwg.org/multipage/parsing.html#create-an-element-for-the-token
1490 // In that case, the exception of callback reactions will be automatically
1491 // reported in CallSetup.
1492 nsIGlobalObject
* global
= GetEntryGlobal();
1493 InvokeReactions(elementQueue
, MOZ_KnownLive(global
));
1496 // InvokeReactions() might create other custom element reactions, but those
1497 // new reactions should be already consumed and removed at this point.
1499 lastIndex
== mReactionsStack
.Length() - 1,
1500 "reactions created by InvokeReactions() should be consumed and removed");
1502 mReactionsStack
.RemoveLastElement();
1503 mIsElementQueuePushedForCurrentRecursionDepth
= false;
1506 void CustomElementReactionsStack::EnqueueUpgradeReaction(
1507 Element
* aElement
, CustomElementDefinition
* aDefinition
) {
1508 Enqueue(aElement
, new CustomElementUpgradeReaction(aDefinition
));
1511 void CustomElementReactionsStack::EnqueueCallbackReaction(
1513 UniquePtr
<CustomElementCallback
> aCustomElementCallback
) {
1515 new CustomElementCallbackReaction(std::move(aCustomElementCallback
)));
1518 void CustomElementReactionsStack::Enqueue(Element
* aElement
,
1519 CustomElementReaction
* aReaction
) {
1520 CustomElementData
* elementData
= aElement
->GetCustomElementData();
1521 MOZ_ASSERT(elementData
, "CustomElementData should exist");
1523 if (mRecursionDepth
) {
1524 // If the element queue is not created for current recursion depth, create
1525 // and push an element queue to reactions stack first.
1526 if (!mIsElementQueuePushedForCurrentRecursionDepth
) {
1527 CreateAndPushElementQueue();
1530 MOZ_ASSERT(!mReactionsStack
.IsEmpty());
1531 // Add element to the current element queue.
1532 mReactionsStack
.LastElement()->AppendElement(aElement
);
1533 elementData
->mReactionQueue
.AppendElement(aReaction
);
1537 // If the custom element reactions stack is empty, then:
1538 // Add element to the backup element queue.
1539 MOZ_ASSERT(mReactionsStack
.IsEmpty(),
1540 "custom element reactions stack should be empty");
1541 mBackupQueue
.AppendElement(aElement
);
1542 elementData
->mReactionQueue
.AppendElement(aReaction
);
1544 if (mIsBackupQueueProcessing
) {
1548 CycleCollectedJSContext
* context
= CycleCollectedJSContext::Get();
1549 RefPtr
<BackupQueueMicroTask
> bqmt
= new BackupQueueMicroTask(this);
1550 context
->DispatchToMicroTask(bqmt
.forget());
1553 void CustomElementReactionsStack::InvokeBackupQueue() {
1554 // Check backup queue size in order to reduce function call overhead.
1555 if (!mBackupQueue
.IsEmpty()) {
1556 // Upgrade reactions won't be scheduled in backup queue and the exception of
1557 // callback reactions will be automatically reported in CallSetup.
1558 // If the reactions are invoked from backup queue (in microtask check
1559 // point), we don't need to pass global object for error reporting.
1560 InvokeReactions(&mBackupQueue
, nullptr);
1563 mBackupQueue
.IsEmpty(),
1564 "There are still some reactions in BackupQueue not being consumed!?!");
1567 void CustomElementReactionsStack::InvokeReactions(ElementQueue
* aElementQueue
,
1568 nsIGlobalObject
* aGlobal
) {
1569 // This is used for error reporting.
1570 Maybe
<AutoEntryScript
> aes
;
1572 aes
.emplace(aGlobal
, "custom elements reaction invocation");
1575 // Note: It's possible to re-enter this method.
1576 for (uint32_t i
= 0; i
< aElementQueue
->Length(); ++i
) {
1577 Element
* element
= aElementQueue
->ElementAt(i
);
1578 // ElementQueue hold a element's strong reference, it should not be a
1580 MOZ_ASSERT(element
);
1582 CustomElementData
* elementData
= element
->GetCustomElementData();
1583 if (!elementData
|| !element
->GetOwnerGlobal()) {
1584 // This happens when the document is destroyed and the element is already
1585 // unlinked, no need to fire the callbacks in this case.
1589 auto& reactions
= elementData
->mReactionQueue
;
1590 for (uint32_t j
= 0; j
< reactions
.Length(); ++j
) {
1591 // Transfer the ownership of the entry due to reentrant invocation of
1593 auto reaction(std::move(reactions
.ElementAt(j
)));
1595 if (!aGlobal
&& reaction
->IsUpgradeReaction()) {
1596 nsIGlobalObject
* global
= element
->GetOwnerGlobal();
1598 aes
.emplace(global
, "custom elements reaction invocation");
1601 reaction
->Invoke(MOZ_KnownLive(element
), rv
);
1603 JSContext
* cx
= aes
->cx();
1604 if (rv
.MaybeSetPendingException(cx
)) {
1605 aes
->ReportException();
1607 MOZ_ASSERT(!JS_IsExceptionPending(cx
));
1608 if (!aGlobal
&& reaction
->IsUpgradeReaction()) {
1612 MOZ_ASSERT(!rv
.Failed());
1617 aElementQueue
->Clear();
1620 //-----------------------------------------------------
1621 // CustomElementDefinition
1623 NS_IMPL_CYCLE_COLLECTION(CustomElementDefinition
, mConstructor
, mCallbacks
,
1624 mFormAssociatedCallbacks
, mConstructionStack
)
1626 CustomElementDefinition::CustomElementDefinition(
1627 nsAtom
* aType
, nsAtom
* aLocalName
, int32_t aNamespaceID
,
1628 CustomElementConstructor
* aConstructor
,
1629 nsTArray
<RefPtr
<nsAtom
>>&& aObservedAttributes
,
1630 UniquePtr
<LifecycleCallbacks
>&& aCallbacks
,
1631 UniquePtr
<FormAssociatedLifecycleCallbacks
>&& aFormAssociatedCallbacks
,
1632 bool aFormAssociated
, bool aDisableInternals
, bool aDisableShadow
)
1634 mLocalName(aLocalName
),
1635 mNamespaceID(aNamespaceID
),
1636 mConstructor(aConstructor
),
1637 mObservedAttributes(std::move(aObservedAttributes
)),
1638 mCallbacks(std::move(aCallbacks
)),
1639 mFormAssociatedCallbacks(std::move(aFormAssociatedCallbacks
)),
1640 mFormAssociated(aFormAssociated
),
1641 mDisableInternals(aDisableInternals
),
1642 mDisableShadow(aDisableShadow
) {}
1644 } // namespace mozilla::dom