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