Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/base/CustomElementRegistry.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
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/AsyncEventDispatcher.h"
10
#include "mozilla/CycleCollectedJSContext.h"
11
#include "mozilla/dom/CustomElementRegistryBinding.h"
12
#include "mozilla/dom/HTMLElementBinding.h"
13
#include "mozilla/dom/XULElementBinding.h"
14
#include "mozilla/dom/Promise.h"
15
#include "mozilla/dom/WebComponentsBinding.h"
16
#include "mozilla/dom/DocGroup.h"
17
#include "mozilla/dom/CustomEvent.h"
18
#include "mozilla/dom/ShadowRoot.h"
19
#include "nsHTMLTags.h"
20
#include "jsapi.h"
21
#include "xpcprivate.h"
22
#include "nsGlobalWindow.h"
23
24
namespace mozilla {
25
namespace dom {
26
27
//-----------------------------------------------------
28
// CustomElementUpgradeReaction
29
30
class CustomElementUpgradeReaction final : public CustomElementReaction
31
{
32
public:
33
  explicit CustomElementUpgradeReaction(CustomElementDefinition* aDefinition)
34
    : mDefinition(aDefinition)
35
0
  {
36
0
    mIsUpgradeReaction = true;
37
0
  }
38
39
  virtual void Traverse(nsCycleCollectionTraversalCallback& aCb) const override
40
0
  {
41
0
    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mDefinition");
42
0
    aCb.NoteNativeChild(mDefinition,
43
0
      NS_CYCLE_COLLECTION_PARTICIPANT(CustomElementDefinition));
44
0
  }
45
46
  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
47
0
  {
48
0
    // We don't really own mDefinition.
49
0
    return aMallocSizeOf(this);
50
0
  }
51
52
private:
53
  virtual void Invoke(Element* aElement, ErrorResult& aRv) override
54
0
  {
55
0
    CustomElementRegistry::Upgrade(aElement, mDefinition, aRv);
56
0
  }
57
58
  RefPtr<CustomElementDefinition> mDefinition;
59
};
60
61
//-----------------------------------------------------
62
// CustomElementCallbackReaction
63
64
class CustomElementCallbackReaction final : public CustomElementReaction
65
{
66
  public:
67
    explicit CustomElementCallbackReaction(UniquePtr<CustomElementCallback> aCustomElementCallback)
68
      : mCustomElementCallback(std::move(aCustomElementCallback))
69
0
    {
70
0
    }
71
72
    virtual void Traverse(nsCycleCollectionTraversalCallback& aCb) const override
73
0
    {
74
0
      mCustomElementCallback->Traverse(aCb);
75
0
    }
76
77
    size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
78
0
    {
79
0
      size_t n = aMallocSizeOf(this);
80
0
81
0
      n += mCustomElementCallback->SizeOfIncludingThis(aMallocSizeOf);
82
0
83
0
      return n;
84
0
    }
85
86
  private:
87
    virtual void Invoke(Element* aElement, ErrorResult& aRv) override
88
0
    {
89
0
      mCustomElementCallback->Call();
90
0
    }
91
92
    UniquePtr<CustomElementCallback> mCustomElementCallback;
93
};
94
95
//-----------------------------------------------------
96
// CustomElementCallback
97
98
size_t
99
LifecycleCallbackArgs::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
100
0
{
101
0
  size_t n = name.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
102
0
  n += oldValue.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
103
0
  n += newValue.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
104
0
  n += namespaceURI.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
105
0
  return n;
106
0
}
107
108
void
109
CustomElementCallback::Call()
110
0
{
111
0
  switch (mType) {
112
0
    case nsIDocument::eConnected:
113
0
      static_cast<LifecycleConnectedCallback *>(mCallback.get())->Call(mThisObject);
114
0
      break;
115
0
    case nsIDocument::eDisconnected:
116
0
      static_cast<LifecycleDisconnectedCallback *>(mCallback.get())->Call(mThisObject);
117
0
      break;
118
0
    case nsIDocument::eAdopted:
119
0
      static_cast<LifecycleAdoptedCallback *>(mCallback.get())->Call(mThisObject,
120
0
        mAdoptedCallbackArgs.mOldDocument, mAdoptedCallbackArgs.mNewDocument);
121
0
      break;
122
0
    case nsIDocument::eAttributeChanged:
123
0
      static_cast<LifecycleAttributeChangedCallback *>(mCallback.get())->Call(mThisObject,
124
0
        mArgs.name, mArgs.oldValue, mArgs.newValue, mArgs.namespaceURI);
125
0
      break;
126
0
    case nsIDocument::eGetCustomInterface:
127
0
      MOZ_ASSERT_UNREACHABLE("Don't call GetCustomInterface through callback");
128
0
      break;
129
0
  }
130
0
}
131
132
void
133
CustomElementCallback::Traverse(nsCycleCollectionTraversalCallback& aCb) const
134
0
{
135
0
  NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mThisObject");
136
0
  aCb.NoteXPCOMChild(mThisObject);
137
0
138
0
  NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mCallback");
139
0
  aCb.NoteXPCOMChild(mCallback);
140
0
}
141
142
size_t
143
CustomElementCallback::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
144
0
{
145
0
  size_t n = aMallocSizeOf(this);
146
0
147
0
  // We don't uniquely own mThisObject.
148
0
149
0
  // We own mCallback but it doesn't have any special memory reporting we can do
150
0
  // for it other than report its own size.
151
0
  n += aMallocSizeOf(mCallback);
152
0
153
0
  n += mArgs.SizeOfExcludingThis(aMallocSizeOf);
154
0
155
0
  // mAdoptedCallbackArgs doesn't really uniquely own its members.
156
0
157
0
  return n;
158
0
}
159
160
CustomElementCallback::CustomElementCallback(Element* aThisObject,
161
                                             nsIDocument::ElementCallbackType aCallbackType,
162
                                             mozilla::dom::CallbackFunction* aCallback)
163
  : mThisObject(aThisObject),
164
    mCallback(aCallback),
165
    mType(aCallbackType)
166
0
{
167
0
}
168
//-----------------------------------------------------
169
// CustomElementConstructor
170
171
already_AddRefed<Element>
172
CustomElementConstructor::Construct(const char* aExecutionReason,
173
                                    ErrorResult& aRv)
174
0
{
175
0
  CallSetup s(this, aRv, aExecutionReason,
176
0
              CallbackFunction::eRethrowExceptions);
177
0
178
0
  JSContext* cx = s.GetContext();
179
0
  if (!cx) {
180
0
    MOZ_ASSERT(aRv.Failed());
181
0
    return nullptr;
182
0
  }
183
0
184
0
  JS::Rooted<JSObject*> result(cx);
185
0
  JS::Rooted<JS::Value> constructor(cx, JS::ObjectValue(*mCallback));
186
0
  if (!JS::Construct(cx, constructor, JS::HandleValueArray::empty(), &result)) {
187
0
    aRv.NoteJSContextException(cx);
188
0
    return nullptr;
189
0
  }
190
0
191
0
  RefPtr<Element> element;
192
0
  if (NS_FAILED(UNWRAP_OBJECT(Element, &result, element))) {
193
0
    return nullptr;
194
0
  }
195
0
196
0
  return element.forget();
197
0
}
198
199
//-----------------------------------------------------
200
// CustomElementData
201
202
CustomElementData::CustomElementData(nsAtom* aType)
203
  : CustomElementData(aType, CustomElementData::State::eUndefined)
204
0
{
205
0
}
206
207
CustomElementData::CustomElementData(nsAtom* aType, State aState)
208
  : mState(aState)
209
  , mType(aType)
210
0
{
211
0
}
212
213
void
214
CustomElementData::SetCustomElementDefinition(CustomElementDefinition* aDefinition)
215
0
{
216
0
  MOZ_ASSERT(mState == State::eCustom);
217
0
  MOZ_ASSERT(!mCustomElementDefinition);
218
0
  MOZ_ASSERT(aDefinition->mType == mType);
219
0
220
0
  mCustomElementDefinition = aDefinition;
221
0
}
222
223
CustomElementDefinition*
224
CustomElementData::GetCustomElementDefinition()
225
0
{
226
0
  MOZ_ASSERT(mCustomElementDefinition ? mState == State::eCustom
227
0
                                      : mState != State::eCustom);
228
0
229
0
  return mCustomElementDefinition;
230
0
}
231
232
void
233
CustomElementData::Traverse(nsCycleCollectionTraversalCallback& aCb) const
234
0
{
235
0
  for (uint32_t i = 0; i < mReactionQueue.Length(); i++) {
236
0
    if (mReactionQueue[i]) {
237
0
      mReactionQueue[i]->Traverse(aCb);
238
0
    }
239
0
  }
240
0
241
0
  if (mCustomElementDefinition) {
242
0
    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mCustomElementDefinition");
243
0
    aCb.NoteNativeChild(mCustomElementDefinition,
244
0
      NS_CYCLE_COLLECTION_PARTICIPANT(CustomElementDefinition));
245
0
  }
246
0
}
247
248
void
249
CustomElementData::Unlink()
250
0
{
251
0
  mReactionQueue.Clear();
252
0
  mCustomElementDefinition = nullptr;
253
0
}
254
255
size_t
256
CustomElementData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
257
0
{
258
0
  size_t n = aMallocSizeOf(this);
259
0
260
0
  n += mReactionQueue.ShallowSizeOfExcludingThis(aMallocSizeOf);
261
0
262
0
  for (auto& reaction : mReactionQueue) {
263
0
    n += reaction->SizeOfIncludingThis(aMallocSizeOf);
264
0
  }
265
0
266
0
  return n;
267
0
}
268
269
//-----------------------------------------------------
270
// CustomElementRegistry
271
272
namespace {
273
274
class MOZ_RAII AutoConstructionStackEntry final
275
{
276
public:
277
  AutoConstructionStackEntry(nsTArray<RefPtr<Element>>& aStack,
278
                             Element* aElement)
279
    : mStack(aStack)
280
0
  {
281
0
    MOZ_ASSERT(aElement->IsHTMLElement() || aElement->IsXULElement());
282
0
283
0
    mIndex = mStack.Length();
284
0
    mStack.AppendElement(aElement);
285
0
  }
286
287
  ~AutoConstructionStackEntry()
288
0
  {
289
0
    MOZ_ASSERT(mIndex == mStack.Length() - 1,
290
0
               "Removed element should be the last element");
291
0
    mStack.RemoveElementAt(mIndex);
292
0
  }
293
294
private:
295
  nsTArray<RefPtr<Element>>& mStack;
296
  uint32_t mIndex;
297
};
298
299
} // namespace anonymous
300
301
// Only needed for refcounted objects.
302
NS_IMPL_CYCLE_COLLECTION_CLASS(CustomElementRegistry)
303
304
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CustomElementRegistry)
305
0
  tmp->mConstructors.clear();
306
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mCustomDefinitions)
307
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWhenDefinedPromiseMap)
308
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mElementCreationCallbacks)
309
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
310
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
311
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
312
313
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CustomElementRegistry)
314
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCustomDefinitions)
315
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWhenDefinedPromiseMap)
316
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElementCreationCallbacks)
317
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
318
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
319
320
0
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CustomElementRegistry)
321
0
  for (auto iter = tmp->mConstructors.iter(); !iter.done(); iter.next()) {
322
0
    aCallbacks.Trace(&iter.get().mutableKey(),
323
0
                     "mConstructors key",
324
0
                     aClosure);
325
0
  }
