Bug 1832044 - Part 1: Rename MovableCellHasher to StableCellHasher r=sfink
[gecko.git] / dom / base / CustomElementRegistry.h
blob5cf3285b461d10e9b8012b15ef5821db832bb5c5
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #ifndef mozilla_dom_CustomElementRegistry_h
8 #define mozilla_dom_CustomElementRegistry_h
10 #include "js/GCHashTable.h"
11 #include "js/TypeDecls.h"
12 #include "mozilla/Attributes.h"
13 #include "mozilla/CycleCollectedJSContext.h" // for MicroTaskRunnable
14 #include "mozilla/dom/BindingDeclarations.h"
15 #include "mozilla/dom/CustomElementRegistryBinding.h"
16 #include "mozilla/dom/Document.h"
17 #include "mozilla/dom/Element.h"
18 #include "mozilla/dom/ElementInternals.h"
19 #include "mozilla/dom/HTMLFormElement.h"
20 #include "mozilla/RefPtr.h"
21 #include "nsCycleCollectionParticipant.h"
22 #include "nsWrapperCache.h"
23 #include "nsTHashSet.h"
25 namespace mozilla {
26 class ErrorResult;
28 namespace dom {
30 struct CustomElementData;
31 struct ElementDefinitionOptions;
32 class CallbackFunction;
33 class CustomElementCallback;
34 class CustomElementReaction;
35 class DocGroup;
36 class Promise;
38 enum class ElementCallbackType {
39 eConnected,
40 eDisconnected,
41 eAdopted,
42 eAttributeChanged,
43 eFormAssociated,
44 eFormReset,
45 eFormDisabled,
46 eGetCustomInterface
49 struct LifecycleCallbackArgs {
50 // Used by the attribute changed callback.
51 nsString mName;
52 nsString mOldValue;
53 nsString mNewValue;
54 nsString mNamespaceURI;
56 // Used by the adopted callback.
57 RefPtr<Document> mOldDocument;
58 RefPtr<Document> mNewDocument;
60 // Used by the form associated callback.
61 RefPtr<HTMLFormElement> mForm;
63 // Used by the form disabled callback.
64 bool mDisabled;
66 size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
69 // Each custom element has an associated callback queue and an element is
70 // being created flag.
71 struct CustomElementData {
72 // https://dom.spec.whatwg.org/#concept-element-custom-element-state
73 // CustomElementData is only created on the element which is a custom element
74 // or an upgrade candidate, so the state of an element without
75 // CustomElementData is "uncustomized".
76 enum class State { eUndefined, eFailed, eCustom, ePrecustomized };
78 explicit CustomElementData(nsAtom* aType);
79 CustomElementData(nsAtom* aType, State aState);
80 ~CustomElementData() = default;
82 // Custom element state as described in the custom element spec.
83 State mState;
84 // custom element reaction queue as described in the custom element spec.
85 // There is 1 reaction in reaction queue, when 1) it becomes disconnected,
86 // 2) it’s adopted into a new document, 3) its attributes are changed,
87 // appended, removed, or replaced.
88 // There are 3 reactions in reaction queue when doing upgrade operation,
89 // e.g., create an element, insert a node.
90 AutoTArray<UniquePtr<CustomElementReaction>, 3> mReactionQueue;
92 void SetCustomElementDefinition(CustomElementDefinition* aDefinition);
93 CustomElementDefinition* GetCustomElementDefinition() const;
94 nsAtom* GetCustomElementType() const { return mType; }
95 void AttachedInternals();
96 bool HasAttachedInternals() const { return mIsAttachedInternals; }
98 bool IsFormAssociated() const;
100 void Traverse(nsCycleCollectionTraversalCallback& aCb) const;
101 void Unlink();
102 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
104 nsAtom* GetIs(const Element* aElement) const {
105 // If mType isn't the same as name atom, this is a customized built-in
106 // element, which has 'is' value set.
107 return aElement->NodeInfo()->NameAtom() == mType ? nullptr : mType.get();
110 ElementInternals* GetElementInternals() const { return mElementInternals; }
112 ElementInternals* GetOrCreateElementInternals(HTMLElement* aTarget) {
113 if (!mElementInternals) {
114 mElementInternals = MakeAndAddRef<ElementInternals>(aTarget);
116 return mElementInternals;
119 private:
120 // Custom element type, for <button is="x-button"> or <x-button>
121 // this would be x-button.
122 RefPtr<nsAtom> mType;
123 RefPtr<CustomElementDefinition> mCustomElementDefinition;
124 RefPtr<ElementInternals> mElementInternals;
125 bool mIsAttachedInternals = false;
128 #define ALREADY_CONSTRUCTED_MARKER nullptr
130 // The required information for a custom element as defined in:
131 // https://html.spec.whatwg.org/multipage/scripting.html#custom-element-definition
132 struct CustomElementDefinition {
133 NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(CustomElementDefinition)
134 NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(CustomElementDefinition)
136 CustomElementDefinition(
137 nsAtom* aType, nsAtom* aLocalName, int32_t aNamespaceID,
138 CustomElementConstructor* aConstructor,
139 nsTArray<RefPtr<nsAtom>>&& aObservedAttributes,
140 UniquePtr<LifecycleCallbacks>&& aCallbacks,
141 UniquePtr<FormAssociatedLifecycleCallbacks>&& aFormAssociatedCallbacks,
142 bool aFormAssociated, bool aDisableInternals, bool aDisableShadow);
144 // The type (name) for this custom element, for <button is="x-foo"> or <x-foo>
145 // this would be x-foo.
146 RefPtr<nsAtom> mType;
148 // The localname to (e.g. <button is=type> -- this would be button).
149 RefPtr<nsAtom> mLocalName;
151 // The namespace for this custom element
152 int32_t mNamespaceID;
154 // The custom element constructor.
155 RefPtr<CustomElementConstructor> mConstructor;
157 // The list of attributes that this custom element observes.
158 nsTArray<RefPtr<nsAtom>> mObservedAttributes;
160 // The lifecycle callbacks to call for this custom element.
161 UniquePtr<LifecycleCallbacks> mCallbacks;
162 UniquePtr<FormAssociatedLifecycleCallbacks> mFormAssociatedCallbacks;
164 // If this is true, user agent treats elements associated to this custom
165 // element definition as form-associated custom elements.
166 bool mFormAssociated = false;
168 // Determine whether to allow to attachInternals() for this custom element.
169 bool mDisableInternals = false;
171 // Determine whether to allow to attachShadow() for this custom element.
172 bool mDisableShadow = false;
174 // A construction stack. Use nullptr to represent an "already constructed
175 // marker".
176 nsTArray<RefPtr<Element>> mConstructionStack;
178 // See step 6.1.10 of https://dom.spec.whatwg.org/#concept-create-element
179 // which set up the prefix after a custom element is created. However, In
180 // Gecko, the prefix isn't allowed to be changed in NodeInfo, so we store the
181 // prefix information here and propagate to where NodeInfo is assigned to a
182 // custom element instead.
183 nsTArray<RefPtr<nsAtom>> mPrefixStack;
185 // This basically is used for distinguishing the custom element constructor
186 // is invoked from document.createElement or directly from JS, i.e.
187 // `new CustomElementConstructor()`.
188 uint32_t mConstructionDepth = 0;
190 bool IsCustomBuiltIn() { return mType != mLocalName; }
192 bool IsInObservedAttributeList(nsAtom* aName) {
193 if (mObservedAttributes.IsEmpty()) {
194 return false;
197 return mObservedAttributes.Contains(aName);
200 private:
201 ~CustomElementDefinition() = default;
204 class CustomElementReaction {
205 public:
206 virtual ~CustomElementReaction() = default;
207 MOZ_CAN_RUN_SCRIPT
208 virtual void Invoke(Element* aElement, ErrorResult& aRv) = 0;
209 virtual void Traverse(nsCycleCollectionTraversalCallback& aCb) const = 0;
210 virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const = 0;
212 bool IsUpgradeReaction() { return mIsUpgradeReaction; }
214 protected:
215 bool mIsUpgradeReaction = false;
218 // https://html.spec.whatwg.org/multipage/scripting.html#custom-element-reactions-stack
219 class CustomElementReactionsStack {
220 public:
221 NS_INLINE_DECL_REFCOUNTING(CustomElementReactionsStack)
223 CustomElementReactionsStack()
224 : mIsBackupQueueProcessing(false),
225 mRecursionDepth(0),
226 mIsElementQueuePushedForCurrentRecursionDepth(false) {}
228 // Hold a strong reference of Element so that it does not get cycle collected
229 // before the reactions in its reaction queue are invoked.
230 // The element reaction queues are stored in CustomElementData.
231 // We need to lookup ElementReactionQueueMap again to get relevant reaction
232 // queue. The choice of 3 for the auto size here is based on running Custom
233 // Elements wpt tests.
234 typedef AutoTArray<RefPtr<Element>, 3> ElementQueue;
237 * Enqueue a custom element upgrade reaction
238 * https://html.spec.whatwg.org/multipage/scripting.html#enqueue-a-custom-element-upgrade-reaction
240 void EnqueueUpgradeReaction(Element* aElement,
241 CustomElementDefinition* aDefinition);
244 * Enqueue a custom element callback reaction
245 * https://html.spec.whatwg.org/multipage/scripting.html#enqueue-a-custom-element-callback-reaction
247 void EnqueueCallbackReaction(
248 Element* aElement,
249 UniquePtr<CustomElementCallback> aCustomElementCallback);
252 * [CEReactions] Before executing the algorithm's steps.
253 * Increase the current recursion depth, and the element queue is pushed
254 * lazily when we really enqueue reactions.
256 * @return true if the element queue is pushed for "previous" recursion depth.
258 bool EnterCEReactions() {
259 bool temp = mIsElementQueuePushedForCurrentRecursionDepth;
260 mRecursionDepth++;
261 // The is-element-queue-pushed flag is initially false when entering a new
262 // recursion level. The original value will be cached in AutoCEReaction
263 // and restored after leaving this recursion level.
264 mIsElementQueuePushedForCurrentRecursionDepth = false;
265 return temp;
269 * [CEReactions] After executing the algorithm's steps.
270 * Pop and invoke the element queue if it is created and pushed for current
271 * recursion depth, then decrease the current recursion depth.
273 * @param aCx JSContext used for handling exception thrown by algorithm's
274 * steps, this could be a nullptr.
275 * aWasElementQueuePushed used for restoring status after leaving
276 * current recursion.
278 MOZ_CAN_RUN_SCRIPT
279 void LeaveCEReactions(JSContext* aCx, bool aWasElementQueuePushed) {
280 MOZ_ASSERT(mRecursionDepth);
282 if (mIsElementQueuePushedForCurrentRecursionDepth) {
283 Maybe<JS::AutoSaveExceptionState> ases;
284 if (aCx) {
285 ases.emplace(aCx);
287 PopAndInvokeElementQueue();
289 mRecursionDepth--;
290 // Restore the is-element-queue-pushed flag cached in AutoCEReaction when
291 // leaving the recursion level.
292 mIsElementQueuePushedForCurrentRecursionDepth = aWasElementQueuePushed;
294 MOZ_ASSERT_IF(!mRecursionDepth, mReactionsStack.IsEmpty());
297 bool IsElementQueuePushedForCurrentRecursionDepth() {
298 MOZ_ASSERT_IF(mIsElementQueuePushedForCurrentRecursionDepth,
299 !mReactionsStack.IsEmpty() &&
300 !mReactionsStack.LastElement()->IsEmpty());
301 return mIsElementQueuePushedForCurrentRecursionDepth;
304 private:
305 ~CustomElementReactionsStack() = default;
309 * Push a new element queue onto the custom element reactions stack.
311 void CreateAndPushElementQueue();
314 * Pop the element queue from the custom element reactions stack, and invoke
315 * custom element reactions in that queue.
317 MOZ_CAN_RUN_SCRIPT void PopAndInvokeElementQueue();
319 // The choice of 8 for the auto size here is based on gut feeling.
320 AutoTArray<UniquePtr<ElementQueue>, 8> mReactionsStack;
321 ElementQueue mBackupQueue;
322 // https://html.spec.whatwg.org/#enqueue-an-element-on-the-appropriate-element-queue
323 bool mIsBackupQueueProcessing;
325 MOZ_CAN_RUN_SCRIPT void InvokeBackupQueue();
328 * Invoke custom element reactions
329 * https://html.spec.whatwg.org/multipage/scripting.html#invoke-custom-element-reactions
331 MOZ_CAN_RUN_SCRIPT
332 void InvokeReactions(ElementQueue* aElementQueue, nsIGlobalObject* aGlobal);
334 void Enqueue(Element* aElement, CustomElementReaction* aReaction);
336 // Current [CEReactions] recursion depth.
337 uint32_t mRecursionDepth;
338 // True if the element queue is pushed into reaction stack for current
339 // recursion depth. This will be cached in AutoCEReaction when entering a new
340 // CEReaction recursion and restored after leaving the recursion.
341 bool mIsElementQueuePushedForCurrentRecursionDepth;
343 private:
344 class BackupQueueMicroTask final : public mozilla::MicroTaskRunnable {
345 public:
346 explicit BackupQueueMicroTask(CustomElementReactionsStack* aReactionStack)
347 : MicroTaskRunnable(), mReactionStack(aReactionStack) {
348 MOZ_ASSERT(!mReactionStack->mIsBackupQueueProcessing,
349 "mIsBackupQueueProcessing should be initially false");
350 mReactionStack->mIsBackupQueueProcessing = true;
353 MOZ_CAN_RUN_SCRIPT virtual void Run(AutoSlowOperation& aAso) override {
354 mReactionStack->InvokeBackupQueue();
355 mReactionStack->mIsBackupQueueProcessing = false;
358 private:
359 const RefPtr<CustomElementReactionsStack> mReactionStack;
363 class CustomElementRegistry final : public nsISupports, public nsWrapperCache {
364 public:
365 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
366 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CustomElementRegistry)
368 public:
369 explicit CustomElementRegistry(nsPIDOMWindowInner* aWindow);
371 private:
372 class RunCustomElementCreationCallback : public mozilla::Runnable {
373 public:
374 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT.
375 // See bug 1535398.
376 MOZ_CAN_RUN_SCRIPT_BOUNDARY
377 NS_DECL_NSIRUNNABLE
379 explicit RunCustomElementCreationCallback(
380 CustomElementRegistry* aRegistry, nsAtom* aAtom,
381 CustomElementCreationCallback* aCallback)
382 : mozilla::Runnable(
383 "CustomElementRegistry::RunCustomElementCreationCallback"),
384 mRegistry(aRegistry),
385 mAtom(aAtom),
386 mCallback(aCallback) {}
388 private:
389 RefPtr<CustomElementRegistry> mRegistry;
390 RefPtr<nsAtom> mAtom;
391 RefPtr<CustomElementCreationCallback> mCallback;
394 public:
396 * Looking up a custom element definition.
397 * https://html.spec.whatwg.org/#look-up-a-custom-element-definition
399 CustomElementDefinition* LookupCustomElementDefinition(nsAtom* aNameAtom,
400 int32_t aNameSpaceID,
401 nsAtom* aTypeAtom);
403 CustomElementDefinition* LookupCustomElementDefinition(
404 JSContext* aCx, JSObject* aConstructor) const;
406 static void EnqueueLifecycleCallback(ElementCallbackType aType,
407 Element* aCustomElement,
408 const LifecycleCallbackArgs& aArgs,
409 CustomElementDefinition* aDefinition);
412 * Upgrade an element.
413 * https://html.spec.whatwg.org/multipage/scripting.html#upgrades
415 MOZ_CAN_RUN_SCRIPT
416 static void Upgrade(Element* aElement, CustomElementDefinition* aDefinition,
417 ErrorResult& aRv);
420 * To allow native code to call methods of chrome-implemented custom elements,
421 * a helper method may be defined in the custom element called
422 * 'getCustomInterfaceCallback'. This method takes an IID and returns an
423 * object which implements an XPCOM interface.
425 * This returns null if aElement is not from a chrome document.
427 static already_AddRefed<nsISupports> CallGetCustomInterface(
428 Element* aElement, const nsIID& aIID);
431 * Registers an unresolved custom element that is a candidate for
432 * upgrade. |aTypeName| is the name of the custom element type, if it is not
433 * provided, then element name is used. |aTypeName| should be provided
434 * when registering a custom element that extends an existing
435 * element. e.g. <button is="x-button">.
437 void RegisterUnresolvedElement(Element* aElement,
438 nsAtom* aTypeName = nullptr);
441 * Unregister an unresolved custom element that is a candidate for
442 * upgrade when a custom element is removed from tree.
444 void UnregisterUnresolvedElement(Element* aElement,
445 nsAtom* aTypeName = nullptr);
448 * Register an element to be upgraded when the custom element creation
449 * callback is executed.
451 * To be used when LookupCustomElementDefinition() didn't return a definition,
452 * but with the callback scheduled to be run.
454 inline void RegisterCallbackUpgradeElement(Element* aElement,
455 nsAtom* aTypeName = nullptr) {
456 if (mElementCreationCallbacksUpgradeCandidatesMap.IsEmpty()) {
457 return;
460 RefPtr<nsAtom> typeName = aTypeName;
461 if (!typeName) {
462 typeName = aElement->NodeInfo()->NameAtom();
465 nsTHashSet<RefPtr<nsIWeakReference>>* elements =
466 mElementCreationCallbacksUpgradeCandidatesMap.Get(typeName);
468 // If there isn't a table, there won't be a definition added by the
469 // callback.
470 if (!elements) {
471 return;
474 nsWeakPtr elem = do_GetWeakReference(aElement);
475 elements->Insert(elem);
478 void TraceDefinitions(JSTracer* aTrc);
480 private:
481 ~CustomElementRegistry();
483 bool JSObjectToAtomArray(JSContext* aCx, JS::Handle<JSObject*> aConstructor,
484 const nsString& aName,
485 nsTArray<RefPtr<nsAtom>>& aArray, ErrorResult& aRv);
487 void UpgradeCandidates(nsAtom* aKey, CustomElementDefinition* aDefinition,
488 ErrorResult& aRv);
490 typedef nsRefPtrHashtable<nsRefPtrHashKey<nsAtom>, CustomElementDefinition>
491 DefinitionMap;
492 typedef nsRefPtrHashtable<nsRefPtrHashKey<nsAtom>,
493 CustomElementCreationCallback>
494 ElementCreationCallbackMap;
495 typedef nsClassHashtable<nsRefPtrHashKey<nsAtom>,
496 nsTHashSet<RefPtr<nsIWeakReference>>>
497 CandidateMap;
498 typedef JS::GCHashMap<JS::Heap<JSObject*>, RefPtr<nsAtom>,
499 js::StableCellHasher<JS::Heap<JSObject*>>,
500 js::SystemAllocPolicy>
501 ConstructorMap;
503 // Hashtable for custom element definitions in web components.
504 // Custom prototypes are stored in the compartment where definition was
505 // defined.
506 DefinitionMap mCustomDefinitions;
508 // Hashtable for chrome-only callbacks that is called *before* we return
509 // a CustomElementDefinition, when the typeAtom matches.
510 // The callbacks are registered with the setElementCreationCallback method.
511 ElementCreationCallbackMap mElementCreationCallbacks;
513 // Hashtable for looking up definitions by using constructor as key.
514 // Custom elements' name are stored here and we need to lookup
515 // mCustomDefinitions again to get definitions.
516 ConstructorMap mConstructors;
518 typedef nsRefPtrHashtable<nsRefPtrHashKey<nsAtom>, Promise>
519 WhenDefinedPromiseMap;
520 WhenDefinedPromiseMap mWhenDefinedPromiseMap;
522 // The "upgrade candidates map" from the web components spec. Maps from a
523 // namespace id and local name to a list of elements to upgrade if that
524 // element is registered as a custom element.
525 CandidateMap mCandidatesMap;
527 // If an element creation callback is found, the nsTHashtable for the
528 // type is created here, and elements will later be upgraded.
529 CandidateMap mElementCreationCallbacksUpgradeCandidatesMap;
531 nsCOMPtr<nsPIDOMWindowInner> mWindow;
533 // It is used to prevent reentrant invocations of element definition.
534 bool mIsCustomDefinitionRunning;
536 private:
537 int32_t InferNamespace(JSContext* aCx, JS::Handle<JSObject*> constructor);
539 public:
540 nsISupports* GetParentObject() const;
542 DocGroup* GetDocGroup() const;
544 virtual JSObject* WrapObject(JSContext* aCx,
545 JS::Handle<JSObject*> aGivenProto) override;
547 void Define(JSContext* aCx, const nsAString& aName,
548 CustomElementConstructor& aFunctionConstructor,
549 const ElementDefinitionOptions& aOptions, ErrorResult& aRv);
551 void Get(const nsAString& name,
552 OwningCustomElementConstructorOrUndefined& aRetVal);
554 already_AddRefed<Promise> WhenDefined(const nsAString& aName,
555 ErrorResult& aRv);
557 // Chrome-only method that give JS an opportunity to only load the custom
558 // element definition script when needed.
559 void SetElementCreationCallback(const nsAString& aName,
560 CustomElementCreationCallback& aCallback,
561 ErrorResult& aRv);
563 void Upgrade(nsINode& aRoot);
566 class MOZ_RAII AutoCEReaction final {
567 public:
568 // JSContext is allowed to be a nullptr if we are guaranteeing that we're
569 // not doing something that might throw but not finish reporting a JS
570 // exception during the lifetime of the AutoCEReaction.
571 AutoCEReaction(CustomElementReactionsStack* aReactionsStack, JSContext* aCx)
572 : mReactionsStack(aReactionsStack), mCx(aCx) {
573 mIsElementQueuePushedForPreviousRecursionDepth =
574 mReactionsStack->EnterCEReactions();
577 // MOZ_CAN_RUN_SCRIPT_BOUNDARY because this is called from Maybe<>.reset().
578 MOZ_CAN_RUN_SCRIPT_BOUNDARY ~AutoCEReaction() {
579 mReactionsStack->LeaveCEReactions(
580 mCx, mIsElementQueuePushedForPreviousRecursionDepth);
583 private:
584 const RefPtr<CustomElementReactionsStack> mReactionsStack;
585 JSContext* mCx;
586 bool mIsElementQueuePushedForPreviousRecursionDepth;
589 } // namespace dom
590 } // namespace mozilla
592 #endif // mozilla_dom_CustomElementRegistry_h