Bug 1842773 - Part 5: Add ArrayBuffer.prototype.{maxByteLength,resizable} getters...
[gecko.git] / dom / base / CustomElementRegistry.h
blob099be3ebef57a8bf7bf7cc493468cc18b89a31f7
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/ElementInternalsBinding.h"
20 #include "mozilla/dom/HTMLFormElement.h"
21 #include "mozilla/RefPtr.h"
22 #include "nsCycleCollectionParticipant.h"
23 #include "nsWrapperCache.h"
24 #include "nsTHashSet.h"
25 #include "nsAtomHashKeys.h"
27 namespace mozilla {
28 class ErrorResult;
30 namespace dom {
32 struct CustomElementData;
33 struct ElementDefinitionOptions;
34 class CallbackFunction;
35 class CustomElementCallback;
36 class CustomElementReaction;
37 class DocGroup;
38 class Promise;
40 enum class ElementCallbackType {
41 eConnected,
42 eDisconnected,
43 eAdopted,
44 eAttributeChanged,
45 eFormAssociated,
46 eFormReset,
47 eFormDisabled,
48 eFormStateRestore,
49 eGetCustomInterface
52 struct LifecycleCallbackArgs {
53 // Used by the attribute changed callback.
54 RefPtr<nsAtom> mName;
55 nsString mOldValue;
56 nsString mNewValue;
57 nsString mNamespaceURI;
59 // Used by the adopted callback.
60 RefPtr<Document> mOldDocument;
61 RefPtr<Document> mNewDocument;
63 // Used by the form associated callback.
64 RefPtr<HTMLFormElement> mForm;
66 // Used by the form disabled callback.
67 bool mDisabled;
69 // Used by the form state restore callback.
70 Nullable<OwningFileOrUSVStringOrFormData> mState;
71 RestoreReason mReason;
73 size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
76 // Each custom element has an associated callback queue and an element is
77 // being created flag.
78 struct CustomElementData {
79 // https://dom.spec.whatwg.org/#concept-element-custom-element-state
80 // CustomElementData is only created on the element which is a custom element
81 // or an upgrade candidate, so the state of an element without
82 // CustomElementData is "uncustomized".
83 enum class State { eUndefined, eFailed, eCustom, ePrecustomized };
85 explicit CustomElementData(nsAtom* aType);
86 CustomElementData(nsAtom* aType, State aState);
87 ~CustomElementData() = default;
89 // Custom element state as described in the custom element spec.
90 State mState;
91 // custom element reaction queue as described in the custom element spec.
92 // There is 1 reaction in reaction queue, when 1) it becomes disconnected,
93 // 2) it’s adopted into a new document, 3) its attributes are changed,
94 // appended, removed, or replaced.
95 // There are 3 reactions in reaction queue when doing upgrade operation,
96 // e.g., create an element, insert a node.
97 AutoTArray<UniquePtr<CustomElementReaction>, 3> mReactionQueue;
99 void SetCustomElementDefinition(CustomElementDefinition* aDefinition);
100 CustomElementDefinition* GetCustomElementDefinition() const;
101 nsAtom* GetCustomElementType() const { return mType; }
102 void AttachedInternals();
103 bool HasAttachedInternals() const { return mIsAttachedInternals; }
105 bool IsFormAssociated() const;
107 void Traverse(nsCycleCollectionTraversalCallback& aCb) const;
108 void Unlink();
109 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
111 nsAtom* GetIs(const Element* aElement) const {
112 // If mType isn't the same as name atom, this is a customized built-in
113 // element, which has 'is' value set.
114 return aElement->NodeInfo()->NameAtom() == mType ? nullptr : mType.get();
117 ElementInternals* GetElementInternals() const { return mElementInternals; }
119 ElementInternals* GetOrCreateElementInternals(HTMLElement* aTarget) {
120 if (!mElementInternals) {
121 mElementInternals = MakeAndAddRef<ElementInternals>(aTarget);
123 return mElementInternals;
126 private:
127 // Custom element type, for <button is="x-button"> or <x-button>
128 // this would be x-button.
129 RefPtr<nsAtom> mType;
130 RefPtr<CustomElementDefinition> mCustomElementDefinition;
131 RefPtr<ElementInternals> mElementInternals;
132 bool mIsAttachedInternals = false;
135 #define ALREADY_CONSTRUCTED_MARKER nullptr
137 // The required information for a custom element as defined in:
138 // https://html.spec.whatwg.org/multipage/scripting.html#custom-element-definition
139 struct CustomElementDefinition {
140 NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(CustomElementDefinition)
141 NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(CustomElementDefinition)
143 CustomElementDefinition(
144 nsAtom* aType, nsAtom* aLocalName, int32_t aNamespaceID,
145 CustomElementConstructor* aConstructor,
146 nsTArray<RefPtr<nsAtom>>&& aObservedAttributes,
147 UniquePtr<LifecycleCallbacks>&& aCallbacks,
148 UniquePtr<FormAssociatedLifecycleCallbacks>&& aFormAssociatedCallbacks,
149 bool aFormAssociated, bool aDisableInternals, bool aDisableShadow);
151 // The type (name) for this custom element, for <button is="x-foo"> or <x-foo>
152 // this would be x-foo.
153 RefPtr<nsAtom> mType;
155 // The localname to (e.g. <button is=type> -- this would be button).
156 RefPtr<nsAtom> mLocalName;
158 // The namespace for this custom element
159 int32_t mNamespaceID;
161 // The custom element constructor.
162 RefPtr<CustomElementConstructor> mConstructor;
164 // The list of attributes that this custom element observes.
165 nsTArray<RefPtr<nsAtom>> mObservedAttributes;
167 // The lifecycle callbacks to call for this custom element.
168 UniquePtr<LifecycleCallbacks> mCallbacks;
169 UniquePtr<FormAssociatedLifecycleCallbacks> mFormAssociatedCallbacks;
171 // If this is true, user agent treats elements associated to this custom
172 // element definition as form-associated custom elements.
173 bool mFormAssociated = false;
175 // Determine whether to allow to attachInternals() for this custom element.
176 bool mDisableInternals = false;
178 // Determine whether to allow to attachShadow() for this custom element.
179 bool mDisableShadow = false;
181 // A construction stack. Use nullptr to represent an "already constructed
182 // marker".
183 nsTArray<RefPtr<Element>> mConstructionStack;
185 // See step 6.1.10 of https://dom.spec.whatwg.org/#concept-create-element
186 // which set up the prefix after a custom element is created. However, In
187 // Gecko, the prefix isn't allowed to be changed in NodeInfo, so we store the
188 // prefix information here and propagate to where NodeInfo is assigned to a
189 // custom element instead.
190 nsTArray<RefPtr<nsAtom>> mPrefixStack;
192 // This basically is used for distinguishing the custom element constructor
193 // is invoked from document.createElement or directly from JS, i.e.
194 // `new CustomElementConstructor()`.
195 uint32_t mConstructionDepth = 0;
197 bool IsCustomBuiltIn() { return mType != mLocalName; }
199 bool IsInObservedAttributeList(nsAtom* aName) {
200 if (mObservedAttributes.IsEmpty()) {
201 return false;
204 return mObservedAttributes.Contains(aName);
207 private:
208 ~CustomElementDefinition() = default;
211 class CustomElementReaction {
212 public:
213 virtual ~CustomElementReaction() = default;
214 MOZ_CAN_RUN_SCRIPT
215 virtual void Invoke(Element* aElement, ErrorResult& aRv) = 0;
216 virtual void Traverse(nsCycleCollectionTraversalCallback& aCb) const = 0;
217 virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const = 0;
219 bool IsUpgradeReaction() { return mIsUpgradeReaction; }
221 protected:
222 bool mIsUpgradeReaction = false;
225 // https://html.spec.whatwg.org/multipage/scripting.html#custom-element-reactions-stack
226 class CustomElementReactionsStack {
227 public:
228 NS_INLINE_DECL_REFCOUNTING(CustomElementReactionsStack)
230 CustomElementReactionsStack()
231 : mIsBackupQueueProcessing(false),
232 mRecursionDepth(0),
233 mIsElementQueuePushedForCurrentRecursionDepth(false) {}
235 // Hold a strong reference of Element so that it does not get cycle collected
236 // before the reactions in its reaction queue are invoked.
237 // The element reaction queues are stored in CustomElementData.
238 // We need to lookup ElementReactionQueueMap again to get relevant reaction
239 // queue. The choice of 3 for the auto size here is based on running Custom
240 // Elements wpt tests.
241 typedef AutoTArray<RefPtr<Element>, 3> ElementQueue;
244 * Enqueue a custom element upgrade reaction
245 * https://html.spec.whatwg.org/multipage/scripting.html#enqueue-a-custom-element-upgrade-reaction
247 void EnqueueUpgradeReaction(Element* aElement,
248 CustomElementDefinition* aDefinition);
251 * Enqueue a custom element callback reaction
252 * https://html.spec.whatwg.org/multipage/scripting.html#enqueue-a-custom-element-callback-reaction
254 void EnqueueCallbackReaction(
255 Element* aElement,
256 UniquePtr<CustomElementCallback> aCustomElementCallback);
259 * [CEReactions] Before executing the algorithm's steps.
260 * Increase the current recursion depth, and the element queue is pushed
261 * lazily when we really enqueue reactions.
263 * @return true if the element queue is pushed for "previous" recursion depth.
265 bool EnterCEReactions() {
266 bool temp = mIsElementQueuePushedForCurrentRecursionDepth;
267 mRecursionDepth++;
268 // The is-element-queue-pushed flag is initially false when entering a new
269 // recursion level. The original value will be cached in AutoCEReaction
270 // and restored after leaving this recursion level.
271 mIsElementQueuePushedForCurrentRecursionDepth = false;
272 return temp;
276 * [CEReactions] After executing the algorithm's steps.
277 * Pop and invoke the element queue if it is created and pushed for current
278 * recursion depth, then decrease the current recursion depth.
280 * @param aCx JSContext used for handling exception thrown by algorithm's
281 * steps, this could be a nullptr.
282 * aWasElementQueuePushed used for restoring status after leaving
283 * current recursion.
285 MOZ_CAN_RUN_SCRIPT
286 void LeaveCEReactions(JSContext* aCx, bool aWasElementQueuePushed) {
287 MOZ_ASSERT(mRecursionDepth);
289 if (mIsElementQueuePushedForCurrentRecursionDepth) {
290 Maybe<JS::AutoSaveExceptionState> ases;
291 if (aCx) {
292 ases.emplace(aCx);
294 PopAndInvokeElementQueue();
296 mRecursionDepth--;
297 // Restore the is-element-queue-pushed flag cached in AutoCEReaction when
298 // leaving the recursion level.
299 mIsElementQueuePushedForCurrentRecursionDepth = aWasElementQueuePushed;
301 MOZ_ASSERT_IF(!mRecursionDepth, mReactionsStack.IsEmpty());
304 bool IsElementQueuePushedForCurrentRecursionDepth() {
305 MOZ_ASSERT_IF(mIsElementQueuePushedForCurrentRecursionDepth,
306 !mReactionsStack.IsEmpty() &&
307 !mReactionsStack.LastElement()->IsEmpty());
308 return mIsElementQueuePushedForCurrentRecursionDepth;
311 private:
312 ~CustomElementReactionsStack() = default;
316 * Push a new element queue onto the custom element reactions stack.
318 void CreateAndPushElementQueue();
321 * Pop the element queue from the custom element reactions stack, and invoke
322 * custom element reactions in that queue.
324 MOZ_CAN_RUN_SCRIPT void PopAndInvokeElementQueue();
326 // The choice of 8 for the auto size here is based on gut feeling.
327 AutoTArray<UniquePtr<ElementQueue>, 8> mReactionsStack;
328 ElementQueue mBackupQueue;
329 // https://html.spec.whatwg.org/#enqueue-an-element-on-the-appropriate-element-queue
330 bool mIsBackupQueueProcessing;
332 MOZ_CAN_RUN_SCRIPT void InvokeBackupQueue();
335 * Invoke custom element reactions
336 * https://html.spec.whatwg.org/multipage/scripting.html#invoke-custom-element-reactions
338 MOZ_CAN_RUN_SCRIPT
339 void InvokeReactions(ElementQueue* aElementQueue, nsIGlobalObject* aGlobal);
341 void Enqueue(Element* aElement, CustomElementReaction* aReaction);
343 // Current [CEReactions] recursion depth.
344 uint32_t mRecursionDepth;
345 // True if the element queue is pushed into reaction stack for current
346 // recursion depth. This will be cached in AutoCEReaction when entering a new
347 // CEReaction recursion and restored after leaving the recursion.
348 bool mIsElementQueuePushedForCurrentRecursionDepth;
350 private:
351 class BackupQueueMicroTask final : public mozilla::MicroTaskRunnable {
352 public:
353 explicit BackupQueueMicroTask(CustomElementReactionsStack* aReactionStack)
354 : MicroTaskRunnable(), mReactionStack(aReactionStack) {
355 MOZ_ASSERT(!mReactionStack->mIsBackupQueueProcessing,
356 "mIsBackupQueueProcessing should be initially false");
357 mReactionStack->mIsBackupQueueProcessing = true;
360 MOZ_CAN_RUN_SCRIPT virtual void Run(AutoSlowOperation& aAso) override {
361 mReactionStack->InvokeBackupQueue();
362 mReactionStack->mIsBackupQueueProcessing = false;
365 private:
366 const RefPtr<CustomElementReactionsStack> mReactionStack;
370 class CustomElementRegistry final : public nsISupports, public nsWrapperCache {
371 public:
372 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
373 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CustomElementRegistry)
375 public:
376 explicit CustomElementRegistry(nsPIDOMWindowInner* aWindow);
378 private:
379 class RunCustomElementCreationCallback : public mozilla::Runnable {
380 public:
381 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT.
382 // See bug 1535398.
383 MOZ_CAN_RUN_SCRIPT_BOUNDARY
384 NS_DECL_NSIRUNNABLE
386 explicit RunCustomElementCreationCallback(
387 CustomElementRegistry* aRegistry, nsAtom* aAtom,
388 CustomElementCreationCallback* aCallback)
389 : mozilla::Runnable(
390 "CustomElementRegistry::RunCustomElementCreationCallback"),
391 mRegistry(aRegistry),
392 mAtom(aAtom),
393 mCallback(aCallback) {}
395 private:
396 RefPtr<CustomElementRegistry> mRegistry;
397 RefPtr<nsAtom> mAtom;
398 RefPtr<CustomElementCreationCallback> mCallback;
401 public:
403 * Looking up a custom element definition.
404 * https://html.spec.whatwg.org/#look-up-a-custom-element-definition
406 CustomElementDefinition* LookupCustomElementDefinition(nsAtom* aNameAtom,
407 int32_t aNameSpaceID,
408 nsAtom* aTypeAtom);
410 CustomElementDefinition* LookupCustomElementDefinition(
411 JSContext* aCx, JSObject* aConstructor) const;
413 static void EnqueueLifecycleCallback(ElementCallbackType aType,
414 Element* aCustomElement,
415 const LifecycleCallbackArgs& aArgs,
416 CustomElementDefinition* aDefinition);
419 * Upgrade an element.
420 * https://html.spec.whatwg.org/multipage/scripting.html#upgrades
422 MOZ_CAN_RUN_SCRIPT
423 static void Upgrade(Element* aElement, CustomElementDefinition* aDefinition,
424 ErrorResult& aRv);
427 * To allow native code to call methods of chrome-implemented custom elements,
428 * a helper method may be defined in the custom element called
429 * 'getCustomInterfaceCallback'. This method takes an IID and returns an
430 * object which implements an XPCOM interface.
432 * This returns null if aElement is not from a chrome document.
434 static already_AddRefed<nsISupports> CallGetCustomInterface(
435 Element* aElement, const nsIID& aIID);
438 * Registers an unresolved custom element that is a candidate for
439 * upgrade. |aTypeName| is the name of the custom element type, if it is not
440 * provided, then element name is used. |aTypeName| should be provided
441 * when registering a custom element that extends an existing
442 * element. e.g. <button is="x-button">.
444 void RegisterUnresolvedElement(Element* aElement,
445 nsAtom* aTypeName = nullptr);
448 * Unregister an unresolved custom element that is a candidate for
449 * upgrade when a custom element is removed from tree.
451 void UnregisterUnresolvedElement(Element* aElement,
452 nsAtom* aTypeName = nullptr);
455 * Register an element to be upgraded when the custom element creation
456 * callback is executed.
458 * To be used when LookupCustomElementDefinition() didn't return a definition,
459 * but with the callback scheduled to be run.
461 inline void RegisterCallbackUpgradeElement(Element* aElement,
462 nsAtom* aTypeName = nullptr) {
463 if (mElementCreationCallbacksUpgradeCandidatesMap.IsEmpty()) {
464 return;
467 RefPtr<nsAtom> typeName = aTypeName;
468 if (!typeName) {
469 typeName = aElement->NodeInfo()->NameAtom();
472 nsTHashSet<RefPtr<nsIWeakReference>>* elements =
473 mElementCreationCallbacksUpgradeCandidatesMap.Get(typeName);
475 // If there isn't a table, there won't be a definition added by the
476 // callback.
477 if (!elements) {
478 return;
481 nsWeakPtr elem = do_GetWeakReference(aElement);
482 elements->Insert(elem);
485 void TraceDefinitions(JSTracer* aTrc);
487 private:
488 ~CustomElementRegistry();
490 bool JSObjectToAtomArray(JSContext* aCx, JS::Handle<JSObject*> aConstructor,
491 const nsString& aName,
492 nsTArray<RefPtr<nsAtom>>& aArray, ErrorResult& aRv);
494 void UpgradeCandidates(nsAtom* aKey, CustomElementDefinition* aDefinition,
495 ErrorResult& aRv);
497 using DefinitionMap =
498 nsRefPtrHashtable<nsAtomHashKey, CustomElementDefinition>;
499 using ElementCreationCallbackMap =
500 nsRefPtrHashtable<nsAtomHashKey, CustomElementCreationCallback>;
501 using CandidateMap =
502 nsClassHashtable<nsAtomHashKey, nsTHashSet<RefPtr<nsIWeakReference>>>;
503 using ConstructorMap =
504 JS::GCHashMap<JS::Heap<JSObject*>, RefPtr<nsAtom>,
505 js::StableCellHasher<JS::Heap<JSObject*>>,
506 js::SystemAllocPolicy>;
508 // Hashtable for custom element definitions in web components.
509 // Custom prototypes are stored in the compartment where definition was
510 // defined.
511 DefinitionMap mCustomDefinitions;
513 // Hashtable for chrome-only callbacks that is called *before* we return
514 // a CustomElementDefinition, when the typeAtom matches.
515 // The callbacks are registered with the setElementCreationCallback method.
516 ElementCreationCallbackMap mElementCreationCallbacks;
518 // Hashtable for looking up definitions by using constructor as key.
519 // Custom elements' name are stored here and we need to lookup
520 // mCustomDefinitions again to get definitions.
521 ConstructorMap mConstructors;
523 using WhenDefinedPromiseMap = nsRefPtrHashtable<nsAtomHashKey, Promise>;
524 WhenDefinedPromiseMap mWhenDefinedPromiseMap;
526 // The "upgrade candidates map" from the web components spec. Maps from a
527 // namespace id and local name to a list of elements to upgrade if that
528 // element is registered as a custom element.
529 CandidateMap mCandidatesMap;
531 // If an element creation callback is found, the nsTHashtable for the
532 // type is created here, and elements will later be upgraded.
533 CandidateMap mElementCreationCallbacksUpgradeCandidatesMap;
535 nsCOMPtr<nsPIDOMWindowInner> mWindow;
537 // It is used to prevent reentrant invocations of element definition.
538 bool mIsCustomDefinitionRunning;
540 private:
541 int32_t InferNamespace(JSContext* aCx, JS::Handle<JSObject*> constructor);
543 public:
544 nsISupports* GetParentObject() const;
546 DocGroup* GetDocGroup() const;
548 virtual JSObject* WrapObject(JSContext* aCx,
549 JS::Handle<JSObject*> aGivenProto) override;
551 void Define(JSContext* aCx, const nsAString& aName,
552 CustomElementConstructor& aFunctionConstructor,
553 const ElementDefinitionOptions& aOptions, ErrorResult& aRv);
555 void Get(const nsAString& name,
556 OwningCustomElementConstructorOrUndefined& aRetVal);
558 void GetName(JSContext* aCx, CustomElementConstructor& aConstructor,
559 nsAString& aResult);
561 already_AddRefed<Promise> WhenDefined(const nsAString& aName,
562 ErrorResult& aRv);
564 // Chrome-only method that give JS an opportunity to only load the custom
565 // element definition script when needed.
566 void SetElementCreationCallback(const nsAString& aName,
567 CustomElementCreationCallback& aCallback,
568 ErrorResult& aRv);
570 void Upgrade(nsINode& aRoot);
573 class MOZ_RAII AutoCEReaction final {
574 public:
575 // JSContext is allowed to be a nullptr if we are guaranteeing that we're
576 // not doing something that might throw but not finish reporting a JS
577 // exception during the lifetime of the AutoCEReaction.
578 AutoCEReaction(CustomElementReactionsStack* aReactionsStack, JSContext* aCx)
579 : mReactionsStack(aReactionsStack), mCx(aCx) {
580 mIsElementQueuePushedForPreviousRecursionDepth =
581 mReactionsStack->EnterCEReactions();
584 // MOZ_CAN_RUN_SCRIPT_BOUNDARY because this is called from Maybe<>.reset().
585 MOZ_CAN_RUN_SCRIPT_BOUNDARY ~AutoCEReaction() {
586 mReactionsStack->LeaveCEReactions(
587 mCx, mIsElementQueuePushedForPreviousRecursionDepth);
590 private:
591 const RefPtr<CustomElementReactionsStack> mReactionsStack;
592 JSContext* mCx;
593 bool mIsElementQueuePushedForPreviousRecursionDepth;
596 } // namespace dom
597 } // namespace mozilla
599 #endif // mozilla_dom_CustomElementRegistry_h