326
0
  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
327
0
NS_IMPL_CYCLE_COLLECTION_TRACE_END
328
329
NS_IMPL_CYCLE_COLLECTING_ADDREF(CustomElementRegistry)
330
NS_IMPL_CYCLE_COLLECTING_RELEASE(CustomElementRegistry)
331
332
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CustomElementRegistry)
333
0
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
334
0
  NS_INTERFACE_MAP_ENTRY(nsISupports)
335
0
NS_INTERFACE_MAP_END
336
337
CustomElementRegistry::CustomElementRegistry(nsPIDOMWindowInner* aWindow)
338
 : mWindow(aWindow)
339
 , mIsCustomDefinitionRunning(false)
340
0
{
341
0
  MOZ_ASSERT(aWindow);
342
0
343
0
  mozilla::HoldJSObjects(this);
344
0
}
345
346
CustomElementRegistry::~CustomElementRegistry()
347
0
{
348
0
  mozilla::DropJSObjects(this);
349
0
}
350
351
bool
352
CustomElementRegistry::IsCustomElementEnabled(JSContext* aCx, JSObject* aObject)
353
0
{
354
0
  if (nsContentUtils::IsCustomElementsEnabled()) {
355
0
    return true;
356
0
  }
357
0
358
0
  if (!XRE_IsParentProcess()) {
359
0
    return false;
360
0
  }
361
0
362
0
  nsIPrincipal* principal =
363
0
    nsContentUtils::ObjectPrincipal(js::UncheckedUnwrap(aObject));
364
0
  return nsContentUtils::AllowXULXBLForPrincipal(principal);
365
0
}
366
367
bool
368
CustomElementRegistry::IsCustomElementEnabled(nsIDocument* aDoc)
369
0
{
370
0
  if (nsContentUtils::IsCustomElementsEnabled()) {
371
0
    return true;
372
0
  }
373
0
374
0
  return XRE_IsParentProcess() && aDoc->AllowXULXBL();
375
0
}
376
377
NS_IMETHODIMP
378
CustomElementRegistry::RunCustomElementCreationCallback::Run()
379
0
{
380
0
  ErrorResult er;
381
0
  nsDependentAtomString value(mAtom);
382
0
  mCallback->Call(value, er);
383
0
  MOZ_ASSERT(NS_SUCCEEDED(er.StealNSResult()),
384
0
    "chrome JavaScript error in the callback.");
385
0
386
0
  CustomElementDefinition* definition =
387
0
    mRegistry->mCustomDefinitions.GetWeak(mAtom);
388
0
  MOZ_ASSERT(definition, "Callback should define the definition of type.");
389
0
  MOZ_ASSERT(!mRegistry->mElementCreationCallbacks.GetWeak(mAtom),
390
0
    "Callback should be removed.");
391
0
392
0
  nsAutoPtr<nsTHashtable<nsRefPtrHashKey<nsIWeakReference>>> elements;
393
0
  mRegistry->mElementCreationCallbacksUpgradeCandidatesMap.Remove(mAtom, &elements);
394
0
  MOZ_ASSERT(elements, "There should be a list");
395
0
396
0
  for (auto iter = elements->Iter(); !iter.Done(); iter.Next()) {
397
0
    nsCOMPtr<Element> elem = do_QueryReferent(iter.Get()->GetKey());
398
0
    if (!elem) {
399
0
      continue;
400
0
    }
401
0
402
0
    CustomElementRegistry::Upgrade(elem, definition, er);
403
0
    MOZ_ASSERT(NS_SUCCEEDED(er.StealNSResult()),
404
0
      "chrome JavaScript error in custom element construction.");
405
0
  }
406
0
407
0
  return NS_OK;
408
0
}
409
410
CustomElementDefinition*
411
CustomElementRegistry::LookupCustomElementDefinition(nsAtom* aNameAtom,
412
                                                     int32_t aNameSpaceID,
413
                                                     nsAtom* aTypeAtom)
414
0
{
415
0
  CustomElementDefinition* data = mCustomDefinitions.GetWeak(aTypeAtom);
416
0
417
0
  if (!data) {
418
0
    RefPtr<CustomElementCreationCallback> callback;
419
0
    mElementCreationCallbacks.Get(aTypeAtom, getter_AddRefs(callback));
420
0
    if (callback) {
421
0
      mElementCreationCallbacks.Remove(aTypeAtom);
422
0
      mElementCreationCallbacksUpgradeCandidatesMap.LookupOrAdd(aTypeAtom);
423
0
      RefPtr<Runnable> runnable =
424
0
        new RunCustomElementCreationCallback(this, aTypeAtom, callback);
425
0
      nsContentUtils::AddScriptRunner(runnable.forget());
426
0
      data = mCustomDefinitions.GetWeak(aTypeAtom);
427
0
    }
428
0
  }
429
0
430
0
  if (data && data->mLocalName == aNameAtom && data->mNamespaceID == aNameSpaceID) {
431
0
    return data;
432
0
  }
433
0
434
0
  return nullptr;
435
0
}
436
437
CustomElementDefinition*
438
CustomElementRegistry::LookupCustomElementDefinition(JSContext* aCx,
439
                                                     JSObject* aConstructor) const
