diff options
-rw-r--r-- | dom/base/CustomElementRegistry.cpp | 186 | ||||
-rw-r--r-- | dom/base/CustomElementRegistry.h | 110 |
2 files changed, 267 insertions, 29 deletions
diff --git a/dom/base/CustomElementRegistry.cpp b/dom/base/CustomElementRegistry.cpp index 6cd213210e..20eb59e94a 100644 --- a/dom/base/CustomElementRegistry.cpp +++ b/dom/base/CustomElementRegistry.cpp @@ -137,6 +137,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CustomElementRegistry) cb.NoteXPCOMChild(callbacks->mDetachedCallback.Value()); } } + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWhenDefinedPromiseMap) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END @@ -237,6 +238,7 @@ CustomElementRegistry::sProcessingStack; CustomElementRegistry::CustomElementRegistry(nsPIDOMWindowInner* aWindow) : mWindow(aWindow) , mIsCustomDefinitionRunning(false) + , mIsBackupQueueProcessing(false) { mozilla::HoldJSObjects(this); @@ -502,35 +504,7 @@ CustomElementRegistry::UpgradeCandidates(JSContext* aCx, 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); + EnqueueUpgradeReaction(elem, aDefinition); } } } @@ -590,6 +564,9 @@ CustomElementRegistry::Define(const nsAString& aName, const ElementDefinitionOptions& aOptions, ErrorResult& aRv) { + // We do this for [CEReaction] temporarily and it will be removed + // after webidl supports [CEReaction] annotation in bug 1309147. + AutoCEReaction ceReaction(this); aRv.MightThrowJSException(); AutoJSAPI jsapi; @@ -883,6 +860,148 @@ CustomElementRegistry::WhenDefined(const nsAString& aName, ErrorResult& aRv) return promise.forget(); } +void +CustomElementRegistry::EnqueueUpgradeReaction(Element* aElement, + CustomElementDefinition* aDefinition) +{ + Enqueue(aElement, new CustomElementUpgradeReaction(this, aDefinition)); +} + +void +CustomElementRegistry::Upgrade(Element* aElement, + CustomElementDefinition* aDefinition) +{ + // TODO: This function will be replaced to v1 upgrade in bug 1299363 + aElement->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 (aElement->NodeInfo()->NameAtom() != aDefinition->mLocalName) { + //Skip over this element because definition does not apply. + return; + } + + MOZ_ASSERT(aElement->IsHTMLElement(aDefinition->mLocalName)); + nsWrapperCache* cache; + CallQueryInterface(aElement, &cache); + MOZ_ASSERT(cache, "Element doesn't support wrapper cache?"); + + AutoJSAPI jsapi; + if (NS_WARN_IF(!jsapi.Init(mWindow))) { + return; + } + + JSContext *cx = jsapi.cx(); + // We want to set the custom prototype in the the compartment of define()'s caller. + // We store the prototype from define() directly, + // hence the prototype's compartment is the caller's compartment. + JS::RootedObject wrapper(cx); + JS::Rooted<JSObject*> prototype(cx, aDefinition->mPrototype); + { // Enter prototype's compartment. + JSAutoCompartment ac(cx, prototype); + + if ((wrapper = cache->GetWrapper()) && JS_WrapObject(cx, &wrapper)) { + if (!JS_SetPrototype(cx, wrapper, prototype)) { + return; + } + } + } // Leave prototype's compartment. + + // Enqueuing the created callback will set the CustomElementData on the + // element, causing prototype swizzling to occur in Element::WrapObject. + EnqueueLifecycleCallback(nsIDocument::eCreated, aElement, nullptr, aDefinition); +} + + +void +CustomElementRegistry::CreateAndPushElementQueue() +{ + // Push a new element queue onto the custom element reactions stack. + mReactionsStack.AppendElement(); +} + +void +CustomElementRegistry::PopAndInvokeElementQueue() +{ + // Pop the element queue from the custom element reactions stack, + // and invoke custom element reactions in that queue. + MOZ_ASSERT(!mReactionsStack.IsEmpty(), + "Reaction stack shouldn't be empty"); + + ElementQueue& elementQueue = mReactionsStack.LastElement(); + CustomElementRegistry::InvokeReactions(elementQueue); + DebugOnly<bool> isRemovedElement = mReactionsStack.RemoveElement(elementQueue); + MOZ_ASSERT(isRemovedElement, + "Reaction stack should have an element queue to remove"); +} + +void +CustomElementRegistry::Enqueue(Element* aElement, + CustomElementReaction* aReaction) +{ + // Add element to the current element queue. + if (!mReactionsStack.IsEmpty()) { + mReactionsStack.LastElement().AppendElement(do_GetWeakReference(aElement)); + ReactionQueue* reactionQueue = + mElementReactionQueueMap.LookupOrAdd(aElement); + reactionQueue->AppendElement(aReaction); + + return; + } + + // If the custom element reactions stack is empty, then: + // Add element to the backup element queue. + mBackupQueue.AppendElement(do_GetWeakReference(aElement)); + + ReactionQueue* reactionQueue = + mElementReactionQueueMap.LookupOrAdd(aElement); + reactionQueue->AppendElement(aReaction); + + if (mIsBackupQueueProcessing) { + return; + } + + CycleCollectedJSContext* context = CycleCollectedJSContext::Get(); + RefPtr<ProcessBackupQueueRunnable> processBackupQueueRunnable = + new ProcessBackupQueueRunnable(this); + context->DispatchToMicroTask(processBackupQueueRunnable.forget()); +} + +void +CustomElementRegistry::InvokeBackupQueue() +{ + CustomElementRegistry::InvokeReactions(mBackupQueue); +} + +void +CustomElementRegistry::InvokeReactions(ElementQueue& aElementQueue) +{ + for (uint32_t i = 0; i < aElementQueue.Length(); ++i) { + nsCOMPtr<Element> element = do_QueryReferent(aElementQueue[i]); + + if (!element) { + continue; + } + + nsAutoPtr<ReactionQueue> reactions; + mElementReactionQueueMap.RemoveAndForget(element, reactions); + + MOZ_ASSERT(reactions, + "Associated ReactionQueue must be found in mElementReactionQueueMap"); + + for (uint32_t j = 0; j < reactions->Length(); ++j) { + nsAutoPtr<CustomElementReaction>& reaction = reactions->ElementAt(j); + reaction->Invoke(element); + } + } + aElementQueue.Clear(); +} + +//----------------------------------------------------- +// CustomElementDefinition + + CustomElementDefinition::CustomElementDefinition(nsIAtom* aType, nsIAtom* aLocalName, JSObject* aConstructor, @@ -898,5 +1017,14 @@ CustomElementDefinition::CustomElementDefinition(nsIAtom* aType, { } +//----------------------------------------------------- +// CustomElementUpgradeReaction + +/* virtual */ void +CustomElementUpgradeReaction::Invoke(Element* aElement) +{ + mRegistry->Upgrade(aElement, mDefinition); +} + } // namespace dom } // namespace mozilla diff --git a/dom/base/CustomElementRegistry.h b/dom/base/CustomElementRegistry.h index 8ef0785af3..b3b4d7c0f0 100644 --- a/dom/base/CustomElementRegistry.h +++ b/dom/base/CustomElementRegistry.h @@ -134,6 +134,37 @@ struct CustomElementDefinition } }; +class CustomElementReaction +{ +public: + explicit CustomElementReaction(CustomElementRegistry* aRegistry, + CustomElementDefinition* aDefinition) + : mRegistry(aRegistry) + , mDefinition(aDefinition) + { + }; + + virtual ~CustomElementReaction() = default; + virtual void Invoke(Element* aElement) = 0; + +protected: + CustomElementRegistry* mRegistry; + CustomElementDefinition* mDefinition; +}; + +class CustomElementUpgradeReaction final : public CustomElementReaction +{ +public: + explicit CustomElementUpgradeReaction(CustomElementRegistry* aRegistry, + CustomElementDefinition* aDefinition) + : CustomElementReaction(aRegistry, aDefinition) + { + } + +private: + virtual void Invoke(Element* aElement) override; +}; + class CustomElementRegistry final : public nsISupports, public nsWrapperCache { @@ -177,6 +208,24 @@ public: void GetCustomPrototype(nsIAtom* aAtom, JS::MutableHandle<JSObject*> aPrototype); + /** + * Enqueue a custom element upgrade reaction + * https://html.spec.whatwg.org/multipage/scripting.html#enqueue-a-custom-element-upgrade-reaction + */ + void EnqueueUpgradeReaction(Element* aElement, + CustomElementDefinition* aDefinition); + + void Upgrade(Element* aElement, CustomElementDefinition* aDefinition); + + // [CEReactions] Before executing the algorithm's steps + // Push a new element queue onto the custom element reactions stack. + void CreateAndPushElementQueue(); + + // [CEReactions] After executing the algorithm's steps + // Pop the element queue from the custom element reactions stack, + // and invoke custom element reactions in that queue. + void PopAndInvokeElementQueue(); + private: explicit CustomElementRegistry(nsPIDOMWindowInner* aWindow); ~CustomElementRegistry(); @@ -198,6 +247,21 @@ private: nsIAtom* aKey, CustomElementDefinition* aDefinition); + void InvokeBackupQueue(); + + void Enqueue(Element* aElement, CustomElementReaction* aReaction); + + // nsWeakPtr is a weak pointer of Element + // The element reaction queues are stored in ElementReactionQueueMap. + // We need to lookup ElementReactionQueueMap again to get relevant reaction queue. + typedef nsTArray<nsWeakPtr> ElementQueue; + + /** + * Invoke custom element reactions + * https://html.spec.whatwg.org/multipage/scripting.html#invoke-custom-element-reactions + */ + void InvokeReactions(ElementQueue& aElementQueue); + typedef nsClassHashtable<nsISupportsHashKey, CustomElementDefinition> DefinitionMap; typedef nsClassHashtable<nsISupportsHashKey, nsTArray<nsWeakPtr>> @@ -238,6 +302,17 @@ private: // It is used to prevent reentrant invocations of element definition. bool mIsCustomDefinitionRunning; + // https://html.spec.whatwg.org/#enqueue-an-element-on-the-appropriate-element-queue + bool mIsBackupQueueProcessing; + + typedef nsTArray<nsAutoPtr<CustomElementReaction>> ReactionQueue; + typedef nsClassHashtable<nsISupportsHashKey, ReactionQueue> + ElementReactionQueueMap; + + ElementReactionQueueMap mElementReactionQueueMap; + + nsTArray<ElementQueue> mReactionsStack; + ElementQueue mBackupQueue; private: class MOZ_RAII AutoSetRunningFlag final { @@ -258,6 +333,28 @@ private: CustomElementRegistry* mRegistry; }; +private: + class ProcessBackupQueueRunnable : public mozilla::Runnable { + public: + explicit ProcessBackupQueueRunnable(CustomElementRegistry* aRegistry) + : mRegistry(aRegistry) + { + MOZ_ASSERT(!mRegistry->mIsBackupQueueProcessing, + "mIsBackupQueueProcessing should be initially false"); + mRegistry->mIsBackupQueueProcessing = true; + } + + NS_IMETHOD Run() override + { + mRegistry->InvokeBackupQueue(); + mRegistry->mIsBackupQueueProcessing = false; + return NS_OK; + } + + private: + RefPtr<CustomElementRegistry> mRegistry; + }; + public: nsISupports* GetParentObject() const; @@ -272,6 +369,19 @@ public: already_AddRefed<Promise> WhenDefined(const nsAString& aName, ErrorResult& aRv); }; +class MOZ_RAII AutoCEReaction final { + public: + explicit AutoCEReaction(CustomElementRegistry* aRegistry) + : mRegistry(aRegistry) { + mRegistry->CreateAndPushElementQueue(); + } + ~AutoCEReaction() { + mRegistry->PopAndInvokeElementQueue(); + } + private: + RefPtr<CustomElementRegistry> mRegistry; +}; + } // namespace dom } // namespace mozilla |