/* -*- 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 "nsIParserService.h" #include "jsapi.h" namespace mozilla { namespace dom { void CustomElementCallback::Call() { ErrorResult 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 attached callback. This is a spec // bug (w3c bug 27437). mOwnerData->mCreatedCallbackInvoked = true; // If ELEMENT is in a document and this document has a browsing context, // enqueue attached callback for ELEMENT. nsIDocument* document = mThisObject->GetComposedDoc(); if (document && document->GetDocShell()) { nsContentUtils::EnqueueLifecycleCallback( document, nsIDocument::eAttached, mThisObject); } static_cast(mCallback.get())->Call(mThisObject, rv); mOwnerData->mElementIsBeingCreated = false; break; } case nsIDocument::eAttached: 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, 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) { } CustomElementData::CustomElementData(nsIAtom* aType) : mType(aType), mCurrentCallback(-1), mElementIsBeingCreated(false), mCreatedCallbackInvoked(true), mAssociatedMicroTask(-1) { } void CustomElementData::RunCallbackQueue() { // Note: It's possible to re-enter this method. while (static_cast(++mCurrentCallback) < mCallbackQueue.Length()) { mCallbackQueue[mCurrentCallback]->Call(); } mCallbackQueue.Clear(); mCurrentCallback = -1; } // Only needed for refcounted objects. NS_IMPL_CYCLE_COLLECTION_CLASS(CustomElementRegistry) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CustomElementRegistry) tmp->mCustomDefinitions.Clear(); tmp->mConstructors.clear(); 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) for (auto iter = tmp->mCustomDefinitions.Iter(); !iter.Done(); iter.Next()) { nsAutoPtr& callbacks = iter.UserData()->mCallbacks; if (callbacks->mAttributeChangedCallback.WasPassed()) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCustomDefinitions->mCallbacks->mAttributeChangedCallback"); cb.NoteXPCOMChild(callbacks->mAttributeChangedCallback.Value()); } if (callbacks->mCreatedCallback.WasPassed()) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCustomDefinitions->mCallbacks->mCreatedCallback"); cb.NoteXPCOMChild(callbacks->mCreatedCallback.Value()); } if (callbacks->mAttachedCallback.WasPassed()) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCustomDefinitions->mCallbacks->mAttachedCallback"); cb.NoteXPCOMChild(callbacks->mAttachedCallback.Value()); } if (callbacks->mDetachedCallback.WasPassed()) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCustomDefinitions->mCallbacks->mDetachedCallback"); cb.NoteXPCOMChild(callbacks->mDetachedCallback.Value()); } } NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWhenDefinedPromiseMap) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS 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()->mConstructor, "mCustomDefinitions constructor", aClosure); 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 /* static */ bool CustomElementRegistry::IsCustomElementEnabled(JSContext* aCx, JSObject* aObject) { return Preferences::GetBool("dom.webcomponents.customelements.enabled") || Preferences::GetBool("dom.webcomponents.enabled"); } /* static */ already_AddRefed CustomElementRegistry::Create(nsPIDOMWindowInner* aWindow) { MOZ_ASSERT(aWindow); MOZ_ASSERT(aWindow->IsInnerWindow()); if (!aWindow->GetDocShell()) { return nullptr; } if (!IsCustomElementEnabled()) { return nullptr; } RefPtr customElementRegistry = new CustomElementRegistry(aWindow); if (!customElementRegistry->Init()) { return nullptr; } return customElementRegistry.forget(); } /* static */ void CustomElementRegistry::ProcessTopElementQueue() { MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); nsTArray>& stack = *sProcessingStack; uint32_t firstQueue = stack.LastIndexOf((CustomElementData*) nullptr); for (uint32_t i = firstQueue + 1; i < stack.Length(); ++i) { // Callback queue may have already been processed in an earlier // element queue or in an element queue that was popped // off more recently. if (stack[i]->mAssociatedMicroTask != -1) { stack[i]->RunCallbackQueue(); stack[i]->mAssociatedMicroTask = -1; } } // If this was actually the base element queue, don't bother trying to pop // the first "queue" marker (sentinel). if (firstQueue != 0) { stack.SetLength(firstQueue); } else { // Don't pop sentinel for base element queue. stack.SetLength(1); } } /* static */ void CustomElementRegistry::XPCOMShutdown() { sProcessingStack.reset(); } /* static */ Maybe>> CustomElementRegistry::sProcessingStack; CustomElementRegistry::CustomElementRegistry(nsPIDOMWindowInner* aWindow) : mWindow(aWindow) , mIsCustomDefinitionRunning(false) { mozilla::HoldJSObjects(this); if (!sProcessingStack) { sProcessingStack.emplace(); // Add the base queue sentinel to the processing stack. sProcessingStack->AppendElement((CustomElementData*) nullptr); } } CustomElementRegistry::~CustomElementRegistry() { mozilla::DropJSObjects(this); } bool CustomElementRegistry::Init() { return mConstructors.init(); } 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.Get(typeAtom); if (data && data->mLocalName == localNameAtom) { return data; } return nullptr; } 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.