440
0
{
441
0
  JS::Rooted<JSObject*> constructor(aCx, js::CheckedUnwrap(aConstructor));
442
0
443
0
  const auto& ptr = mConstructors.lookup(constructor);
444
0
  if (!ptr) {
445
0
    return nullptr;
446
0
  }
447
0
448
0
  CustomElementDefinition* definition = mCustomDefinitions.GetWeak(ptr->value());
449
0
  MOZ_ASSERT(definition, "Definition must be found in mCustomDefinitions");
450
0
451
0
  return definition;
452
0
}
453
454
void
455
CustomElementRegistry::RegisterUnresolvedElement(Element* aElement, nsAtom* aTypeName)
456
0
{
457
0
  // We don't have a use-case for a Custom Element inside NAC, and continuing
458
0
  // here causes performance issues for NAC + XBL anonymous content.
459
0
  if (aElement->IsInNativeAnonymousSubtree()) {
460
0
    return;
461
0
  }
462
0
463
0
  mozilla::dom::NodeInfo* info = aElement->NodeInfo();
464
0
465
0
  // Candidate may be a custom element through extension,
466
0
  // in which case the custom element type name will not
467
0
  // match the element tag name. e.g. <button is="x-button">.
468
0
  RefPtr<nsAtom> typeName = aTypeName;
469
0
  if (!typeName) {
470
0
    typeName = info->NameAtom();
471
0
  }
472
0
473
0
  if (mCustomDefinitions.GetWeak(typeName)) {
474
0
    return;
475
0
  }
476
0
477
0
  nsTHashtable<nsRefPtrHashKey<nsIWeakReference>>* unresolved =
478
0
    mCandidatesMap.LookupOrAdd(typeName);
479
0
  nsWeakPtr elem = do_GetWeakReference(aElement);
480
0
  unresolved->PutEntry(elem);
481
0
}
482
483
void
484
CustomElementRegistry::UnregisterUnresolvedElement(Element* aElement,
485
                                                   nsAtom* aTypeName)
486
0
{
487
0
  nsIWeakReference* weak = aElement->GetExistingWeakReference();
488
0
  if (!weak) {
489
0
    return;
490
0
  }
491
0
492
#ifdef DEBUG
493
  {
494
    nsWeakPtr weakPtr = do_GetWeakReference(aElement);
495
    MOZ_ASSERT(weak == weakPtr.get(),
496
               "do_GetWeakReference should reuse the existing nsIWeakReference.");
497
  }
498
#endif
499
500
0
  nsTHashtable<nsRefPtrHashKey<nsIWeakReference>>* candidates = nullptr;
501
0
  if (mCandidatesMap.Get(aTypeName, &candidates)) {
502
0
    MOZ_ASSERT(candidates);
503
0
    candidates->RemoveEntry(weak);
504
0
  }
505
0
}
506
507
/* static */ UniquePtr<CustomElementCallback>
508
CustomElementRegistry::CreateCustomElementCallback(
509
  nsIDocument::ElementCallbackType aType, Element* aCustomElement,
510
  LifecycleCallbackArgs* aArgs,
511
  LifecycleAdoptedCallbackArgs* aAdoptedCallbackArgs,
512
  CustomElementDefinition* aDefinition)
513
0
{
514
0
  MOZ_ASSERT(aDefinition, "CustomElementDefinition should not be null");
515
0
  MOZ_ASSERT(aCustomElement->GetCustomElementData(),
516
0
             "CustomElementData should exist");
517
0
518
0
  // Let CALLBACK be the callback associated with the key NAME in CALLBACKS.
519
0
  CallbackFunction* func = nullptr;
520
0
  switch (aType) {
521
0
    case nsIDocument::eConnected:
522
0
      if (aDefinition->mCallbacks->mConnectedCallback.WasPassed()) {
523
0
        func = aDefinition->mCallbacks->mConnectedCallback.Value();
524
0
      }
525
0
      break;
526
0
527
0
    case nsIDocument::eDisconnected:
528
0
      if (aDefinition->mCallbacks->mDisconnectedCallback.WasPassed()) {
529
0
        func = aDefinition->mCallbacks->mDisconnectedCallback.Value();
530
0
      }
531
0
      break;
532
0
533
0
    case nsIDocument::eAdopted:
534
0
      if (aDefinition->mCallbacks->mAdoptedCallback.WasPassed()) {
535
0
        func = aDefinition->mCallbacks->mAdoptedCallback.Value();
536
0
      }
537
0
      break;
538
0
539
0
    case nsIDocument::eAttributeChanged:
540
0
      if (aDefinition->mCallbacks->mAttributeChangedCallback.WasPassed()) {
541
0
        func = aDefinition->mCallbacks->mAttributeChangedCallback.Value();
542
0
      }
543
0
      break;
544
0
545
0
    case nsIDocument::eGetCustomInterface:
546
0
      MOZ_ASSERT_UNREACHABLE("Don't call GetCustomInterface through callback");
547
0
      break;
548
0
  }
549
0
550
0
  // If there is no such callback, stop.
551
0
  if (!func) {
552
0
    return nullptr;
553
0
  }
554
0
555
0
  // Add CALLBACK to ELEMENT's callback queue.
556
0
  auto callback =
557
0
    MakeUnique<CustomElementCallback>(aCustomElement, aType, func);
558
0
559
0
  if (aArgs) {
560
0
    callback->SetArgs(*aArgs);
561
0
  }
562
0
563
0
  if (aAdoptedCallbackArgs) {
564
0
    callback->SetAdoptedCallbackArgs(*aAdoptedCallbackArgs);
565
0
  }
566
0
  return callback;
567
0
}
568
569
/* static */ void
570
CustomElementRegistry::EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType,
571
                                                Element* aCustomElement,
572
                                                LifecycleCallbackArgs* aArgs,
573
                                                LifecycleAdoptedCallbackArgs* aAdoptedCallbackArgs,
574
                                                CustomElementDefinition* aDefinition)
