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/. */
6 
7 #include "mozilla/dom/CustomElementRegistry.h"
8 
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/AutoRestore.h"
24 #include "mozilla/HoldDropJSObjects.h"
25 #include "nsContentUtils.h"
26 #include "nsHTMLTags.h"
27 #include "jsapi.h"
28 #include "js/ForOfIterator.h"       // JS::ForOfIterator
29 #include "js/PropertyAndElement.h"  // JS_GetProperty, JS_GetUCProperty
30 #include "xpcprivate.h"
31 #include "nsGlobalWindow.h"
32 #include "nsNameSpaceManager.h"
33 
34 namespace mozilla::dom {
35 
36 //-----------------------------------------------------
37 // CustomElementUpgradeReaction
38 
39 class CustomElementUpgradeReaction final : public CustomElementReaction {
40  public:
CustomElementUpgradeReaction(CustomElementDefinition * aDefinition)41   explicit CustomElementUpgradeReaction(CustomElementDefinition* aDefinition)
42       : mDefinition(aDefinition) {
43     mIsUpgradeReaction = true;
44   }
45 
Traverse(nsCycleCollectionTraversalCallback & aCb) const46   virtual void Traverse(
47       nsCycleCollectionTraversalCallback& aCb) const override {
48     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mDefinition");
49     aCb.NoteNativeChild(
50         mDefinition, NS_CYCLE_COLLECTION_PARTICIPANT(CustomElementDefinition));
51   }
52 
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const53   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
54     // We don't really own mDefinition.
55     return aMallocSizeOf(this);
56   }
57 
58  private:
59   MOZ_CAN_RUN_SCRIPT
Invoke(Element * aElement,ErrorResult & aRv)60   virtual void Invoke(Element* aElement, ErrorResult& aRv) override {
61     CustomElementRegistry::Upgrade(aElement, mDefinition, aRv);
62   }
63 
64   const RefPtr<CustomElementDefinition> mDefinition;
65 };
66 
67 //-----------------------------------------------------
68 // CustomElementCallbackReaction
69 
70 class CustomElementCallback {
71  public:
72   CustomElementCallback(Element* aThisObject, ElementCallbackType aCallbackType,
73                         CallbackFunction* aCallback,
74                         const LifecycleCallbackArgs& aArgs);
75   void Traverse(nsCycleCollectionTraversalCallback& aCb) const;
76   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
77   void Call();
78 
79   static UniquePtr<CustomElementCallback> Create(
80       ElementCallbackType aType, Element* aCustomElement,
81       const LifecycleCallbackArgs& aArgs, CustomElementDefinition* aDefinition);
82 
83  private:
84   // The this value to use for invocation of the callback.
85   RefPtr<Element> mThisObject;
86   RefPtr<CallbackFunction> mCallback;
87   // The type of callback (eCreated, eAttached, etc.)
88   ElementCallbackType mType;
89   // Arguments to be passed to the callback,
90   LifecycleCallbackArgs mArgs;
91 };
92 
93 class CustomElementCallbackReaction final : public CustomElementReaction {
94  public:
CustomElementCallbackReaction(UniquePtr<CustomElementCallback> aCustomElementCallback)95   explicit CustomElementCallbackReaction(
96       UniquePtr<CustomElementCallback> aCustomElementCallback)
97       : mCustomElementCallback(std::move(aCustomElementCallback)) {}
98 
Traverse(nsCycleCollectionTraversalCallback & aCb) const99   virtual void Traverse(
100       nsCycleCollectionTraversalCallback& aCb) const override {
101     mCustomElementCallback->Traverse(aCb);
102   }
103 
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const104   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
105     size_t n = aMallocSizeOf(this);
106 
107     n += mCustomElementCallback->SizeOfIncludingThis(aMallocSizeOf);
108 
109     return n;
110   }
111 
112  private:
Invoke(Element * aElement,ErrorResult & aRv)113   virtual void Invoke(Element* aElement, ErrorResult& aRv) override {
114     mCustomElementCallback->Call();
115   }
116 
117   UniquePtr<CustomElementCallback> mCustomElementCallback;
118 };
119 
120 //-----------------------------------------------------
121 // CustomElementCallback
122 
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const123 size_t LifecycleCallbackArgs::SizeOfExcludingThis(
124     MallocSizeOf aMallocSizeOf) const {
125   size_t n = mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
126   n += mOldValue.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
127   n += mNewValue.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
128   n += mNamespaceURI.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
129   return n;
130 }
131 
132 /* static */
Create(ElementCallbackType aType,Element * aCustomElement,const LifecycleCallbackArgs & aArgs,CustomElementDefinition * aDefinition)133 UniquePtr<CustomElementCallback> CustomElementCallback::Create(
134     ElementCallbackType aType, Element* aCustomElement,
135     const LifecycleCallbackArgs& aArgs, CustomElementDefinition* aDefinition) {
136   MOZ_ASSERT(aDefinition, "CustomElementDefinition should not be null");
137   MOZ_ASSERT(aCustomElement->GetCustomElementData(),
138              "CustomElementData should exist");
139 
140   // Let CALLBACK be the callback associated with the key NAME in CALLBACKS.
141   CallbackFunction* func = nullptr;
142   switch (aType) {
143     case ElementCallbackType::eConnected:
144       if (aDefinition->mCallbacks->mConnectedCallback.WasPassed()) {
145         func = aDefinition->mCallbacks->mConnectedCallback.Value();
146       }
147       break;
148 
149     case ElementCallbackType::eDisconnected:
150       if (aDefinition->mCallbacks->mDisconnectedCallback.WasPassed()) {
151         func = aDefinition->mCallbacks->mDisconnectedCallback.Value();
152       }
153       break;
154 
155     case ElementCallbackType::eAdopted:
156       if (aDefinition->mCallbacks->mAdoptedCallback.WasPassed()) {
157         func = aDefinition->mCallbacks->mAdoptedCallback.Value();
158       }
159       break;
160 
161     case ElementCallbackType::eAttributeChanged:
162       if (aDefinition->mCallbacks->mAttributeChangedCallback.WasPassed()) {
163         func = aDefinition->mCallbacks->mAttributeChangedCallback.Value();
164       }
165       break;
166 
167     case ElementCallbackType::eFormAssociated:
168       if (aDefinition->mCallbacks->mFormAssociatedCallback.WasPassed()) {
169         func = aDefinition->mCallbacks->mFormAssociatedCallback.Value();
170       }
171       break;
172 
173     case ElementCallbackType::eFormReset:
174       if (aDefinition->mCallbacks->mFormResetCallback.WasPassed()) {
175         func = aDefinition->mCallbacks->mFormResetCallback.Value();
176       }
177       break;
178 
179     case ElementCallbackType::eFormDisabled:
180       if (aDefinition->mCallbacks->mFormDisabledCallback.WasPassed()) {
181         func = aDefinition->mCallbacks->mFormDisabledCallback.Value();
182       }
183       break;
184 
185     case ElementCallbackType::eGetCustomInterface:
186       MOZ_ASSERT_UNREACHABLE("Don't call GetCustomInterface through callback");
187       break;
188   }
189 
190   // If there is no such callback, stop.
191   if (!func) {
192     return nullptr;
193   }
194 
195   // Add CALLBACK to ELEMENT's callback queue.
196   return MakeUnique<CustomElementCallback>(aCustomElement, aType, func, aArgs);
197 }
198 
Call()199 void CustomElementCallback::Call() {
200   switch (mType) {
201     case ElementCallbackType::eConnected:
202       static_cast<LifecycleConnectedCallback*>(mCallback.get())
203           ->Call(mThisObject);
204       break;
205     case ElementCallbackType::eDisconnected:
206       static_cast<LifecycleDisconnectedCallback*>(mCallback.get())
207           ->Call(mThisObject);
208       break;
209     case ElementCallbackType::eAdopted:
210       static_cast<LifecycleAdoptedCallback*>(mCallback.get())
211           ->Call(mThisObject, mArgs.mOldDocument, mArgs.mNewDocument);
212       break;
213     case ElementCallbackType::eAttributeChanged:
214       static_cast<LifecycleAttributeChangedCallback*>(mCallback.get())
215           ->Call(mThisObject, mArgs.mName, mArgs.mOldValue, mArgs.mNewValue,
216                  mArgs.mNamespaceURI);
217       break;
218     case ElementCallbackType::eFormAssociated:
219       static_cast<LifecycleFormAssociatedCallback*>(mCallback.get())
220           ->Call(mThisObject, mArgs.mForm);
221       break;
222     case ElementCallbackType::eFormReset:
223       static_cast<LifecycleFormResetCallback*>(mCallback.get())
224           ->Call(mThisObject);
225       break;
226     case ElementCallbackType::eFormDisabled:
227       static_cast<LifecycleFormDisabledCallback*>(mCallback.get())
228           ->Call(mThisObject, mArgs.mDisabled);
229       break;
230     case ElementCallbackType::eGetCustomInterface:
231       MOZ_ASSERT_UNREACHABLE("Don't call GetCustomInterface through callback");
232       break;
233   }
234 }
235 
Traverse(nsCycleCollectionTraversalCallback & aCb) const236 void CustomElementCallback::Traverse(
237     nsCycleCollectionTraversalCallback& aCb) const {
238   NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mThisObject");
239   aCb.NoteXPCOMChild(mThisObject);
240 
241   NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mCallback");
242   aCb.NoteXPCOMChild(mCallback);
243 }
244 
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const245 size_t CustomElementCallback::SizeOfIncludingThis(
246     MallocSizeOf aMallocSizeOf) const {
247   size_t n = aMallocSizeOf(this);
248 
249   // We don't uniquely own mThisObject.
250 
251   // We own mCallback but it doesn't have any special memory reporting we can do
252   // for it other than report its own size.
253   n += aMallocSizeOf(mCallback);
254 
255   n += mArgs.SizeOfExcludingThis(aMallocSizeOf);
256 
257   return n;
258 }
259 
CustomElementCallback(Element * aThisObject,ElementCallbackType aCallbackType,mozilla::dom::CallbackFunction * aCallback,const LifecycleCallbackArgs & aArgs)260 CustomElementCallback::CustomElementCallback(
261     Element* aThisObject, ElementCallbackType aCallbackType,
262     mozilla::dom::CallbackFunction* aCallback,
263     const LifecycleCallbackArgs& aArgs)
264     : mThisObject(aThisObject),
265       mCallback(aCallback),
266       mType(aCallbackType),
267       mArgs(aArgs) {}
268 
269 //-----------------------------------------------------
270 // CustomElementData
271 
CustomElementData(nsAtom * aType)272 CustomElementData::CustomElementData(nsAtom* aType)
273     : CustomElementData(aType, CustomElementData::State::eUndefined) {}
274 
CustomElementData(nsAtom * aType,State aState)275 CustomElementData::CustomElementData(nsAtom* aType, State aState)
276     : mState(aState), mType(aType) {}
277 
SetCustomElementDefinition(CustomElementDefinition * aDefinition)278 void CustomElementData::SetCustomElementDefinition(
279     CustomElementDefinition* aDefinition) {
280   // Only allow reset definition to nullptr if the custom element state is
281   // "failed".
282   MOZ_ASSERT(aDefinition ? !mCustomElementDefinition
283                          : mState == State::eFailed);
284   MOZ_ASSERT_IF(aDefinition, aDefinition->mType == mType);
285 
286   mCustomElementDefinition = aDefinition;
287 }
288 
AttachedInternals()289 void CustomElementData::AttachedInternals() {
290   MOZ_ASSERT(!mIsAttachedInternals);
291 
292   mIsAttachedInternals = true;
293 }
294 
GetCustomElementDefinition() const295 CustomElementDefinition* CustomElementData::GetCustomElementDefinition() const {
296   // Per spec, if there is a definition, the custom element state should be
297   // either "failed" (during upgrade) or "customized".
298   MOZ_ASSERT_IF(mCustomElementDefinition, mState != State::eUndefined);
299 
300   return mCustomElementDefinition;
301 }
302 
IsFormAssociated() const303 bool CustomElementData::IsFormAssociated() const {
304   // https://html.spec.whatwg.org/#form-associated-custom-element
305   return mCustomElementDefinition &&
306          !mCustomElementDefinition->IsCustomBuiltIn() &&
307          mCustomElementDefinition->mFormAssociated;
308 }
309 
Traverse(nsCycleCollectionTraversalCallback & aCb) const310 void CustomElementData::Traverse(
311     nsCycleCollectionTraversalCallback& aCb) const {
312   for (uint32_t i = 0; i < mReactionQueue.Length(); i++) {
313     if (mReactionQueue[i]) {
314       mReactionQueue[i]->Traverse(aCb);
315     }
316   }
317 
318   if (mCustomElementDefinition) {
319     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mCustomElementDefinition");
320     aCb.NoteNativeChild(
321         mCustomElementDefinition,
322         NS_CYCLE_COLLECTION_PARTICIPANT(CustomElementDefinition));
323   }
324 
325   if (mElementInternals) {
326     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mElementInternals");
327     aCb.NoteNativeChild(mElementInternals,
328                         NS_CYCLE_COLLECTION_PARTICIPANT(ElementInternals));
329   }
330 }
331 
Unlink()332 void CustomElementData::Unlink() {
333   mReactionQueue.Clear();
334   if (mElementInternals) {
335     mElementInternals->Unlink();
336     mElementInternals = nullptr;
337   }
338   mCustomElementDefinition = nullptr;
339 }
340 
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const341 size_t CustomElementData::SizeOfIncludingThis(
342     MallocSizeOf aMallocSizeOf) const {
343   size_t n = aMallocSizeOf(this);
344 
345   n += mReactionQueue.ShallowSizeOfExcludingThis(aMallocSizeOf);
346 
347   for (auto& reaction : mReactionQueue) {
348     // "reaction" can be null if we're being called indirectly from
349     // InvokeReactions (e.g. due to a reaction causing a memory report to be
350     // captured somehow).
351     if (reaction) {
352       n += reaction->SizeOfIncludingThis(aMallocSizeOf);
353     }
354   }
355 
356   return n;
357 }
358 
359 //-----------------------------------------------------
360 // CustomElementRegistry
361 
362 namespace {
363 
364 class MOZ_RAII AutoConstructionStackEntry final {
365  public:
AutoConstructionStackEntry(nsTArray<RefPtr<Element>> & aStack,Element * aElement)366   AutoConstructionStackEntry(nsTArray<RefPtr<Element>>& aStack,
367                              Element* aElement)
368       : mStack(aStack) {
369     MOZ_ASSERT(aElement->IsHTMLElement() || aElement->IsXULElement());
370 
371 #ifdef DEBUG
372     mIndex = mStack.Length();
373 #endif
374     mStack.AppendElement(aElement);
375   }
376 
~AutoConstructionStackEntry()377   ~AutoConstructionStackEntry() {
378     MOZ_ASSERT(mIndex == mStack.Length() - 1,
379                "Removed element should be the last element");
380     mStack.RemoveLastElement();
381   }
382 
383  private:
384   nsTArray<RefPtr<Element>>& mStack;
385 #ifdef DEBUG
386   uint32_t mIndex;
387 #endif
388 };
389 
390 }  // namespace
391 
392 // Only needed for refcounted objects.
393 NS_IMPL_CYCLE_COLLECTION_CLASS(CustomElementRegistry)
394 
395 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CustomElementRegistry)
396   tmp->mConstructors.clear();
397   NS_IMPL_CYCLE_COLLECTION_UNLINK(mCustomDefinitions)
398   NS_IMPL_CYCLE_COLLECTION_UNLINK(mWhenDefinedPromiseMap)
399   NS_IMPL_CYCLE_COLLECTION_UNLINK(mElementCreationCallbacks)
400   NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
401   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
402 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
403 
404 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CustomElementRegistry)
405   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCustomDefinitions)
406   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWhenDefinedPromiseMap)
407   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElementCreationCallbacks)
408   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
409 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
410 
411 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CustomElementRegistry)
412   for (auto iter = tmp->mConstructors.iter(); !iter.done(); iter.next()) {
413     aCallbacks.Trace(&iter.get().mutableKey(), "mConstructors key", aClosure);
414   }
415   NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
416 NS_IMPL_CYCLE_COLLECTION_TRACE_END
417 
NS_IMPL_CYCLE_COLLECTING_ADDREF(CustomElementRegistry)418 NS_IMPL_CYCLE_COLLECTING_ADDREF(CustomElementRegistry)
419 NS_IMPL_CYCLE_COLLECTING_RELEASE(CustomElementRegistry)
420 
421 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CustomElementRegistry)
422   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
423   NS_INTERFACE_MAP_ENTRY(nsISupports)
424 NS_INTERFACE_MAP_END
425 
426 CustomElementRegistry::CustomElementRegistry(nsPIDOMWindowInner* aWindow)
427     : mWindow(aWindow), mIsCustomDefinitionRunning(false) {
428   MOZ_ASSERT(aWindow);
429 
430   mozilla::HoldJSObjects(this);
431 }
432 
~CustomElementRegistry()433 CustomElementRegistry::~CustomElementRegistry() {
434   mozilla::DropJSObjects(this);
435 }
436 
437 NS_IMETHODIMP
Run()438 CustomElementRegistry::RunCustomElementCreationCallback::Run() {
439   ErrorResult er;
440   nsDependentAtomString value(mAtom);
441   mCallback->Call(value, er);
442   MOZ_ASSERT(NS_SUCCEEDED(er.StealNSResult()),
443              "chrome JavaScript error in the callback.");
444 
445   RefPtr<CustomElementDefinition> definition =
446       mRegistry->mCustomDefinitions.Get(mAtom);
447   MOZ_ASSERT(definition, "Callback should define the definition of type.");
448   MOZ_ASSERT(!mRegistry->mElementCreationCallbacks.GetWeak(mAtom),
449              "Callback should be removed.");
450 
451   mozilla::UniquePtr<nsTHashSet<RefPtr<nsIWeakReference>>> elements;
452   mRegistry->mElementCreationCallbacksUpgradeCandidatesMap.Remove(mAtom,
453                                                                   &elements);
454   MOZ_ASSERT(elements, "There should be a list");
455 
456   for (const auto& key : *elements) {
457     nsCOMPtr<Element> elem = do_QueryReferent(key);
458     if (!elem) {
459       continue;
460     }
461 
462     CustomElementRegistry::Upgrade(elem, definition, er);
463     MOZ_ASSERT(NS_SUCCEEDED(er.StealNSResult()),
464                "chrome JavaScript error in custom element construction.");
465   }
466 
467   return NS_OK;
468 }
469 
LookupCustomElementDefinition(nsAtom * aNameAtom,int32_t aNameSpaceID,nsAtom * aTypeAtom)470 CustomElementDefinition* CustomElementRegistry::LookupCustomElementDefinition(
471     nsAtom* aNameAtom, int32_t aNameSpaceID, nsAtom* aTypeAtom) {
472   CustomElementDefinition* data = mCustomDefinitions.GetWeak(aTypeAtom);
473 
474   if (!data) {
475     RefPtr<CustomElementCreationCallback> callback;
476     mElementCreationCallbacks.Get(aTypeAtom, getter_AddRefs(callback));
477     if (callback) {
478       mElementCreationCallbacks.Remove(aTypeAtom);
479       mElementCreationCallbacksUpgradeCandidatesMap.GetOrInsertNew(aTypeAtom);
480       RefPtr<Runnable> runnable =
481           new RunCustomElementCreationCallback(this, aTypeAtom, callback);
482       nsContentUtils::AddScriptRunner(runnable.forget());
483       data = mCustomDefinitions.GetWeak(aTypeAtom);
484     }
485   }
486 
487   if (data && data->mLocalName == aNameAtom &&
488       data->mNamespaceID == aNameSpaceID) {
489     return data;
490   }
491 
492   return nullptr;
493 }
494 
LookupCustomElementDefinition(JSContext * aCx,JSObject * aConstructor) const495 CustomElementDefinition* CustomElementRegistry::LookupCustomElementDefinition(
496     JSContext* aCx, JSObject* aConstructor) const {
497   // We're looking up things that tested true for JS::IsConstructor,
498   // so doing a CheckedUnwrapStatic is fine here.
499   JS::Rooted<JSObject*> constructor(aCx, js::CheckedUnwrapStatic(aConstructor));
500 
501   const auto& ptr = mConstructors.lookup(constructor);
502   if (!ptr) {
503     return nullptr;
504   }
505 
506   CustomElementDefinition* definition =
507       mCustomDefinitions.GetWeak(ptr->value());
508   MOZ_ASSERT(definition, "Definition must be found in mCustomDefinitions");
509 
510   return definition;
511 }
512 
RegisterUnresolvedElement(Element * aElement,nsAtom * aTypeName)513 void CustomElementRegistry::RegisterUnresolvedElement(Element* aElement,
514                                                       nsAtom* aTypeName) {
515   // We don't have a use-case for a Custom Element inside NAC, and continuing
516   // here causes performance issues for NAC + XBL anonymous content.
517   if (aElement->IsInNativeAnonymousSubtree()) {
518     return;
519   }
520 
521   mozilla::dom::NodeInfo* info = aElement->NodeInfo();
522 
523   // Candidate may be a custom element through extension,
524   // in which case the custom element type name will not
525   // match the element tag name. e.g. <button is="x-button">.
526   RefPtr<nsAtom> typeName = aTypeName;
527   if (!typeName) {
528     typeName = info->NameAtom();
529   }
530 
531   if (mCustomDefinitions.GetWeak(typeName)) {
532     return;
533   }
534 
535   nsTHashSet<RefPtr<nsIWeakReference>>* unresolved =
536       mCandidatesMap.GetOrInsertNew(typeName);
537   nsWeakPtr elem = do_GetWeakReference(aElement);
538   unresolved->Insert(elem);
539 }
540 
UnregisterUnresolvedElement(Element * aElement,nsAtom * aTypeName)541 void CustomElementRegistry::UnregisterUnresolvedElement(Element* aElement,
542                                                         nsAtom* aTypeName) {
543   nsIWeakReference* weak = aElement->GetExistingWeakReference();
544   if (!weak) {
545     return;
546   }
547 
548 #ifdef DEBUG
549   {
550     nsWeakPtr weakPtr = do_GetWeakReference(aElement);
551     MOZ_ASSERT(
552         weak == weakPtr.get(),
553         "do_GetWeakReference should reuse the existing nsIWeakReference.");
554   }
555 #endif
556 
557   nsTHashSet<RefPtr<nsIWeakReference>>* candidates = nullptr;
558   if (mCandidatesMap.Get(aTypeName, &candidates)) {
559     MOZ_ASSERT(candidates);
560     candidates->Remove(weak);
561   }
562 }
563 
564 // https://html.spec.whatwg.org/commit-snapshots/65f39c6fc0efa92b0b2b23b93197016af6ac0de6/#enqueue-a-custom-element-callback-reaction
565 /* static */
EnqueueLifecycleCallback(ElementCallbackType aType,Element * aCustomElement,const LifecycleCallbackArgs & aArgs,CustomElementDefinition * aDefinition)566 void CustomElementRegistry::EnqueueLifecycleCallback(
567     ElementCallbackType aType, Element* aCustomElement,
568     const LifecycleCallbackArgs& aArgs, CustomElementDefinition* aDefinition) {
569   CustomElementDefinition* definition = aDefinition;
570   if (!definition) {
571     definition = aCustomElement->GetCustomElementDefinition();
572     if (!definition ||
573         definition->mLocalName != aCustomElement->NodeInfo()->NameAtom()) {
574       return;
575     }
576 
577     if (!definition->mCallbacks) {
578       // definition has been unlinked.  Don't try to mess with it.
579       return;
580     }
581   }
582 
583   auto callback =
584       CustomElementCallback::Create(aType, aCustomElement, aArgs, definition);
585   if (!callback) {
586     return;
587   }
588 
589   DocGroup* docGroup = aCustomElement->OwnerDoc()->GetDocGroup();
590   if (!docGroup) {
591     return;
592   }
593 
594   if (aType == ElementCallbackType::eAttributeChanged) {
595     RefPtr<nsAtom> attrName = NS_Atomize(aArgs.mName);
596     if (definition->mObservedAttributes.IsEmpty() ||
597         !definition->mObservedAttributes.Contains(attrName)) {
598       return;
599     }
600   }
601 
602   CustomElementReactionsStack* reactionsStack =
603       docGroup->CustomElementReactionsStack();
604   reactionsStack->EnqueueCallbackReaction(aCustomElement, std::move(callback));
605 }
606 
607 namespace {
608 
609 class CandidateFinder {
610  public:
611   CandidateFinder(nsTHashSet<RefPtr<nsIWeakReference>>& aCandidates,
612                   Document* aDoc);
613   nsTArray<nsCOMPtr<Element>> OrderedCandidates();
614 
615  private:
616   nsCOMPtr<Document> mDoc;
617   nsInterfaceHashtable<nsPtrHashKey<Element>, Element> mCandidates;
618 };
619 
CandidateFinder(nsTHashSet<RefPtr<nsIWeakReference>> & aCandidates,Document * aDoc)620 CandidateFinder::CandidateFinder(
621     nsTHashSet<RefPtr<nsIWeakReference>>& aCandidates, Document* aDoc)
622     : mDoc(aDoc), mCandidates(aCandidates.Count()) {
623   MOZ_ASSERT(mDoc);
624   for (const auto& candidate : aCandidates) {
625     nsCOMPtr<Element> elem = do_QueryReferent(candidate);
626     if (!elem) {
627       continue;
628     }
629 
630     Element* key = elem.get();
631     mCandidates.InsertOrUpdate(key, elem.forget());
632   }
633 }
634 
OrderedCandidates()635 nsTArray<nsCOMPtr<Element>> CandidateFinder::OrderedCandidates() {
636   if (mCandidates.Count() == 1) {
637     // Fast path for one candidate.
638     auto iter = mCandidates.Iter();
639     nsTArray<nsCOMPtr<Element>> rval({std::move(iter.Data())});
640     iter.Remove();
641     return rval;
642   }
643 
644   nsTArray<nsCOMPtr<Element>> orderedElements(mCandidates.Count());
645   for (nsINode* node : ShadowIncludingTreeIterator(*mDoc)) {
646     Element* element = Element::FromNode(node);
647     if (!element) {
648       continue;
649     }
650 
651     nsCOMPtr<Element> elem;
652     if (mCandidates.Remove(element, getter_AddRefs(elem))) {
653       orderedElements.AppendElement(std::move(elem));
654       if (mCandidates.Count() == 0) {
655         break;
656       }
657     }
658   }
659 
660   return orderedElements;
661 }
662 
663 }  // namespace
664 
UpgradeCandidates(nsAtom * aKey,CustomElementDefinition * aDefinition,ErrorResult & aRv)665 void CustomElementRegistry::UpgradeCandidates(
666     nsAtom* aKey, CustomElementDefinition* aDefinition, ErrorResult& aRv) {
667   DocGroup* docGroup = mWindow->GetDocGroup();
668   if (!docGroup) {
669     aRv.Throw(NS_ERROR_UNEXPECTED);
670     return;
671   }
672 
673   mozilla::UniquePtr<nsTHashSet<RefPtr<nsIWeakReference>>> candidates;
674   if (mCandidatesMap.Remove(aKey, &candidates)) {
675     MOZ_ASSERT(candidates);
676     CustomElementReactionsStack* reactionsStack =
677         docGroup->CustomElementReactionsStack();
678 
679     CandidateFinder finder(*candidates, mWindow->GetExtantDoc());
680     for (auto& elem : finder.OrderedCandidates()) {
681       reactionsStack->EnqueueUpgradeReaction(elem, aDefinition);
682     }
683   }
684 }
685 
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)686 JSObject* CustomElementRegistry::WrapObject(JSContext* aCx,
687                                             JS::Handle<JSObject*> aGivenProto) {
688   return CustomElementRegistry_Binding::Wrap(aCx, this, aGivenProto);
689 }
690 
GetParentObject() const691 nsISupports* CustomElementRegistry::GetParentObject() const { return mWindow; }
692 
GetDocGroup() const693 DocGroup* CustomElementRegistry::GetDocGroup() const {
694   return mWindow ? mWindow->GetDocGroup() : nullptr;
695 }
696 
InferNamespace(JSContext * aCx,JS::Handle<JSObject * > constructor)697 int32_t CustomElementRegistry::InferNamespace(
698     JSContext* aCx, JS::Handle<JSObject*> constructor) {
699   JS::Rooted<JSObject*> XULConstructor(
700       aCx, XULElement_Binding::GetConstructorObject(aCx));
701 
702   JS::Rooted<JSObject*> proto(aCx, constructor);
703   while (proto) {
704     if (proto == XULConstructor) {
705       return kNameSpaceID_XUL;
706     }
707 
708     JS_GetPrototype(aCx, proto, &proto);
709   }
710 
711   return kNameSpaceID_XHTML;
712 }
713 
JSObjectToAtomArray(JSContext * aCx,JS::Handle<JSObject * > aConstructor,const nsString & aName,nsTArray<RefPtr<nsAtom>> & aArray,ErrorResult & aRv)714 bool CustomElementRegistry::JSObjectToAtomArray(
715     JSContext* aCx, JS::Handle<JSObject*> aConstructor, const nsString& aName,
716     nsTArray<RefPtr<nsAtom>>& aArray, ErrorResult& aRv) {
717   JS::RootedValue iterable(aCx, JS::UndefinedValue());
718   if (!JS_GetUCProperty(aCx, aConstructor, aName.get(), aName.Length(),
719                         &iterable)) {
720     aRv.NoteJSContextException(aCx);
721     return false;
722   }
723 
724   if (!iterable.isUndefined()) {
725     if (!iterable.isObject()) {
726       aRv.ThrowTypeError<MSG_CONVERSION_ERROR>(NS_ConvertUTF16toUTF8(aName),
727                                                "sequence");
728       return false;
729     }
730 
731     JS::ForOfIterator iter(aCx);
732     if (!iter.init(iterable, JS::ForOfIterator::AllowNonIterable)) {
733       aRv.NoteJSContextException(aCx);
734       return false;
735     }
736 
737     if (!iter.valueIsIterable()) {
738       aRv.ThrowTypeError<MSG_CONVERSION_ERROR>(NS_ConvertUTF16toUTF8(aName),
739                                                "sequence");
740       return false;
741     }
742 
743     JS::Rooted<JS::Value> attribute(aCx);
744     while (true) {
745       bool done;
746       if (!iter.next(&attribute, &done)) {
747         aRv.NoteJSContextException(aCx);
748         return false;
749       }
750       if (done) {
751         break;
752       }
753 
754       nsAutoString attrStr;
755       if (!ConvertJSValueToString(aCx, attribute, eStringify, eStringify,
756                                   attrStr)) {
757         aRv.NoteJSContextException(aCx);
758         return false;
759       }
760 
761       // XXX(Bug 1631371) Check if this should use a fallible operation as it
762       // pretended earlier.
763       aArray.AppendElement(NS_Atomize(attrStr));
764     }
765   }
766 
767   return true;
768 }
769 
770 // https://html.spec.whatwg.org/commit-snapshots/b48bb2238269d90ea4f455a52cdf29505aff3df0/#dom-customelementregistry-define
Define(JSContext * aCx,const nsAString & aName,CustomElementConstructor & aFunctionConstructor,const ElementDefinitionOptions & aOptions,ErrorResult & aRv)771 void CustomElementRegistry::Define(
772     JSContext* aCx, const nsAString& aName,
773     CustomElementConstructor& aFunctionConstructor,
774     const ElementDefinitionOptions& aOptions, ErrorResult& aRv) {
775   JS::Rooted<JSObject*> constructor(aCx, aFunctionConstructor.CallableOrNull());
776 
777   // We need to do a dynamic unwrap in order to throw the right exception.  We
778   // could probably avoid that if we just threw MSG_NOT_CONSTRUCTOR if unwrap
779   // fails.
780   //
781   // In any case, aCx represents the global we want to be using for the unwrap
782   // here.
783   JS::Rooted<JSObject*> constructorUnwrapped(
784       aCx, js::CheckedUnwrapDynamic(constructor, aCx));
785   if (!constructorUnwrapped) {
786     // If the caller's compartment does not have permission to access the
787     // unwrapped constructor then throw.
788     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
789     return;
790   }
791 
792   /**
793    * 1. If IsConstructor(constructor) is false, then throw a TypeError and abort
794    *    these steps.
795    */
796   if (!JS::IsConstructor(constructorUnwrapped)) {
797     aRv.ThrowTypeError<MSG_NOT_CONSTRUCTOR>("Argument 2");
798     return;
799   }
800 
801   int32_t nameSpaceID = InferNamespace(aCx, constructor);
802 
803   /**
804    * 2. If name is not a valid custom element name, then throw a "SyntaxError"
805    *    DOMException and abort these steps.
806    */
807   Document* doc = mWindow->GetExtantDoc();
808   RefPtr<nsAtom> nameAtom(NS_Atomize(aName));
809   if (!nsContentUtils::IsCustomElementName(nameAtom, nameSpaceID)) {
810     aRv.ThrowSyntaxError(
811         nsPrintfCString("'%s' is not a valid custom element name",
812                         NS_ConvertUTF16toUTF8(aName).get()));
813     return;
814   }
815 
816   /**
817    * 3. If this CustomElementRegistry contains an entry with name name, then
818    *    throw a "NotSupportedError" DOMException and abort these steps.
819    */
820   if (mCustomDefinitions.GetWeak(nameAtom)) {
821     aRv.ThrowNotSupportedError(
822         nsPrintfCString("'%s' has already been defined as a custom element",
823                         NS_ConvertUTF16toUTF8(aName).get()));
824     return;
825   }
826 
827   /**
828    * 4. If this CustomElementRegistry contains an entry with constructor
829    * constructor, then throw a "NotSupportedError" DOMException and abort these
830    * steps.
831    */
832   const auto& ptr = mConstructors.lookup(constructorUnwrapped);
833   if (ptr) {
834     MOZ_ASSERT(mCustomDefinitions.GetWeak(ptr->value()),
835                "Definition must be found in mCustomDefinitions");
836     nsAutoCString name;
837     ptr->value()->ToUTF8String(name);
838     aRv.ThrowNotSupportedError(
839         nsPrintfCString("'%s' and '%s' have the same constructor",
840                         NS_ConvertUTF16toUTF8(aName).get(), name.get()));
841     return;
842   }
843 
844   /**
845    * 5. Let localName be name.
846    * 6. Let extends be the value of the extends member of options, or null if
847    *    no such member exists.
848    * 7. If extends is not null, then:
849    *    1. If extends is a valid custom element name, then throw a
850    *       "NotSupportedError" DOMException.
851    *    2. If the element interface for extends and the HTML namespace is
852    *       HTMLUnknownElement (e.g., if extends does not indicate an element
853    *       definition in this specification), then throw a "NotSupportedError"
854    *       DOMException.
855    *    3. Set localName to extends.
856    *
857    * Special note for XUL elements:
858    *
859    * For step 7.1, we'll subject XUL to the same rules as HTML, so that a
860    * custom built-in element will not be extending from a dashed name.
861    * Step 7.2 is disregarded. But, we do check if the name is a dashed name
862    * (i.e. step 2) given that there is no reason for a custom built-in element
863    * type to take on a non-dashed name.
864    * This also ensures the name of the built-in custom element type can never
865    * be the same as the built-in element name, so we don't break the assumption
866    * elsewhere.
867    */
868   nsAutoString localName(aName);
869   if (aOptions.mExtends.WasPassed()) {
870     RefPtr<nsAtom> extendsAtom(NS_Atomize(aOptions.mExtends.Value()));
871     if (nsContentUtils::IsCustomElementName(extendsAtom, kNameSpaceID_XHTML)) {
872       aRv.ThrowNotSupportedError(
873           nsPrintfCString("'%s' cannot extend a custom element",
874                           NS_ConvertUTF16toUTF8(aName).get()));
875       return;
876     }
877 
878     if (nameSpaceID == kNameSpaceID_XHTML) {
879       // bgsound and multicol are unknown html element.
880       int32_t tag = nsHTMLTags::CaseSensitiveAtomTagToId(extendsAtom);
881       if (tag == eHTMLTag_userdefined || tag == eHTMLTag_bgsound ||
882           tag == eHTMLTag_multicol) {
883         aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
884         return;
885       }
886     } else {  // kNameSpaceID_XUL
887       // As stated above, ensure the name of the customized built-in element
888       // (the one that goes to the |is| attribute) is a dashed name.
889       if (!nsContentUtils::IsNameWithDash(nameAtom)) {
890         aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
891         return;
892       }
893     }
894 
895     localName.Assign(aOptions.mExtends.Value());
896   }
897 
898   /**
899    * 8. If this CustomElementRegistry's element definition is running flag is
900    * set, then throw a "NotSupportedError" DOMException and abort these steps.
901    */
902   if (mIsCustomDefinitionRunning) {
903     aRv.ThrowNotSupportedError(
904         "Cannot define a custom element while defining another custom element");
905     return;
906   }
907 
908   auto callbacksHolder = MakeUnique<LifecycleCallbacks>();
909   nsTArray<RefPtr<nsAtom>> observedAttributes;
910   AutoTArray<RefPtr<nsAtom>, 2> disabledFeatures;
911   bool formAssociated = false;
912   bool disableInternals = false;
913   bool disableShadow = false;
914   {  // Set mIsCustomDefinitionRunning.
915     /**
916      * 9. Set this CustomElementRegistry's element definition is running flag.
917      */
918     AutoRestore<bool> restoreRunning(mIsCustomDefinitionRunning);
919     mIsCustomDefinitionRunning = true;
920 
921     /**
922      * 14.1. Let prototype be Get(constructor, "prototype"). Rethrow any
923      * exceptions.
924      */
925     // The .prototype on the constructor passed could be an "expando" of a
926     // wrapper. So we should get it from wrapper instead of the underlying
927     // object.
928     JS::Rooted<JS::Value> prototype(aCx);
929     if (!JS_GetProperty(aCx, constructor, "prototype", &prototype)) {
930       aRv.NoteJSContextException(aCx);
931       return;
932     }
933 
934     /**
935      * 14.2. If Type(prototype) is not Object, then throw a TypeError exception.
936      */
937     if (!prototype.isObject()) {
938       aRv.ThrowTypeError<MSG_NOT_OBJECT>("constructor.prototype");
939       return;
940     }
941 
942     /**
943      * 14.3. Let lifecycleCallbacks be a map with the four keys
944      *       "connectedCallback", "disconnectedCallback", "adoptedCallback", and
945      *       "attributeChangedCallback", each of which belongs to an entry whose
946      *       value is null. The 'getCustomInterface' callback is also included
947      *       for chrome usage.
948      * 14.4. For each of the four keys callbackName in lifecycleCallbacks:
949      *       1. Let callbackValue be Get(prototype, callbackName). Rethrow any
950      *          exceptions.
951      *       2. If callbackValue is not undefined, then set the value of the
952      *          entry in lifecycleCallbacks with key callbackName to the result
953      *          of converting callbackValue to the Web IDL Function callback
954      * type. Rethrow any exceptions from the conversion.
955      */
956     if (!callbacksHolder->Init(aCx, prototype)) {
957       aRv.NoteJSContextException(aCx);
958       return;
959     }
960 
961     /**
962      * 14.5. If the value of the entry in lifecycleCallbacks with key
963      *       "attributeChangedCallback" is not null, then:
964      *       1. Let observedAttributesIterable be Get(constructor,
965      *          "observedAttributes"). Rethrow any exceptions.
966      *       2. If observedAttributesIterable is not undefined, then set
967      *          observedAttributes to the result of converting
968      *          observedAttributesIterable to a sequence<DOMString>. Rethrow
969      *          any exceptions from the conversion.
970      */
971     if (callbacksHolder->mAttributeChangedCallback.WasPassed()) {
972       if (!JSObjectToAtomArray(aCx, constructor, u"observedAttributes"_ns,
973                                observedAttributes, aRv)) {
974         return;
975       }
976     }
977 
978     if (StaticPrefs::dom_webcomponents_disabledFeatures_enabled()) {
979       /**
980        * 14.6. Let disabledFeatures be an empty sequence<DOMString>.
981        * 14.7. Let disabledFeaturesIterable be Get(constructor,
982        *       "disabledFeatures"). Rethrow any exceptions.
983        * 14.8. If disabledFeaturesIterable is not undefined, then set
984        *       disabledFeatures to the result of converting
985        *       disabledFeaturesIterable to a sequence<DOMString>.
986        *       Rethrow any exceptions from the conversion.
987        */
988       if (!JSObjectToAtomArray(aCx, constructor, u"disabledFeatures"_ns,
989                                disabledFeatures, aRv)) {
990         return;
991       }
992 
993       // 14.9. Set disableInternals to true if disabledFeaturesSequence contains
994       //       "internals".
995       disableInternals = disabledFeatures.Contains(
996           static_cast<nsStaticAtom*>(nsGkAtoms::internals));
997 
998       // 14.10. Set disableShadow to true if disabledFeaturesSequence contains
999       //        "shadow".
1000       disableShadow = disabledFeatures.Contains(
1001           static_cast<nsStaticAtom*>(nsGkAtoms::shadow));
1002     }
1003 
1004     if (StaticPrefs::dom_webcomponents_formAssociatedCustomElement_enabled()) {
1005       // 14.11. Let formAssociatedValue be Get(constructor, "formAssociated").
1006       //        Rethrow any exceptions.
1007       JS::Rooted<JS::Value> formAssociatedValue(aCx);
1008       if (!JS_GetProperty(aCx, constructor, "formAssociated",
1009                           &formAssociatedValue)) {
1010         aRv.NoteJSContextException(aCx);
1011         return;
1012       }
1013 
1014       // 14.12. Set formAssociated to the result of converting
1015       //        formAssociatedValue to a boolean. Rethrow any exceptions from
1016       //        the conversion.
1017       if (!ValueToPrimitive<bool, eDefault>(
1018               aCx, formAssociatedValue, "formAssociated", &formAssociated)) {
1019         aRv.NoteJSContextException(aCx);
1020         return;
1021       }
1022     }
1023   }  // Unset mIsCustomDefinitionRunning
1024 
1025   /**
1026    * 15. Let definition be a new custom element definition with name name,
1027    *     local name localName, constructor constructor, prototype prototype,
1028    *     observed attributes observedAttributes, and lifecycle callbacks
1029    *     lifecycleCallbacks.
1030    */
1031   // Associate the definition with the custom element.
1032   RefPtr<nsAtom> localNameAtom(NS_Atomize(localName));
1033 
1034   /**
1035    * 16. Add definition to this CustomElementRegistry.
1036    */
1037   if (!mConstructors.put(constructorUnwrapped, nameAtom)) {
1038     aRv.Throw(NS_ERROR_FAILURE);
1039     return;
1040   }
1041 
1042   RefPtr<CustomElementDefinition> definition = new CustomElementDefinition(
1043       nameAtom, localNameAtom, nameSpaceID, &aFunctionConstructor,
1044       std::move(observedAttributes), std::move(callbacksHolder), formAssociated,
1045       disableInternals, disableShadow);
1046 
1047   CustomElementDefinition* def = definition.get();
1048   mCustomDefinitions.InsertOrUpdate(nameAtom, std::move(definition));
1049 
1050   MOZ_ASSERT(mCustomDefinitions.Count() == mConstructors.count(),
1051              "Number of entries should be the same");
1052 
1053   /**
1054    * 17. 18. 19. Upgrade candidates
1055    */
1056   UpgradeCandidates(nameAtom, def, aRv);
1057 
1058   /**
1059    * 20. If this CustomElementRegistry's when-defined promise map contains an
1060    *     entry with key name:
1061    *     1. Let promise be the value of that entry.
1062    *     2. Resolve promise with undefined.
1063    *     3. Delete the entry with key name from this CustomElementRegistry's
1064    *        when-defined promise map.
1065    */
1066   RefPtr<Promise> promise;
1067   mWhenDefinedPromiseMap.Remove(nameAtom, getter_AddRefs(promise));
1068   if (promise) {
1069     promise->MaybeResolve(def->mConstructor);
1070   }
1071 
1072   // Dispatch a "customelementdefined" event for DevTools.
1073   {
1074     JSString* nameJsStr =
1075         JS_NewUCStringCopyN(aCx, aName.BeginReading(), aName.Length());
1076 
1077     JS::Rooted<JS::Value> detail(aCx, JS::StringValue(nameJsStr));
1078     RefPtr<CustomEvent> event = NS_NewDOMCustomEvent(doc, nullptr, nullptr);
1079     event->InitCustomEvent(aCx, u"customelementdefined"_ns,
1080                            /* CanBubble */ true,
1081                            /* Cancelable */ true, detail);
1082     event->SetTrusted(true);
1083 
1084     AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(doc, event);
1085     dispatcher->mOnlyChromeDispatch = ChromeOnlyDispatch::eYes;
1086 
1087     dispatcher->PostDOMEvent();
1088   }
1089 
1090   /**
1091    * Clean-up mElementCreationCallbacks (if it exists)
1092    */
1093   mElementCreationCallbacks.Remove(nameAtom);
1094 }
1095 
SetElementCreationCallback(const nsAString & aName,CustomElementCreationCallback & aCallback,ErrorResult & aRv)1096 void CustomElementRegistry::SetElementCreationCallback(
1097     const nsAString& aName, CustomElementCreationCallback& aCallback,
1098     ErrorResult& aRv) {
1099   RefPtr<nsAtom> nameAtom(NS_Atomize(aName));
1100   if (mElementCreationCallbacks.GetWeak(nameAtom) ||
1101       mCustomDefinitions.GetWeak(nameAtom)) {
1102     aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
1103     return;
1104   }
1105 
1106   RefPtr<CustomElementCreationCallback> callback = &aCallback;
1107   mElementCreationCallbacks.InsertOrUpdate(nameAtom, std::move(callback));
1108 }
1109 
Upgrade(nsINode & aRoot)1110 void CustomElementRegistry::Upgrade(nsINode& aRoot) {
1111   for (nsINode* node : ShadowIncludingTreeIterator(aRoot)) {
1112     Element* element = Element::FromNode(node);
1113     if (!element) {
1114       continue;
1115     }
1116 
1117     CustomElementData* ceData = element->GetCustomElementData();
1118     if (ceData) {
1119       NodeInfo* nodeInfo = element->NodeInfo();
1120       nsAtom* typeAtom = ceData->GetCustomElementType();
1121       CustomElementDefinition* definition =
1122           nsContentUtils::LookupCustomElementDefinition(
1123               nodeInfo->GetDocument(), nodeInfo->NameAtom(),
1124               nodeInfo->NamespaceID(), typeAtom);
1125       if (definition) {
1126         nsContentUtils::EnqueueUpgradeReaction(element, definition);
1127       }
1128     }
1129   }
1130 }
1131 
Get(JSContext * aCx,const nsAString & aName,JS::MutableHandle<JS::Value> aRetVal)1132 void CustomElementRegistry::Get(JSContext* aCx, const nsAString& aName,
1133                                 JS::MutableHandle<JS::Value> aRetVal) {
1134   RefPtr<nsAtom> nameAtom(NS_Atomize(aName));
1135   CustomElementDefinition* data = mCustomDefinitions.GetWeak(nameAtom);
1136 
1137   if (!data) {
1138     aRetVal.setUndefined();
1139     return;
1140   }
1141 
1142   aRetVal.setObject(*data->mConstructor->Callback(aCx));
1143 }
1144 
WhenDefined(const nsAString & aName,ErrorResult & aRv)1145 already_AddRefed<Promise> CustomElementRegistry::WhenDefined(
1146     const nsAString& aName, ErrorResult& aRv) {
1147   // Define a function that lazily creates a Promise and perform some action on
1148   // it when creation succeeded. It's needed in multiple cases below, but not in
1149   // all of them.
1150   auto createPromise = [&](auto&& action) -> already_AddRefed<Promise> {
1151     nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
1152     RefPtr<Promise> promise = Promise::Create(global, aRv);
1153 
1154     if (aRv.Failed()) {
1155       return nullptr;
1156     }
1157 
1158     action(promise);
1159 
1160     return promise.forget();
1161   };
1162 
1163   RefPtr<nsAtom> nameAtom(NS_Atomize(aName));
1164   Document* doc = mWindow->GetExtantDoc();
1165   uint32_t nameSpaceID =
1166       doc ? doc->GetDefaultNamespaceID() : kNameSpaceID_XHTML;
1167   if (!nsContentUtils::IsCustomElementName(nameAtom, nameSpaceID)) {
1168     return createPromise([](const RefPtr<Promise>& promise) {
1169       promise->MaybeReject(NS_ERROR_DOM_SYNTAX_ERR);
1170     });
1171   }
1172 
1173   if (CustomElementDefinition* definition =
1174           mCustomDefinitions.GetWeak(nameAtom)) {
1175     return createPromise([&](const RefPtr<Promise>& promise) {
1176       promise->MaybeResolve(definition->mConstructor);
1177     });
1178   }
1179 
1180   return mWhenDefinedPromiseMap.WithEntryHandle(
1181       nameAtom, [&](auto&& entry) -> already_AddRefed<Promise> {
1182         if (!entry) {
1183           return createPromise([&entry](const RefPtr<Promise>& promise) {
1184             entry.Insert(promise);
1185           });
1186         }
1187         return do_AddRef(entry.Data());
1188       });
1189 }
1190 
1191 namespace {
1192 
1193 MOZ_CAN_RUN_SCRIPT
DoUpgrade(Element * aElement,CustomElementDefinition * aDefinition,CustomElementConstructor * aConstructor,ErrorResult & aRv)1194 static void DoUpgrade(Element* aElement, CustomElementDefinition* aDefinition,
1195                       CustomElementConstructor* aConstructor,
1196                       ErrorResult& aRv) {
1197   if (aDefinition->mDisableShadow && aElement->GetShadowRoot()) {
1198     aRv.ThrowNotSupportedError(nsPrintfCString(
1199         "Custom element upgrade to '%s' is disabled because a shadow root "
1200         "already exists",
1201         NS_ConvertUTF16toUTF8(aDefinition->mType->GetUTF16String()).get()));
1202     return;
1203   }
1204 
1205   CustomElementData* data = aElement->GetCustomElementData();
1206   MOZ_ASSERT(data, "CustomElementData should exist");
1207   data->mState = CustomElementData::State::ePrecustomized;
1208 
1209   JS::Rooted<JS::Value> constructResult(RootingCx());
1210   // Rethrow the exception since it might actually throw the exception from the
1211   // upgrade steps back out to the caller of document.createElement.
1212   aConstructor->Construct(&constructResult, aRv, "Custom Element Upgrade",
1213                           CallbackFunction::eRethrowExceptions);
1214   if (aRv.Failed()) {
1215     return;
1216   }
1217 
1218   Element* element;
1219   // constructResult is an ObjectValue because construction with a callback
1220   // always forms the return value from a JSObject.
1221   if (NS_FAILED(UNWRAP_OBJECT(Element, &constructResult, element)) ||
1222       element != aElement) {
1223     aRv.ThrowTypeError("Custom element constructor returned a wrong element");
1224     return;
1225   }
1226 }
1227 
1228 }  // anonymous namespace
1229 
1230 // https://html.spec.whatwg.org/commit-snapshots/2793ee4a461c6c39896395f1a45c269ea820c47e/#upgrades
1231 /* static */
Upgrade(Element * aElement,CustomElementDefinition * aDefinition,ErrorResult & aRv)1232 void CustomElementRegistry::Upgrade(Element* aElement,
1233                                     CustomElementDefinition* aDefinition,
1234                                     ErrorResult& aRv) {
1235   CustomElementData* data = aElement->GetCustomElementData();
1236   MOZ_ASSERT(data, "CustomElementData should exist");
1237 
1238   // Step 1.
1239   if (data->mState != CustomElementData::State::eUndefined) {
1240     return;
1241   }
1242 
1243   // Step 2.
1244   aElement->SetCustomElementDefinition(aDefinition);
1245 
1246   // Step 3.
1247   data->mState = CustomElementData::State::eFailed;
1248 
1249   // Step 4.
1250   if (!aDefinition->mObservedAttributes.IsEmpty()) {
1251     uint32_t count = aElement->GetAttrCount();
1252     for (uint32_t i = 0; i < count; i++) {
1253       mozilla::dom::BorrowedAttrInfo info = aElement->GetAttrInfoAt(i);
1254 
1255       const nsAttrName* name = info.mName;
1256       nsAtom* attrName = name->LocalName();
1257 
1258       if (aDefinition->IsInObservedAttributeList(attrName)) {
1259         int32_t namespaceID = name->NamespaceID();
1260         nsAutoString attrValue, namespaceURI;
1261         info.mValue->ToString(attrValue);
1262         nsNameSpaceManager::GetInstance()->GetNameSpaceURI(namespaceID,
1263                                                            namespaceURI);
1264 
1265         LifecycleCallbackArgs args;
1266         args.mName = nsDependentAtomString(attrName);
1267         args.mOldValue = VoidString();
1268         args.mNewValue = attrValue;
1269         args.mNamespaceURI =
1270             (namespaceURI.IsEmpty() ? VoidString() : namespaceURI);
1271 
1272         nsContentUtils::EnqueueLifecycleCallback(
1273             ElementCallbackType::eAttributeChanged, aElement, args,
1274             aDefinition);
1275       }
1276     }
1277   }
1278 
1279   // Step 5.
1280   if (aElement->IsInComposedDoc()) {
1281     nsContentUtils::EnqueueLifecycleCallback(ElementCallbackType::eConnected,
1282                                              aElement, {}, aDefinition);
1283   }
1284 
1285   // Step 6.
1286   AutoConstructionStackEntry acs(aDefinition->mConstructionStack, aElement);
1287 
1288   // Step 7 and step 8.
1289   DoUpgrade(aElement, aDefinition, MOZ_KnownLive(aDefinition->mConstructor),
1290             aRv);
1291   if (aRv.Failed()) {
1292     MOZ_ASSERT(data->mState == CustomElementData::State::eFailed ||
1293                data->mState == CustomElementData::State::ePrecustomized);
1294     // Spec doesn't set custom element state to failed here, but without this we
1295     // would have inconsistent state on a custom elemet that is failed to
1296     // upgrade, see https://github.com/whatwg/html/issues/6929, and
1297     // https://github.com/web-platform-tests/wpt/pull/29911 for the test.
1298     data->mState = CustomElementData::State::eFailed;
1299     aElement->SetCustomElementDefinition(nullptr);
1300     // Empty element's custom element reaction queue.
1301     data->mReactionQueue.Clear();
1302     return;
1303   }
1304 
1305   // Step 9.
1306   if (data->IsFormAssociated()) {
1307     ElementInternals* internals = data->GetElementInternals();
1308     MOZ_ASSERT(internals);
1309     MOZ_ASSERT(aElement->IsHTMLElement());
1310     MOZ_ASSERT(!aDefinition->IsCustomBuiltIn());
1311 
1312     internals->UpdateFormOwner();
1313   }
1314 
1315   // Step 10.
1316   data->mState = CustomElementData::State::eCustom;
1317   aElement->SetDefined(true);
1318 }
1319 
CallGetCustomInterface(Element * aElement,const nsIID & aIID)1320 already_AddRefed<nsISupports> CustomElementRegistry::CallGetCustomInterface(
1321     Element* aElement, const nsIID& aIID) {
1322   MOZ_ASSERT(aElement);
1323 
1324   if (!nsContentUtils::IsChromeDoc(aElement->OwnerDoc())) {
1325     return nullptr;
1326   }
1327 
1328   // Try to get our GetCustomInterfaceCallback callback.
1329   CustomElementDefinition* definition = aElement->GetCustomElementDefinition();
1330   if (!definition || !definition->mCallbacks ||
1331       !definition->mCallbacks->mGetCustomInterfaceCallback.WasPassed() ||
1332       (definition->mLocalName != aElement->NodeInfo()->NameAtom())) {
1333     return nullptr;
1334   }
1335   LifecycleGetCustomInterfaceCallback* func =
1336       definition->mCallbacks->mGetCustomInterfaceCallback.Value();
1337 
1338   // Initialize a AutoJSAPI to enter the compartment of the callback.
1339   AutoJSAPI jsapi;
1340   JS::RootedObject funcGlobal(RootingCx(), func->CallbackGlobalOrNull());
1341   if (!funcGlobal || !jsapi.Init(funcGlobal)) {
1342     return nullptr;
1343   }
1344 
1345   // Grab our JSContext.
1346   JSContext* cx = jsapi.cx();
1347 
1348   // Convert our IID to a JSValue to call our callback.
1349   JS::RootedValue jsiid(cx);
1350   if (!xpc::ID2JSValue(cx, aIID, &jsiid)) {
1351     return nullptr;
1352   }
1353 
1354   JS::RootedObject customInterface(cx);
1355   func->Call(aElement, jsiid, &customInterface);
1356   if (!customInterface) {
1357     return nullptr;
1358   }
1359 
1360   // Wrap our JSObject into a nsISupports through XPConnect
1361   nsCOMPtr<nsISupports> wrapper;
1362   nsresult rv = nsContentUtils::XPConnect()->WrapJSAggregatedToNative(
1363       aElement, cx, customInterface, aIID, getter_AddRefs(wrapper));
1364   if (NS_WARN_IF(NS_FAILED(rv))) {
1365     return nullptr;
1366   }
1367 
1368   return wrapper.forget();
1369 }
1370 
TraceDefinitions(JSTracer * aTrc)1371 void CustomElementRegistry::TraceDefinitions(JSTracer* aTrc) {
1372   for (const RefPtr<CustomElementDefinition>& definition :
1373        mCustomDefinitions.Values()) {
1374     if (definition && definition->mConstructor) {
1375       mozilla::TraceScriptHolder(definition->mConstructor, aTrc);
1376     }
1377   }
1378 }
1379 
1380 //-----------------------------------------------------
1381 // CustomElementReactionsStack
1382 
CreateAndPushElementQueue()1383 void CustomElementReactionsStack::CreateAndPushElementQueue() {
1384   MOZ_ASSERT(mRecursionDepth);
1385   MOZ_ASSERT(!mIsElementQueuePushedForCurrentRecursionDepth);
1386 
1387   // Push a new element queue onto the custom element reactions stack.
1388   mReactionsStack.AppendElement(MakeUnique<ElementQueue>());
1389   mIsElementQueuePushedForCurrentRecursionDepth = true;
1390 }
1391 
PopAndInvokeElementQueue()1392 void CustomElementReactionsStack::PopAndInvokeElementQueue() {
1393   MOZ_ASSERT(mRecursionDepth);
1394   MOZ_ASSERT(mIsElementQueuePushedForCurrentRecursionDepth);
1395   MOZ_ASSERT(!mReactionsStack.IsEmpty(), "Reaction stack shouldn't be empty");
1396 
1397   // Pop the element queue from the custom element reactions stack,
1398   // and invoke custom element reactions in that queue.
1399   const uint32_t lastIndex = mReactionsStack.Length() - 1;
1400   ElementQueue* elementQueue = mReactionsStack.ElementAt(lastIndex).get();
1401   // Check element queue size in order to reduce function call overhead.
1402   if (!elementQueue->IsEmpty()) {
1403     // It is still not clear what error reporting will look like in custom
1404     // element, see https://github.com/w3c/webcomponents/issues/635.
1405     // We usually report the error to entry global in gecko, so just follow the
1406     // same behavior here.
1407     // This may be null if it's called from parser, see the case of
1408     // attributeChangedCallback in
1409     // https://html.spec.whatwg.org/multipage/parsing.html#create-an-element-for-the-token
1410     // In that case, the exception of callback reactions will be automatically
1411     // reported in CallSetup.
1412     nsIGlobalObject* global = GetEntryGlobal();
1413     InvokeReactions(elementQueue, MOZ_KnownLive(global));
1414   }
1415 
1416   // InvokeReactions() might create other custom element reactions, but those
1417   // new reactions should be already consumed and removed at this point.
1418   MOZ_ASSERT(
1419       lastIndex == mReactionsStack.Length() - 1,
1420       "reactions created by InvokeReactions() should be consumed and removed");
1421 
1422   mReactionsStack.RemoveLastElement();
1423   mIsElementQueuePushedForCurrentRecursionDepth = false;
1424 }
1425 
EnqueueUpgradeReaction(Element * aElement,CustomElementDefinition * aDefinition)1426 void CustomElementReactionsStack::EnqueueUpgradeReaction(
1427     Element* aElement, CustomElementDefinition* aDefinition) {
1428   Enqueue(aElement, new CustomElementUpgradeReaction(aDefinition));
1429 }
1430 
EnqueueCallbackReaction(Element * aElement,UniquePtr<CustomElementCallback> aCustomElementCallback)1431 void CustomElementReactionsStack::EnqueueCallbackReaction(
1432     Element* aElement,
1433     UniquePtr<CustomElementCallback> aCustomElementCallback) {
1434   Enqueue(aElement,
1435           new CustomElementCallbackReaction(std::move(aCustomElementCallback)));
1436 }
1437 
Enqueue(Element * aElement,CustomElementReaction * aReaction)1438 void CustomElementReactionsStack::Enqueue(Element* aElement,
1439                                           CustomElementReaction* aReaction) {
1440   CustomElementData* elementData = aElement->GetCustomElementData();
1441   MOZ_ASSERT(elementData, "CustomElementData should exist");
1442 
1443   if (mRecursionDepth) {
1444     // If the element queue is not created for current recursion depth, create
1445     // and push an element queue to reactions stack first.
1446     if (!mIsElementQueuePushedForCurrentRecursionDepth) {
1447       CreateAndPushElementQueue();
1448     }
1449 
1450     MOZ_ASSERT(!mReactionsStack.IsEmpty());
1451     // Add element to the current element queue.
1452     mReactionsStack.LastElement()->AppendElement(aElement);
1453     elementData->mReactionQueue.AppendElement(aReaction);
1454     return;
1455   }
1456 
1457   // If the custom element reactions stack is empty, then:
1458   // Add element to the backup element queue.
1459   MOZ_ASSERT(mReactionsStack.IsEmpty(),
1460              "custom element reactions stack should be empty");
1461   mBackupQueue.AppendElement(aElement);
1462   elementData->mReactionQueue.AppendElement(aReaction);
1463 
1464   if (mIsBackupQueueProcessing) {
1465     return;
1466   }
1467 
1468   CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
1469   RefPtr<BackupQueueMicroTask> bqmt = new BackupQueueMicroTask(this);
1470   context->DispatchToMicroTask(bqmt.forget());
1471 }
1472 
InvokeBackupQueue()1473 void CustomElementReactionsStack::InvokeBackupQueue() {
1474   // Check backup queue size in order to reduce function call overhead.
1475   if (!mBackupQueue.IsEmpty()) {
1476     // Upgrade reactions won't be scheduled in backup queue and the exception of
1477     // callback reactions will be automatically reported in CallSetup.
1478     // If the reactions are invoked from backup queue (in microtask check
1479     // point), we don't need to pass global object for error reporting.
1480     InvokeReactions(&mBackupQueue, nullptr);
1481   }
1482   MOZ_ASSERT(
1483       mBackupQueue.IsEmpty(),
1484       "There are still some reactions in BackupQueue not being consumed!?!");
1485 }
1486 
InvokeReactions(ElementQueue * aElementQueue,nsIGlobalObject * aGlobal)1487 void CustomElementReactionsStack::InvokeReactions(ElementQueue* aElementQueue,
1488                                                   nsIGlobalObject* aGlobal) {
1489   // This is used for error reporting.
1490   Maybe<AutoEntryScript> aes;
1491   if (aGlobal) {
1492     aes.emplace(aGlobal, "custom elements reaction invocation");
1493   }
1494 
1495   // Note: It's possible to re-enter this method.
1496   for (uint32_t i = 0; i < aElementQueue->Length(); ++i) {
1497     Element* element = aElementQueue->ElementAt(i);
1498     // ElementQueue hold a element's strong reference, it should not be a
1499     // nullptr.
1500     MOZ_ASSERT(element);
1501 
1502     CustomElementData* elementData = element->GetCustomElementData();
1503     if (!elementData || !element->GetOwnerGlobal()) {
1504       // This happens when the document is destroyed and the element is already
1505       // unlinked, no need to fire the callbacks in this case.
1506       continue;
1507     }
1508 
1509     auto& reactions = elementData->mReactionQueue;
1510     for (uint32_t j = 0; j < reactions.Length(); ++j) {
1511       // Transfer the ownership of the entry due to reentrant invocation of
1512       // this function.
1513       auto reaction(std::move(reactions.ElementAt(j)));
1514       if (reaction) {
1515         if (!aGlobal && reaction->IsUpgradeReaction()) {
1516           nsIGlobalObject* global = element->GetOwnerGlobal();
1517           MOZ_ASSERT(!aes);
1518           aes.emplace(global, "custom elements reaction invocation");
1519         }
1520         ErrorResult rv;
1521         reaction->Invoke(MOZ_KnownLive(element), rv);
1522         if (aes) {
1523           JSContext* cx = aes->cx();
1524           if (rv.MaybeSetPendingException(cx)) {
1525             aes->ReportException();
1526           }
1527           MOZ_ASSERT(!JS_IsExceptionPending(cx));
1528           if (!aGlobal && reaction->IsUpgradeReaction()) {
1529             aes.reset();
1530           }
1531         }
1532         MOZ_ASSERT(!rv.Failed());
1533       }
1534     }
1535     reactions.Clear();
1536   }
1537   aElementQueue->Clear();
1538 }
1539 
1540 //-----------------------------------------------------
1541 // CustomElementDefinition
1542 
NS_IMPL_CYCLE_COLLECTION(CustomElementDefinition,mConstructor,mCallbacks,mConstructionStack)1543 NS_IMPL_CYCLE_COLLECTION(CustomElementDefinition, mConstructor, mCallbacks,
1544                          mConstructionStack)
1545 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CustomElementDefinition, AddRef)
1546 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CustomElementDefinition, Release)
1547 
1548 CustomElementDefinition::CustomElementDefinition(
1549     nsAtom* aType, nsAtom* aLocalName, int32_t aNamespaceID,
1550     CustomElementConstructor* aConstructor,
1551     nsTArray<RefPtr<nsAtom>>&& aObservedAttributes,
1552     UniquePtr<LifecycleCallbacks>&& aCallbacks, bool aFormAssociated,
1553     bool aDisableInternals, bool aDisableShadow)
1554     : mType(aType),
1555       mLocalName(aLocalName),
1556       mNamespaceID(aNamespaceID),
1557       mConstructor(aConstructor),
1558       mObservedAttributes(std::move(aObservedAttributes)),
1559       mCallbacks(std::move(aCallbacks)),
1560       mFormAssociated(aFormAssociated),
1561       mDisableInternals(aDisableInternals),
1562       mDisableShadow(aDisableShadow) {}
1563 
1564 }  // namespace mozilla::dom
1565