summaryrefslogtreecommitdiffstats
path: root/dom/base/CustomElementRegistry.h
diff options
context:
space:
mode:
Diffstat (limited to 'dom/base/CustomElementRegistry.h')
-rw-r--r--dom/base/CustomElementRegistry.h425
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