575
0
{
576
0
  CustomElementDefinition* definition = aDefinition;
577
0
  if (!definition) {
578
0
    definition = aCustomElement->GetCustomElementDefinition();
579
0
    if (!definition ||
580
0
        definition->mLocalName != aCustomElement->NodeInfo()->NameAtom()) {
581
0
      return;
582
0
    }
583
0
584
0
    if (!definition->mCallbacks) {
585
0
      // definition has been unlinked.  Don't try to mess with it.
586
0
      return;
587
0
    }
588
0
  }
589
0
590
0
  auto callback =
591
0
    CreateCustomElementCallback(aType, aCustomElement, aArgs,
592
0
                                aAdoptedCallbackArgs, definition);
593
0
  if (!callback) {
594
0
    return;
595
0
  }
596
0
597
0
  DocGroup* docGroup = aCustomElement->OwnerDoc()->GetDocGroup();
598
0
  if (!docGroup) {
599
0
    return;
600
0
  }
601
0
602
0
  if (aType == nsIDocument::eAttributeChanged) {
603
0
    RefPtr<nsAtom> attrName = NS_Atomize(aArgs->name);
604
0
    if (definition->mObservedAttributes.IsEmpty() ||
605
0
        !definition->mObservedAttributes.Contains(attrName)) {
606
0
      return;
607
0
    }
608
0
  }
609
0
610
0
  CustomElementReactionsStack* reactionsStack =
611
0
    docGroup->CustomElementReactionsStack();
612
0
  reactionsStack->EnqueueCallbackReaction(aCustomElement, std::move(callback));
613
0
}
614
615
namespace {
616
617
class CandidateFinder
618
{
619
public:
620
  CandidateFinder(nsTHashtable<nsRefPtrHashKey<nsIWeakReference>>& aCandidates,
621
                  nsIDocument* aDoc);
622
  nsTArray<nsCOMPtr<Element>> OrderedCandidates();
623
624
private:
625
  bool Traverse(Element* aRoot, nsTArray<nsCOMPtr<Element>>& aOrderedElements);
626
627
  nsCOMPtr<nsIDocument> mDoc;
628
  nsInterfaceHashtable<nsPtrHashKey<Element>, Element> mCandidates;
629
};
630
631
CandidateFinder::CandidateFinder(nsTHashtable<nsRefPtrHashKey<nsIWeakReference>>& aCandidates,
632
                                 nsIDocument* aDoc)
633
  : mDoc(aDoc)
634
  , mCandidates(aCandidates.Count())
635
0
{
636
0
  MOZ_ASSERT(mDoc);
637
0
  for (auto iter = aCandidates.Iter(); !iter.Done(); iter.Next()) {
638
0
    nsCOMPtr<Element> elem = do_QueryReferent(iter.Get()->GetKey());
639
0
    if (!elem) {
640
0
      continue;
641
0
    }
642
0
643
0
    Element* key = elem.get();
644
0
    mCandidates.Put(key, elem.forget());
645
0
  }
646
0
}
647
648
nsTArray<nsCOMPtr<Element>>
649
CandidateFinder::OrderedCandidates()
650
0
{
651
0
  if (mCandidates.Count() == 1) {
652
0
    // Fast path for one candidate.
653
0
    for (auto iter = mCandidates.Iter(); !iter.Done(); iter.Next()) {
654
0
      nsTArray<nsCOMPtr<Element>> rval({ std::move(iter.Data()) });
655
0
      iter.Remove();
656
0
      return rval;
657
0
    }
658
0
  }
659
0
660
0
  nsTArray<nsCOMPtr<Element>> orderedElements(mCandidates.Count());
661
0
  for (Element* child = mDoc->GetFirstElementChild(); child; child = child->GetNextElementSibling()) {
662
0
    if (!Traverse(child, orderedElements)) {
663
0
      break;
664
0
    }
665
0
  }
666
0
667
0
  return orderedElements;
668
0
}
669
670
bool
671
CandidateFinder::Traverse(Element* aRoot, nsTArray<nsCOMPtr<Element>>& aOrderedElements)
672
0
{
673
0
  nsCOMPtr<Element> elem;
674
0
  if (mCandidates.Remove(aRoot, getter_AddRefs(elem))) {
675
0
    aOrderedElements.AppendElement(std::move(elem));
676
0
    if (mCandidates.Count() == 0) {
677
0
      return false;
678
0
    }
679
0
  }
680
0
681
0
  if (ShadowRoot* root = aRoot->GetShadowRoot()) {
682
0
    // First iterate the children of the shadow root if aRoot is a shadow host.
683
0
    for (Element* child = root->GetFirstElementChild(); child;
684
0
         child = child->GetNextElementSibling()) {
685
0
      if (!Traverse(child, aOrderedElements)) {
686
0
        return false;
687
0
      }
688
0
    }
689
0
  }
690
0
691
0
  // Iterate the explicit children of aRoot.
692
0
  for (Element* child = aRoot->GetFirstElementChild(); child;
693
0
       child = child->GetNextElementSibling()) {
694
0
    if (!Traverse(child, aOrderedElements)) {
695
0
      return false;
696
0
    }
697
0
  }
698
0
699
0
  return true;
700
0
}
701
702
}
703
704
void
705
CustomElementRegistry::UpgradeCandidates(nsAtom* aKey,
706
                                         CustomElementDefinition* aDefinition,
707
                                         ErrorResult& aRv)
708
0
{
709
0
  DocGroup* docGroup = mWindow->GetDocGroup();
710
0
  if (!docGroup) {
711
0
    aRv.Throw(NS_ERROR_UNEXPECTED);
712
0
    return;
713
0
  }
714
0
715
0
  nsAutoPtr<nsTHashtable<nsRefPtrHashKey<nsIWeakReference>>> candidates;
716
0
  if (mCandidatesMap.Remove(aKey, &candidates)) {
717
0
    MOZ_ASSERT(candidates);
718
0
    CustomElementReactionsStack* reactionsStack =
719
0
      docGroup->CustomElementReactionsStack();
720
0
721
0
    CandidateFinder finder(*candidates, mWindow->GetExtantDoc());
722
0
    for (auto& elem : finder.OrderedCandidates()) {
723
0
      reactionsStack->EnqueueUpgradeReaction(elem, aDefinition);
724
0
    }
725
0
  }
726
0
}
727
728
JSObject*
729
CustomElementRegistry::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
730
0
{
731
0
  return CustomElementRegistry_Binding::Wrap(aCx, this, aGivenProto);
732
0
}
733
734
nsISupports* CustomElementRegistry::GetParentObject() const
735
0
{
736
0
  return mWindow;
737
0
}
738
739
DocGroup*
740
CustomElementRegistry::GetDocGroup() const
741
0
{
742
0
  return mWindow ? mWindow->GetDocGroup() : nullptr;
743
0
}
744
745
int32_t
746
CustomElementRegistry::InferNamespace(JSContext* aCx,
747
                                      JS::Handle<JSObject*> constructor)
748
0
{
749
0
  JS::Rooted<JSObject*> XULConstructor(aCx, XULElement_Binding::GetConstructorObject(aCx));
750
0
751
0
  JS::Rooted<JSObject*> proto(aCx, constructor);
752
0
  while (proto) {
753
0
    if (proto == XULConstructor) {
754
0
      return kNameSpaceID_XUL;
755
0
    }
756
0
757
0
    JS_GetPrototype(aCx, proto, &proto);
758
0
  }
759
0
760
0
  return kNameSpaceID_XHTML;
761
0
}
762
763
// https://html.spec.whatwg.org/multipage/scripting.html#element-definition
764
void
765
CustomElementRegistry::Define(JSContext* aCx,
766
                              const nsAString& aName,
767
                              Function& aFunctionConstructor,
768
                              const ElementDefinitionOptions& aOptions,
769
                              ErrorResult& aRv)
