/* -*- 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