summaryrefslogtreecommitdiff
path: root/dom/base/CustomElementRegistry.h
diff options
context:
space:
mode:
Diffstat (limited to 'dom/base/CustomElementRegistry.h')
-rw-r--r--dom/base/CustomElementRegistry.h259
1 files changed, 259 insertions, 0 deletions
diff --git a/dom/base/CustomElementRegistry.h b/dom/base/CustomElementRegistry.h
new file mode 100644
index 0000000000..ff803a0542
--- /dev/null
+++ b/dom/base/CustomElementRegistry.h
@@ -0,0 +1,259 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_CustomElementRegistry_h
+#define mozilla_dom_CustomElementRegistry_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "mozilla/dom/FunctionBinding.h"
+
+class nsDocument;
+
+namespace mozilla {
+namespace dom {
+
+struct CustomElementData;
+struct ElementDefinitionOptions;
+struct LifecycleCallbacks;
+class CallbackFunction;
+class Function;
+class Promise;
+
+struct LifecycleCallbackArgs
+{
+ nsString name;
+ nsString oldValue;
+ nsString newValue;
+};
+
+class CustomElementCallback
+{
+public:
+ CustomElementCallback(Element* aThisObject,
+ nsIDocument::ElementCallbackType aCallbackType,
+ CallbackFunction* aCallback,
+ CustomElementData* aOwnerData);
+ void Traverse(nsCycleCollectionTraversalCallback& aCb) const;
+ void Call();
+ void SetArgs(LifecycleCallbackArgs& aArgs)
+ {
+ MOZ_ASSERT(mType == nsIDocument::eAttributeChanged,
+ "Arguments are only used by attribute changed callback.");
+ mArgs = aArgs;
+ }
+
+private:
+ // The this value to use for invocation of the callback.
+ RefPtr<Element> mThisObject;
+ RefPtr<CallbackFunction> mCallback;
+ // The type of callback (eCreated, eAttached, etc.)
+ nsIDocument::ElementCallbackType mType;
+ // 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;
+};
+
+// Each custom element has an associated callback queue and an element is
+// being created flag.
+struct CustomElementData
+{
+ NS_INLINE_DECL_REFCOUNTING(CustomElementData)
+
+ 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();
+
+private:
+ virtual ~CustomElementData() {}
+};
+
+// The required information for a custom element as defined in:
+// https://html.spec.whatwg.org/multipage/scripting.html#custom-element-definition
+struct CustomElementDefinition
+{
+ CustomElementDefinition(nsIAtom* aType,
+ nsIAtom* aLocalName,
+ JSObject* aConstructor,
+ JSObject* aPrototype,
+ mozilla::dom::LifecycleCallbacks* aCallbacks,
+ uint32_t aDocOrder);
+
+ // The type (name) for this custom element.
+ 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;
+
+ // 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;
+
+ // A construction stack.
+ // TODO: Bug 1287348 - Implement construction stack for upgrading an element
+
+ // The document custom element order.
+ uint32_t mDocOrder;
+};
+
+class CustomElementRegistry final : public nsISupports,
+ public nsWrapperCache
+{
+ // Allow nsDocument to access mCustomDefinitions and mCandidatesMap.
+ friend class ::nsDocument;
+
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CustomElementRegistry)
+
+public:
+ static bool IsCustomElementEnabled(JSContext* aCx = nullptr,
+ JSObject* aObject = nullptr);
+ static already_AddRefed<CustomElementRegistry> Create(nsPIDOMWindowInner* aWindow);
+ static void ProcessTopElementQueue();
+
+ static void XPCOMShutdown();
+
+ /**
+ * 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;
+
+ /**
+ * 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);
+
+ void EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType,
+ Element* aCustomElement,
+ LifecycleCallbackArgs* aArgs,
+ CustomElementDefinition* aDefinition);
+
+ void GetCustomPrototype(nsIAtom* aAtom,
+ JS::MutableHandle<JSObject*> aPrototype);
+
+private:
+ explicit CustomElementRegistry(nsPIDOMWindowInner* aWindow);
+ ~CustomElementRegistry();
+
+ /**
+ * Registers an unresolved custom element that is a candidate for
+ * upgrade when the definition is registered via registerElement.
+ * |aTypeName| is the name of the custom element type, if it is not
+ * provided, then element name is used. |aTypeName| should be provided
+ * when registering a custom element that extends an existing
+ * element. e.g. <button is="x-button">.
+ */
+ void RegisterUnresolvedElement(Element* aElement,
+ nsIAtom* aTypeName = nullptr);
+
+ void UpgradeCandidates(JSContext* aCx,
+ nsIAtom* aKey,
+ CustomElementDefinition* aDefinition);
+
+ typedef nsClassHashtable<nsISupportsHashKey, CustomElementDefinition>
+ DefinitionMap;
+ typedef nsClassHashtable<nsISupportsHashKey, nsTArray<nsWeakPtr>>
+ CandidateMap;
+
+ // Hashtable for custom element definitions in web components.
+ // Custom prototypes are stored in the compartment where
+ // registerElement was called.
+ DefinitionMap mCustomDefinitions;
+
+ 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.
+ CandidateMap mCandidatesMap;
+
+ 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;
+
+private:
+ class MOZ_RAII AutoSetRunningFlag final {
+ public:
+ explicit AutoSetRunningFlag(CustomElementRegistry* aRegistry)
+ : mRegistry(aRegistry)
+ {
+ MOZ_ASSERT(!mRegistry->mIsCustomDefinitionRunning,
+ "IsCustomDefinitionRunning flag should be initially false");
+ mRegistry->mIsCustomDefinitionRunning = true;
+ }
+
+ ~AutoSetRunningFlag() {
+ mRegistry->mIsCustomDefinitionRunning = false;
+ }
+
+ private:
+ CustomElementRegistry* mRegistry;
+ };
+
+public:
+ nsISupports* GetParentObject() const;
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ void Define(const nsAString& aName, Function& aFunctionConstructor,
+ const ElementDefinitionOptions& aOptions, ErrorResult& aRv);
+
+ void Get(JSContext* cx, const nsAString& name,
+ JS::MutableHandle<JS::Value> aRetVal);
+
+ already_AddRefed<Promise> WhenDefined(const nsAString& aName, ErrorResult& aRv);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+
+#endif // mozilla_dom_CustomElementRegistry_h