770
0
{
771
0
  // Note: No calls that might run JS or trigger CC before this point, or
772
0
  // there's a (vanishingly small) chance of our constructor being nulled
773
0
  // before we access it.
774
0
  JS::Rooted<JSObject*> constructor(aCx, aFunctionConstructor.CallableOrNull());
775
0
776
0
  JS::Rooted<JSObject*> constructorUnwrapped(aCx, js::CheckedUnwrap(constructor));
777
0
  if (!constructorUnwrapped) {
778
0
    // If the caller's compartment does not have permission to access the
779
0
    // unwrapped constructor then throw.
780
0
    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
781
0
    return;
782
0
  }
783
0
784
0
  /**
785
0
   * 1. If IsConstructor(constructor) is false, then throw a TypeError and abort
786
0
   *    these steps.
787
0
   */
788
0
  if (!JS::IsConstructor(constructorUnwrapped)) {
789
0
    aRv.ThrowTypeError<MSG_NOT_CONSTRUCTOR>(NS_LITERAL_STRING("Argument 2 of CustomElementRegistry.define"));
790
0
    return;
791
0
  }
792
0
793
0
  int32_t nameSpaceID = InferNamespace(aCx, constructor);
794
0
795
0
  /**
796
0
   * 2. If name is not a valid custom element name, then throw a "SyntaxError"
797
0
   *    DOMException and abort these steps.
798
0
   */
799
0
  nsIDocument* doc = mWindow->GetExtantDoc();
800
0
  RefPtr<nsAtom> nameAtom(NS_Atomize(aName));
801
0
  if (!nsContentUtils::IsCustomElementName(nameAtom, nameSpaceID)) {
802
0
    aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
803
0
    return;
804
0
  }
805
0
806
0
  /**
807
0
   * 3. If this CustomElementRegistry contains an entry with name name, then
808
0
   *    throw a "NotSupportedError" DOMException and abort these steps.
809
0
   */
810
0
  if (mCustomDefinitions.GetWeak(nameAtom)) {
811
0
    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
812
0
    return;
813
0
  }
814
0
815
0
  /**
816
0
   * 4. If this CustomElementRegistry contains an entry with constructor constructor,
817
0
   *    then throw a "NotSupportedError" DOMException and abort these steps.
818
0
   */
819
0
  const auto& ptr = mConstructors.lookup(constructorUnwrapped);
820
0
  if (ptr) {
821
0
    MOZ_ASSERT(mCustomDefinitions.GetWeak(ptr->value()),
822
0
               "Definition must be found in mCustomDefinitions");
823
0
    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
824
0
    return;
825
0
  }
826
0
827
0
  /**
828
0
   * 5. Let localName be name.
829
0
   * 6. Let extends be the value of the extends member of options, or null if
830
0
   *    no such member exists.
831
0
   * 7. If extends is not null, then:
832
0
   *    1. If extends is a valid custom element name, then throw a
833
0
   *       "NotSupportedError" DOMException.
834
0
   *    2. If the element interface for extends and the HTML namespace is
835
0
   *       HTMLUnknownElement (e.g., if extends does not indicate an element
836
0
   *       definition in this specification), then throw a "NotSupportedError"
837
0
   *       DOMException.
838
0
   *    3. Set localName to extends.
839
0
   *
840
0
   * Special note for XUL elements:
841
0
   *
842
0
   * For step 7.1, we'll subject XUL to the same rules as HTML, so that a
843
0
   * custom built-in element will not be extending from a dashed name.
844
0
   * Step 7.2 is disregarded. But, we do check if the name is a dashed name
845
0
   * (i.e. step 2) given that there is no reason for a custom built-in element
846
0
   * type to take on a non-dashed name.
847
0
   * This also ensures the name of the built-in custom element type can never
848
0
   * be the same as the built-in element name, so we don't break the assumption
849
0
   * elsewhere.
850
0
   */
851
0
  nsAutoString localName(aName);
852
0
  if (aOptions.mExtends.WasPassed()) {
853
0
    RefPtr<nsAtom> extendsAtom(NS_Atomize(aOptions.mExtends.Value()));
854
0
    if (nsContentUtils::IsCustomElementName(extendsAtom, kNameSpaceID_XHTML)) {
855
0
      aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
856
0
      return;
857
0
    }
858
0
859
0
    if (nameSpaceID == kNameSpaceID_XHTML) {
860
0
      // bgsound and multicol are unknown html element.
861
0
      int32_t tag = nsHTMLTags::CaseSensitiveAtomTagToId(extendsAtom);
862
0
      if (tag == eHTMLTag_userdefined ||
863
0
          tag == eHTMLTag_bgsound ||
864
0
          tag == eHTMLTag_multicol) {
865
0
        aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
866
0
        return;
867
0
      }
868
0
    } else { // kNameSpaceID_XUL
869
0
      // As stated above, ensure the name of the customized built-in element
870
0
      // (the one that goes to the |is| attribute) is a dashed name.
871
0
      if (!nsContentUtils::IsNameWithDash(nameAtom)) {
872
0
        aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
873
0
        return;
874
0
      }
875
0
    }
876
0
877
0
    localName.Assign(aOptions.mExtends.Value());
878
0
  }
879
0
880
0
  /**
881
0
   * 8. If this CustomElementRegistry's element definition is running flag is set,
882
0
   *    then throw a "NotSupportedError" DOMException and abort these steps.
883
0
   */
884
0
  if (mIsCustomDefinitionRunning) {
885
0
    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
886
0
    return;
887
0
  }
888
0
889
0
  auto callbacksHolder = MakeUnique<LifecycleCallbacks>();
890
0
  nsTArray<RefPtr<nsAtom>> observedAttributes;
891
0
  { // Set mIsCustomDefinitionRunning.
892
0
    /**
893
0
     * 9. Set this CustomElementRegistry's element definition is running flag.
894
0
     */
895
0
    AutoSetRunningFlag as(this);
896
0
897
0
    /**
898
0
     * 10.1. Let prototype be Get(constructor, "prototype"). Rethrow any exceptions.
899
0
     */
900
0
    // The .prototype on the constructor passed could be an "expando" of a
901
0
    // wrapper. So we should get it from wrapper instead of the underlying
902
0
    // object.
903
0
    JS::Rooted<JS::Value> prototype(aCx);
904
0
    if (!JS_GetProperty(aCx, constructor, "prototype", &prototype)) {
905
0
      aRv.NoteJSContextException(aCx);
906
0
      return;
907
0
    }
908
0
909
0
    /**
910
0
     * 10.2. If Type(prototype) is not Object, then throw a TypeError exception.
911
0
     */
912
0
    if (!prototype.isObject()) {
913
0
      aRv.ThrowTypeError<MSG_NOT_OBJECT>(NS_LITERAL_STRING("constructor.prototype"));
914
0
      return;
915
0
    }
916
0
917
0
    /**
918
0
     * 10.3. Let lifecycleCallbacks be a map with the four keys
919
0
     *       "connectedCallback", "disconnectedCallback", "adoptedCallback", and
920
0
     *       "attributeChangedCallback", each of which belongs to an entry whose
921
0
     *       value is null. The 'getCustomInterface' callback is also included
922
0
     *       for chrome usage.
923
0
     * 10.4. For each of the four keys callbackName in lifecycleCallbacks:
924
0
     *       1. Let callbackValue be Get(prototype, callbackName). Rethrow any
925
0
     *          exceptions.
926
0
     *       2. If callbackValue is not undefined, then set the value of the
927
0
     *          entry in lifecycleCallbacks with key callbackName to the result
928
0
     *          of converting callbackValue to the Web IDL Function callback type.
929
0
     *          Rethrow any exceptions from the conversion.
930
0
     */
931
0
    if (!callbacksHolder->Init(aCx, prototype)) {
932
0
      aRv.NoteJSContextException(aCx);
933
0
      return;
934
0
    }
935
0
936
0
    /**
937
0
     * 10.5. Let observedAttributes be an empty sequence<DOMString>.
938
0
     * 10.6. If the value of the entry in lifecycleCallbacks with key
939
0
     *       "attributeChangedCallback" is not null, then:
940
0
     *       1. Let observedAttributesIterable be Get(constructor,
941
0
     *          "observedAttributes"). Rethrow any exceptions.
942
0
     *       2. If observedAttributesIterable is not undefined, then set
943
0
     *          observedAttributes to the result of converting
944
0
     *          observedAttributesIterable to a sequence<DOMString>. Rethrow
945
0
     *          any exceptions from the conversion.
946
0
     */
947
0
    if (callbacksHolder->mAttributeChangedCallback.WasPassed()) {
948
0
      JS::Rooted<JS::Value> observedAttributesIterable(aCx);
949
0
950
0
      if (!JS_GetProperty(aCx, constructor, "observedAttributes",
951
0
                          &observedAttributesIterable)) {
952
0
        aRv.NoteJSContextException(aCx);
953
0
        return;
954
0
      }
955
0
956
0
      if (!observedAttributesIterable.isUndefined()) {
957
0
        if (!observedAttributesIterable.isObject()) {
958
0
          aRv.ThrowTypeError<MSG_NOT_SEQUENCE>(NS_LITERAL_STRING("observedAttributes"));
959
0
          return;
960
0
        }
961
0
962
0
        JS::ForOfIterator iter(aCx);
963
0
        if (!iter.init(observedAttributesIterable, JS::ForOfIterator::AllowNonIterable)) {
964
0
          aRv.NoteJSContextException(aCx);
965
0
          return;
966
0
        }
967
0
968
0
        if (!iter.valueIsIterable()) {
969
0
          aRv.ThrowTypeError<MSG_NOT_SEQUENCE>(NS_LITERAL_STRING("observedAttributes"));
970
0
          return;
971
0
        }
972
0
973
0
        JS::Rooted<JS::Value> attribute(aCx);
974
0
        while (true) {
975
0
          bool done;
976
0
          if (!iter.next(&attribute, &done)) {
977
0
            aRv.NoteJSContextException(aCx);
978
0
            return;
979
0
          }
980
0
          if (done) {
981
0
            break;
982
0
          }
983
0
984
0
          nsAutoString attrStr;
985
0
          if (!ConvertJSValueToString(aCx, attribute, eStringify, eStringify, attrStr)) {
986
0
            aRv.NoteJSContextException(aCx);
987
0
            return;
988
0
          }
989
0
990
0
          if (!observedAttributes.AppendElement(NS_Atomize(attrStr))) {
991
0
            aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
992
0
            return;
993
0
          }
994
0
        }
995
0
      }
996
0
    }
997
0
  } // Unset mIsCustomDefinitionRunning
998
0
999
0
  /**
1000
0
   * 11. Let definition be a new custom element definition with name name,
1001
0
   *     local name localName, constructor constructor, prototype prototype,
1002
0
   *     observed attributes observedAttributes, and lifecycle callbacks
1003
0
   *     lifecycleCallbacks.
1004
0
   */
1005
0
  // Associate the definition with the custom element.
1006
0
  RefPtr<nsAtom> localNameAtom(NS_Atomize(localName));
1007
0
1008
0
  /**
1009
0
   * 12. Add definition to this CustomElementRegistry.
1010
0
   */
1011
0
  if (!mConstructors.put(constructorUnwrapped, nameAtom)) {
1012
0
    aRv.Throw(NS_ERROR_FAILURE);
1013
0
    return;
1014
0
  }
1015
0
1016
0
  RefPtr<CustomElementDefinition> definition =
1017
0
    new CustomElementDefinition(nameAtom,
1018
0
                                localNameAtom,
1019
0
                                nameSpaceID,
1020
0
                                &aFunctionConstructor,
1021
0
                                std::move(observedAttributes),
1022
0
                                std::move(callbacksHolder));
1023
0
1024
0
  CustomElementDefinition* def = definition.get();
1025
0
  mCustomDefinitions.Put(nameAtom, definition.forget());
1026
0
1027
0
  MOZ_ASSERT(mCustomDefinitions.Count() == mConstructors.count(),
1028
0
             "Number of entries should be the same");
1029
0
1030
0
  /**
1031
0
   * 13. 14. 15. Upgrade candidates
1032
0
   */
1033
0
  UpgradeCandidates(nameAtom, def, aRv);
1034
0
1035
0
  /**
1036
0
   * 16. If this CustomElementRegistry's when-defined promise map contains an
1037
0
   *     entry with key name:
1038
0
   *     1. Let promise be the value of that entry.
1039
0
   *     2. Resolve promise with undefined.
1040
0
   *     3. Delete the entry with key name from this CustomElementRegistry's
1041
0
   *        when-defined promise map.
1042
0
   */
1043
0
  RefPtr<Promise> promise;
1044
0
  mWhenDefinedPromiseMap.Remove(nameAtom, getter_AddRefs(promise));
1045
0
  if (promise) {
1046
0
    promise->MaybeResolveWithUndefined();
1047
0
  }
1048
0
1049
0
  // Dispatch a "customelementdefined" event for DevTools.
1050
0
  {
1051
0
    JSString* nameJsStr = JS_NewUCStringCopyN(aCx,
1052
0
                                              aName.BeginReading(),
1053
0
                                              aName.Length());
1054
0
1055
0
    JS::Rooted<JS::Value> detail(aCx, JS::StringValue(nameJsStr));
1056
0
    RefPtr<CustomEvent> event = NS_NewDOMCustomEvent(doc, nullptr, nullptr);
1057
0
    event->InitCustomEvent(aCx,
1058
0
                           NS_LITERAL_STRING("customelementdefined"),
1059
0
                           /* CanBubble */ true,
1060
0
                           /* Cancelable */ true,
1061
0
                           detail);
1062
0
    event->SetTrusted(true);
1063
0
1064
0
    AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(doc, event);
1065
0
    dispatcher->mOnlyChromeDispatch = ChromeOnlyDispatch::eYes;
1066
0
1067
0
    dispatcher->PostDOMEvent();
1068
0
  }
1069
0
1070
0
  /**
1071
0
   * Clean-up mElementCreationCallbacks (if it exists)
1072
0
   */
1073
0
  mElementCreationCallbacks.Remove(nameAtom);
1074
0
1075
0
}
1076
1077
void
1078
CustomElementRegistry::SetElementCreationCallback(const nsAString& aName,
1079
                                                  CustomElementCreationCallback& aCallback,
1080
                                                  ErrorResult& aRv)
