diff options
Diffstat (limited to 'dom/base/CustomElementRegistry.h')
-rw-r--r-- | dom/base/CustomElementRegistry.h | 425 |
1 files changed, 363 insertions, 62 deletions
diff --git a/dom/base/CustomElementRegistry.h b/dom/base/CustomElementRegistry.h index ff803a054..c416e5043 100644 --- a/dom/base/CustomElementRegistry.h +++ b/dom/base/CustomElementRegistry.h @@ -7,13 +7,16 @@ #ifndef mozilla_dom_CustomElementRegistry_h #define mozilla_dom_CustomElementRegistry_h +#include "js/GCHashTable.h" #include "js/TypeDecls.h" #include "mozilla/Attributes.h" #include "mozilla/ErrorResult.h" #include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/FunctionBinding.h" +#include "mozilla/dom/WebComponentsBinding.h" #include "nsCycleCollectionParticipant.h" +#include "nsGenericHTMLElement.h" #include "nsWrapperCache.h" -#include "mozilla/dom/FunctionBinding.h" class nsDocument; @@ -22,8 +25,8 @@ namespace dom { struct CustomElementData; struct ElementDefinitionOptions; -struct LifecycleCallbacks; class CallbackFunction; +class CustomElementReaction; class Function; class Promise; @@ -32,6 +35,13 @@ struct LifecycleCallbackArgs nsString name; nsString oldValue; nsString newValue; + nsString namespaceURI; +}; + +struct LifecycleAdoptedCallbackArgs +{ + nsCOMPtr<nsIDocument> mOldDocument; + nsCOMPtr<nsIDocument> mNewDocument; }; class CustomElementCallback @@ -39,8 +49,7 @@ class CustomElementCallback public: CustomElementCallback(Element* aThisObject, nsIDocument::ElementCallbackType aCallbackType, - CallbackFunction* aCallback, - CustomElementData* aOwnerData); + CallbackFunction* aCallback); void Traverse(nsCycleCollectionTraversalCallback& aCb) const; void Call(); void SetArgs(LifecycleCallbackArgs& aArgs) @@ -50,6 +59,13 @@ public: mArgs = aArgs; } + void SetAdoptedCallbackArgs(LifecycleAdoptedCallbackArgs& aAdoptedCallbackArgs) + { + MOZ_ASSERT(mType == nsIDocument::eAdopted, + "Arguments are only used by adopted callback."); + mAdoptedCallbackArgs = aAdoptedCallbackArgs; + } + private: // The this value to use for invocation of the callback. RefPtr<Element> mThisObject; @@ -59,9 +75,19 @@ private: // Arguments to be passed to the callback, // used by the attribute changed callback. LifecycleCallbackArgs mArgs; - // CustomElementData that contains this callback in the - // callback queue. - CustomElementData* mOwnerData; + LifecycleAdoptedCallbackArgs mAdoptedCallbackArgs; +}; + +class CustomElementConstructor final : public CallbackFunction +{ +public: + explicit CustomElementConstructor(CallbackFunction* aOther) + : CallbackFunction(aOther) + { + MOZ_ASSERT(JS::IsConstructor(mCallback)); + } + + already_AddRefed<Element> Construct(const char* aExecutionReason, ErrorResult& aRv); }; // Each custom element has an associated callback queue and an element is @@ -70,63 +96,299 @@ struct CustomElementData { NS_INLINE_DECL_REFCOUNTING(CustomElementData) + // https://dom.spec.whatwg.org/#concept-element-custom-element-state + // CustomElementData is only created on the element which is a custom element + // or an upgrade candidate, so the state of an element without + // CustomElementData is "uncustomized". + enum class State { + eUndefined, + eFailed, + eCustom + }; + explicit CustomElementData(nsIAtom* aType); - // Objects in this array are transient and empty after each microtask - // checkpoint. - nsTArray<nsAutoPtr<CustomElementCallback>> mCallbackQueue; - // Custom element type, for <button is="x-button"> or <x-button> - // this would be x-button. - nsCOMPtr<nsIAtom> mType; - // The callback that is next to be processed upon calling RunCallbackQueue. - int32_t mCurrentCallback; - // Element is being created flag as described in the custom elements spec. - bool mElementIsBeingCreated; - // Flag to determine if the created callback has been invoked, thus it - // determines if other callbacks can be enqueued. - bool mCreatedCallbackInvoked; - // The microtask level associated with the callbacks in the callback queue, - // it is used to determine if a new queue needs to be pushed onto the - // processing stack. - int32_t mAssociatedMicroTask; - - // Empties the callback queue. - void RunCallbackQueue(); + CustomElementData(nsIAtom* aType, State aState); + + // Custom element state as described in the custom element spec. + State mState; + // custom element reaction queue as described in the custom element spec. + // There is 1 reaction in reaction queue, when 1) it becomes disconnected, + // 2) it’s adopted into a new document, 3) its attributes are changed, + // appended, removed, or replaced. + // There are 3 reactions in reaction queue when doing upgrade operation, + // e.g., create an element, insert a node. + AutoTArray<UniquePtr<CustomElementReaction>, 3> mReactionQueue; + + void SetCustomElementDefinition(CustomElementDefinition* aDefinition); + CustomElementDefinition* GetCustomElementDefinition(); + nsIAtom* GetCustomElementType(); + + void Traverse(nsCycleCollectionTraversalCallback& aCb) const; + void Unlink(); private: virtual ~CustomElementData() {} + + // Custom element type, for <button is="x-button"> or <x-button> + // this would be x-button. + RefPtr<nsIAtom> mType; + RefPtr<CustomElementDefinition> mCustomElementDefinition; }; +#define ALEADY_CONSTRUCTED_MARKER nullptr + // The required information for a custom element as defined in: // https://html.spec.whatwg.org/multipage/scripting.html#custom-element-definition struct CustomElementDefinition { + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(CustomElementDefinition) + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(CustomElementDefinition) + CustomElementDefinition(nsIAtom* aType, nsIAtom* aLocalName, - JSObject* aConstructor, + Function* aConstructor, + nsCOMArray<nsIAtom>&& aObservedAttributes, JSObject* aPrototype, mozilla::dom::LifecycleCallbacks* aCallbacks, uint32_t aDocOrder); - // The type (name) for this custom element. + // The type (name) for this custom element, for <button is="x-foo"> or <x-foo> + // this would be x-foo. nsCOMPtr<nsIAtom> mType; // The localname to (e.g. <button is=type> -- this would be button). nsCOMPtr<nsIAtom> mLocalName; // The custom element constructor. - JS::Heap<JSObject *> mConstructor; + RefPtr<CustomElementConstructor> mConstructor; + + // The list of attributes that this custom element observes. + nsCOMArray<nsIAtom> mObservedAttributes; // The prototype to use for new custom elements of this type. JS::Heap<JSObject *> mPrototype; // The lifecycle callbacks to call for this custom element. - nsAutoPtr<mozilla::dom::LifecycleCallbacks> mCallbacks; + UniquePtr<mozilla::dom::LifecycleCallbacks> mCallbacks; - // A construction stack. - // TODO: Bug 1287348 - Implement construction stack for upgrading an element + // A construction stack. Use nullptr to represent an "already constructed marker". + nsTArray<RefPtr<nsGenericHTMLElement>> mConstructionStack; // The document custom element order. uint32_t mDocOrder; + + bool IsCustomBuiltIn() + { + return mType != mLocalName; + } + + bool IsInObservedAttributeList(nsIAtom* aName) + { + if (mObservedAttributes.IsEmpty()) { + return false; + } + + return mObservedAttributes.Contains(aName); + } + +private: + ~CustomElementDefinition() {} +}; + +class CustomElementReaction +{ +public: + virtual ~CustomElementReaction() = default; + virtual void Invoke(Element* aElement, ErrorResult& aRv) = 0; + virtual void Traverse(nsCycleCollectionTraversalCallback& aCb) const + { + } + +#if DEBUG + bool IsUpgradeReaction() + { + return mIsUpgradeReaction; + } + +protected: + bool mIsUpgradeReaction = false; +#endif +}; + +class CustomElementUpgradeReaction final : public CustomElementReaction +{ +public: + explicit CustomElementUpgradeReaction(CustomElementDefinition* aDefinition) + : mDefinition(aDefinition) + { +#if DEBUG + mIsUpgradeReaction = true; +#endif + } + +private: + virtual void Invoke(Element* aElement, ErrorResult& aRv) override; + + CustomElementDefinition* mDefinition; +}; + +class CustomElementCallbackReaction final : public CustomElementReaction +{ + public: + explicit CustomElementCallbackReaction(UniquePtr<CustomElementCallback> aCustomElementCallback) + : mCustomElementCallback(Move(aCustomElementCallback)) + { + } + + virtual void Traverse(nsCycleCollectionTraversalCallback& aCb) const override + { + mCustomElementCallback->Traverse(aCb); + } + + private: + virtual void Invoke(Element* aElement, ErrorResult& aRv) override; + UniquePtr<CustomElementCallback> mCustomElementCallback; +}; + +// https://html.spec.whatwg.org/multipage/scripting.html#custom-element-reactions-stack +class CustomElementReactionsStack +{ +public: + NS_INLINE_DECL_REFCOUNTING(CustomElementReactionsStack) + + CustomElementReactionsStack() + : mIsBackupQueueProcessing(false) + , mRecursionDepth(0) + , mIsElementQueuePushedForCurrentRecursionDepth(false) + { + } + + // Hold a strong reference of Element so that it does not get cycle collected + // before the reactions in its reaction queue are invoked. + // The element reaction queues are stored in CustomElementData. + // We need to lookup ElementReactionQueueMap again to get relevant reaction queue. + // The choice of 1 for the auto size here is based on gut feeling. + typedef AutoTArray<RefPtr<Element>, 1> ElementQueue; + + /** + * Enqueue a custom element upgrade reaction + * https://html.spec.whatwg.org/multipage/scripting.html#enqueue-a-custom-element-upgrade-reaction + */ + void EnqueueUpgradeReaction(Element* aElement, + CustomElementDefinition* aDefinition); + + /** + * Enqueue a custom element callback reaction + * https://html.spec.whatwg.org/multipage/scripting.html#enqueue-a-custom-element-callback-reaction + */ + void EnqueueCallbackReaction(Element* aElement, + UniquePtr<CustomElementCallback> aCustomElementCallback); + + /** + * [CEReactions] Before executing the algorithm's steps. + * Increase the current recursion depth, and the element queue is pushed + * lazily when we really enqueue reactions. + * + * @return true if the element queue is pushed for "previous" recursion depth. + */ + bool EnterCEReactions() + { + bool temp = mIsElementQueuePushedForCurrentRecursionDepth; + mRecursionDepth++; + // The is-element-queue-pushed flag is initially false when entering a new + // recursion level. The original value will be cached in AutoCEReaction + // and restored after leaving this recursion level. + mIsElementQueuePushedForCurrentRecursionDepth = false; + return temp; + } + + /** + * [CEReactions] After executing the algorithm's steps. + * Pop and invoke the element queue if it is created and pushed for current + * recursion depth, then decrease the current recursion depth. + * + * @param aCx JSContext used for handling exception thrown by algorithm's + * steps, this could be a nullptr. + * aWasElementQueuePushed used for restoring status after leaving + * current recursion. + */ + void LeaveCEReactions(JSContext* aCx, bool aWasElementQueuePushed) + { + MOZ_ASSERT(mRecursionDepth); + + if (mIsElementQueuePushedForCurrentRecursionDepth) { + Maybe<JS::AutoSaveExceptionState> ases; + if (aCx) { + ases.emplace(aCx); + } + PopAndInvokeElementQueue(); + } + mRecursionDepth--; + // Restore the is-element-queue-pushed flag cached in AutoCEReaction when + // leaving the recursion level. + mIsElementQueuePushedForCurrentRecursionDepth = aWasElementQueuePushed; + + MOZ_ASSERT_IF(!mRecursionDepth, mReactionsStack.IsEmpty()); + } + +private: + ~CustomElementReactionsStack() {}; + + /** + * Push a new element queue onto the custom element reactions stack. + */ + void CreateAndPushElementQueue(); + + /** + * Pop the element queue from the custom element reactions stack, and invoke + * custom element reactions in that queue. + */ + void PopAndInvokeElementQueue(); + + // The choice of 8 for the auto size here is based on gut feeling. + AutoTArray<UniquePtr<ElementQueue>, 8> mReactionsStack; + ElementQueue mBackupQueue; + // https://html.spec.whatwg.org/#enqueue-an-element-on-the-appropriate-element-queue + bool mIsBackupQueueProcessing; + + void InvokeBackupQueue(); + + /** + * Invoke custom element reactions + * https://html.spec.whatwg.org/multipage/scripting.html#invoke-custom-element-reactions + */ + void InvokeReactions(ElementQueue* aElementQueue, nsIGlobalObject* aGlobal); + + void Enqueue(Element* aElement, CustomElementReaction* aReaction); + + // Current [CEReactions] recursion depth. + uint32_t mRecursionDepth; + // True if the element queue is pushed into reaction stack for current + // recursion depth. This will be cached in AutoCEReaction when entering a new + // CEReaction recursion and restored after leaving the recursion. + bool mIsElementQueuePushedForCurrentRecursionDepth; + +private: + class BackupQueueMicroTask final : public mozilla::MicroTaskRunnable { + public: + explicit BackupQueueMicroTask(CustomElementReactionsStack* aReactionStack) + : MicroTaskRunnable() + , mReactionStack(aReactionStack) + { + MOZ_ASSERT(!mReactionStack->mIsBackupQueueProcessing, + "mIsBackupQueueProcessing should be initially false"); + mReactionStack->mIsBackupQueueProcessing = true; + } + + virtual void Run(AutoSlowOperation& aAso) override + { + mReactionStack->InvokeBackupQueue(); + mReactionStack->mIsBackupQueueProcessing = false; + } + + private: + RefPtr<CustomElementReactionsStack> mReactionStack; + }; }; class CustomElementRegistry final : public nsISupports, @@ -142,36 +404,33 @@ public: public: static bool IsCustomElementEnabled(JSContext* aCx = nullptr, JSObject* aObject = nullptr); - static already_AddRefed<CustomElementRegistry> Create(nsPIDOMWindowInner* aWindow); - static void ProcessTopElementQueue(); - static void XPCOMShutdown(); + explicit CustomElementRegistry(nsPIDOMWindowInner* aWindow); /** * Looking up a custom element definition. * https://html.spec.whatwg.org/#look-up-a-custom-element-definition */ CustomElementDefinition* LookupCustomElementDefinition( - const nsAString& aLocalName, const nsAString* aIs = nullptr) const; + nsIAtom* aNameAtom, nsIAtom* aTypeAtom) const; - /** - * Enqueue created callback or register upgrade candidate for - * newly created custom elements, possibly extending an existing type. - * ex. <x-button>, <button is="x-button> (type extension) - */ - void SetupCustomElement(Element* aElement, const nsAString* aTypeExtension); + CustomElementDefinition* LookupCustomElementDefinition( + JSContext* aCx, JSObject *aConstructor) const; - void EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType, - Element* aCustomElement, - LifecycleCallbackArgs* aArgs, - CustomElementDefinition* aDefinition); + static void EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType, + Element* aCustomElement, + LifecycleCallbackArgs* aArgs, + LifecycleAdoptedCallbackArgs* aAdoptedCallbackArgs, + CustomElementDefinition* aDefinition); void GetCustomPrototype(nsIAtom* aAtom, JS::MutableHandle<JSObject*> aPrototype); -private: - explicit CustomElementRegistry(nsPIDOMWindowInner* aWindow); - ~CustomElementRegistry(); + /** + * Upgrade an element. + * https://html.spec.whatwg.org/multipage/scripting.html#upgrades + */ + static void Upgrade(Element* aElement, CustomElementDefinition* aDefinition, ErrorResult& aRv); /** * Registers an unresolved custom element that is a candidate for @@ -184,23 +443,48 @@ private: void RegisterUnresolvedElement(Element* aElement, nsIAtom* aTypeName = nullptr); - void UpgradeCandidates(JSContext* aCx, - nsIAtom* aKey, - CustomElementDefinition* aDefinition); + /** + * Unregister an unresolved custom element that is a candidate for + * upgrade when a custom element is removed from tree. + */ + void UnregisterUnresolvedElement(Element* aElement, + nsIAtom* aTypeName = nullptr); +private: + ~CustomElementRegistry(); + + static UniquePtr<CustomElementCallback> CreateCustomElementCallback( + nsIDocument::ElementCallbackType aType, Element* aCustomElement, + LifecycleCallbackArgs* aArgs, + LifecycleAdoptedCallbackArgs* aAdoptedCallbackArgs, + CustomElementDefinition* aDefinition); - typedef nsClassHashtable<nsISupportsHashKey, CustomElementDefinition> + void UpgradeCandidates(nsIAtom* aKey, + CustomElementDefinition* aDefinition, + ErrorResult& aRv); + + typedef nsRefPtrHashtable<nsISupportsHashKey, CustomElementDefinition> DefinitionMap; typedef nsClassHashtable<nsISupportsHashKey, nsTArray<nsWeakPtr>> CandidateMap; + typedef JS::GCHashMap<JS::Heap<JSObject*>, + nsCOMPtr<nsIAtom>, + js::MovableCellHasher<JS::Heap<JSObject*>>, + js::SystemAllocPolicy> ConstructorMap; // Hashtable for custom element definitions in web components. // Custom prototypes are stored in the compartment where // registerElement was called. DefinitionMap mCustomDefinitions; + // Hashtable for looking up definitions by using constructor as key. + // Custom elements' name are stored here and we need to lookup + // mCustomDefinitions again to get definitions. + ConstructorMap mConstructors; + typedef nsRefPtrHashtable<nsISupportsHashKey, Promise> WhenDefinedPromiseMap; WhenDefinedPromiseMap mWhenDefinedPromiseMap; + // The "upgrade candidates map" from the web components spec. Maps from a // namespace id and local name to a list of elements to upgrade if that // element is registered as a custom element. @@ -208,14 +492,6 @@ private: nsCOMPtr<nsPIDOMWindowInner> mWindow; - // Array representing the processing stack in the custom elements - // specification. The processing stack is conceptually a stack of - // element queues. Each queue is represented by a sequence of - // CustomElementData in this array, separated by nullptr that - // represent the boundaries of the items in the stack. The first - // queue in the stack is the base element queue. - static mozilla::Maybe<nsTArray<RefPtr<CustomElementData>>> sProcessingStack; - // It is used to prevent reentrant invocations of element definition. bool mIsCustomDefinitionRunning; @@ -252,6 +528,31 @@ public: already_AddRefed<Promise> WhenDefined(const nsAString& aName, ErrorResult& aRv); }; +class MOZ_RAII AutoCEReaction final { + public: + // JSContext is allowed to be a nullptr if we are guaranteeing that we're + // not doing something that might throw but not finish reporting a JS + // exception during the lifetime of the AutoCEReaction. + AutoCEReaction(CustomElementReactionsStack* aReactionsStack, JSContext* aCx) + : mReactionsStack(aReactionsStack) + , mCx(aCx) + { + mIsElementQueuePushedForPreviousRecursionDepth = + mReactionsStack->EnterCEReactions(); + } + + ~AutoCEReaction() + { + mReactionsStack->LeaveCEReactions( + mCx, mIsElementQueuePushedForPreviousRecursionDepth); + } + + private: + RefPtr<CustomElementReactionsStack> mReactionsStack; + JSContext* mCx; + bool mIsElementQueuePushedForPreviousRecursionDepth; +}; + } // namespace dom } // namespace mozilla |