Bug 1315885 - Part 4: Implement callback reaction for custom element reactions. r=wchen

MozReview-Commit-ID: L462de2JmzT

--HG--
extra : rebase_source : 6d8e17a2cdf6635fcf7708fd57912cac89526b1d
This commit is contained in:
John Dai 2017-07-11 17:46:16 +08:00
Родитель d42ab7cdaa
Коммит 214e4b9b24
5 изменённых файлов: 147 добавлений и 139 удалений

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

@ -92,26 +92,12 @@ CustomElementData::CustomElementData(nsIAtom* aType)
CustomElementData::CustomElementData(nsIAtom* aType, State aState)
: mType(aType)
, mCurrentCallback(-1)
, mElementIsBeingCreated(false)
, mCreatedCallbackInvoked(true)
, mAssociatedMicroTask(-1)
, mState(aState)
{
}
void
CustomElementData::RunCallbackQueue()
{
// Note: It's possible to re-enter this method.
while (static_cast<uint32_t>(++mCurrentCallback) < mCallbackQueue.Length()) {
mCallbackQueue[mCurrentCallback]->Call();
}
mCallbackQueue.Clear();
mCurrentCallback = -1;
}
//-----------------------------------------------------
// CustomElementRegistry
@ -128,7 +114,7 @@ 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;
auto& callbacks = iter.UserData()->mCallbacks;
if (callbacks->mAttributeChangedCallback.WasPassed()) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
@ -192,43 +178,6 @@ CustomElementRegistry::IsCustomElementEnabled(JSContext* aCx, JSObject* aObject)
nsContentUtils::IsWebComponentsEnabled();
}
/* 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);
}
}
/* static */ void
CustomElementRegistry::XPCOMShutdown()
{
sProcessingStack.reset();
}
/* static */ Maybe<nsTArray<RefPtr<CustomElementData>>>
CustomElementRegistry::sProcessingStack;
CustomElementRegistry::CustomElementRegistry(nsPIDOMWindowInner* aWindow)
: mWindow(aWindow)
, mIsCustomDefinitionRunning(false)
@ -238,12 +187,6 @@ CustomElementRegistry::CustomElementRegistry(nsPIDOMWindowInner* aWindow)
MOZ_ALWAYS_TRUE(mConstructors.init());
mozilla::HoldJSObjects(this);
if (!sProcessingStack) {
sProcessingStack.emplace();
// Add the base queue sentinel to the processing stack.
sProcessingStack->AppendElement((CustomElementData*) nullptr);
}
}
CustomElementRegistry::~CustomElementRegistry()
@ -347,14 +290,15 @@ CustomElementRegistry::SetupCustomElement(Element* aElement,
// Enqueuing the created callback will set the CustomElementData on the
// element, causing prototype swizzling to occur in Element::WrapObject.
EnqueueLifecycleCallback(nsIDocument::eCreated, aElement, nullptr, definition);
// We make it synchronously for createElement/createElementNS in order to
// pass tests. It'll be removed when we deprecate custom elements v0.
SyncInvokeReactions(nsIDocument::eCreated, aElement, definition);
}
void
CustomElementRegistry::EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType,
Element* aCustomElement,
LifecycleCallbackArgs* aArgs,
CustomElementDefinition* aDefinition)
UniquePtr<CustomElementCallback>
CustomElementRegistry::CreateCustomElementCallback(
nsIDocument::ElementCallbackType aType, Element* aCustomElement,
LifecycleCallbackArgs* aArgs, CustomElementDefinition* aDefinition)
{
RefPtr<CustomElementData> elementData = aCustomElement->GetCustomElementData();
MOZ_ASSERT(elementData, "CustomElementData should exist");
@ -373,7 +317,7 @@ CustomElementRegistry::EnqueueLifecycleCallback(nsIDocument::ElementCallbackType
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;
return nullptr;
}
}
@ -407,7 +351,7 @@ CustomElementRegistry::EnqueueLifecycleCallback(nsIDocument::ElementCallbackType
// If there is no such callback, stop.
if (!func) {
return;
return nullptr;
}
if (aType == nsIDocument::eCreated) {
@ -415,55 +359,62 @@ CustomElementRegistry::EnqueueLifecycleCallback(nsIDocument::ElementCallbackType
} 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, elementData);
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()));
// 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);
return Move(callback);
}
sProcessingStack->AppendElement(elementData);
elementData->mAssociatedMicroTask =
static_cast<int32_t>(nsContentUtils::MicroTaskLevel());
void
CustomElementRegistry::SyncInvokeReactions(nsIDocument::ElementCallbackType aType,
Element* aCustomElement,
CustomElementDefinition* aDefinition)
{
auto callback = CreateCustomElementCallback(aType, aCustomElement, nullptr,
aDefinition);
if (!callback) {
return;
}
UniquePtr<CustomElementReaction> reaction(Move(
MakeUnique<CustomElementCallbackReaction>(this, aDefinition,
Move(callback))));
RefPtr<SyncInvokeReactionRunnable> runnable =
new SyncInvokeReactionRunnable(Move(reaction), aCustomElement);
// 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(
"dom::CustomElementRegistry::EnqueueLifecycleCallback",
&CustomElementRegistry::ProcessTopElementQueue);
nsContentUtils::AddScriptRunner(runnable);
}
void
CustomElementRegistry::EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType,
Element* aCustomElement,
LifecycleCallbackArgs* aArgs,
CustomElementDefinition* aDefinition)
{
auto callback =
CreateCustomElementCallback(aType, aCustomElement, aArgs, aDefinition);
if (!callback) {
return;
}
DocGroup* docGroup = mWindow->GetDocGroup();
if (!docGroup) {
return;
}
CustomElementReactionsStack* reactionsStack =
docGroup->CustomElementReactionsStack();
reactionsStack->EnqueueCallbackReaction(this, aCustomElement, aDefinition,
Move(callback));
}
void
@ -947,6 +898,16 @@ CustomElementReactionsStack::EnqueueUpgradeReaction(CustomElementRegistry* aRegi
Enqueue(aElement, new CustomElementUpgradeReaction(aRegistry, aDefinition));
}
void
CustomElementReactionsStack::EnqueueCallbackReaction(CustomElementRegistry* aRegistry,
Element* aElement,
CustomElementDefinition* aDefinition,
UniquePtr<CustomElementCallback> aCustomElementCallback)
{
Enqueue(aElement, new CustomElementCallbackReaction(aRegistry, aDefinition,
Move(aCustomElementCallback)));
}
void
CustomElementReactionsStack::Enqueue(Element* aElement,
CustomElementReaction* aReaction)
@ -1041,5 +1002,14 @@ CustomElementUpgradeReaction::Invoke(Element* aElement)
mRegistry->Upgrade(aElement, mDefinition);
}
//-----------------------------------------------------
// CustomElementCallbackReaction
/* virtual */ void
CustomElementCallbackReaction::Invoke(Element* aElement)
{
mCustomElementCallback->Call();
}
} // namespace dom
} // namespace mozilla

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

