/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent: */ /* 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/dom/CustomElementRegistryBinding.h" #include "mozilla/dom/HTMLElementBinding.h" #include "mozilla/dom/WebComponentsBinding.h" #include "mozilla/dom/DocGroup.h" #include "nsHTMLTags.h" #include "jsapi.h" namespace mozilla { namespace dom { void CustomElementCallback::Call() { IgnoredErrorResult rv; switch (mType) { case nsIDocument::eCreated: { // For the duration of this callback invocation, the element is being created // flag must be set to true. mOwnerData->mElementIsBeingCreated = true; // The callback hasn't actually been invoked yet, but we need to flip // this now in order to enqueue the connected callback. This is a spec // bug (w3c bug 27437). mOwnerData->mCreatedCallbackInvoked = true; // If ELEMENT is connected, enqueue connected callback for ELEMENT. nsIDocument* document = mThisObject->GetComposedDoc(); if (document) { NodeInfo* ni = mThisObject->NodeInfo(); nsDependentAtomString extType(mOwnerData->mType); // We need to do this because at this point, CustomElementDefinition is // not set to CustomElementData yet, so EnqueueLifecycleCallback will // fail to find the CE definition for this custom element. // This will go away eventually since there is no created callback in v1. CustomElementDefinition* definition = nsContentUtils::LookupCustomElementDefinition(document, ni->LocalName(), ni->NamespaceID(), extType.IsEmpty() ? nullptr : &extType); nsContentUtils::EnqueueLifecycleCallback( nsIDocument::eConnected, mThisObject, nullptr, definition); } static_cast(mCallback.get())->Call(mThisObject, rv); mOwnerData->mElementIsBeingCreated = false; break; } case nsIDocument::eConnected: static_cast(mCallback.get())->Call(mThisObject, rv); break; case nsIDocument::eDetached: static_cast(mCallback.get())->Call(mThisObject, rv); break; case nsIDocument::eAttributeChanged: static_cast(mCallback.get())->Call(mThisObject, mArgs.name, mArgs.oldValue, mArgs.newValue, mArgs.namespaceURI, rv); 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); } CustomElementCallback::CustomElementCallback(Element* aThisObject, nsIDocument::ElementCallbackType aCallbackType, mozilla::dom::CallbackFunction* aCallback, CustomElementData* aOwnerData) : mThisObject(aThisObject), mCallback(aCallback), mType(aCallbackType), mOwnerData(aOwnerData) { } //----------------------------------------------------- // CustomElementConstructor already_AddRefed CustomElementConstructor::Construct(const char* aExecutionReason, ErrorResult& aRv) { CallSetup s(this, aRv, aExecutionReason, CallbackFunction::eRethrowExceptions); JSContext* cx = s.GetContext(); if (!cx) { MOZ_ASSERT(aRv.Failed()); return nullptr; } JS::Rooted result(cx); JS::Rooted constructor(cx, JS::ObjectValue(*mCallback)); if (!JS::Construct(cx, constructor, JS::HandleValueArray::empty(), &result)) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return nullptr; } RefPtr element; if (NS_FAILED(UNWRAP_OBJECT(Element, &result, element))) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return nullptr; } return element.forget(); } //----------------------------------------------------- // CustomElementData CustomElementData::CustomElementData(nsIAtom* aType) : CustomElementData(aType, CustomElementData::State::eUndefined) { } CustomElementData::CustomElementData(nsIAtom* aType, State aState) : mType(aType) , mElementIsBeingCreated(false) , mCreatedCallbackInvoked(true) , mState(aState) { } //----------------------------------------------------- // CustomElementRegistry namespace { class MOZ_RAII AutoConstructionStackEntry final { public: AutoConstructionStackEntry(nsTArray>& aStack, nsGenericHTMLElement* aElement) : mStack(aStack) { mIndex = mStack.Length(); mStack.AppendElement(aElement); } ~AutoConstructionStackEntry() { MOZ_ASSERT(mIndex == mStack.Length() - 1, "Removed element should be the last element"); mStack.RemoveElementAt(mIndex); } private: nsTArray>& mStack; uint32_t mIndex; }; } // namespace anonymous // Only needed for refcounted objects. 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(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(mWindow) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CustomElementRegistry) for (auto iter = tmp->mCustomDefinitions.Iter(); !iter.Done(); iter.Next()) { aCallbacks.Trace(&iter.UserData()->mPrototype, "mCustomDefinitions prototype", aClosure); } for (ConstructorMap::Enum iter(tmp->mConstructors); !iter.empty(); iter.popFront()) { aCallbacks.Trace(&iter.front().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); MOZ_ASSERT(aWindow->IsInnerWindow()); MOZ_ALWAYS_TRUE(mConstructors.init()); mozilla::HoldJSObjects(this); } CustomElementRegistry::~CustomElementRegistry() { mozilla::DropJSObjects(this); } CustomElementDefinition* CustomElementRegistry::LookupCustomElementDefinition(const nsAString& aLocalName, const nsAString* aIs) const { nsCOMPtr localNameAtom = NS_Atomize(aLocalName); nsCOMPtr typeAtom = aIs ? NS_Atomize(*aIs) : localNameAtom; CustomElementDefinition* data = mCustomDefinitions.GetWeak(typeAtom); if (data && data->mLocalName == localNameAtom) { return data; } return nullptr; } CustomElementDefinition* CustomElementRegistry::LookupCustomElementDefinition(JSContext* aCx, JSObject* aConstructor) const { JS::Rooted constructor(aCx, js::CheckedUnwrap(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, nsIAtom* aTypeName) { 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.