/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/CustomElementRegistry.h" #include "mozilla/AsyncEventDispatcher.h" #include "mozilla/CycleCollectedJSContext.h" #include "mozilla/dom/AutoEntryScript.h" #include "mozilla/dom/CustomElementRegistryBinding.h" #include "mozilla/dom/ElementBinding.h" #include "mozilla/dom/HTMLElement.h" #include "mozilla/dom/HTMLElementBinding.h" #include "mozilla/dom/PrimitiveConversions.h" #include "mozilla/dom/ShadowIncludingTreeIterator.h" #include "mozilla/dom/XULElementBinding.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/DocGroup.h" #include "mozilla/dom/CustomEvent.h" #include "mozilla/dom/ShadowRoot.h" #include "mozilla/AutoRestore.h" #include "mozilla/HoldDropJSObjects.h" #include "mozilla/UseCounter.h" #include "nsContentUtils.h" #include "nsHTMLTags.h" #include "jsapi.h" #include "js/ForOfIterator.h" // JS::ForOfIterator #include "js/PropertyAndElement.h" // JS_GetProperty, JS_GetUCProperty #include "xpcprivate.h" #include "nsGlobalWindow.h" #include "nsNameSpaceManager.h" namespace mozilla::dom { //----------------------------------------------------- // CustomElementUpgradeReaction class CustomElementUpgradeReaction final : public CustomElementReaction { public: explicit CustomElementUpgradeReaction(CustomElementDefinition* aDefinition) : mDefinition(aDefinition) { mIsUpgradeReaction = true; } virtual void Traverse( nsCycleCollectionTraversalCallback& aCb) const override { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mDefinition"); aCb.NoteNativeChild( mDefinition, NS_CYCLE_COLLECTION_PARTICIPANT(CustomElementDefinition)); } size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override { // We don't really own mDefinition. return aMallocSizeOf(this); } private: MOZ_CAN_RUN_SCRIPT virtual void Invoke(Element* aElement, ErrorResult& aRv) override { CustomElementRegistry::Upgrade(aElement, mDefinition, aRv); } const RefPtr mDefinition; }; //----------------------------------------------------- // CustomElementCallbackReaction class CustomElementCallback { public: CustomElementCallback(Element* aThisObject, ElementCallbackType aCallbackType, CallbackFunction* aCallback, const LifecycleCallbackArgs& aArgs); void Traverse(nsCycleCollectionTraversalCallback& aCb) const; size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const; void Call(); static UniquePtr Create( ElementCallbackType aType, Element* aCustomElement, const LifecycleCallbackArgs& aArgs, CustomElementDefinition* aDefinition); private: // The this value to use for invocation of the callback. RefPtr mThisObject; RefPtr mCallback; // The type of callback (eCreated, eAttached, etc.) ElementCallbackType mType; // Arguments to be passed to the callback, LifecycleCallbackArgs mArgs; }; class CustomElementCallbackReaction final : public CustomElementReaction { public: explicit CustomElementCallbackReaction( UniquePtr aCustomElementCallback) : mCustomElementCallback(std::move(aCustomElementCallback)) {} virtual void Traverse( nsCycleCollectionTraversalCallback& aCb) const override { mCustomElementCallback->Traverse(aCb); } size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override { size_t n = aMallocSizeOf(this); n += mCustomElementCallback->SizeOfIncludingThis(aMallocSizeOf); return n; } private: virtual void Invoke(Element* aElement, ErrorResult& aRv) override { mCustomElementCallback->Call(); } UniquePtr mCustomElementCallback; }; //----------------------------------------------------- // CustomElementCallback size_t LifecycleCallbackArgs::SizeOfExcludingThis( MallocSizeOf aMallocSizeOf) const { size_t n = mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf); n += mOldValue.SizeOfExcludingThisIfUnshared(aMallocSizeOf); n += mNewValue.SizeOfExcludingThisIfUnshared(aMallocSizeOf); n += mNamespaceURI.SizeOfExcludingThisIfUnshared(aMallocSizeOf); return n; } /* static */ UniquePtr CustomElementCallback::Create( ElementCallbackType aType, Element* aCustomElement, const LifecycleCallbackArgs& aArgs, CustomElementDefinition* aDefinition) { MOZ_ASSERT(aDefinition, "CustomElementDefinition should not be null"); MOZ_ASSERT(aCustomElement->GetCustomElementData(), "CustomElementData should exist"); // Let CALLBACK be the callback associated with the key NAME in CALLBACKS. CallbackFunction* func = nullptr; switch (aType) { case ElementCallbackType::eConnected: if (aDefinition->mCallbacks->mConnectedCallback.WasPassed()) { func = aDefinition->mCallbacks->mConnectedCallback.Value(); } break; case ElementCallbackType::eDisconnected: if (aDefinition->mCallbacks->mDisconnectedCallback.WasPassed()) { func = aDefinition->mCallbacks->mDisconnectedCallback.Value(); } break; case ElementCallbackType::eAdopted: if (aDefinition->mCallbacks->mAdoptedCallback.WasPassed()) { func = aDefinition->mCallbacks->mAdoptedCallback.Value(); } break; case ElementCallbackType::eAttributeChanged: if (aDefinition->mCallbacks->mAttributeChangedCallback.WasPassed()) { func = aDefinition->mCallbacks->mAttributeChangedCallback.Value(); } break; case ElementCallbackType::eFormAssociated: if (aDefinition->mFormAssociatedCallbacks->mFormAssociatedCallback .WasPassed()) { func = aDefinition->mFormAssociatedCallbacks->mFormAssociatedCallback .Value(); } break; case ElementCallbackType::eFormReset: if (aDefinition->mFormAssociatedCallbacks->mFormResetCallback .WasPassed()) { func = aDefinition->mFormAssociatedCallbacks->mFormResetCallback.Value(); } break; case ElementCallbackType::eFormDisabled: if (aDefinition->mFormAssociatedCallbacks->mFormDisabledCallback .WasPassed()) { func = aDefinition->mFormAssociatedCallbacks->mFormDisabledCallback .Value(); } break; case ElementCallbackType::eGetCustomInterface: MOZ_ASSERT_UNREACHABLE("Don't call GetCustomInterface through callback"); break; } // If there is no such callback, stop. if (!func) { return nullptr; } // Add CALLBACK to ELEMENT's callback queue. return MakeUnique(aCustomElement, aType, func, aArgs); } void CustomElementCallback::Call() { switch (mType) { case ElementCallbackType::eConnected: static_cast(mCallback.get()) ->Call(mThisObject); break; case ElementCallbackType::eDisconnected: static_cast(mCallback.get()) ->Call(mThisObject); break; case ElementCallbackType::eAdopted: static_cast(mCallback.get()) ->Call(mThisObject, mArgs.mOldDocument, mArgs.mNewDocument); break; case ElementCallbackType::eAttributeChanged: static_cast(mCallback.get()) ->Call(mThisObject, mArgs.mName, mArgs.mOldValue, mArgs.mNewValue, mArgs.mNamespaceURI); break; case ElementCallbackType::eFormAssociated: static_cast(mCallback.get()) ->Call(mThisObject, mArgs.mForm); break; case ElementCallbackType::eFormReset: static_cast(mCallback.get()) ->Call(mThisObject); break; case ElementCallbackType::eFormDisabled: static_cast(mCallback.get()) ->Call(mThisObject, mArgs.mDisabled); break; case ElementCallbackType::eGetCustomInterface: MOZ_ASSERT_UNREACHABLE("Don't call GetCustomInterface through callback"); break; } } void CustomElementCallback::Traverse( nsCycleCollectionTraversalCallback& aCb) const { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mThisObject"); aCb.NoteXPCOMChild(mThisObject); NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mCallback"); aCb.NoteXPCOMChild(mCallback); } size_t CustomElementCallback::SizeOfIncludingThis( MallocSizeOf aMallocSizeOf) const { size_t n = aMallocSizeOf(this); // We don't uniquely own mThisObject. // We own mCallback but it doesn't have any special memory reporting we can do // for it other than report its own size. n += aMallocSizeOf(mCallback); n += mArgs.SizeOfExcludingThis(aMallocSizeOf); return n; } CustomElementCallback::CustomElementCallback( Element* aThisObject, ElementCallbackType aCallbackType, mozilla::dom::CallbackFunction* aCallback, const LifecycleCallbackArgs& aArgs) : mThisObject(aThisObject), mCallback(aCallback), mType(aCallbackType), mArgs(aArgs) {} //----------------------------------------------------- // CustomElementData CustomElementData::CustomElementData(nsAtom* aType) : CustomElementData(aType, CustomElementData::State::eUndefined) {} CustomElementData::CustomElementData(nsAtom* aType, State aState) : mState(aState), mType(aType) {} void CustomElementData::SetCustomElementDefinition( CustomElementDefinition* aDefinition) { // Only allow reset definition to nullptr if the custom element state is // "failed". MOZ_ASSERT(aDefinition ? !mCustomElementDefinition : mState == State::eFailed); MOZ_ASSERT_IF(aDefinition, aDefinition->mType == mType); mCustomElementDefinition = aDefinition; } void CustomElementData::AttachedInternals() { MOZ_ASSERT(!mIsAttachedInternals); mIsAttachedInternals = true; } CustomElementDefinition* CustomElementData::GetCustomElementDefinition() const { // Per spec, if there is a definition, the custom element state should be // either "failed" (during upgrade) or "customized". MOZ_ASSERT_IF(mCustomElementDefinition, mState != State::eUndefined); return mCustomElementDefinition; } bool CustomElementData::IsFormAssociated() const { // https://html.spec.whatwg.org/#form-associated-custom-element return mCustomElementDefinition && !mCustomElementDefinition->IsCustomBuiltIn() && mCustomElementDefinition->mFormAssociated; } void CustomElementData::Traverse( nsCycleCollectionTraversalCallback& aCb) const { for (uint32_t i = 0; i < mReactionQueue.Length(); i++) { if (mReactionQueue[i]) { mReactionQueue[i]->Traverse(aCb); } } if (mCustomElementDefinition) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mCustomElementDefinition"); aCb.NoteNativeChild( mCustomElementDefinition, NS_CYCLE_COLLECTION_PARTICIPANT(CustomElementDefinition)); } if (mElementInternals) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mElementInternals"); aCb.NoteXPCOMChild(ToSupports(mElementInternals.get())); } } void CustomElementData::Unlink() { mReactionQueue.Clear(); if (mElementInternals) { mElementInternals->Unlink(); mElementInternals = nullptr; } mCustomElementDefinition = nullptr; } size_t CustomElementData::SizeOfIncludingThis( MallocSizeOf aMallocSizeOf) const { size_t n = aMallocSizeOf(this); n += mReactionQueue.ShallowSizeOfExcludingThis(aMallocSizeOf); for (auto& reaction : mReactionQueue) { // "reaction" can be null if we're being called indirectly from // InvokeReactions (e.g. due to a reaction causing a memory report to be // captured somehow). if (reaction) { n += reaction->SizeOfIncludingThis(aMallocSizeOf); } } return n; } //----------------------------------------------------- // CustomElementRegistry namespace { class MOZ_RAII AutoConstructionStackEntry final { public: AutoConstructionStackEntry(nsTArray>& aStack, Element* aElement) : mStack(aStack) { MOZ_ASSERT(aElement->IsHTMLElement() || aElement->IsXULElement()); #ifdef DEBUG mIndex = mStack.Length(); #endif mStack.AppendElement(aElement); } ~AutoConstructionStackEntry() { MOZ_ASSERT(mIndex == mStack.Length() - 1, "Removed element should be the last element"); mStack.RemoveLastElement(); } private: nsTArray>& mStack; #ifdef DEBUG uint32_t mIndex; #endif }; } // namespace NS_IMPL_CYCLE_COLLECTION_CLASS(CustomElementRegistry) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CustomElementRegistry) tmp->mConstructors.clear(); NS_IMPL_CYCLE_COLLECTION_UNLINK(mCustomDefinitions) NS_IMPL_CYCLE_COLLECTION_UNLINK(mWhenDefinedPromiseMap) NS_IMPL_CYCLE_COLLECTION_UNLINK(mElementCreationCallbacks) NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow) NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CustomElementRegistry) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCustomDefinitions) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWhenDefinedPromiseMap) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElementCreationCallbacks) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CustomElementRegistry) for (auto iter = tmp->mConstructors.iter(); !iter.done(); iter.next()) { aCallbacks.Trace(&iter.get().mutableKey(), "mConstructors key", aClosure); } NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_IMPL_CYCLE_COLLECTING_ADDREF(CustomElementRegistry) NS_IMPL_CYCLE_COLLECTING_RELEASE(CustomElementRegistry) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CustomElementRegistry) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END CustomElementRegistry::CustomElementRegistry(nsPIDOMWindowInner* aWindow) : mWindow(aWindow), mIsCustomDefinitionRunning(false) { MOZ_ASSERT(aWindow); mozilla::HoldJSObjects(this); } CustomElementRegistry::~CustomElementRegistry() { mozilla::DropJSObjects(this); } NS_IMETHODIMP CustomElementRegistry::RunCustomElementCreationCallback::Run() { ErrorResult er; nsDependentAtomString value(mAtom); mCallback->Call(value, er); MOZ_ASSERT(NS_SUCCEEDED(er.StealNSResult()), "chrome JavaScript error in the callback."); RefPtr definition = mRegistry->mCustomDefinitions.Get(mAtom); MOZ_ASSERT(definition, "Callback should define the definition of type."); MOZ_ASSERT(!mRegistry->mElementCreationCallbacks.GetWeak(mAtom), "Callback should be removed."); mozilla::UniquePtr>> elements; mRegistry->mElementCreationCallbacksUpgradeCandidatesMap.Remove(mAtom, &elements); MOZ_ASSERT(elements, "There should be a list"); for (const auto& key : *elements) { nsCOMPtr elem = do_QueryReferent(key); if (!elem) { continue; } CustomElementRegistry::Upgrade(elem, definition, er); MOZ_ASSERT(NS_SUCCEEDED(er.StealNSResult()), "chrome JavaScript error in custom element construction."); } return NS_OK; } CustomElementDefinition* CustomElementRegistry::LookupCustomElementDefinition( nsAtom* aNameAtom, int32_t aNameSpaceID, nsAtom* aTypeAtom) { CustomElementDefinition* data = mCustomDefinitions.GetWeak(aTypeAtom); if (!data) { RefPtr callback; mElementCreationCallbacks.Get(aTypeAtom, getter_AddRefs(callback)); if (callback) { mElementCreationCallbacks.Remove(aTypeAtom); mElementCreationCallbacksUpgradeCandidatesMap.GetOrInsertNew(aTypeAtom); RefPtr runnable = new RunCustomElementCreationCallback(this, aTypeAtom, callback); nsContentUtils::AddScriptRunner(runnable.forget()); data = mCustomDefinitions.GetWeak(aTypeAtom); } } if (data && data->mLocalName == aNameAtom && data->mNamespaceID == aNameSpaceID) { return data; } return nullptr; } CustomElementDefinition* CustomElementRegistry::LookupCustomElementDefinition( JSContext* aCx, JSObject* aConstructor) const { // We're looking up things that tested true for JS::IsConstructor, // so doing a CheckedUnwrapStatic is fine here. JS::Rooted constructor(aCx, js::CheckedUnwrapStatic(aConstructor)); const auto& ptr = mConstructors.lookup(constructor); if (!ptr) { return nullptr; } CustomElementDefinition* definition = mCustomDefinitions.GetWeak(ptr->value()); MOZ_ASSERT(definition, "Definition must be found in mCustomDefinitions"); return definition; } void CustomElementRegistry::RegisterUnresolvedElement(Element* aElement, nsAtom* aTypeName) { // We don't have a use-case for a Custom Element inside NAC, and continuing // here causes performance issues for NAC + XBL anonymous content. if (aElement->IsInNativeAnonymousSubtree()) { return; } mozilla::dom::NodeInfo* info = aElement->NodeInfo(); // Candidate may be a custom element through extension, // in which case the custom element type name will not // match the element tag name. e.g.