diff options
author | New Tobin Paradigm <email@mattatobin.com> | 2020-01-26 17:13:16 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-01-26 17:13:16 -0500 |
commit | 22ed46f7cca6d388e7e9e2bf53851ab488cc62ab (patch) | |
tree | fb5b7a509abe61a05bf6f3f88371271be4e5ccaa /dom/base/CustomElementRegistry.cpp | |
parent | a1cce3b2b00bbd9f4983013ddd8934a7bccb9e99 (diff) | |
parent | 3c70b297c7be12b7d7e120de04a38f40b600928b (diff) | |
download | UXP-22ed46f7cca6d388e7e9e2bf53851ab488cc62ab.tar UXP-22ed46f7cca6d388e7e9e2bf53851ab488cc62ab.tar.gz UXP-22ed46f7cca6d388e7e9e2bf53851ab488cc62ab.tar.lz UXP-22ed46f7cca6d388e7e9e2bf53851ab488cc62ab.tar.xz UXP-22ed46f7cca6d388e7e9e2bf53851ab488cc62ab.zip |
Merge pull request #1374 from g4jc/custom_elementsv1
Custom Elements v1
Diffstat (limited to 'dom/base/CustomElementRegistry.cpp')
-rw-r--r-- | dom/base/CustomElementRegistry.cpp | 996 |
1 files changed, 667 insertions, 329 deletions
diff --git a/dom/base/CustomElementRegistry.cpp b/dom/base/CustomElementRegistry.cpp index 3f202d33b..99452df65 100644 --- a/dom/base/CustomElementRegistry.cpp +++ b/dom/base/CustomElementRegistry.cpp @@ -6,9 +6,11 @@ #include "mozilla/dom/CustomElementRegistry.h" +#include "mozilla/CycleCollectedJSContext.h" #include "mozilla/dom/CustomElementRegistryBinding.h" #include "mozilla/dom/HTMLElementBinding.h" #include "mozilla/dom/WebComponentsBinding.h" +#include "mozilla/dom/DocGroup.h" #include "nsIParserService.h" #include "jsapi.h" @@ -18,40 +20,21 @@ namespace dom { void CustomElementCallback::Call() { - ErrorResult rv; + 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 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<LifecycleCreatedCallback *>(mCallback.get())->Call(mThisObject, rv); - mOwnerData->mElementIsBeingCreated = false; + case nsIDocument::eConnected: + static_cast<LifecycleConnectedCallback *>(mCallback.get())->Call(mThisObject, rv); break; - } - case nsIDocument::eAttached: - static_cast<LifecycleAttachedCallback *>(mCallback.get())->Call(mThisObject, rv); + case nsIDocument::eDisconnected: + static_cast<LifecycleDisconnectedCallback *>(mCallback.get())->Call(mThisObject, rv); break; - case nsIDocument::eDetached: - static_cast<LifecycleDetachedCallback *>(mCallback.get())->Call(mThisObject, rv); + case nsIDocument::eAdopted: + static_cast<LifecycleAdoptedCallback *>(mCallback.get())->Call(mThisObject, + mAdoptedCallbackArgs.mOldDocument, mAdoptedCallbackArgs.mNewDocument, rv); break; case nsIDocument::eAttributeChanged: static_cast<LifecycleAttributeChangedCallback *>(mCallback.get())->Call(mThisObject, - mArgs.name, mArgs.oldValue, mArgs.newValue, rv); + mArgs.name, mArgs.oldValue, mArgs.newValue, mArgs.namespaceURI, rv); break; } } @@ -68,87 +51,164 @@ CustomElementCallback::Traverse(nsCycleCollectionTraversalCallback& aCb) const CustomElementCallback::CustomElementCallback(Element* aThisObject, nsIDocument::ElementCallbackType aCallbackType, - mozilla::dom::CallbackFunction* aCallback, - CustomElementData* aOwnerData) + mozilla::dom::CallbackFunction* aCallback) : mThisObject(aThisObject), mCallback(aCallback), - mType(aCallbackType), - mOwnerData(aOwnerData) + mType(aCallbackType) +{ +} + +//----------------------------------------------------- +// CustomElementConstructor + +already_AddRefed<Element> +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<JSObject*> result(cx); + JS::Rooted<JS::Value> constructor(cx, JS::ObjectValue(*mCallback)); + if (!JS::Construct(cx, constructor, JS::HandleValueArray::empty(), &result)) { + aRv.NoteJSContextException(cx); + return nullptr; + } + + RefPtr<Element> element; + if (NS_FAILED(UNWRAP_OBJECT(Element, &result, element))) { + return nullptr; + } + + return element.forget(); } +//----------------------------------------------------- +// CustomElementData + CustomElementData::CustomElementData(nsIAtom* aType) - : mType(aType), - mCurrentCallback(-1), - mElementIsBeingCreated(false), - mCreatedCallbackInvoked(true), - mAssociatedMicroTask(-1) + : CustomElementData(aType, CustomElementData::State::eUndefined) +{ +} + +CustomElementData::CustomElementData(nsIAtom* aType, State aState) + : mState(aState) + , mType(aType) { } void -CustomElementData::RunCallbackQueue() +CustomElementData::SetCustomElementDefinition(CustomElementDefinition* aDefinition) { - // Note: It's possible to re-enter this method. - while (static_cast<uint32_t>(++mCurrentCallback) < mCallbackQueue.Length()) { - mCallbackQueue[mCurrentCallback]->Call(); + MOZ_ASSERT(mState == State::eCustom); + MOZ_ASSERT(!mCustomElementDefinition); + MOZ_ASSERT(aDefinition->mType == mType); + + mCustomElementDefinition = aDefinition; +} + +CustomElementDefinition* +CustomElementData::GetCustomElementDefinition() +{ + MOZ_ASSERT(mCustomElementDefinition ? mState == State::eCustom + : mState != State::eCustom); + + return mCustomElementDefinition; +} + +nsIAtom* +CustomElementData::GetCustomElementType() +{ + return mType; +} + +void +CustomElementData::Traverse(nsCycleCollectionTraversalCallback& aCb) const +{ + for (uint32_t i = 0; i < mReactionQueue.Length(); i++) { + if (mReactionQueue[i]) { + mReactionQueue[i]->Traverse(aCb); + } } - mCallbackQueue.Clear(); - mCurrentCallback = -1; + if (mCustomElementDefinition) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mCustomElementDefinition"); + aCb.NoteNativeChild(mCustomElementDefinition, + NS_CYCLE_COLLECTION_PARTICIPANT(CustomElementDefinition)); + } } +void +CustomElementData::Unlink() +{ + mReactionQueue.Clear(); + mCustomElementDefinition = nullptr; +} + +//----------------------------------------------------- +// CustomElementRegistry + +namespace { + +class MOZ_RAII AutoConstructionStackEntry final +{ +public: + AutoConstructionStackEntry(nsTArray<RefPtr<nsGenericHTMLElement>>& 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<RefPtr<nsGenericHTMLElement>>& 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->mCustomDefinitions.Clear(); + 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) - for (auto iter = tmp->mCustomDefinitions.Iter(); !iter.Done(); iter.Next()) { - nsAutoPtr<LifecycleCallbacks>& 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(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()->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 @@ -163,97 +223,52 @@ NS_INTERFACE_MAP_END /* static */ bool CustomElementRegistry::IsCustomElementEnabled(JSContext* aCx, JSObject* aObject) { - return Preferences::GetBool("dom.webcomponents.customelements.enabled") || - Preferences::GetBool("dom.webcomponents.enabled"); + return nsContentUtils::IsCustomElementsEnabled(); } -/* static */ already_AddRefed<CustomElementRegistry> -CustomElementRegistry::Create(nsPIDOMWindowInner* aWindow) +CustomElementRegistry::CustomElementRegistry(nsPIDOMWindowInner* aWindow) + : mWindow(aWindow) + , mIsCustomDefinitionRunning(false) { MOZ_ASSERT(aWindow); MOZ_ASSERT(aWindow->IsInnerWindow()); + MOZ_ALWAYS_TRUE(mConstructors.init()); - if (!aWindow->GetDocShell()) { - return nullptr; - } - - if (!IsCustomElementEnabled()) { - return nullptr; - } - - RefPtr<CustomElementRegistry> customElementRegistry = - new CustomElementRegistry(aWindow); - return customElementRegistry.forget(); -} - -/* static */ void -CustomElementRegistry::ProcessTopElementQueue() -{ - MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); - - nsTArray<RefPtr<CustomElementData>>& 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); - } + mozilla::HoldJSObjects(this); } -/* static */ void -CustomElementRegistry::XPCOMShutdown() +CustomElementRegistry::~CustomElementRegistry() { - sProcessingStack.reset(); + mozilla::DropJSObjects(this); } -/* static */ Maybe<nsTArray<RefPtr<CustomElementData>>> -CustomElementRegistry::sProcessingStack; - -CustomElementRegistry::CustomElementRegistry(nsPIDOMWindowInner* aWindow) - : mWindow(aWindow) - , mIsCustomDefinitionRunning(false) +CustomElementDefinition* +CustomElementRegistry::LookupCustomElementDefinition(nsIAtom* aNameAtom, + nsIAtom* aTypeAtom) const { - mozilla::HoldJSObjects(this); - - if (!sProcessingStack) { - sProcessingStack.emplace(); - // Add the base queue sentinel to the processing stack. - sProcessingStack->AppendElement((CustomElementData*) nullptr); + CustomElementDefinition* data = mCustomDefinitions.GetWeak(aTypeAtom); + if (data && data->mLocalName == aNameAtom) { + return data; } -} -CustomElementRegistry::~CustomElementRegistry() -{ - mozilla::DropJSObjects(this); + return nullptr; } CustomElementDefinition* -CustomElementRegistry::LookupCustomElementDefinition(const nsAString& aLocalName, - const nsAString* aIs) const +CustomElementRegistry::LookupCustomElementDefinition(JSContext* aCx, + JSObject* aConstructor) const { - nsCOMPtr<nsIAtom> localNameAtom = NS_Atomize(aLocalName); - nsCOMPtr<nsIAtom> typeAtom = aIs ? NS_Atomize(*aIs) : localNameAtom; + JS::Rooted<JSObject*> constructor(aCx, js::CheckedUnwrap(aConstructor)); - CustomElementDefinition* data = mCustomDefinitions.Get(typeAtom); - if (data && data->mLocalName == localNameAtom) { - return data; + const auto& ptr = mConstructors.lookup(constructor); + if (!ptr) { + return nullptr; } - return nullptr; + CustomElementDefinition* definition = mCustomDefinitions.GetWeak(ptr->value()); + MOZ_ASSERT(definition, "Definition must be found in mCustomDefinitions"); + + return definition; } void @@ -269,7 +284,7 @@ CustomElementRegistry::RegisterUnresolvedElement(Element* aElement, nsIAtom* aTy typeName = info->NameAtom(); } - if (mCustomDefinitions.Get(typeName)) { + if (mCustomDefinitions.GetWeak(typeName)) { return; } @@ -282,171 +297,129 @@ CustomElementRegistry::RegisterUnresolvedElement(Element* aElement, nsIAtom* aTy } void -CustomElementRegistry::SetupCustomElement(Element* aElement, - const nsAString* aTypeExtension) +CustomElementRegistry::UnregisterUnresolvedElement(Element* aElement, + nsIAtom* aTypeName) { - nsCOMPtr<nsIAtom> tagAtom = aElement->NodeInfo()->NameAtom(); - nsCOMPtr<nsIAtom> typeAtom = aTypeExtension ? - NS_Atomize(*aTypeExtension) : tagAtom; - - if (aTypeExtension && !aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::is)) { - // Custom element setup in the parser happens after the "is" - // attribute is added. - aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::is, *aTypeExtension, true); - } - - CustomElementDefinition* data = LookupCustomElementDefinition( - aElement->NodeInfo()->LocalName(), aTypeExtension); - - if (!data) { - // The type extension doesn't exist in the registry, - // thus we don't need to enqueue callback or adjust - // the "is" attribute, but it is possibly an upgrade candidate. - RegisterUnresolvedElement(aElement, typeAtom); - return; - } - - if (data->mLocalName != tagAtom) { - // The element doesn't match the local name for the - // definition, thus the element isn't a custom element - // and we don't need to do anything more. - return; + nsTArray<nsWeakPtr>* candidates; + if (mCandidatesMap.Get(aTypeName, &candidates)) { + MOZ_ASSERT(candidates); + // We don't need to iterate the candidates array and remove the element from + // the array for performance reason. It'll be handled by bug 1396620. + for (size_t i = 0; i < candidates->Length(); ++i) { + nsCOMPtr<Element> elem = do_QueryReferent(candidates->ElementAt(i)); + if (elem && elem.get() == aElement) { + candidates->RemoveElementAt(i); + } + } } - - // Enqueuing the created callback will set the CustomElementData on the - // element, causing prototype swizzling to occur in Element::WrapObject. - EnqueueLifecycleCallback(nsIDocument::eCreated, aElement, nullptr, data); } -void -CustomElementRegistry::EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType, - Element* aCustomElement, - LifecycleCallbackArgs* aArgs, - CustomElementDefinition* aDefinition) +/* static */ UniquePtr<CustomElementCallback> +CustomElementRegistry::CreateCustomElementCallback( + nsIDocument::ElementCallbackType aType, Element* aCustomElement, + LifecycleCallbackArgs* aArgs, + LifecycleAdoptedCallbackArgs* aAdoptedCallbackArgs, + CustomElementDefinition* aDefinition) { - CustomElementData* elementData = aCustomElement->GetCustomElementData(); - - // Let DEFINITION be ELEMENT's definition - CustomElementDefinition* definition = aDefinition; - if (!definition) { - mozilla::dom::NodeInfo* info = aCustomElement->NodeInfo(); - - // Make sure we get the correct definition in case the element - // is a extended custom element e.g. <button is="x-button">. - nsCOMPtr<nsIAtom> typeAtom = elementData ? - elementData->mType.get() : info->NameAtom(); + MOZ_ASSERT(aDefinition, "CustomElementDefinition should not be null"); - definition = mCustomDefinitions.Get(typeAtom); - if (!definition || definition->mLocalName != info->NameAtom()) { - // Trying to enqueue a callback for an element that is not - // a custom element. We are done, nothing to do. - return; - } - } - - if (!elementData) { - // Create the custom element data the first time - // that we try to enqueue a callback. - elementData = new CustomElementData(definition->mType); - // aCustomElement takes ownership of elementData - aCustomElement->SetCustomElementData(elementData); - MOZ_ASSERT(aType == nsIDocument::eCreated, - "First callback should be the created callback"); - } + 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 nsIDocument::eCreated: - if (definition->mCallbacks->mCreatedCallback.WasPassed()) { - func = definition->mCallbacks->mCreatedCallback.Value(); + case nsIDocument::eConnected: + if (aDefinition->mCallbacks->mConnectedCallback.WasPassed()) { + func = aDefinition->mCallbacks->mConnectedCallback.Value(); } break; - case nsIDocument::eAttached: - if (definition->mCallbacks->mAttachedCallback.WasPassed()) { - func = definition->mCallbacks->mAttachedCallback.Value(); + case nsIDocument::eDisconnected: + if (aDefinition->mCallbacks->mDisconnectedCallback.WasPassed()) { + func = aDefinition->mCallbacks->mDisconnectedCallback.Value(); } break; - case nsIDocument::eDetached: - if (definition->mCallbacks->mDetachedCallback.WasPassed()) { - func = definition->mCallbacks->mDetachedCallback.Value(); + case nsIDocument::eAdopted: + if (aDefinition->mCallbacks->mAdoptedCallback.WasPassed()) { + func = aDefinition->mCallbacks->mAdoptedCallback.Value(); } break; case nsIDocument::eAttributeChanged: - if (definition->mCallbacks->mAttributeChangedCallback.WasPassed()) { - func = definition->mCallbacks->mAttributeChangedCallback.Value(); + if (aDefinition->mCallbacks->mAttributeChangedCallback.WasPassed()) { + func = aDefinition->mCallbacks->mAttributeChangedCallback.Value(); } break; } // If there is no such callback, stop. if (!func) { - return; - } - - if (aType == nsIDocument::eCreated) { - elementData->mCreatedCallbackInvoked = false; - } else if (!elementData->mCreatedCallbackInvoked) { - // Callbacks other than created callback must not be enqueued - // until after the created callback has been invoked. - return; + return nullptr; } // Add CALLBACK to ELEMENT's callback queue. - CustomElementCallback* callback = new CustomElementCallback(aCustomElement, - aType, - func, - elementData); - // Ownership of callback is taken by mCallbackQueue. - elementData->mCallbackQueue.AppendElement(callback); + auto callback = + MakeUnique<CustomElementCallback>(aCustomElement, aType, func); + if (aArgs) { callback->SetArgs(*aArgs); } - if (!elementData->mElementIsBeingCreated) { - CustomElementData* lastData = - sProcessingStack->SafeLastElement(nullptr); - - // A new element queue needs to be pushed if the queue at the - // top of the stack is associated with another microtask level. - bool shouldPushElementQueue = - (!lastData || lastData->mAssociatedMicroTask < - static_cast<int32_t>(nsContentUtils::MicroTaskLevel())); + if (aAdoptedCallbackArgs) { + callback->SetAdoptedCallbackArgs(*aAdoptedCallbackArgs); + } + return Move(callback); +} - // Push a new element queue onto the processing stack when appropriate - // (when we enter a new microtask). - if (shouldPushElementQueue) { - // Push a sentinel value on the processing stack to mark the - // boundary between the element queues. - sProcessingStack->AppendElement((CustomElementData*) nullptr); +/* static */ void +CustomElementRegistry::EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType, + Element* aCustomElement, + LifecycleCallbackArgs* aArgs, + LifecycleAdoptedCallbackArgs* aAdoptedCallbackArgs, + CustomElementDefinition* aDefinition) +{ + CustomElementDefinition* definition = aDefinition; + if (!definition) { + definition = aCustomElement->GetCustomElementDefinition(); + if (!definition || + definition->mLocalName != aCustomElement->NodeInfo()->NameAtom()) { + return; } + } - sProcessingStack->AppendElement(elementData); - elementData->mAssociatedMicroTask = - static_cast<int32_t>(nsContentUtils::MicroTaskLevel()); - - // Add a script runner to pop and process the element queue at - // the top of the processing stack. - if (shouldPushElementQueue) { - // Lifecycle callbacks enqueued by user agent implementation - // should be invoked prior to returning control back to script. - // Create a script runner to process the top of the processing - // stack as soon as it is safe to run script. - nsCOMPtr<nsIRunnable> runnable = - NS_NewRunnableFunction(&CustomElementRegistry::ProcessTopElementQueue); - nsContentUtils::AddScriptRunner(runnable); + auto callback = + CreateCustomElementCallback(aType, aCustomElement, aArgs, + aAdoptedCallbackArgs, definition); + if (!callback) { + return; + } + + DocGroup* docGroup = aCustomElement->OwnerDoc()->GetDocGroup(); + if (!docGroup) { + return; + } + + if (aType == nsIDocument::eAttributeChanged) { + nsCOMPtr<nsIAtom> attrName = NS_Atomize(aArgs->name); + if (definition->mObservedAttributes.IsEmpty() || + !definition->mObservedAttributes.Contains(attrName)) { + return; } } + + CustomElementReactionsStack* reactionsStack = + docGroup->CustomElementReactionsStack(); + reactionsStack->EnqueueCallbackReaction(aCustomElement, Move(callback)); } void CustomElementRegistry::GetCustomPrototype(nsIAtom* aAtom, JS::MutableHandle<JSObject*> aPrototype) { - mozilla::dom::CustomElementDefinition* definition = mCustomDefinitions.Get(aAtom); + mozilla::dom::CustomElementDefinition* definition = + mCustomDefinitions.GetWeak(aAtom); if (definition) { aPrototype.set(definition->mPrototype); } else { @@ -455,48 +428,29 @@ CustomElementRegistry::GetCustomPrototype(nsIAtom* aAtom, } void -CustomElementRegistry::UpgradeCandidates(JSContext* aCx, - nsIAtom* aKey, - CustomElementDefinition* aDefinition) +CustomElementRegistry::UpgradeCandidates(nsIAtom* aKey, + CustomElementDefinition* aDefinition, + ErrorResult& aRv) { + DocGroup* docGroup = mWindow->GetDocGroup(); + if (!docGroup) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return; + } + + // TODO: Bug 1326028 - Upgrade custom element in shadow-including tree order nsAutoPtr<nsTArray<nsWeakPtr>> candidates; mCandidatesMap.RemoveAndForget(aKey, candidates); if (candidates) { + CustomElementReactionsStack* reactionsStack = + docGroup->CustomElementReactionsStack(); for (size_t i = 0; i < candidates->Length(); ++i) { nsCOMPtr<Element> elem = do_QueryReferent(candidates->ElementAt(i)); if (!elem) { continue; } - elem->RemoveStates(NS_EVENT_STATE_UNRESOLVED); - - // Make sure that the element name matches the name in the definition. - // (e.g. a definition for x-button extending button should match - // <button is="x-button"> but not <x-button>. - if (elem->NodeInfo()->NameAtom() != aDefinition->mLocalName) { - //Skip over this element because definition does not apply. - continue; - } - - MOZ_ASSERT(elem->IsHTMLElement(aDefinition->mLocalName)); - nsWrapperCache* cache; - CallQueryInterface(elem, &cache); - MOZ_ASSERT(cache, "Element doesn't support wrapper cache?"); - - // We want to set the custom prototype in the caller's comparment. - // In the case that element is in a different compartment, - // this will set the prototype on the element's wrapper and - // thus only visible in the wrapper's compartment. - JS::RootedObject wrapper(aCx); - JS::Rooted<JSObject*> prototype(aCx, aDefinition->mPrototype); - if ((wrapper = cache->GetWrapper()) && JS_WrapObject(aCx, &wrapper)) { - if (!JS_SetPrototype(aCx, wrapper, prototype)) { - continue; - } - } - - nsContentUtils::EnqueueLifecycleCallback( - elem->OwnerDoc(), nsIDocument::eCreated, elem, nullptr, aDefinition); + reactionsStack->EnqueueUpgradeReaction(elem, aDefinition); } } } @@ -516,11 +470,7 @@ static const char* kLifeCycleCallbackNames[] = { "connectedCallback", "disconnectedCallback", "adoptedCallback", - "attributeChangedCallback", - // The life cycle callbacks from v0 spec. - "createdCallback", - "attachedCallback", - "detachedCallback" + "attributeChangedCallback" }; static void @@ -600,7 +550,7 @@ CustomElementRegistry::Define(const nsAString& aName, * 3. If this CustomElementRegistry contains an entry with name name, then * throw a "NotSupportedError" DOMException and abort these steps. */ - if (mCustomDefinitions.Get(nameAtom)) { + if (mCustomDefinitions.GetWeak(nameAtom)) { aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return; } @@ -609,9 +559,13 @@ CustomElementRegistry::Define(const nsAString& aName, * 4. If this CustomElementRegistry contains an entry with constructor constructor, * then throw a "NotSupportedError" DOMException and abort these steps. */ - // TODO: Step 3 of HTMLConstructor also needs a way to look up definition by - // using constructor. So I plans to figure out a solution to support both of - // them in bug 1274159. + const auto& ptr = mConstructors.lookup(constructorUnwrapped); + if (ptr) { + MOZ_ASSERT(mCustomDefinitions.GetWeak(ptr->value()), + "Definition must be found in mCustomDefinitions"); + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return; + } /** * 5. Let localName be name. @@ -663,6 +617,7 @@ CustomElementRegistry::Define(const nsAString& aName, JS::Rooted<JSObject*> constructorPrototype(cx); nsAutoPtr<LifecycleCallbacks> callbacksHolder(new LifecycleCallbacks()); + nsCOMArray<nsIAtom> observedAttributes; { // Set mIsCustomDefinitionRunning. /** * 9. Set this CustomElementRegistry's element definition is running flag. @@ -724,6 +679,14 @@ CustomElementRegistry::Define(const nsAString& aName, return; } + // Note: We call the init from the constructorProtoUnwrapped's compartment + // here. + JS::RootedValue rootedv(cx, JS::ObjectValue(*constructorProtoUnwrapped)); + if (!JS_WrapValue(cx, &rootedv) || !callbacksHolder->Init(cx, rootedv)) { + aRv.StealExceptionFromJSContext(cx); + return; + } + /** * 10.5. Let observedAttributes be an empty sequence<DOMString>. * 10.6. If the value of the entry in lifecycleCallbacks with key @@ -735,15 +698,54 @@ CustomElementRegistry::Define(const nsAString& aName, * observedAttributesIterable to a sequence<DOMString>. Rethrow * any exceptions from the conversion. */ - // TODO: Bug 1293921 - Implement connected/disconnected/adopted/attributeChanged lifecycle callbacks for custom elements + if (callbacksHolder->mAttributeChangedCallback.WasPassed()) { + // Enter constructor's compartment. + JSAutoCompartment ac(cx, constructor); + JS::Rooted<JS::Value> observedAttributesIterable(cx); + + if (!JS_GetProperty(cx, constructor, "observedAttributes", + &observedAttributesIterable)) { + aRv.StealExceptionFromJSContext(cx); + return; + } - // Note: We call the init from the constructorProtoUnwrapped's compartment - // here. - JS::RootedValue rootedv(cx, JS::ObjectValue(*constructorProtoUnwrapped)); - if (!JS_WrapValue(cx, &rootedv) || !callbacksHolder->Init(cx, rootedv)) { - aRv.Throw(NS_ERROR_FAILURE); - return; - } + if (!observedAttributesIterable.isUndefined()) { + if (!observedAttributesIterable.isObject()) { + aRv.ThrowTypeError<MSG_NOT_SEQUENCE>(NS_LITERAL_STRING("observedAttributes")); + return; + } + + JS::ForOfIterator iter(cx); + if (!iter.init(observedAttributesIterable, JS::ForOfIterator::AllowNonIterable)) { + aRv.StealExceptionFromJSContext(cx); + return; + } + + if (!iter.valueIsIterable()) { + aRv.ThrowTypeError<MSG_NOT_SEQUENCE>(NS_LITERAL_STRING("observedAttributes")); + return; + } + + JS::Rooted<JS::Value> attribute(cx); + while (true) { + bool done; + if (!iter.next(&attribute, &done)) { + aRv.StealExceptionFromJSContext(cx); + return; + } + if (done) { + break; + } + + nsAutoString attrStr; + if (!ConvertJSValueToString(cx, attribute, eStringify, eStringify, attrStr)) { + aRv.StealExceptionFromJSContext(cx); + return; + } + observedAttributes.AppendElement(NS_Atomize(attrStr)); + } + } + } // Leave constructor's compartment. } // Leave constructorProtoUnwrapped's compartment. } // Unset mIsCustomDefinitionRunning @@ -756,24 +758,34 @@ CustomElementRegistry::Define(const nsAString& aName, // Associate the definition with the custom element. nsCOMPtr<nsIAtom> localNameAtom(NS_Atomize(localName)); LifecycleCallbacks* callbacks = callbacksHolder.forget(); - CustomElementDefinition* definition = + + /** + * 12. Add definition to this CustomElementRegistry. + */ + if (!mConstructors.put(constructorUnwrapped, nameAtom)) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + RefPtr<CustomElementDefinition> definition = new CustomElementDefinition(nameAtom, localNameAtom, - constructor, + &aFunctionConstructor, + Move(observedAttributes), constructorPrototype, callbacks, 0 /* TODO dependent on HTML imports. Bug 877072 */); - /** - * 12. Add definition to this CustomElementRegistry. - */ - mCustomDefinitions.Put(nameAtom, definition); + CustomElementDefinition* def = definition.get(); + mCustomDefinitions.Put(nameAtom, definition.forget()); + + MOZ_ASSERT(mCustomDefinitions.Count() == mConstructors.count(), + "Number of entries should be the same"); /** * 13. 14. 15. Upgrade candidates */ - // TODO: Bug 1299363 - Implement custom element v1 upgrade algorithm - UpgradeCandidates(cx, nameAtom, definition); + UpgradeCandidates(nameAtom, def, aRv); /** * 16. If this CustomElementRegistry's when-defined promise map contains an @@ -796,14 +808,14 @@ CustomElementRegistry::Get(JSContext* aCx, const nsAString& aName, JS::MutableHandle<JS::Value> aRetVal) { nsCOMPtr<nsIAtom> nameAtom(NS_Atomize(aName)); - CustomElementDefinition* data = mCustomDefinitions.Get(nameAtom); + CustomElementDefinition* data = mCustomDefinitions.GetWeak(nameAtom); if (!data) { aRetVal.setUndefined(); return; } - aRetVal.setObject(*data->mConstructor); + aRetVal.setObjectOrNull(data->mConstructor->Callable()); return; } @@ -823,7 +835,7 @@ CustomElementRegistry::WhenDefined(const nsAString& aName, ErrorResult& aRv) return promise.forget(); } - if (mCustomDefinitions.Get(nameAtom)) { + if (mCustomDefinitions.GetWeak(nameAtom)) { promise->MaybeResolve(JS::UndefinedHandleValue); return promise.forget(); } @@ -837,20 +849,346 @@ CustomElementRegistry::WhenDefined(const nsAString& aName, ErrorResult& aRv) return promise.forget(); } +namespace { + +static void +DoUpgrade(Element* aElement, + CustomElementConstructor* aConstructor, + ErrorResult& aRv) +{ + // Rethrow the exception since it might actually throw the exception from the + // upgrade steps back out to the caller of document.createElement. + RefPtr<Element> constructResult = + aConstructor->Construct("Custom Element Upgrade", aRv); + if (aRv.Failed()) { + return; + } + + if (!constructResult || constructResult.get() != aElement) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } +} + +} // anonymous namespace + +// https://html.spec.whatwg.org/multipage/scripting.html#upgrades +/* static */ void +CustomElementRegistry::Upgrade(Element* aElement, + CustomElementDefinition* aDefinition, + ErrorResult& aRv) +{ + aElement->RemoveStates(NS_EVENT_STATE_UNRESOLVED); + + RefPtr<CustomElementData> data = aElement->GetCustomElementData(); + MOZ_ASSERT(data, "CustomElementData should exist"); + + // Step 1 and step 2. + if (data->mState == CustomElementData::State::eCustom || + data->mState == CustomElementData::State::eFailed) { + return; + } + + // Step 3. + if (!aDefinition->mObservedAttributes.IsEmpty()) { + uint32_t count = aElement->GetAttrCount(); + for (uint32_t i = 0; i < count; i++) { + mozilla::dom::BorrowedAttrInfo info = aElement->GetAttrInfoAt(i); + + const nsAttrName* name = info.mName; + nsIAtom* attrName = name->LocalName(); + + if (aDefinition->IsInObservedAttributeList(attrName)) { + int32_t namespaceID = name->NamespaceID(); + nsAutoString attrValue, namespaceURI; + info.mValue->ToString(attrValue); + nsContentUtils::NameSpaceManager()->GetNameSpaceURI(namespaceID, + namespaceURI); + + LifecycleCallbackArgs args = { + nsDependentAtomString(attrName), + NullString(), + attrValue, + (namespaceURI.IsEmpty() ? NullString() : namespaceURI) + }; + nsContentUtils::EnqueueLifecycleCallback(nsIDocument::eAttributeChanged, + aElement, + &args, nullptr, aDefinition); + } + } + } + + // Step 4. + if (aElement->IsInComposedDoc()) { + nsContentUtils::EnqueueLifecycleCallback(nsIDocument::eConnected, aElement, + nullptr, nullptr, aDefinition); + } + + // Step 5. + AutoConstructionStackEntry acs(aDefinition->mConstructionStack, + nsGenericHTMLElement::FromContent(aElement)); + + // Step 6 and step 7. + DoUpgrade(aElement, aDefinition->mConstructor, aRv); + if (aRv.Failed()) { + data->mState = CustomElementData::State::eFailed; + // Empty element's custom element reaction queue. + data->mReactionQueue.Clear(); + return; + } + + // Step 8. + data->mState = CustomElementData::State::eCustom; + + // Step 9. + aElement->SetCustomElementDefinition(aDefinition); +} + +//----------------------------------------------------- +// CustomElementReactionsStack + +void +CustomElementReactionsStack::CreateAndPushElementQueue() +{ + MOZ_ASSERT(mRecursionDepth); + MOZ_ASSERT(!mIsElementQueuePushedForCurrentRecursionDepth); + + // Push a new element queue onto the custom element reactions stack. + mReactionsStack.AppendElement(MakeUnique<ElementQueue>()); + mIsElementQueuePushedForCurrentRecursionDepth = true; +} + +void +CustomElementReactionsStack::PopAndInvokeElementQueue() +{ + MOZ_ASSERT(mRecursionDepth); + MOZ_ASSERT(mIsElementQueuePushedForCurrentRecursionDepth); + MOZ_ASSERT(!mReactionsStack.IsEmpty(), + "Reaction stack shouldn't be empty"); + + // Pop the element queue from the custom element reactions stack, + // and invoke custom element reactions in that queue. + const uint32_t lastIndex = mReactionsStack.Length() - 1; + ElementQueue* elementQueue = mReactionsStack.ElementAt(lastIndex).get(); + // Check element queue size in order to reduce function call overhead. + if (!elementQueue->IsEmpty()) { + // It is still not clear what error reporting will look like in custom + // element, see https://github.com/w3c/webcomponents/issues/635. + // We usually report the error to entry global in gecko, so just follow the + // same behavior here. + // This may be null if it's called from parser, see the case of + // attributeChangedCallback in + // https://html.spec.whatwg.org/multipage/parsing.html#create-an-element-for-the-token + // In that case, the exception of callback reactions will be automatically + // reported in CallSetup. + nsIGlobalObject* global = GetEntryGlobal(); + InvokeReactions(elementQueue, global); + } + + // InvokeReactions() might create other custom element reactions, but those + // new reactions should be already consumed and removed at this point. + MOZ_ASSERT(lastIndex == mReactionsStack.Length() - 1, + "reactions created by InvokeReactions() should be consumed and removed"); + + mReactionsStack.RemoveElementAt(lastIndex); + mIsElementQueuePushedForCurrentRecursionDepth = false; +} + +void +CustomElementReactionsStack::EnqueueUpgradeReaction(Element* aElement, + CustomElementDefinition* aDefinition) +{ + Enqueue(aElement, new CustomElementUpgradeReaction(aDefinition)); +} + +void +CustomElementReactionsStack::EnqueueCallbackReaction(Element* aElement, + UniquePtr<CustomElementCallback> aCustomElementCallback) +{ + Enqueue(aElement, new CustomElementCallbackReaction(Move(aCustomElementCallback))); +} + +void +CustomElementReactionsStack::Enqueue(Element* aElement, + CustomElementReaction* aReaction) +{ + RefPtr<CustomElementData> elementData = aElement->GetCustomElementData(); + MOZ_ASSERT(elementData, "CustomElementData should exist"); + + if (mRecursionDepth) { + // If the element queue is not created for current recursion depth, create + // and push an element queue to reactions stack first. + if (!mIsElementQueuePushedForCurrentRecursionDepth) { + CreateAndPushElementQueue(); + } + + MOZ_ASSERT(!mReactionsStack.IsEmpty()); + // Add element to the current element queue. + mReactionsStack.LastElement()->AppendElement(aElement); + elementData->mReactionQueue.AppendElement(aReaction); + return; + } + + // If the custom element reactions stack is empty, then: + // Add element to the backup element queue. + MOZ_ASSERT(mReactionsStack.IsEmpty(), + "custom element reactions stack should be empty"); + MOZ_ASSERT(!aReaction->IsUpgradeReaction(), + "Upgrade reaction should not be scheduled to backup queue"); + mBackupQueue.AppendElement(aElement); + elementData->mReactionQueue.AppendElement(aReaction); + + if (mIsBackupQueueProcessing) { + return; + } + + CycleCollectedJSContext* context = CycleCollectedJSContext::Get(); + RefPtr<BackupQueueMicroTask> bqmt = new BackupQueueMicroTask(this); + context->DispatchMicroTaskRunnable(bqmt.forget()); +} + +void +CustomElementReactionsStack::InvokeBackupQueue() +{ + // Check backup queue size in order to reduce function call overhead. + if (!mBackupQueue.IsEmpty()) { + // Upgrade reactions won't be scheduled in backup queue and the exception of + // callback reactions will be automatically reported in CallSetup. + // If the reactions are invoked from backup queue (in microtask check point), + // we don't need to pass global object for error reporting. + InvokeReactions(&mBackupQueue, nullptr); + } + MOZ_ASSERT(mBackupQueue.IsEmpty(), + "There are still some reactions in BackupQueue not being consumed!?!"); +} + +void +CustomElementReactionsStack::InvokeReactions(ElementQueue* aElementQueue, + nsIGlobalObject* aGlobal) +{ + // This is used for error reporting. + Maybe<AutoEntryScript> aes; + if (aGlobal) { + aes.emplace(aGlobal, "custom elements reaction invocation"); + } + + // Note: It's possible to re-enter this method. + for (uint32_t i = 0; i < aElementQueue->Length(); ++i) { + Element* element = aElementQueue->ElementAt(i); + + // ElementQueue hold a element's strong reference, it should not be a nullptr. + MOZ_ASSERT(element); + + RefPtr<CustomElementData> elementData = element->GetCustomElementData(); + if (!elementData) { + // This happens when the document is destroyed and the element is already + // unlinked, no need to fire the callbacks in this case. + continue; + } + + auto& reactions = elementData->mReactionQueue; + for (uint32_t j = 0; j < reactions.Length(); ++j) { + // Transfer the ownership of the entry due to reentrant invocation of + // this funciton. The entry will be removed when bug 1379573 is landed. + auto reaction(Move(reactions.ElementAt(j))); + if (reaction) { + ErrorResult rv; + reaction->Invoke(element, rv); + if (aes) { + JSContext* cx = aes->cx(); + if (rv.MaybeSetPendingException(cx)) { + aes->ReportException(); + } + MOZ_ASSERT(!JS_IsExceptionPending(cx)); + } + MOZ_ASSERT(!rv.Failed()); + } + } + reactions.Clear(); + } + aElementQueue->Clear(); +} + +//----------------------------------------------------- +// CustomElementDefinition + +NS_IMPL_CYCLE_COLLECTION_CLASS(CustomElementDefinition) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CustomElementDefinition) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mConstructor) + tmp->mPrototype = nullptr; + tmp->mCallbacks = nullptr; +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CustomElementDefinition) + mozilla::dom::LifecycleCallbacks* callbacks = tmp->mCallbacks.get(); + + if (callbacks->mAttributeChangedCallback.WasPassed()) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, + "mCallbacks->mAttributeChangedCallback"); + cb.NoteXPCOMChild(callbacks->mAttributeChangedCallback.Value()); + } + + if (callbacks->mConnectedCallback.WasPassed()) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCallbacks->mConnectedCallback"); + cb.NoteXPCOMChild(callbacks->mConnectedCallback.Value()); + } + + if (callbacks->mDisconnectedCallback.WasPassed()) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCallbacks->mDisconnectedCallback"); + cb.NoteXPCOMChild(callbacks->mDisconnectedCallback.Value()); + } + + if (callbacks->mAdoptedCallback.WasPassed()) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCallbacks->mAdoptedCallback"); + cb.NoteXPCOMChild(callbacks->mAdoptedCallback.Value()); + } + + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mConstructor"); + cb.NoteXPCOMChild(tmp->mConstructor); +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CustomElementDefinition) + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mPrototype) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CustomElementDefinition, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CustomElementDefinition, Release) + CustomElementDefinition::CustomElementDefinition(nsIAtom* aType, nsIAtom* aLocalName, - JSObject* aConstructor, + Function* aConstructor, + nsCOMArray<nsIAtom>&& aObservedAttributes, JSObject* aPrototype, LifecycleCallbacks* aCallbacks, uint32_t aDocOrder) : mType(aType), mLocalName(aLocalName), - mConstructor(aConstructor), + mConstructor(new CustomElementConstructor(aConstructor)), + mObservedAttributes(Move(aObservedAttributes)), mPrototype(aPrototype), mCallbacks(aCallbacks), mDocOrder(aDocOrder) { } +//----------------------------------------------------- +// CustomElementUpgradeReaction + +/* virtual */ void +CustomElementUpgradeReaction::Invoke(Element* aElement, ErrorResult& aRv) +{ + CustomElementRegistry::Upgrade(aElement, mDefinition, aRv); +} + +//----------------------------------------------------- +// CustomElementCallbackReaction + +/* virtual */ void +CustomElementCallbackReaction::Invoke(Element* aElement, ErrorResult& aRv) +{ + mCustomElementCallback->Call(); +} + } // namespace dom -} // namespace mozilla
\ No newline at end of file +} // namespace mozilla |