@ -85,23 +85,14 @@ struct CustomElementData
explicit CustomElementData(nsIAtom* aType);
CustomElementData(nsIAtom* aType, State aState);
// Objects in this array are transient and empty after each microtask
// checkpoint.
nsTArray<nsAutoPtr<CustomElementCallback>> mCallbackQueue;
// Custom element type, for <button is="x-button"> or <x-button>
// this would be x-button.
nsCOMPtr<nsIAtom> mType;
// The callback that is next to be processed upon calling RunCallbackQueue.
int32_t mCurrentCallback;
// Element is being created flag as described in the custom elements spec.
bool mElementIsBeingCreated;
// Flag to determine if the created callback has been invoked, thus it
// determines if other callbacks can be enqueued.
bool mCreatedCallbackInvoked;
// The microtask level associated with the callbacks in the callback queue,
// it is used to determine if a new queue needs to be pushed onto the
// processing stack.
int32_t mAssociatedMicroTask;
// Custom element state as described in the custom element spec.
State mState;
// custom element reaction queue as described in the custom element spec.
@ -110,10 +101,7 @@ struct CustomElementData
// appended, removed, or replaced.
// There are 3 reactions in reaction queue when doing upgrade operation,
// e.g., create an element, insert a node.
AutoTArray<nsAutoPtr<CustomElementReaction>, 3> mReactionQueue;
// Empties the callback queue.
void RunCallbackQueue();
AutoTArray<UniquePtr<CustomElementReaction>, 3> mReactionQueue;
private:
virtual ~CustomElementData() {}
@ -143,7 +131,7 @@ struct CustomElementDefinition
JS::Heap<JSObject *> mPrototype;
// The lifecycle callbacks to call for this custom element.
nsAutoPtr<mozilla::dom::LifecycleCallbacks> mCallbacks;
UniquePtr<mozilla::dom::LifecycleCallbacks> mCallbacks;
// A construction stack.
// TODO: Bug 1287348 - Implement construction stack for upgrading an element
@ -164,10 +152,13 @@ public:
: mRegistry(aRegistry)
, mDefinition(aDefinition)
{
};
}
virtual ~CustomElementReaction() = default;
virtual void Invoke(Element* aElement) = 0;
virtual void Traverse(nsCycleCollectionTraversalCallback& aCb) const
{
}
protected:
CustomElementRegistry* mRegistry;
@ -187,6 +178,27 @@ private:
virtual void Invoke(Element* aElement) override;
};
class CustomElementCallbackReaction final : public CustomElementReaction
{
public:
CustomElementCallbackReaction(CustomElementRegistry* aRegistry,
CustomElementDefinition* aDefinition,
UniquePtr<CustomElementCallback> aCustomElementCallback)
: CustomElementReaction(aRegistry, aDefinition)
, mCustomElementCallback(Move(aCustomElementCallback))
{
}
virtual void Traverse(nsCycleCollectionTraversalCallback& aCb) const override
{
mCustomElementCallback->Traverse(aCb);
}
private:
virtual void Invoke(Element* aElement) override;
UniquePtr<CustomElementCallback> mCustomElementCallback;
};
// https://html.spec.whatwg.org/multipage/scripting.html#custom-element-reactions-stack
class CustomElementReactionsStack
{
@ -212,6 +224,15 @@ public:
Element* aElement,
CustomElementDefinition* aDefinition);
/**
* Enqueue a custom element callback reaction
* https://html.spec.whatwg.org/multipage/scripting.html#enqueue-a-custom-element-callback-reaction
*/
void EnqueueCallbackReaction(CustomElementRegistry* aRegistry,
Element* aElement,
CustomElementDefinition* aDefinition,
UniquePtr<CustomElementCallback> aCustomElementCallback);
// [CEReactions] Before executing the algorithm's steps
// Push a new element queue onto the custom element reactions stack.
void CreateAndPushElementQueue();
@ -280,10 +301,6 @@ public:
static bool IsCustomElementEnabled(JSContext* aCx = nullptr,
JSObject* aObject = nullptr);
static void ProcessTopElementQueue();
static void XPCOMShutdown();
explicit CustomElementRegistry(nsPIDOMWindowInner* aWindow);
/**
@ -316,6 +333,13 @@ public:
private:
~CustomElementRegistry();
UniquePtr<CustomElementCallback> CreateCustomElementCallback(
nsIDocument::ElementCallbackType aType, Element* aCustomElement,
LifecycleCallbackArgs* aArgs, CustomElementDefinition* aDefinition);
void SyncInvokeReactions(nsIDocument::ElementCallbackType aType,
Element* aCustomElement,
CustomElementDefinition* aDefinition);
/**
* Registers an unresolved custom element that is a candidate for
* upgrade when the definition is registered via registerElement.
@ -362,14 +386,6 @@ private:
nsCOMPtr<nsPIDOMWindowInner> mWindow;
// Array representing the processing stack in the custom elements
// specification. The processing stack is conceptually a stack of
// element queues. Each queue is represented by a sequence of
// CustomElementData in this array, separated by nullptr that
// represent the boundaries of the items in the stack. The first
// queue in the stack is the base element queue.
static mozilla::Maybe<nsTArray<RefPtr<CustomElementData>>> sProcessingStack;
// It is used to prevent reentrant invocations of element definition.
bool mIsCustomDefinitionRunning;
@ -392,6 +408,28 @@ private:
CustomElementRegistry* mRegistry;
};
class SyncInvokeReactionRunnable : public mozilla::Runnable {
public:
SyncInvokeReactionRunnable(
UniquePtr<CustomElementReaction> aReaction, Element* aCustomElement)
: Runnable(
"dom::CustomElementRegistry::SyncInvokeReactionRunnable")
, mReaction(Move(aReaction))
, mCustomElement(aCustomElement)
{
}
NS_IMETHOD Run() override
{
mReaction->Invoke(mCustomElement);
return NS_OK;
}
private:
UniquePtr<CustomElementReaction> mReaction;
Element* mCustomElement;
};
public:
nsISupports* GetParentObject() const;

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

@ -711,8 +711,10 @@ FragmentOrElement::nsDOMSlots::Traverse(nsCycleCollectionTraversalCallback &cb,
cb.NoteXPCOMChild(mClassList.get());
if (mCustomElementData) {
for (uint32_t i = 0; i < mCustomElementData->mCallbackQueue.Length(); i++) {
mCustomElementData->mCallbackQueue[i]->Traverse(cb);
for (uint32_t i = 0; i < mCustomElementData->mReactionQueue.Length(); i++) {
if (mCustomElementData->mReactionQueue[i]) {
mCustomElementData->mReactionQueue[i]->Traverse(cb);
}
}
}

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

@ -28,7 +28,8 @@ function testChangeAttributeInCreatedCallback() {
createdCallbackCalled = true;
is(attributeChangedCallbackCalled, false, "Attribute changed callback should not have been called prior to setting the attribute.");
this.setAttribute("foo", "bar");
is(attributeChangedCallbackCalled, false, "While element is being created, element should not be added to the current element callback queue.");
is(attributeChangedCallbackCalled, true, "While element is being created, element should be added to the current element callback queue.");
runNextTest();
};
p.attributeChangedCallback = function(name, oldValue, newValue) {
@ -36,7 +37,6 @@ function testChangeAttributeInCreatedCallback() {
is(attributeChangedCallbackCalled, false, "attributeChanged callback should only be called once in this tests.");
is(newValue, "bar", "The new value should be 'bar'");
attributeChangedCallbackCalled = true;
runNextTest();
};
document.registerElement("x-one", { prototype: p });

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

@ -434,8 +434,6 @@ nsLayoutStatics::Shutdown()
DisplayItemClip::Shutdown();
CustomElementRegistry::XPCOMShutdown();
CacheObserver::Shutdown();
PromiseDebugging::Shutdown();