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