Bug 1309184 - Implement upgrade reaction for custom element reactions. r=wchen

--HG--
extra : rebase_source : 0333c91029b6e08961e2ad0e7c04c3364cb429b5
This commit is contained in:
John Dai 2017-01-05 18:38:00 -05:00
Родитель 254a3c92b7
Коммит 4335473f7c
2 изменённых файлов: 267 добавлений и 29 удалений

Просмотреть файл

@ -137,6 +137,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CustomElementRegistry)
cb.NoteXPCOMChild(callbacks->mDetachedCallback.Value()); cb.NoteXPCOMChild(callbacks->mDetachedCallback.Value());
} }
} }
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWhenDefinedPromiseMap) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWhenDefinedPromiseMap)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
@ -238,6 +239,7 @@ CustomElementRegistry::sProcessingStack;
CustomElementRegistry::CustomElementRegistry(nsPIDOMWindowInner* aWindow) CustomElementRegistry::CustomElementRegistry(nsPIDOMWindowInner* aWindow)
: mWindow(aWindow) : mWindow(aWindow)
, mIsCustomDefinitionRunning(false) , mIsCustomDefinitionRunning(false)
, mIsBackupQueueProcessing(false)
{ {
mozilla::HoldJSObjects(this); mozilla::HoldJSObjects(this);
@ -503,35 +505,7 @@ CustomElementRegistry::UpgradeCandidates(JSContext* aCx,
continue; continue;
} }
elem->RemoveStates(NS_EVENT_STATE_UNRESOLVED); EnqueueUpgradeReaction(elem, aDefinition);
// 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);
} }
} }
} }
@ -591,6 +565,9 @@ CustomElementRegistry::Define(const nsAString& aName,
const ElementDefinitionOptions& aOptions, const ElementDefinitionOptions& aOptions,
ErrorResult& aRv) 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(); aRv.MightThrowJSException();
AutoJSAPI jsapi; AutoJSAPI jsapi;
@ -884,6 +861,147 @@ CustomElementRegistry::WhenDefined(const nsAString& aName, ErrorResult& aRv)
return promise.forget(); 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, CustomElementDefinition::CustomElementDefinition(nsIAtom* aType,
nsIAtom* aLocalName, nsIAtom* aLocalName,
JSObject* aConstructor, JSObject* aConstructor,
@ -899,5 +1017,15 @@ CustomElementDefinition::CustomElementDefinition(nsIAtom* aType,
{ {
} }
//-----------------------------------------------------
// CustomElementUpgradeReaction
/* virtual */ void
CustomElementUpgradeReaction::Invoke(Element* aElement)
{
mRegistry->Upgrade(aElement, mDefinition);
}
} // namespace dom } // namespace dom
} // namespace mozilla } // namespace mozilla

Просмотреть файл

@ -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, class CustomElementRegistry final : public nsISupports,
public nsWrapperCache public nsWrapperCache
{ {
@ -177,6 +208,24 @@ public:
void GetCustomPrototype(nsIAtom* aAtom, void GetCustomPrototype(nsIAtom* aAtom,
JS::MutableHandle<JSObject*> aPrototype); 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: private:
explicit CustomElementRegistry(nsPIDOMWindowInner* aWindow); explicit CustomElementRegistry(nsPIDOMWindowInner* aWindow);
~CustomElementRegistry(); ~CustomElementRegistry();
@ -198,6 +247,21 @@ private:
nsIAtom* aKey, nsIAtom* aKey,
CustomElementDefinition* aDefinition); 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> typedef nsClassHashtable<nsISupportsHashKey, CustomElementDefinition>
DefinitionMap; DefinitionMap;
typedef nsClassHashtable<nsISupportsHashKey, nsTArray<nsWeakPtr>> typedef nsClassHashtable<nsISupportsHashKey, nsTArray<nsWeakPtr>>
@ -238,6 +302,17 @@ private:
// It is used to prevent reentrant invocations of element definition. // It is used to prevent reentrant invocations of element definition.
bool mIsCustomDefinitionRunning; 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: private:
class MOZ_RAII AutoSetRunningFlag final { class MOZ_RAII AutoSetRunningFlag final {
@ -258,6 +333,28 @@ private:
CustomElementRegistry* mRegistry; 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: public:
nsISupports* GetParentObject() const; nsISupports* GetParentObject() const;
@ -272,6 +369,19 @@ public:
already_AddRefed<Promise> WhenDefined(const nsAString& aName, ErrorResult& aRv); 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 dom
} // namespace mozilla } // namespace mozilla