1081
0
{
1082
0
  RefPtr<nsAtom> nameAtom(NS_Atomize(aName));
1083
0
  if (mElementCreationCallbacks.GetWeak(nameAtom) ||
1084
0
      mCustomDefinitions.GetWeak(nameAtom)) {
1085
0
    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
1086
0
    return;
1087
0
  }
1088
0
1089
0
  RefPtr<CustomElementCreationCallback> callback = &aCallback;
1090
0
  mElementCreationCallbacks.Put(nameAtom, callback.forget());
1091
0
  return;
1092
0
}
1093
1094
static void
1095
TryUpgrade(nsINode& aNode)
1096
0
{
1097
0
  Element* element = aNode.IsElement() ? aNode.AsElement() : nullptr;
1098
0
  if (element) {
1099
0
    CustomElementData* ceData = element->GetCustomElementData();
1100
0
    if (ceData) {
1101
0
      NodeInfo* nodeInfo = element->NodeInfo();
1102
0
      nsAtom* typeAtom = ceData->GetCustomElementType();
1103
0
      CustomElementDefinition* definition =
1104
0
        nsContentUtils::LookupCustomElementDefinition(nodeInfo->GetDocument(),
1105
0
                                                      nodeInfo->NameAtom(),
1106
0
                                                      nodeInfo->NamespaceID(),
1107
0
                                                      typeAtom);
1108
0
      if (definition) {
1109
0
        nsContentUtils::EnqueueUpgradeReaction(element, definition);
1110
0
      }
1111
0
    }
1112
0
1113
0
    if (ShadowRoot* root = element->GetShadowRoot()) {
1114
0
      for (Element* child = root->GetFirstElementChild(); child;
1115
0
           child = child->GetNextElementSibling()) {
1116
0
        TryUpgrade(*child);
1117
0
      }
1118
0
    }
1119
0
  }
1120
0
1121
0
  for (Element* child = aNode.GetFirstElementChild(); child;
1122
0
       child = child->GetNextElementSibling()) {
1123
0
    TryUpgrade(*child);
1124
0
  }
1125
0
}
1126
1127
void
1128
CustomElementRegistry::Upgrade(nsINode& aRoot)
1129
0
{
1130
0
  TryUpgrade(aRoot);
1131
0
}
1132
1133
void
1134
CustomElementRegistry::Get(JSContext* aCx, const nsAString& aName,
1135
                           JS::MutableHandle<JS::Value> aRetVal)
1136
0
{
1137
0
  RefPtr<nsAtom> nameAtom(NS_Atomize(aName));
1138
0
  CustomElementDefinition* data = mCustomDefinitions.GetWeak(nameAtom);
1139
0
1140
0
  if (!data) {
1141
0
    aRetVal.setUndefined();
1142
0
    return;
1143
0
  }
1144
0
1145
0
  aRetVal.setObject(*data->mConstructor->Callback(aCx));
1146
0
}
1147
1148
already_AddRefed<Promise>
1149
CustomElementRegistry::WhenDefined(const nsAString& aName, ErrorResult& aRv)
1150
0
{
1151
0
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
1152
0
  RefPtr<Promise> promise = Promise::Create(global, aRv);
1153
0
1154
0
  if (aRv.Failed()) {
1155
0
    return nullptr;
1156
0
  }
1157
0
1158
0
  RefPtr<nsAtom> nameAtom(NS_Atomize(aName));
1159
0
  nsIDocument* doc = mWindow->GetExtantDoc();
1160
0
  uint32_t nameSpaceID = doc ? doc->GetDefaultNamespaceID() : kNameSpaceID_XHTML;
1161
0
  if (!nsContentUtils::IsCustomElementName(nameAtom, nameSpaceID)) {
1162
0
    promise->MaybeReject(NS_ERROR_DOM_SYNTAX_ERR);
1163
0
    return promise.forget();
1164
0
  }
1165
0
1166
0
  if (mCustomDefinitions.GetWeak(nameAtom)) {
1167
0
    promise->MaybeResolve(JS::UndefinedHandleValue);
1168
0
    return promise.forget();
1169
0
  }
1170
0
1171
0
  auto entry = mWhenDefinedPromiseMap.LookupForAdd(nameAtom);
1172
0
  if (entry) {
1173
0
    promise = entry.Data();
1174
0
  } else {
1175
0
    entry.OrInsert([&promise](){ return promise; });
1176
0
  }
1177
0
1178
0
  return promise.forget();
1179
0
}
1180
1181
namespace {
1182
1183
static void
1184
DoUpgrade(Element* aElement,
1185
          CustomElementConstructor* aConstructor,
1186
          ErrorResult& aRv)
1187
0
{
1188
0
  // Rethrow the exception since it might actually throw the exception from the
1189
0
  // upgrade steps back out to the caller of document.createElement.
1190
0
  RefPtr<Element> constructResult =
1191
0
    aConstructor->Construct("Custom Element Upgrade", aRv);
1192
0
  if (aRv.Failed()) {
1193
0
    return;
1194
0
  }
1195
0
1196
0
  if (!constructResult || constructResult.get() != aElement) {
1197
0
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1198
0
    return;
1199
0
  }
1200
0
}
1201
1202
} // anonymous namespace
1203
1204
// https://html.spec.whatwg.org/multipage/scripting.html#upgrades
1205
/* static */ void
1206
CustomElementRegistry::Upgrade(Element* aElement,
1207
                               CustomElementDefinition* aDefinition,
1208
                               ErrorResult& aRv)
