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 : #include "mozilla/dom/CustomElementRegistry.h"
8 :
9 : #include "mozilla/dom/CustomElementRegistryBinding.h"
10 : #include "mozilla/dom/HTMLElementBinding.h"
11 : #include "mozilla/dom/WebComponentsBinding.h"
12 : #include "mozilla/dom/DocGroup.h"
13 : #include "nsIParserService.h"
14 : #include "jsapi.h"
15 :
16 : namespace mozilla {
17 : namespace dom {
18 :
19 : void
20 0 : CustomElementCallback::Call()
21 : {
22 0 : ErrorResult rv;
23 0 : switch (mType) {
24 : case nsIDocument::eCreated:
25 : {
26 : // For the duration of this callback invocation, the element is being created
27 : // flag must be set to true.
28 0 : mOwnerData->mElementIsBeingCreated = true;
29 :
30 : // The callback hasn't actually been invoked yet, but we need to flip
31 : // this now in order to enqueue the attached callback. This is a spec
32 : // bug (w3c bug 27437).
33 0 : mOwnerData->mCreatedCallbackInvoked = true;
34 :
35 : // If ELEMENT is in a document and this document has a browsing context,
36 : // enqueue attached callback for ELEMENT.
37 0 : nsIDocument* document = mThisObject->GetComposedDoc();
38 0 : if (document && document->GetDocShell()) {
39 0 : nsContentUtils::EnqueueLifecycleCallback(
40 0 : document, nsIDocument::eAttached, mThisObject);
41 : }
42 :
43 0 : static_cast<LifecycleCreatedCallback *>(mCallback.get())->Call(mThisObject, rv);
44 0 : mOwnerData->mElementIsBeingCreated = false;
45 0 : break;
46 : }
47 : case nsIDocument::eAttached:
48 0 : static_cast<LifecycleAttachedCallback *>(mCallback.get())->Call(mThisObject, rv);
49 0 : break;
50 : case nsIDocument::eDetached:
51 0 : static_cast<LifecycleDetachedCallback *>(mCallback.get())->Call(mThisObject, rv);
52 0 : break;
53 : case nsIDocument::eAttributeChanged:
54 0 : static_cast<LifecycleAttributeChangedCallback *>(mCallback.get())->Call(mThisObject,
55 0 : mArgs.name, mArgs.oldValue, mArgs.newValue, rv);
56 0 : break;
57 : }
58 0 : }
59 :
60 : void
61 0 : CustomElementCallback::Traverse(nsCycleCollectionTraversalCallback& aCb) const
62 : {
63 0 : NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mThisObject");
64 0 : aCb.NoteXPCOMChild(mThisObject);
65 :
66 0 : NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mCallback");
67 0 : aCb.NoteXPCOMChild(mCallback);
68 0 : }
69 :
70 0 : CustomElementCallback::CustomElementCallback(Element* aThisObject,
71 : nsIDocument::ElementCallbackType aCallbackType,
72 : mozilla::dom::CallbackFunction* aCallback,
73 0 : CustomElementData* aOwnerData)
74 : : mThisObject(aThisObject),
75 : mCallback(aCallback),
76 : mType(aCallbackType),
77 0 : mOwnerData(aOwnerData)
78 : {
79 0 : }
80 :
81 : //-----------------------------------------------------
82 : // CustomElementData
83 :
84 0 : CustomElementData::CustomElementData(nsIAtom* aType)
85 0 : : CustomElementData(aType, CustomElementData::State::eUndefined)
86 : {
87 0 : }
88 :
89 0 : CustomElementData::CustomElementData(nsIAtom* aType, State aState)
90 : : mType(aType)
91 : , mCurrentCallback(-1)
92 : , mElementIsBeingCreated(false)
93 : , mCreatedCallbackInvoked(true)
94 : , mAssociatedMicroTask(-1)
95 0 : , mState(aState)
96 : {
97 0 : }
98 :
99 : void
100 0 : CustomElementData::RunCallbackQueue()
101 : {
102 : // Note: It's possible to re-enter this method.
103 0 : while (static_cast<uint32_t>(++mCurrentCallback) < mCallbackQueue.Length()) {
104 0 : mCallbackQueue[mCurrentCallback]->Call();
105 : }
106 :
107 0 : mCallbackQueue.Clear();
108 0 : mCurrentCallback = -1;
109 0 : }
110 :
111 : //-----------------------------------------------------
112 : // CustomElementRegistry
113 :
114 : // Only needed for refcounted objects.
115 : NS_IMPL_CYCLE_COLLECTION_CLASS(CustomElementRegistry)
116 :
117 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CustomElementRegistry)
118 0 : tmp->mCustomDefinitions.Clear();
119 0 : tmp->mConstructors.clear();
120 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mWhenDefinedPromiseMap)
121 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
122 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
123 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
124 :
125 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CustomElementRegistry)
126 0 : for (auto iter = tmp->mCustomDefinitions.Iter(); !iter.Done(); iter.Next()) {
127 0 : nsAutoPtr<LifecycleCallbacks>& callbacks = iter.UserData()->mCallbacks;
128 :
129 0 : if (callbacks->mAttributeChangedCallback.WasPassed()) {
130 : NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
131 0 : "mCustomDefinitions->mCallbacks->mAttributeChangedCallback");
132 0 : cb.NoteXPCOMChild(callbacks->mAttributeChangedCallback.Value());
133 : }
134 :
135 0 : if (callbacks->mCreatedCallback.WasPassed()) {
136 : NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
137 0 : "mCustomDefinitions->mCallbacks->mCreatedCallback");
138 0 : cb.NoteXPCOMChild(callbacks->mCreatedCallback.Value());
139 : }
140 :
141 0 : if (callbacks->mAttachedCallback.WasPassed()) {
142 : NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
143 0 : "mCustomDefinitions->mCallbacks->mAttachedCallback");
144 0 : cb.NoteXPCOMChild(callbacks->mAttachedCallback.Value());
145 : }
146 :
147 0 : if (callbacks->mDetachedCallback.WasPassed()) {
148 : NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
149 0 : "mCustomDefinitions->mCallbacks->mDetachedCallback");
150 0 : cb.NoteXPCOMChild(callbacks->mDetachedCallback.Value());
151 : }
152 : }
153 :
154 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWhenDefinedPromiseMap)
155 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
156 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
157 :
158 0 : NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CustomElementRegistry)
159 0 : for (auto iter = tmp->mCustomDefinitions.Iter(); !iter.Done(); iter.Next()) {
160 0 : aCallbacks.Trace(&iter.UserData()->mConstructor,
161 : "mCustomDefinitions constructor",
162 0 : aClosure);
163 0 : aCallbacks.Trace(&iter.UserData()->mPrototype,
164 : "mCustomDefinitions prototype",
165 0 : aClosure);
166 : }
167 :
168 0 : for (ConstructorMap::Enum iter(tmp->mConstructors); !iter.empty(); iter.popFront()) {
169 0 : aCallbacks.Trace(&iter.front().mutableKey(),
170 : "mConstructors key",
171 0 : aClosure);
172 : }
173 0 : NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
174 0 : NS_IMPL_CYCLE_COLLECTION_TRACE_END
175 :
176 0 : NS_IMPL_CYCLE_COLLECTING_ADDREF(CustomElementRegistry)
177 0 : NS_IMPL_CYCLE_COLLECTING_RELEASE(CustomElementRegistry)
178 :
179 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CustomElementRegistry)
180 0 : NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
181 0 : NS_INTERFACE_MAP_ENTRY(nsISupports)
182 0 : NS_INTERFACE_MAP_END
183 :
184 : /* static */ bool
185 169 : CustomElementRegistry::IsCustomElementEnabled(JSContext* aCx, JSObject* aObject)
186 : {
187 338 : return nsContentUtils::IsCustomElementsEnabled() ||
188 338 : nsContentUtils::IsWebComponentsEnabled();
189 : }
190 :
191 : /* static */ void
192 0 : CustomElementRegistry::ProcessTopElementQueue()
193 : {
194 0 : MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
195 :
196 0 : nsTArray<RefPtr<CustomElementData>>& stack = *sProcessingStack;
197 0 : uint32_t firstQueue = stack.LastIndexOf((CustomElementData*) nullptr);
198 :
199 0 : for (uint32_t i = firstQueue + 1; i < stack.Length(); ++i) {
200 : // Callback queue may have already been processed in an earlier
201 : // element queue or in an element queue that was popped
202 : // off more recently.
203 0 : if (stack[i]->mAssociatedMicroTask != -1) {
204 0 : stack[i]->RunCallbackQueue();
205 0 : stack[i]->mAssociatedMicroTask = -1;
206 : }
207 : }
208 :
209 : // If this was actually the base element queue, don't bother trying to pop
210 : // the first "queue" marker (sentinel).
211 0 : if (firstQueue != 0) {
212 0 : stack.SetLength(firstQueue);
213 : } else {
214 : // Don't pop sentinel for base element queue.
215 0 : stack.SetLength(1);
216 : }
217 0 : }
218 :
219 : /* static */ void
220 0 : CustomElementRegistry::XPCOMShutdown()
221 : {
222 0 : sProcessingStack.reset();
223 0 : }
224 :
225 : /* static */ Maybe<nsTArray<RefPtr<CustomElementData>>>
226 3 : CustomElementRegistry::sProcessingStack;
227 :
228 0 : CustomElementRegistry::CustomElementRegistry(nsPIDOMWindowInner* aWindow)
229 : : mWindow(aWindow)
230 0 : , mIsCustomDefinitionRunning(false)
231 : {
232 0 : MOZ_ASSERT(aWindow);
233 0 : MOZ_ASSERT(aWindow->IsInnerWindow());
234 0 : MOZ_ALWAYS_TRUE(mConstructors.init());
235 :
236 0 : mozilla::HoldJSObjects(this);
237 :
238 0 : if (!sProcessingStack) {
239 0 : sProcessingStack.emplace();
240 : // Add the base queue sentinel to the processing stack.
241 0 : sProcessingStack->AppendElement((CustomElementData*) nullptr);
242 : }
243 0 : }
244 :
245 0 : CustomElementRegistry::~CustomElementRegistry()
246 : {
247 0 : mozilla::DropJSObjects(this);
248 0 : }
249 :
250 : CustomElementDefinition*
251 0 : CustomElementRegistry::LookupCustomElementDefinition(const nsAString& aLocalName,
252 : const nsAString* aIs) const
253 : {
254 0 : nsCOMPtr<nsIAtom> localNameAtom = NS_Atomize(aLocalName);
255 0 : nsCOMPtr<nsIAtom> typeAtom = aIs ? NS_Atomize(*aIs) : localNameAtom;
256 :
257 0 : CustomElementDefinition* data = mCustomDefinitions.Get(typeAtom);
258 0 : if (data && data->mLocalName == localNameAtom) {
259 0 : return data;
260 : }
261 :
262 0 : return nullptr;
263 : }
264 :
265 : CustomElementDefinition*
266 0 : CustomElementRegistry::LookupCustomElementDefinition(JSContext* aCx,
267 : JSObject* aConstructor) const
268 : {
269 0 : JS::Rooted<JSObject*> constructor(aCx, js::CheckedUnwrap(aConstructor));
270 :
271 0 : const auto& ptr = mConstructors.lookup(constructor);
272 0 : if (!ptr) {
273 0 : return nullptr;
274 : }
275 :
276 0 : CustomElementDefinition* definition = mCustomDefinitions.Get(ptr->value());
277 0 : MOZ_ASSERT(definition, "Definition must be found in mCustomDefinitions");
278 :
279 0 : return definition;
280 : }
281 :
282 : void
283 0 : CustomElementRegistry::RegisterUnresolvedElement(Element* aElement, nsIAtom* aTypeName)
284 : {
285 0 : mozilla::dom::NodeInfo* info = aElement->NodeInfo();
286 :
287 : // Candidate may be a custom element through extension,
288 : // in which case the custom element type name will not
289 : // match the element tag name. e.g. <button is="x-button">.
290 0 : nsCOMPtr<nsIAtom> typeName = aTypeName;
291 0 : if (!typeName) {
292 0 : typeName = info->NameAtom();
293 : }
294 :
295 0 : if (mCustomDefinitions.Get(typeName)) {
296 0 : return;
297 : }
298 :
299 0 : nsTArray<nsWeakPtr>* unresolved = mCandidatesMap.LookupOrAdd(typeName);
300 0 : nsWeakPtr* elem = unresolved->AppendElement();
301 0 : *elem = do_GetWeakReference(aElement);
302 0 : aElement->AddStates(NS_EVENT_STATE_UNRESOLVED);
303 :
304 0 : return;
305 : }
306 :
307 : void
308 0 : CustomElementRegistry::SetupCustomElement(Element* aElement,
309 : const nsAString* aTypeExtension)
310 : {
311 0 : nsCOMPtr<nsIAtom> tagAtom = aElement->NodeInfo()->NameAtom();
312 : nsCOMPtr<nsIAtom> typeAtom = aTypeExtension ?
313 0 : NS_Atomize(*aTypeExtension) : tagAtom;
314 :
315 0 : if (aTypeExtension && !aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::is)) {
316 : // Custom element setup in the parser happens after the "is"
317 : // attribute is added.
318 0 : aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::is, *aTypeExtension, true);
319 : }
320 :
321 : // SetupCustomElement() should be called with an element that don't have
322 : // CustomElementData setup, if not we will hit the assertion in
323 : // SetCustomElementData().
324 0 : aElement->SetCustomElementData(new CustomElementData(typeAtom));
325 :
326 0 : CustomElementDefinition* definition = LookupCustomElementDefinition(
327 0 : aElement->NodeInfo()->LocalName(), aTypeExtension);
328 :
329 0 : if (!definition) {
330 : // The type extension doesn't exist in the registry,
331 : // thus we don't need to enqueue callback or adjust
332 : // the "is" attribute, but it is possibly an upgrade candidate.
333 0 : RegisterUnresolvedElement(aElement, typeAtom);
334 0 : return;
335 : }
336 :
337 0 : if (definition->mLocalName != tagAtom) {
338 : // The element doesn't match the local name for the
339 : // definition, thus the element isn't a custom element
340 : // and we don't need to do anything more.
341 0 : return;
342 : }
343 :
344 : // Enqueuing the created callback will set the CustomElementData on the
345 : // element, causing prototype swizzling to occur in Element::WrapObject.
346 0 : EnqueueLifecycleCallback(nsIDocument::eCreated, aElement, nullptr, definition);
347 : }
348 :
349 : void
350 0 : CustomElementRegistry::EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType,
351 : Element* aCustomElement,
352 : LifecycleCallbackArgs* aArgs,
353 : CustomElementDefinition* aDefinition)
354 : {
355 0 : RefPtr<CustomElementData> elementData = aCustomElement->GetCustomElementData();
356 0 : MOZ_ASSERT(elementData, "CustomElementData should exist");
357 :
358 : // Let DEFINITION be ELEMENT's definition
359 0 : CustomElementDefinition* definition = aDefinition;
360 0 : if (!definition) {
361 0 : mozilla::dom::NodeInfo* info = aCustomElement->NodeInfo();
362 :
363 : // Make sure we get the correct definition in case the element
364 : // is a extended custom element e.g. <button is="x-button">.
365 : nsCOMPtr<nsIAtom> typeAtom = elementData ?
366 0 : elementData->mType.get() : info->NameAtom();
367 :
368 0 : definition = mCustomDefinitions.Get(typeAtom);
369 0 : if (!definition || definition->mLocalName != info->NameAtom()) {
370 : // Trying to enqueue a callback for an element that is not
371 : // a custom element. We are done, nothing to do.
372 0 : return;
373 : }
374 : }
375 :
376 : // Let CALLBACK be the callback associated with the key NAME in CALLBACKS.
377 0 : CallbackFunction* func = nullptr;
378 0 : switch (aType) {
379 : case nsIDocument::eCreated:
380 0 : if (definition->mCallbacks->mCreatedCallback.WasPassed()) {
381 0 : func = definition->mCallbacks->mCreatedCallback.Value();
382 : }
383 0 : break;
384 :
385 : case nsIDocument::eAttached:
386 0 : if (definition->mCallbacks->mAttachedCallback.WasPassed()) {
387 0 : func = definition->mCallbacks->mAttachedCallback.Value();
388 : }
389 0 : break;
390 :
391 : case nsIDocument::eDetached:
392 0 : if (definition->mCallbacks->mDetachedCallback.WasPassed()) {
393 0 : func = definition->mCallbacks->mDetachedCallback.Value();
394 : }
395 0 : break;
396 :
397 : case nsIDocument::eAttributeChanged:
398 0 : if (definition->mCallbacks->mAttributeChangedCallback.WasPassed()) {
399 0 : func = definition->mCallbacks->mAttributeChangedCallback.Value();
400 : }
401 0 : break;
402 : }
403 :
404 : // If there is no such callback, stop.
405 0 : if (!func) {
406 0 : return;
407 : }
408 :
409 0 : if (aType == nsIDocument::eCreated) {
410 0 : elementData->mCreatedCallbackInvoked = false;
411 0 : } else if (!elementData->mCreatedCallbackInvoked) {
412 : // Callbacks other than created callback must not be enqueued
413 : // until after the created callback has been invoked.
414 0 : return;
415 : }
416 :
417 : // Add CALLBACK to ELEMENT's callback queue.
418 : CustomElementCallback* callback = new CustomElementCallback(aCustomElement,
419 : aType,
420 : func,
421 0 : elementData);
422 : // Ownership of callback is taken by mCallbackQueue.
423 0 : elementData->mCallbackQueue.AppendElement(callback);
424 0 : if (aArgs) {
425 0 : callback->SetArgs(*aArgs);
426 : }
427 :
428 0 : if (!elementData->mElementIsBeingCreated) {
429 : CustomElementData* lastData =
430 0 : sProcessingStack->SafeLastElement(nullptr);
431 :
432 : // A new element queue needs to be pushed if the queue at the
433 : // top of the stack is associated with another microtask level.
434 : bool shouldPushElementQueue =
435 0 : (!lastData || lastData->mAssociatedMicroTask <
436 0 : static_cast<int32_t>(nsContentUtils::MicroTaskLevel()));
437 :
438 : // Push a new element queue onto the processing stack when appropriate
439 : // (when we enter a new microtask).
440 0 : if (shouldPushElementQueue) {
441 : // Push a sentinel value on the processing stack to mark the
442 : // boundary between the element queues.
443 0 : sProcessingStack->AppendElement((CustomElementData*) nullptr);
444 : }
445 :
446 0 : sProcessingStack->AppendElement(elementData);
447 0 : elementData->mAssociatedMicroTask =
448 0 : static_cast<int32_t>(nsContentUtils::MicroTaskLevel());
449 :
450 : // Add a script runner to pop and process the element queue at
451 : // the top of the processing stack.
452 0 : if (shouldPushElementQueue) {
453 : // Lifecycle callbacks enqueued by user agent implementation
454 : // should be invoked prior to returning control back to script.
455 : // Create a script runner to process the top of the processing
456 : // stack as soon as it is safe to run script.
457 0 : nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
458 : "dom::CustomElementRegistry::EnqueueLifecycleCallback",
459 0 : &CustomElementRegistry::ProcessTopElementQueue);
460 0 : nsContentUtils::AddScriptRunner(runnable);
461 : }
462 : }
463 : }
464 :
465 : void
466 0 : CustomElementRegistry::GetCustomPrototype(nsIAtom* aAtom,
467 : JS::MutableHandle<JSObject*> aPrototype)
468 : {
469 0 : mozilla::dom::CustomElementDefinition* definition = mCustomDefinitions.Get(aAtom);
470 0 : if (definition) {
471 0 : aPrototype.set(definition->mPrototype);
472 : } else {
473 0 : aPrototype.set(nullptr);
474 : }
475 0 : }
476 :
477 : void
478 0 : CustomElementRegistry::UpgradeCandidates(JSContext* aCx,
479 : nsIAtom* aKey,
480 : CustomElementDefinition* aDefinition,
481 : ErrorResult& aRv)
482 : {
483 0 : DocGroup* docGroup = mWindow->GetDocGroup();
484 0 : if (!docGroup) {
485 0 : aRv.Throw(NS_ERROR_UNEXPECTED);
486 0 : return;
487 : }
488 :
489 0 : nsAutoPtr<nsTArray<nsWeakPtr>> candidates;
490 0 : if (mCandidatesMap.Remove(aKey, &candidates)) {
491 0 : MOZ_ASSERT(candidates);
492 : CustomElementReactionsStack* reactionsStack =
493 0 : docGroup->CustomElementReactionsStack();
494 0 : for (size_t i = 0; i < candidates->Length(); ++i) {
495 0 : nsCOMPtr<Element> elem = do_QueryReferent(candidates->ElementAt(i));
496 0 : if (!elem) {
497 0 : continue;
498 : }
499 :
500 0 : reactionsStack->EnqueueUpgradeReaction(this, elem, aDefinition);
501 : }
502 : }
503 : }
504 :
505 : JSObject*
506 0 : CustomElementRegistry::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
507 : {
508 0 : return CustomElementRegistryBinding::Wrap(aCx, this, aGivenProto);
509 : }
510 :
511 0 : nsISupports* CustomElementRegistry::GetParentObject() const
512 : {
513 0 : return mWindow;
514 : }
515 :
516 : static const char* kLifeCycleCallbackNames[] = {
517 : "connectedCallback",
518 : "disconnectedCallback",
519 : "adoptedCallback",
520 : "attributeChangedCallback",
521 : // The life cycle callbacks from v0 spec.
522 : "createdCallback",
523 : "attachedCallback",
524 : "detachedCallback"
525 : };
526 :
527 : static void
528 0 : CheckLifeCycleCallbacks(JSContext* aCx,
529 : JS::Handle<JSObject*> aConstructor,
530 : ErrorResult& aRv)
531 : {
532 0 : for (size_t i = 0; i < ArrayLength(kLifeCycleCallbackNames); ++i) {
533 0 : const char* callbackName = kLifeCycleCallbackNames[i];
534 0 : JS::Rooted<JS::Value> callbackValue(aCx);
535 0 : if (!JS_GetProperty(aCx, aConstructor, callbackName, &callbackValue)) {
536 0 : aRv.StealExceptionFromJSContext(aCx);
537 0 : return;
538 : }
539 0 : if (!callbackValue.isUndefined()) {
540 0 : if (!callbackValue.isObject()) {
541 0 : aRv.ThrowTypeError<MSG_NOT_OBJECT>(NS_ConvertASCIItoUTF16(callbackName));
542 0 : return;
543 : }
544 0 : JS::Rooted<JSObject*> callback(aCx, &callbackValue.toObject());
545 0 : if (!JS::IsCallable(callback)) {
546 0 : aRv.ThrowTypeError<MSG_NOT_CALLABLE>(NS_ConvertASCIItoUTF16(callbackName));
547 0 : return;
548 : }
549 : }
550 : }
551 : }
552 :
553 : // https://html.spec.whatwg.org/multipage/scripting.html#element-definition
554 : void
555 0 : CustomElementRegistry::Define(const nsAString& aName,
556 : Function& aFunctionConstructor,
557 : const ElementDefinitionOptions& aOptions,
558 : ErrorResult& aRv)
559 : {
560 0 : aRv.MightThrowJSException();
561 :
562 0 : AutoJSAPI jsapi;
563 0 : if (NS_WARN_IF(!jsapi.Init(mWindow))) {
564 0 : aRv.Throw(NS_ERROR_FAILURE);
565 0 : return;
566 : }
567 :
568 0 : JSContext *cx = jsapi.cx();
569 0 : JS::Rooted<JSObject*> constructor(cx, aFunctionConstructor.CallableOrNull());
570 :
571 : /**
572 : * 1. If IsConstructor(constructor) is false, then throw a TypeError and abort
573 : * these steps.
574 : */
575 : // For now, all wrappers are constructable if they are callable. So we need to
576 : // unwrap constructor to check it is really constructable.
577 0 : JS::Rooted<JSObject*> constructorUnwrapped(cx, js::CheckedUnwrap(constructor));
578 0 : if (!constructorUnwrapped) {
579 : // If the caller's compartment does not have permission to access the
580 : // unwrapped constructor then throw.
581 0 : aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
582 0 : return;
583 : }
584 :
585 0 : if (!JS::IsConstructor(constructorUnwrapped)) {
586 0 : aRv.ThrowTypeError<MSG_NOT_CONSTRUCTOR>(NS_LITERAL_STRING("Argument 2 of CustomElementRegistry.define"));
587 0 : return;
588 : }
589 :
590 : /**
591 : * 2. If name is not a valid custom element name, then throw a "SyntaxError"
592 : * DOMException and abort these steps.
593 : */
594 0 : nsCOMPtr<nsIAtom> nameAtom(NS_Atomize(aName));
595 0 : if (!nsContentUtils::IsCustomElementName(nameAtom)) {
596 0 : aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
597 0 : return;
598 : }
599 :
600 : /**
601 : * 3. If this CustomElementRegistry contains an entry with name name, then
602 : * throw a "NotSupportedError" DOMException and abort these steps.
603 : */
604 0 : if (mCustomDefinitions.Get(nameAtom)) {
605 0 : aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
606 0 : return;
607 : }
608 :
609 : /**
610 : * 4. If this CustomElementRegistry contains an entry with constructor constructor,
611 : * then throw a "NotSupportedError" DOMException and abort these steps.
612 : */
613 0 : const auto& ptr = mConstructors.lookup(constructorUnwrapped);
614 0 : if (ptr) {
615 0 : MOZ_ASSERT(mCustomDefinitions.Get(ptr->value()),
616 : "Definition must be found in mCustomDefinitions");
617 0 : aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
618 0 : return;
619 : }
620 :
621 : /**
622 : * 5. Let localName be name.
623 : * 6. Let extends be the value of the extends member of options, or null if
624 : * no such member exists.
625 : * 7. If extends is not null, then:
626 : * 1. If extends is a valid custom element name, then throw a
627 : * "NotSupportedError" DOMException.
628 : * 2. If the element interface for extends and the HTML namespace is
629 : * HTMLUnknownElement (e.g., if extends does not indicate an element
630 : * definition in this specification), then throw a "NotSupportedError"
631 : * DOMException.
632 : * 3. Set localName to extends.
633 : */
634 0 : nsAutoString localName(aName);
635 0 : if (aOptions.mExtends.WasPassed()) {
636 0 : nsCOMPtr<nsIAtom> extendsAtom(NS_Atomize(aOptions.mExtends.Value()));
637 0 : if (nsContentUtils::IsCustomElementName(extendsAtom)) {
638 0 : aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
639 0 : return;
640 : }
641 :
642 0 : nsIParserService* ps = nsContentUtils::GetParserService();
643 0 : if (!ps) {
644 0 : aRv.Throw(NS_ERROR_UNEXPECTED);
645 0 : return;
646 : }
647 :
648 : // bgsound and multicol are unknown html element.
649 0 : int32_t tag = ps->HTMLCaseSensitiveAtomTagToId(extendsAtom);
650 0 : if (tag == eHTMLTag_userdefined ||
651 0 : tag == eHTMLTag_bgsound ||
652 : tag == eHTMLTag_multicol) {
653 0 : aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
654 0 : return;
655 : }
656 :
657 0 : localName.Assign(aOptions.mExtends.Value());
658 : }
659 :
660 : /**
661 : * 8. If this CustomElementRegistry's element definition is running flag is set,
662 : * then throw a "NotSupportedError" DOMException and abort these steps.
663 : */
664 0 : if (mIsCustomDefinitionRunning) {
665 0 : aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
666 0 : return;
667 : }
668 :
669 0 : JS::Rooted<JSObject*> constructorPrototype(cx);
670 0 : nsAutoPtr<LifecycleCallbacks> callbacksHolder(new LifecycleCallbacks());
671 : { // Set mIsCustomDefinitionRunning.
672 : /**
673 : * 9. Set this CustomElementRegistry's element definition is running flag.
674 : */
675 0 : AutoSetRunningFlag as(this);
676 :
677 : { // Enter constructor's compartment.
678 : /**
679 : * 10.1. Let prototype be Get(constructor, "prototype"). Rethrow any exceptions.
680 : */
681 0 : JSAutoCompartment ac(cx, constructor);
682 0 : JS::Rooted<JS::Value> prototypev(cx);
683 : // The .prototype on the constructor passed from document.registerElement
684 : // is the "expando" of a wrapper. So we should get it from wrapper instead
685 : // instead of underlying object.
686 0 : if (!JS_GetProperty(cx, constructor, "prototype", &prototypev)) {
687 0 : aRv.StealExceptionFromJSContext(cx);
688 0 : return;
689 : }
690 :
691 : /**
692 : * 10.2. If Type(prototype) is not Object, then throw a TypeError exception.
693 : */
694 0 : if (!prototypev.isObject()) {
695 0 : aRv.ThrowTypeError<MSG_NOT_OBJECT>(NS_LITERAL_STRING("constructor.prototype"));
696 0 : return;
697 : }
698 :
699 0 : constructorPrototype = &prototypev.toObject();
700 : } // Leave constructor's compartment.
701 :
702 0 : JS::Rooted<JSObject*> constructorProtoUnwrapped(cx, js::CheckedUnwrap(constructorPrototype));
703 0 : if (!constructorProtoUnwrapped) {
704 : // If the caller's compartment does not have permission to access the
705 : // unwrapped prototype then throw.
706 0 : aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
707 0 : return;
708 : }
709 :
710 : { // Enter constructorProtoUnwrapped's compartment
711 0 : JSAutoCompartment ac(cx, constructorProtoUnwrapped);
712 :
713 : /**
714 : * 10.3. Let lifecycleCallbacks be a map with the four keys
715 : * "connectedCallback", "disconnectedCallback", "adoptedCallback", and
716 : * "attributeChangedCallback", each of which belongs to an entry whose
717 : * value is null.
718 : * 10.4. For each of the four keys callbackName in lifecycleCallbacks:
719 : * 1. Let callbackValue be Get(prototype, callbackName). Rethrow any
720 : * exceptions.
721 : * 2. If callbackValue is not undefined, then set the value of the
722 : * entry in lifecycleCallbacks with key callbackName to the result
723 : * of converting callbackValue to the Web IDL Function callback type.
724 : * Rethrow any exceptions from the conversion.
725 : */
726 : // Will do the same checking for the life cycle callbacks from v0 spec.
727 0 : CheckLifeCycleCallbacks(cx, constructorProtoUnwrapped, aRv);
728 0 : if (aRv.Failed()) {
729 0 : return;
730 : }
731 :
732 : /**
733 : * 10.5. Let observedAttributes be an empty sequence<DOMString>.
734 : * 10.6. If the value of the entry in lifecycleCallbacks with key
735 : * "attributeChangedCallback" is not null, then:
736 : * 1. Let observedAttributesIterable be Get(constructor,
737 : * "observedAttributes"). Rethrow any exceptions.
738 : * 2. If observedAttributesIterable is not undefined, then set
739 : * observedAttributes to the result of converting
740 : * observedAttributesIterable to a sequence<DOMString>. Rethrow
741 : * any exceptions from the conversion.
742 : */
743 : // TODO: Bug 1293921 - Implement connected/disconnected/adopted/attributeChanged lifecycle callbacks for custom elements
744 :
745 : // Note: We call the init from the constructorProtoUnwrapped's compartment
746 : // here.
747 0 : JS::RootedValue rootedv(cx, JS::ObjectValue(*constructorProtoUnwrapped));
748 0 : if (!JS_WrapValue(cx, &rootedv) || !callbacksHolder->Init(cx, rootedv)) {
749 0 : aRv.StealExceptionFromJSContext(cx);
750 0 : return;
751 : }
752 : } // Leave constructorProtoUnwrapped's compartment.
753 : } // Unset mIsCustomDefinitionRunning
754 :
755 : /**
756 : * 11. Let definition be a new custom element definition with name name,
757 : * local name localName, constructor constructor, prototype prototype,
758 : * observed attributes observedAttributes, and lifecycle callbacks
759 : * lifecycleCallbacks.
760 : */
761 : // Associate the definition with the custom element.
762 0 : nsCOMPtr<nsIAtom> localNameAtom(NS_Atomize(localName));
763 0 : LifecycleCallbacks* callbacks = callbacksHolder.forget();
764 :
765 : /**
766 : * 12. Add definition to this CustomElementRegistry.
767 : */
768 0 : if (!mConstructors.put(constructorUnwrapped, nameAtom)) {
769 0 : aRv.Throw(NS_ERROR_FAILURE);
770 0 : return;
771 : }
772 :
773 : CustomElementDefinition* definition =
774 : new CustomElementDefinition(nameAtom,
775 : localNameAtom,
776 : constructor,
777 : constructorPrototype,
778 : callbacks,
779 0 : 0 /* TODO dependent on HTML imports. Bug 877072 */);
780 :
781 0 : mCustomDefinitions.Put(nameAtom, definition);
782 :
783 0 : MOZ_ASSERT(mCustomDefinitions.Count() == mConstructors.count(),
784 : "Number of entries should be the same");
785 :
786 : /**
787 : * 13. 14. 15. Upgrade candidates
788 : */
789 : // TODO: Bug 1299363 - Implement custom element v1 upgrade algorithm
790 0 : UpgradeCandidates(cx, nameAtom, definition, aRv);
791 :
792 : /**
793 : * 16. If this CustomElementRegistry's when-defined promise map contains an
794 : * entry with key name:
795 : * 1. Let promise be the value of that entry.
796 : * 2. Resolve promise with undefined.
797 : * 3. Delete the entry with key name from this CustomElementRegistry's
798 : * when-defined promise map.
799 : */
800 0 : RefPtr<Promise> promise;
801 0 : mWhenDefinedPromiseMap.Remove(nameAtom, getter_AddRefs(promise));
802 0 : if (promise) {
803 0 : promise->MaybeResolveWithUndefined();
804 : }
805 :
806 : }
807 :
808 : void
809 0 : CustomElementRegistry::Get(JSContext* aCx, const nsAString& aName,
810 : JS::MutableHandle<JS::Value> aRetVal)
811 : {
812 0 : nsCOMPtr<nsIAtom> nameAtom(NS_Atomize(aName));
813 0 : CustomElementDefinition* data = mCustomDefinitions.Get(nameAtom);
814 :
815 0 : if (!data) {
816 0 : aRetVal.setUndefined();
817 0 : return;
818 : }
819 :
820 0 : aRetVal.setObject(*data->mConstructor);
821 0 : return;
822 : }
823 :
824 : already_AddRefed<Promise>
825 0 : CustomElementRegistry::WhenDefined(const nsAString& aName, ErrorResult& aRv)
826 : {
827 0 : nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
828 0 : RefPtr<Promise> promise = Promise::Create(global, aRv);
829 :
830 0 : if (aRv.Failed()) {
831 0 : return nullptr;
832 : }
833 :
834 0 : nsCOMPtr<nsIAtom> nameAtom(NS_Atomize(aName));
835 0 : if (!nsContentUtils::IsCustomElementName(nameAtom)) {
836 0 : promise->MaybeReject(NS_ERROR_DOM_SYNTAX_ERR);
837 0 : return promise.forget();
838 : }
839 :
840 0 : if (mCustomDefinitions.Get(nameAtom)) {
841 0 : promise->MaybeResolve(JS::UndefinedHandleValue);
842 0 : return promise.forget();
843 : }
844 :
845 0 : auto entry = mWhenDefinedPromiseMap.LookupForAdd(nameAtom);
846 0 : if (entry) {
847 0 : promise = entry.Data();
848 : } else {
849 0 : entry.OrInsert([&promise](){ return promise; });
850 : }
851 :
852 0 : return promise.forget();
853 : }
854 :
855 : void
856 0 : CustomElementRegistry::Upgrade(Element* aElement,
857 : CustomElementDefinition* aDefinition)
858 : {
859 : // TODO: This function will be replaced to v1 upgrade in bug 1299363
860 0 : aElement->RemoveStates(NS_EVENT_STATE_UNRESOLVED);
861 :
862 : // Make sure that the element name matches the name in the definition.
863 : // (e.g. a definition for x-button extending button should match
864 : // <button is="x-button"> but not <x-button>.
865 0 : if (aElement->NodeInfo()->NameAtom() != aDefinition->mLocalName) {
866 : //Skip over this element because definition does not apply.
867 0 : return;
868 : }
869 :
870 0 : MOZ_ASSERT(aElement->IsHTMLElement(aDefinition->mLocalName));
871 :
872 0 : AutoJSAPI jsapi;
873 0 : if (NS_WARN_IF(!jsapi.Init(mWindow))) {
874 0 : return;
875 : }
876 :
877 0 : JSContext* cx = jsapi.cx();
878 :
879 0 : JS::Rooted<JSObject*> reflector(cx, aElement->GetWrapper());
880 0 : if (reflector) {
881 0 : Maybe<JSAutoCompartment> ac;
882 0 : JS::Rooted<JSObject*> prototype(cx, aDefinition->mPrototype);
883 0 : if (aElement->NodePrincipal()->SubsumesConsideringDomain(nsContentUtils::ObjectPrincipal(prototype))) {
884 0 : ac.emplace(cx, reflector);
885 0 : if (!JS_WrapObject(cx, &prototype) ||
886 0 : !JS_SetPrototype(cx, reflector, prototype)) {
887 0 : return;
888 : }
889 : } else {
890 : // We want to set the custom prototype in the compartment where it was
891 : // registered. We store the prototype from define() without unwrapped,
892 : // hence the prototype's compartment is the compartment where it was
893 : // registered.
894 : // In the case that |reflector| and |prototype| are in different
895 : // compartments, this will set the prototype on the |reflector|'s wrapper
896 : // and thus only visible in the wrapper's compartment, since we know
897 : // reflector's principal does not subsume prototype's in this case.
898 0 : ac.emplace(cx, prototype);
899 0 : if (!JS_WrapObject(cx, &reflector) ||
900 0 : !JS_SetPrototype(cx, reflector, prototype)) {
901 0 : return;
902 : }
903 : }
904 : }
905 :
906 0 : EnqueueLifecycleCallback(nsIDocument::eCreated, aElement, nullptr, aDefinition);
907 : }
908 :
909 : //-----------------------------------------------------
910 : // CustomElementReactionsStack
911 :
912 : void
913 602 : CustomElementReactionsStack::CreateAndPushElementQueue()
914 : {
915 : // Push a new element queue onto the custom element reactions stack.
916 602 : mReactionsStack.AppendElement();
917 602 : }
918 :
919 : void
920 602 : CustomElementReactionsStack::PopAndInvokeElementQueue()
921 : {
922 : // Pop the element queue from the custom element reactions stack,
923 : // and invoke custom element reactions in that queue.
924 602 : MOZ_ASSERT(!mReactionsStack.IsEmpty(),
925 : "Reaction stack shouldn't be empty");
926 :
927 602 : ElementQueue& elementQueue = mReactionsStack.LastElement();
928 : // Check element queue size in order to reduce function call overhead.
929 602 : if (!elementQueue.IsEmpty()) {
930 0 : InvokeReactions(elementQueue);
931 : }
932 :
933 1204 : DebugOnly<bool> isRemovedElement = mReactionsStack.RemoveElement(elementQueue);
934 602 : MOZ_ASSERT(isRemovedElement,
935 : "Reaction stack should have an element queue to remove");
936 602 : }
937 :
938 : void
939 0 : CustomElementReactionsStack::EnqueueUpgradeReaction(CustomElementRegistry* aRegistry,
940 : Element* aElement,
941 : CustomElementDefinition* aDefinition)
942 : {
943 0 : Enqueue(aElement, new CustomElementUpgradeReaction(aRegistry, aDefinition));
944 0 : }
945 :
946 : void
947 0 : CustomElementReactionsStack::Enqueue(Element* aElement,
948 : CustomElementReaction* aReaction)
949 : {
950 0 : RefPtr<CustomElementData> elementData = aElement->GetCustomElementData();
951 0 : MOZ_ASSERT(elementData, "CustomElementData should exist");
952 :
953 : // Add element to the current element queue.
954 0 : if (!mReactionsStack.IsEmpty()) {
955 0 : mReactionsStack.LastElement().AppendElement(do_GetWeakReference(aElement));
956 0 : elementData->mReactionQueue.AppendElement(aReaction);
957 0 : return;
958 : }
959 :
960 : // If the custom element reactions stack is empty, then:
961 : // Add element to the backup element queue.
962 0 : mBackupQueue.AppendElement(do_GetWeakReference(aElement));
963 0 : elementData->mReactionQueue.AppendElement(aReaction);
964 :
965 0 : if (mIsBackupQueueProcessing) {
966 0 : return;
967 : }
968 :
969 0 : CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
970 : RefPtr<ProcessBackupQueueRunnable> processBackupQueueRunnable =
971 0 : new ProcessBackupQueueRunnable(this);
972 0 : context->DispatchToMicroTask(processBackupQueueRunnable.forget());
973 : }
974 :
975 : void
976 0 : CustomElementReactionsStack::InvokeBackupQueue()
977 : {
978 : // Check backup queue size in order to reduce function call overhead.
979 0 : if (!mBackupQueue.IsEmpty()) {
980 0 : InvokeReactions(mBackupQueue);
981 : }
982 0 : }
983 :
984 : void
985 0 : CustomElementReactionsStack::InvokeReactions(ElementQueue& aElementQueue)
986 : {
987 0 : for (uint32_t i = 0; i < aElementQueue.Length(); ++i) {
988 0 : nsCOMPtr<Element> element = do_QueryReferent(aElementQueue[i]);
989 :
990 0 : if (!element) {
991 0 : continue;
992 : }
993 :
994 0 : RefPtr<CustomElementData> elementData = element->GetCustomElementData();
995 0 : MOZ_ASSERT(elementData, "CustomElementData should exist");
996 :
997 : nsTArray<nsAutoPtr<CustomElementReaction>>& reactions =
998 0 : elementData->mReactionQueue;
999 0 : for (uint32_t j = 0; j < reactions.Length(); ++j) {
1000 0 : reactions.ElementAt(j)->Invoke(element);
1001 : }
1002 0 : reactions.Clear();
1003 : }
1004 0 : aElementQueue.Clear();
1005 0 : }
1006 :
1007 : //-----------------------------------------------------
1008 : // CustomElementDefinition
1009 :
1010 0 : CustomElementDefinition::CustomElementDefinition(nsIAtom* aType,
1011 : nsIAtom* aLocalName,
1012 : JSObject* aConstructor,
1013 : JSObject* aPrototype,
1014 : LifecycleCallbacks* aCallbacks,
1015 0 : uint32_t aDocOrder)
1016 : : mType(aType),
1017 : mLocalName(aLocalName),
1018 : mConstructor(aConstructor),
1019 : mPrototype(aPrototype),
1020 : mCallbacks(aCallbacks),
1021 0 : mDocOrder(aDocOrder)
1022 : {
1023 0 : }
1024 :
1025 :
1026 : //-----------------------------------------------------
1027 : // CustomElementUpgradeReaction
1028 :
1029 : /* virtual */ void
1030 0 : CustomElementUpgradeReaction::Invoke(Element* aElement)
1031 : {
1032 0 : mRegistry->Upgrade(aElement, mDefinition);
1033 0 : }
1034 :
1035 : } // namespace dom
1036 : } // namespace mozilla
|