Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim:set ts=2 sw=2 sts=2 et cindent: */
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/. */
6 :
7 : #ifndef mozilla_dom_CustomElementRegistry_h
8 : #define mozilla_dom_CustomElementRegistry_h
9 :
10 : #include "js/GCHashTable.h"
11 : #include "js/TypeDecls.h"
12 : #include "mozilla/Attributes.h"
13 : #include "mozilla/ErrorResult.h"
14 : #include "mozilla/dom/BindingDeclarations.h"
15 : #include "mozilla/dom/Element.h"
16 : #include "mozilla/dom/FunctionBinding.h"
17 : #include "nsCycleCollectionParticipant.h"
18 : #include "nsWrapperCache.h"
19 :
20 : class nsDocument;
21 :
22 : namespace mozilla {
23 : namespace dom {
24 :
25 : struct CustomElementData;
26 : struct ElementDefinitionOptions;
27 : struct LifecycleCallbacks;
28 : class CallbackFunction;
29 : class CustomElementReaction;
30 : class Function;
31 : class Promise;
32 :
33 0 : struct LifecycleCallbackArgs
34 : {
35 : nsString name;
36 : nsString oldValue;
37 : nsString newValue;
38 : };
39 :
40 0 : class CustomElementCallback
41 : {
42 : public:
43 : CustomElementCallback(Element* aThisObject,
44 : nsIDocument::ElementCallbackType aCallbackType,
45 : CallbackFunction* aCallback,
46 : CustomElementData* aOwnerData);
47 : void Traverse(nsCycleCollectionTraversalCallback& aCb) const;
48 : void Call();
49 0 : void SetArgs(LifecycleCallbackArgs& aArgs)
50 : {
51 0 : MOZ_ASSERT(mType == nsIDocument::eAttributeChanged,
52 : "Arguments are only used by attribute changed callback.");
53 0 : mArgs = aArgs;
54 0 : }
55 :
56 : private:
57 : // The this value to use for invocation of the callback.
58 : RefPtr<Element> mThisObject;
59 : RefPtr<CallbackFunction> mCallback;
60 : // The type of callback (eCreated, eAttached, etc.)
61 : nsIDocument::ElementCallbackType mType;
62 : // Arguments to be passed to the callback,
63 : // used by the attribute changed callback.
64 : LifecycleCallbackArgs mArgs;
65 : // CustomElementData that contains this callback in the
66 : // callback queue.
67 : CustomElementData* mOwnerData;
68 : };
69 :
70 : // Each custom element has an associated callback queue and an element is
71 : // being created flag.
72 : struct CustomElementData
73 : {
74 0 : NS_INLINE_DECL_REFCOUNTING(CustomElementData)
75 :
76 : // https://dom.spec.whatwg.org/#concept-element-custom-element-state
77 : // CustomElementData is only created on the element which is a custom element
78 : // or an upgrade candidate, so the state of an element without
79 : // CustomElementData is "uncustomized".
80 : enum class State {
81 : eUndefined,
82 : eFailed,
83 : eCustom
84 : };
85 :
86 : explicit CustomElementData(nsIAtom* aType);
87 : CustomElementData(nsIAtom* aType, State aState);
88 : // Objects in this array are transient and empty after each microtask
89 : // checkpoint.
90 : nsTArray<nsAutoPtr<CustomElementCallback>> mCallbackQueue;
91 : // Custom element type, for <button is="x-button"> or <x-button>
92 : // this would be x-button.
93 : nsCOMPtr<nsIAtom> mType;
94 : // The callback that is next to be processed upon calling RunCallbackQueue.
95 : int32_t mCurrentCallback;
96 : // Element is being created flag as described in the custom elements spec.
97 : bool mElementIsBeingCreated;
98 : // Flag to determine if the created callback has been invoked, thus it
99 : // determines if other callbacks can be enqueued.
100 : bool mCreatedCallbackInvoked;
101 : // The microtask level associated with the callbacks in the callback queue,
102 : // it is used to determine if a new queue needs to be pushed onto the
103 : // processing stack.
104 : int32_t mAssociatedMicroTask;
105 : // Custom element state as described in the custom element spec.
106 : State mState;
107 : // custom element reaction queue as described in the custom element spec.
108 : // There is 1 reaction in reaction queue, when 1) it becomes disconnected,
109 : // 2) it’s adopted into a new document, 3) its attributes are changed,
110 : // appended, removed, or replaced.
111 : // There are 3 reactions in reaction queue when doing upgrade operation,
112 : // e.g., create an element, insert a node.
113 : AutoTArray<nsAutoPtr<CustomElementReaction>, 3> mReactionQueue;
114 :
115 : // Empties the callback queue.
116 : void RunCallbackQueue();
117 :
118 : private:
119 0 : virtual ~CustomElementData() {}
120 : };
121 :
122 : // The required information for a custom element as defined in:
123 : // https://html.spec.whatwg.org/multipage/scripting.html#custom-element-definition
124 0 : struct CustomElementDefinition
125 : {
126 : CustomElementDefinition(nsIAtom* aType,
127 : nsIAtom* aLocalName,
128 : JSObject* aConstructor,
129 : JSObject* aPrototype,
130 : mozilla::dom::LifecycleCallbacks* aCallbacks,
131 : uint32_t aDocOrder);
132 :
133 : // The type (name) for this custom element.
134 : nsCOMPtr<nsIAtom> mType;
135 :
136 : // The localname to (e.g. <button is=type> -- this would be button).
137 : nsCOMPtr<nsIAtom> mLocalName;
138 :
139 : // The custom element constructor.
140 : JS::Heap<JSObject *> mConstructor;
141 :
142 : // The prototype to use for new custom elements of this type.
143 : JS::Heap<JSObject *> mPrototype;
144 :
145 : // The lifecycle callbacks to call for this custom element.
146 : nsAutoPtr<mozilla::dom::LifecycleCallbacks> mCallbacks;
147 :
148 : // A construction stack.
149 : // TODO: Bug 1287348 - Implement construction stack for upgrading an element
150 :
151 : // The document custom element order.
152 : uint32_t mDocOrder;
153 :
154 0 : bool IsCustomBuiltIn() {
155 0 : return mType != mLocalName;
156 : }
157 : };
158 :
159 : class CustomElementReaction
160 : {
161 : public:
162 0 : explicit CustomElementReaction(CustomElementRegistry* aRegistry,
163 : CustomElementDefinition* aDefinition)
164 0 : : mRegistry(aRegistry)
165 0 : , mDefinition(aDefinition)
166 : {
167 0 : };
168 :
169 0 : virtual ~CustomElementReaction() = default;
170 : virtual void Invoke(Element* aElement) = 0;
171 :
172 : protected:
173 : CustomElementRegistry* mRegistry;
174 : CustomElementDefinition* mDefinition;
175 : };
176 :
177 0 : class CustomElementUpgradeReaction final : public CustomElementReaction
178 : {
179 : public:
180 0 : explicit CustomElementUpgradeReaction(CustomElementRegistry* aRegistry,
181 : CustomElementDefinition* aDefinition)
182 0 : : CustomElementReaction(aRegistry, aDefinition)
183 : {
184 0 : }
185 :
186 : private:
187 : virtual void Invoke(Element* aElement) override;
188 : };
189 :
190 : // https://html.spec.whatwg.org/multipage/scripting.html#custom-element-reactions-stack
191 : class CustomElementReactionsStack
192 : {
193 : public:
194 1206 : NS_INLINE_DECL_REFCOUNTING(CustomElementReactionsStack)
195 :
196 2 : CustomElementReactionsStack()
197 2 : : mIsBackupQueueProcessing(false)
198 : {
199 2 : }
200 :
201 : // nsWeakPtr is a weak pointer of Element
202 : // The element reaction queues are stored in CustomElementData.
203 : // We need to lookup ElementReactionQueueMap again to get relevant reaction queue.
204 : // The choice of 1 for the auto size here is based on gut feeling.
205 : typedef AutoTArray<nsWeakPtr, 1> ElementQueue;
206 :
207 : /**
208 : * Enqueue a custom element upgrade reaction
209 : * https://html.spec.whatwg.org/multipage/scripting.html#enqueue-a-custom-element-upgrade-reaction
210 : */
211 : void EnqueueUpgradeReaction(CustomElementRegistry* aRegistry,
212 : Element* aElement,
213 : CustomElementDefinition* aDefinition);
214 :
215 : // [CEReactions] Before executing the algorithm's steps
216 : // Push a new element queue onto the custom element reactions stack.
217 : void CreateAndPushElementQueue();
218 :
219 : // [CEReactions] After executing the algorithm's steps
220 : // Pop the element queue from the custom element reactions stack,
221 : // and invoke custom element reactions in that queue.
222 : void PopAndInvokeElementQueue();
223 :
224 : private:
225 0 : ~CustomElementReactionsStack() {};
226 :
227 : // The choice of 8 for the auto size here is based on gut feeling.
228 : AutoTArray<ElementQueue, 8> mReactionsStack;
229 : ElementQueue mBackupQueue;
230 : // https://html.spec.whatwg.org/#enqueue-an-element-on-the-appropriate-element-queue
231 : bool mIsBackupQueueProcessing;
232 :
233 : void InvokeBackupQueue();
234 :
235 : /**
236 : * Invoke custom element reactions
237 : * https://html.spec.whatwg.org/multipage/scripting.html#invoke-custom-element-reactions
238 : */
239 : void InvokeReactions(ElementQueue& aElementQueue);
240 :
241 : void Enqueue(Element* aElement, CustomElementReaction* aReaction);
242 :
243 : private:
244 0 : class ProcessBackupQueueRunnable : public mozilla::Runnable {
245 : public:
246 0 : explicit ProcessBackupQueueRunnable(
247 : CustomElementReactionsStack* aReactionStack)
248 0 : : Runnable(
249 : "dom::CustomElementReactionsStack::ProcessBackupQueueRunnable")
250 0 : , mReactionStack(aReactionStack)
251 : {
252 0 : MOZ_ASSERT(!mReactionStack->mIsBackupQueueProcessing,
253 : "mIsBackupQueueProcessing should be initially false");
254 0 : mReactionStack->mIsBackupQueueProcessing = true;
255 0 : }
256 :
257 0 : NS_IMETHOD Run() override
258 : {
259 0 : mReactionStack->InvokeBackupQueue();
260 0 : mReactionStack->mIsBackupQueueProcessing = false;
261 0 : return NS_OK;
262 : }
263 :
264 : private:
265 : RefPtr<CustomElementReactionsStack> mReactionStack;
266 : };
267 : };
268 :
269 : class CustomElementRegistry final : public nsISupports,
270 : public nsWrapperCache
271 : {
272 : // Allow nsDocument to access mCustomDefinitions and mCandidatesMap.
273 : friend class ::nsDocument;
274 :
275 : public:
276 : NS_DECL_CYCLE_COLLECTING_ISUPPORTS
277 1 : NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CustomElementRegistry)
278 :
279 : public:
280 : static bool IsCustomElementEnabled(JSContext* aCx = nullptr,
281 : JSObject* aObject = nullptr);
282 :
283 : static void ProcessTopElementQueue();
284 :
285 : static void XPCOMShutdown();
286 :
287 : explicit CustomElementRegistry(nsPIDOMWindowInner* aWindow);
288 :
289 : /**
290 : * Looking up a custom element definition.
291 : * https://html.spec.whatwg.org/#look-up-a-custom-element-definition
292 : */
293 : CustomElementDefinition* LookupCustomElementDefinition(
294 : const nsAString& aLocalName, const nsAString* aIs = nullptr) const;
295 :
296 : CustomElementDefinition* LookupCustomElementDefinition(
297 : JSContext* aCx, JSObject *aConstructor) const;
298 :
299 : /**
300 : * Enqueue created callback or register upgrade candidate for
301 : * newly created custom elements, possibly extending an existing type.
302 : * ex. <x-button>, <button is="x-button> (type extension)
303 : */
304 : void SetupCustomElement(Element* aElement, const nsAString* aTypeExtension);
305 :
306 : void EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType,
307 : Element* aCustomElement,
308 : LifecycleCallbackArgs* aArgs,
309 : CustomElementDefinition* aDefinition);
310 :
311 : void GetCustomPrototype(nsIAtom* aAtom,
312 : JS::MutableHandle<JSObject*> aPrototype);
313 :
314 : void Upgrade(Element* aElement, CustomElementDefinition* aDefinition);
315 :
316 : private:
317 : ~CustomElementRegistry();
318 :
319 : /**
320 : * Registers an unresolved custom element that is a candidate for
321 : * upgrade when the definition is registered via registerElement.
322 : * |aTypeName| is the name of the custom element type, if it is not
323 : * provided, then element name is used. |aTypeName| should be provided
324 : * when registering a custom element that extends an existing
325 : * element. e.g. <button is="x-button">.
326 : */
327 : void RegisterUnresolvedElement(Element* aElement,
328 : nsIAtom* aTypeName = nullptr);
329 :
330 : void UpgradeCandidates(JSContext* aCx,
331 : nsIAtom* aKey,
332 : CustomElementDefinition* aDefinition,
333 : ErrorResult& aRv);
334 :
335 : typedef nsClassHashtable<nsISupportsHashKey, CustomElementDefinition>
336 : DefinitionMap;
337 : typedef nsClassHashtable<nsISupportsHashKey, nsTArray<nsWeakPtr>>
338 : CandidateMap;
339 : typedef JS::GCHashMap<JS::Heap<JSObject*>,
340 : nsCOMPtr<nsIAtom>,
341 : js::MovableCellHasher<JS::Heap<JSObject*>>,
342 : js::SystemAllocPolicy> ConstructorMap;
343 :
344 : // Hashtable for custom element definitions in web components.
345 : // Custom prototypes are stored in the compartment where
346 : // registerElement was called.
347 : DefinitionMap mCustomDefinitions;
348 :
349 : // Hashtable for looking up definitions by using constructor as key.
350 : // Custom elements' name are stored here and we need to lookup
351 : // mCustomDefinitions again to get definitions.
352 : ConstructorMap mConstructors;
353 :
354 : typedef nsRefPtrHashtable<nsISupportsHashKey, Promise>
355 : WhenDefinedPromiseMap;
356 : WhenDefinedPromiseMap mWhenDefinedPromiseMap;
357 :
358 : // The "upgrade candidates map" from the web components spec. Maps from a
359 : // namespace id and local name to a list of elements to upgrade if that
360 : // element is registered as a custom element.
361 : CandidateMap mCandidatesMap;
362 :
363 : nsCOMPtr<nsPIDOMWindowInner> mWindow;
364 :
365 : // Array representing the processing stack in the custom elements
366 : // specification. The processing stack is conceptually a stack of
367 : // element queues. Each queue is represented by a sequence of
368 : // CustomElementData in this array, separated by nullptr that
369 : // represent the boundaries of the items in the stack. The first
370 : // queue in the stack is the base element queue.
371 : static mozilla::Maybe<nsTArray<RefPtr<CustomElementData>>> sProcessingStack;
372 :
373 : // It is used to prevent reentrant invocations of element definition.
374 : bool mIsCustomDefinitionRunning;
375 :
376 : private:
377 : class MOZ_RAII AutoSetRunningFlag final {
378 : public:
379 0 : explicit AutoSetRunningFlag(CustomElementRegistry* aRegistry)
380 0 : : mRegistry(aRegistry)
381 : {
382 0 : MOZ_ASSERT(!mRegistry->mIsCustomDefinitionRunning,
383 : "IsCustomDefinitionRunning flag should be initially false");
384 0 : mRegistry->mIsCustomDefinitionRunning = true;
385 0 : }
386 :
387 0 : ~AutoSetRunningFlag() {
388 0 : mRegistry->mIsCustomDefinitionRunning = false;
389 0 : }
390 :
391 : private:
392 : CustomElementRegistry* mRegistry;
393 : };
394 :
395 : public:
396 : nsISupports* GetParentObject() const;
397 :
398 : virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
399 :
400 : void Define(const nsAString& aName, Function& aFunctionConstructor,
401 : const ElementDefinitionOptions& aOptions, ErrorResult& aRv);
402 :
403 : void Get(JSContext* cx, const nsAString& name,
404 : JS::MutableHandle<JS::Value> aRetVal);
405 :
406 : already_AddRefed<Promise> WhenDefined(const nsAString& aName, ErrorResult& aRv);
407 : };
408 :
409 : class MOZ_RAII AutoCEReaction final {
410 : public:
411 602 : explicit AutoCEReaction(CustomElementReactionsStack* aReactionsStack)
412 602 : : mReactionsStack(aReactionsStack) {
413 602 : mReactionsStack->CreateAndPushElementQueue();
414 602 : }
415 1204 : ~AutoCEReaction() {
416 602 : mReactionsStack->PopAndInvokeElementQueue();
417 602 : }
418 : private:
419 : RefPtr<CustomElementReactionsStack> mReactionsStack;
420 : };
421 :
422 : } // namespace dom
423 : } // namespace mozilla
424 :
425 :
426 : #endif // mozilla_dom_CustomElementRegistry_h
|