1209
0
{
1210
0
  RefPtr<CustomElementData> data = aElement->GetCustomElementData();
1211
0
  MOZ_ASSERT(data, "CustomElementData should exist");
1212
0
1213
0
  // Step 1 and step 2.
1214
0
  if (data->mState == CustomElementData::State::eCustom ||
1215
0
      data->mState == CustomElementData::State::eFailed) {
1216
0
    return;
1217
0
  }
1218
0
1219
0
  // Step 3.
1220
0
  if (!aDefinition->mObservedAttributes.IsEmpty()) {
1221
0
    uint32_t count = aElement->GetAttrCount();
1222
0
    for (uint32_t i = 0; i < count; i++) {
1223
0
      mozilla::dom::BorrowedAttrInfo info = aElement->GetAttrInfoAt(i);
1224
0
1225
0
      const nsAttrName* name = info.mName;
1226
0
      nsAtom* attrName = name->LocalName();
1227
0
1228
0
      if (aDefinition->IsInObservedAttributeList(attrName)) {
1229
0
        int32_t namespaceID = name->NamespaceID();
1230
0
        nsAutoString attrValue, namespaceURI;
1231
0
        info.mValue->ToString(attrValue);
1232
0
        nsContentUtils::NameSpaceManager()->GetNameSpaceURI(namespaceID,
1233
0
                                                            namespaceURI);
1234
0
1235
0
        LifecycleCallbackArgs args = {
1236
0
          nsDependentAtomString(attrName),
1237
0
          VoidString(),
1238
0
          attrValue,
1239
0
          (namespaceURI.IsEmpty() ? VoidString() : namespaceURI)
1240
0
        };
1241
0
        nsContentUtils::EnqueueLifecycleCallback(nsIDocument::eAttributeChanged,
1242
0
                                                 aElement,
1243
0
                                                 &args, nullptr, aDefinition);
1244
0
      }
1245
0
    }
1246
0
  }
1247
0
1248
0
  // Step 4.
1249
0
  if (aElement->IsInComposedDoc()) {
1250
0
    nsContentUtils::EnqueueLifecycleCallback(nsIDocument::eConnected, aElement,
1251
0
      nullptr, nullptr, aDefinition);
1252
0
  }
1253
0
1254
0
  // Step 5.
1255
0
  AutoConstructionStackEntry acs(aDefinition->mConstructionStack, aElement);
1256
0
1257
0
  // Step 6 and step 7.
1258
0
  DoUpgrade(aElement, aDefinition->mConstructor, aRv);
1259
0
  if (aRv.Failed()) {
1260
0
    data->mState = CustomElementData::State::eFailed;
1261
0
    // Empty element's custom element reaction queue.
1262
0
    data->mReactionQueue.Clear();
1263
0
    return;
1264
0
  }
1265
0
1266
0
  // Step 8.
1267
0
  data->mState = CustomElementData::State::eCustom;
1268
0
  aElement->SetDefined(true);
1269
0
1270
0
  // Step 9.
1271
0
  aElement->SetCustomElementDefinition(aDefinition);
1272
0
}
1273
1274
already_AddRefed<nsISupports>
1275
CustomElementRegistry::CallGetCustomInterface(Element* aElement,
1276
                                              const nsIID& aIID)
1277
0
{
1278
0
  MOZ_ASSERT(aElement);
1279
0
1280
0
  if (nsContentUtils::IsChromeDoc(aElement->OwnerDoc())) {
1281
0
    CustomElementDefinition* definition = aElement->GetCustomElementDefinition();
1282
0
    if (definition && definition->mCallbacks &&
1283
0
        definition->mCallbacks->mGetCustomInterfaceCallback.WasPassed() &&
1284
0
        definition->mLocalName == aElement->NodeInfo()->NameAtom()) {
1285
0
1286
0
      LifecycleGetCustomInterfaceCallback* func =
1287
0
        definition->mCallbacks->mGetCustomInterfaceCallback.Value();
1288
0
      JS::Rooted<JSObject*> customInterface(RootingCx());
1289
0
1290
0
      nsCOMPtr<nsIJSID> iid = nsJSID::NewID(aIID);
1291
0
      func->Call(aElement, iid, &customInterface);
1292
0
      JS::Rooted<JSObject*> funcGlobal(RootingCx(), func->CallbackGlobalOrNull());
1293
0
      if (customInterface && funcGlobal) {
1294
0
        AutoJSAPI jsapi;
1295
0
        if (jsapi.Init(funcGlobal)) {
1296
0
          nsIXPConnect *xpConnect = nsContentUtils::XPConnect();
1297
0
          JSContext* cx = jsapi.cx();
1298
0
1299
0
          nsCOMPtr<nsISupports> wrapper;
1300
0
          nsresult rv = xpConnect->WrapJSAggregatedToNative(aElement, cx, customInterface,
1301
0
                                                            aIID, getter_AddRefs(wrapper));
1302
0
          if (NS_SUCCEEDED(rv)) {
1303
0
            return wrapper.forget();
1304
0
          }
1305
0
        }
1306
0
      }
1307
0
    }
1308
0
  }
1309
0
1310
0
  return nullptr;
1311
0
}
1312
1313
//-----------------------------------------------------
1314
// CustomElementReactionsStack
1315
1316
void
1317
CustomElementReactionsStack::CreateAndPushElementQueue()
1318
0
{
1319
0
  MOZ_ASSERT(mRecursionDepth);
1320
0
  MOZ_ASSERT(!mIsElementQueuePushedForCurrentRecursionDepth);
1321
0
1322
0
  // Push a new element queue onto the custom element reactions stack.
1323
0
  mReactionsStack.AppendElement(MakeUnique<ElementQueue>());
1324
0
  mIsElementQueuePushedForCurrentRecursionDepth = true;
1325
0
}
1326
1327
void
1328
CustomElementReactionsStack::PopAndInvokeElementQueue()
1329
0
{
1330
0
  MOZ_ASSERT(mRecursionDepth);
1331
0
  MOZ_ASSERT(mIsElementQueuePushedForCurrentRecursionDepth);
1332
0
  MOZ_ASSERT(!mReactionsStack.IsEmpty(),
1333
0
             "Reaction stack shouldn't be empty");
1334
0
1335
0
  // Pop the element queue from the custom element reactions stack,
1336
0
  // and invoke custom element reactions in that queue.
1337
0
  const uint32_t lastIndex = mReactionsStack.Length() - 1;
1338
0
  ElementQueue* elementQueue = mReactionsStack.ElementAt(lastIndex).get();
1339
0
  // Check element queue size in order to reduce function call overhead.
1340
0
  if (!elementQueue->IsEmpty()) {
1341
0
    // It is still not clear what error reporting will look like in custom
1342
0
    // element, see https://github.com/w3c/webcomponents/issues/635.
1343
0
    // We usually report the error to entry global in gecko, so just follow the
1344
0
    // same behavior here.
1345
0
    // This may be null if it's called from parser, see the case of
1346
0
    // attributeChangedCallback in
1347
0
    // https://html.spec.whatwg.org/multipage/parsing.html#create-an-element-for-the-token
1348
0
    // In that case, the exception of callback reactions will be automatically
1349
0
    // reported in CallSetup.
1350
0
    nsIGlobalObject* global = GetEntryGlobal();
1351
0
    InvokeReactions(elementQueue, global);
1352
0
  }
1353
0
1354
0
  // InvokeReactions() might create other custom element reactions, but those
1355
0
  // new reactions should be already consumed and removed at this point.
1356
0
  MOZ_ASSERT(lastIndex == mReactionsStack.Length() - 1,
1357
0
             "reactions created by InvokeReactions() should be consumed and removed");
1358
0
1359
0
  mReactionsStack.RemoveElementAt(lastIndex);
1360
0
  mIsElementQueuePushedForCurrentRecursionDepth = false;
1361
0
}
1362
1363
void
1364
CustomElementReactionsStack::EnqueueUpgradeReaction(Element* aElement,
1365
                                                    CustomElementDefinition* aDefinition)
1366
0
{
1367
0
  Enqueue(aElement, new CustomElementUpgradeReaction(aDefinition));
1368
0
}
1369
1370
void
1371
CustomElementReactionsStack::EnqueueCallbackReaction(Element* aElement,
1372
                                                     UniquePtr<CustomElementCallback> aCustomElementCallback)
1373
0
{
1374
0
  Enqueue(aElement, new CustomElementCallbackReaction(std::move(aCustomElementCallback)));
1375
0
}
1376
1377
void
1378
CustomElementReactionsStack::Enqueue(Element* aElement,
1379
                                     CustomElementReaction* aReaction)
1380
0
{
1381
0
  RefPtr<CustomElementData> elementData = aElement->GetCustomElementData();
1382
0
  MOZ_ASSERT(elementData, "CustomElementData should exist");
1383
0
1384
0
  if (mRecursionDepth) {
1385
0
    // If the element queue is not created for current recursion depth, create
1386
0
    // and push an element queue to reactions stack first.
1387
0
    if (!mIsElementQueuePushedForCurrentRecursionDepth) {
1388
0
      CreateAndPushElementQueue();
1389
0
    }
1390
0
1391
0
    MOZ_ASSERT(!mReactionsStack.IsEmpty());
1392
0
    // Add element to the current element queue.
1393
0
    mReactionsStack.LastElement()->AppendElement(aElement);
1394
0
    elementData->mReactionQueue.AppendElement(aReaction);
1395
0
    return;
1396
0
  }
1397
0
1398
0
  // If the custom element reactions stack is empty, then:
1399
0
  // Add element to the backup element queue.
1400
0
  MOZ_ASSERT(mReactionsStack.IsEmpty(),
1401
0
             "custom element reactions stack should be empty");
1402
0
  mBackupQueue.AppendElement(aElement);
1403
0
  elementData->mReactionQueue.AppendElement(aReaction);
1404
0
1405
0
  if (mIsBackupQueueProcessing) {
1406
0
    return;
1407
0
  }
1408
0
1409
0
  CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
1410
0
  RefPtr<BackupQueueMicroTask> bqmt = new BackupQueueMicroTask(this);
1411
0
  context->DispatchToMicroTask(bqmt.forget());
1412
0
}
1413
1414
void
1415
CustomElementReactionsStack::InvokeBackupQueue()
1416
0
{
1417
0
  // Check backup queue size in order to reduce function call overhead.
1418
0
  if (!mBackupQueue.IsEmpty()) {
1419
0
    // Upgrade reactions won't be scheduled in backup queue and the exception of
1420
0
    // callback reactions will be automatically reported in CallSetup.
1421
0
    // If the reactions are invoked from backup queue (in microtask check point),
1422
0
    // we don't need to pass global object for error reporting.
1423
0
    InvokeReactions(&mBackupQueue, nullptr);
1424
0
  }
1425
0
  MOZ_ASSERT(mBackupQueue.IsEmpty(),
1426
0
             "There are still some reactions in BackupQueue not being consumed!?!");
1427
0
}
1428
1429
void
1430
CustomElementReactionsStack::InvokeReactions(ElementQueue* aElementQueue,
1431
                                             nsIGlobalObject* aGlobal)
1432
0
{
1433
0
  // This is used for error reporting.
1434
0
  Maybe<AutoEntryScript> aes;
1435
0
  if (aGlobal) {
1436
0
    aes.emplace(aGlobal, "custom elements reaction invocation");
1437
0
  }
1438
0
1439
0
  // Note: It's possible to re-enter this method.
1440
0
  for (uint32_t i = 0; i < aElementQueue->Length(); ++i) {
1441
0
    Element* element = aElementQueue->ElementAt(i);
1442
0
    // ElementQueue hold a element's strong reference, it should not be a nullptr.
1443
0
    MOZ_ASSERT(element);
1444
0
1445
0
    RefPtr<CustomElementData> elementData = element->GetCustomElementData();
1446
0
    if (!elementData || !element->GetOwnerGlobal()) {
1447
0
      // This happens when the document is destroyed and the element is already
1448
0
      // unlinked, no need to fire the callbacks in this case.
1449
0
      continue;
1450
0
    }
1451
0
1452
0
    auto& reactions = elementData->mReactionQueue;
1453
0
    for (uint32_t j = 0; j < reactions.Length(); ++j) {
1454
0
      // Transfer the ownership of the entry due to reentrant invocation of
1455
0
      // this function.
1456
0
      auto reaction(std::move(reactions.ElementAt(j)));
1457
0
      if (reaction) {
1458
0
        if (!aGlobal && reaction->IsUpgradeReaction()) {
1459
0
          nsIGlobalObject* global = element->GetOwnerGlobal();
1460
0
          MOZ_ASSERT(!aes);
1461
0
          aes.emplace(global, "custom elements reaction invocation");
1462
0
        }
1463
0
        ErrorResult rv;
1464
0
        reaction->Invoke(element, rv);
1465
0
        if (aes) {
1466
0
          JSContext* cx = aes->cx();
1467
0
          if (rv.MaybeSetPendingException(cx)) {
1468
0
            aes->ReportException();
1469
0
          }
1470
0
          MOZ_ASSERT(!JS_IsExceptionPending(cx));
1471
0
          if (!aGlobal && reaction->IsUpgradeReaction()) {
1472
0
            aes.reset();
1473
0
          }
1474
0
        }
1475
0
        MOZ_ASSERT(!rv.Failed());
1476
0
      }
1477
0
    }
1478
0
    reactions.Clear();
1479
0
  }
1480
0
  aElementQueue->Clear();
1481
0
}
1482
1483
//-----------------------------------------------------
1484
// CustomElementDefinition
1485
1486
NS_IMPL_CYCLE_COLLECTION_CLASS(CustomElementDefinition)
1487
1488
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CustomElementDefinition)
1489
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mConstructor)
1490
0
  tmp->mCallbacks = nullptr;
1491
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1492
1493
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CustomElementDefinition)
1494
0
  mozilla::dom::LifecycleCallbacks* callbacks = tmp->mCallbacks.get();
1495
0
1496
0
  if (callbacks->mAttributeChangedCallback.WasPassed()) {
1497
0
    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
1498
0
      "mCallbacks->mAttributeChangedCallback");
1499
0
    cb.NoteXPCOMChild(callbacks->mAttributeChangedCallback.Value());
1500
0
  }
1501
0
1502
0
  if (callbacks->mConnectedCallback.WasPassed()) {
1503
0
    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCallbacks->mConnectedCallback");
1504
0
    cb.NoteXPCOMChild(callbacks->mConnectedCallback.Value());
1505
0
  }
1506
0
1507
0
  if (callbacks->mDisconnectedCallback.WasPassed()) {
1508
0
    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCallbacks->mDisconnectedCallback");
1509
0
    cb.NoteXPCOMChild(callbacks->mDisconnectedCallback.Value());
1510
0
  }
1511
0
1512
0
  if (callbacks->mAdoptedCallback.WasPassed()) {
1513
0
    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCallbacks->mAdoptedCallback");
1514
0
    cb.NoteXPCOMChild(callbacks->mAdoptedCallback.Value());
1515
0
  }
1516
0
1517
0
  if (callbacks->mGetCustomInterfaceCallback.WasPassed()) {
1518
0
    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCallbacks->mGetCustomInterfaceCallback");
1519
0
    cb.NoteXPCOMChild(callbacks->mGetCustomInterfaceCallback.Value());
1520
0
  }
1521
0
1522
0
  NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mConstructor");
1523
0
  cb.NoteXPCOMChild(tmp->mConstructor);
1524
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1525
1526
0
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CustomElementDefinition)
1527
0
NS_IMPL_CYCLE_COLLECTION_TRACE_END
1528
1529
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CustomElementDefinition, AddRef)
1530
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CustomElementDefinition, Release)
1531
1532
1533
CustomElementDefinition::CustomElementDefinition(nsAtom* aType,
1534
                                                 nsAtom* aLocalName,
1535
                                                 int32_t aNamespaceID,
1536
                                                 Function* aConstructor,
1537
                                                 nsTArray<RefPtr<nsAtom>>&& aObservedAttributes,
1538
                                                 UniquePtr<LifecycleCallbacks>&& aCallbacks)
1539
  : mType(aType),
1540
    mLocalName(aLocalName),
1541
    mNamespaceID(aNamespaceID),
1542
    mConstructor(new CustomElementConstructor(aConstructor)),
1543
    mObservedAttributes(std::move(aObservedAttributes)),
1544
    mCallbacks(std::move(aCallbacks))
1545
0
{
1546
0
}
1547
1548
} // namespace dom
1549
} // namespace mozilla