Commit e1a83859 authored by dominicc's avatar dominicc Committed by Commit bot

Add a stack of queues of elements with reaction queues.

The ordering of custom element 'reactions' (various kinds of
callbacks) is determined by a stack which is pushed and popped within
the scope of an operation. The stack entry is a queue of involved
elements; this is so callbacks occur in order. However each element
gets callbacks in an internally consistent chronological order. To
effect this, each element has an associated reaction queue.

BUG=594918

Review-Url: https://codereview.chromium.org/2027513002
Cr-Commit-Position: refs/heads/master@{#396806}
parent c7701850
......@@ -2639,6 +2639,8 @@
'dom/custom/CustomElementReaction.h',
'dom/custom/CustomElementReactionQueue.cpp',
'dom/custom/CustomElementReactionQueue.h',
'dom/custom/CustomElementReactionStack.cpp',
'dom/custom/CustomElementReactionStack.h',
'dom/custom/CustomElementUpgradeSorter.cpp',
'dom/custom/CustomElementUpgradeSorter.h',
'dom/custom/CustomElementsRegistry.cpp',
......@@ -3985,9 +3987,12 @@
'dom/TreeScopeStyleSheetCollectionTest.cpp',
'dom/TreeScopeTest.cpp',
'dom/URLSearchParamsTest.cpp',
'dom/custom/CustomElementTest.cpp',
'dom/custom/CustomElementDescriptorTest.cpp',
'dom/custom/CustomElementReactionQueueTest.cpp',
'dom/custom/CustomElementReactionStackTest.cpp',
'dom/custom/CustomElementReactionTestHelpers.h',
'dom/custom/CustomElementTest.cpp',
'dom/custom/CustomElementTestHelpers.h',
'dom/custom/CustomElementUpgradeSorterTest.cpp',
'dom/shadow/FlatTreeTraversalTest.cpp',
'editing/EditingCommandTest.cpp',
......
......@@ -25,9 +25,10 @@ public:
void add(CustomElementReaction*);
void invokeReactions(Element*);
bool isEmpty() { return m_reactions.isEmpty(); }
private:
HeapVector<Member<CustomElementReaction>> m_reactions;
HeapVector<Member<CustomElementReaction>, 1> m_reactions;
size_t m_index;
};
......
......@@ -5,98 +5,13 @@
#include "core/dom/custom/CustomElementReactionQueue.h"
#include "core/dom/custom/CustomElementReaction.h"
#include "core/dom/custom/CustomElementReactionTestHelpers.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "wtf/text/AtomicString.h"
#include <initializer_list>
#include <vector>
namespace blink {
class Command : public GarbageCollectedFinalized<Command> {
WTF_MAKE_NONCOPYABLE(Command);
public:
Command() { }
virtual ~Command() { }
DEFINE_INLINE_VIRTUAL_TRACE() { }
virtual void run(Element*) = 0;
};
class Log : public Command {
WTF_MAKE_NONCOPYABLE(Log);
public:
Log(char what, std::vector<char>& where) : m_what(what), m_where(where) { }
virtual ~Log() { }
void run(Element*) override { m_where.push_back(m_what); }
private:
char m_what;
std::vector<char>& m_where;
};
class Recurse : public Command {
WTF_MAKE_NONCOPYABLE(Recurse);
public:
Recurse(CustomElementReactionQueue* queue) : m_queue(queue) { }
virtual ~Recurse() { }
DEFINE_INLINE_VIRTUAL_TRACE()
{
Command::trace(visitor);
visitor->trace(m_queue);
}
void run(Element* element) override { m_queue->invokeReactions(element); }
private:
Member<CustomElementReactionQueue> m_queue;
};
class Enqueue : public Command {
WTF_MAKE_NONCOPYABLE(Enqueue);
public:
Enqueue(CustomElementReactionQueue* queue, CustomElementReaction* reaction)
: m_queue(queue)
, m_reaction(reaction)
{
}
virtual ~Enqueue() { }
DEFINE_INLINE_VIRTUAL_TRACE()
{
Command::trace(visitor);
visitor->trace(m_queue);
visitor->trace(m_reaction);
}
void run(Element*) override
{
m_queue->add(m_reaction);
}
private:
Member<CustomElementReactionQueue> m_queue;
Member<CustomElementReaction> m_reaction;
};
class TestReaction : public CustomElementReaction {
WTF_MAKE_NONCOPYABLE(TestReaction);
public:
TestReaction(std::initializer_list<Command*> commands)
{
// TODO(dominicc): Simply pass the initializer list when
// HeapVector supports initializer lists like Vector.
for (auto& command : commands)
m_commands.append(command);
}
virtual ~TestReaction() = default;
DEFINE_INLINE_VIRTUAL_TRACE()
{
CustomElementReaction::trace(visitor);
visitor->trace(m_commands);
}
void invoke(Element* element) override
{
for (auto& command : m_commands)
command->run(element);
}
private:
HeapVector<Member<Command>> m_commands;
};
TEST(CustomElementReactionQueueTest, invokeReactions_one)
{
std::vector<char> log;
......
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/dom/custom/CustomElementReactionStack.h"
#include "core/dom/Element.h"
#include "core/dom/custom/CustomElementReactionQueue.h"
namespace blink {
// TODO(dominicc): Consider using linked heap structures, avoiding
// finalizers, to make short-lived entries fast.
CustomElementReactionStack::CustomElementReactionStack()
{
}
CustomElementReactionStack::~CustomElementReactionStack()
{
}
DEFINE_TRACE(CustomElementReactionStack)
{
visitor->trace(m_map);
visitor->trace(m_stack);
}
void CustomElementReactionStack::push()
{
m_stack.append(nullptr);
}
void CustomElementReactionStack::popInvokingReactions()
{
ElementQueue* queue = m_stack.last();
m_stack.removeLast();
if (!queue)
return;
for (auto& element : *queue) {
if (CustomElementReactionQueue* reactions = m_map.get(element)) {
reactions->invokeReactions(element);
CHECK(reactions->isEmpty());
m_map.remove(element);
}
}
}
void CustomElementReactionStack::enqueue(
Element* element,
CustomElementReaction* reaction)
{
ElementQueue* queue = m_stack.last();
if (!queue)
m_stack.last() = queue = new ElementQueue();
queue->append(element);
CustomElementReactionQueue* reactions = m_map.get(element);
if (!reactions) {
reactions = new CustomElementReactionQueue();
m_map.add(element, reactions);
}
reactions->add(reaction);
}
} // namespace blink
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CustomElementReactionStack_h
#define CustomElementReactionStack_h
#include "core/CoreExport.h"
#include "platform/heap/Handle.h"
#include "wtf/Noncopyable.h"
namespace blink {
class CustomElementReaction;
class CustomElementReactionQueue;
class Element;
// https://html.spec.whatwg.org/multipage/scripting.html#custom-element-reactions
class CORE_EXPORT CustomElementReactionStack final
: public GarbageCollectedFinalized<CustomElementReactionStack> {
WTF_MAKE_NONCOPYABLE(CustomElementReactionStack);
public:
CustomElementReactionStack();
~CustomElementReactionStack();
DECLARE_TRACE();
void push();
void popInvokingReactions();
void enqueue(Element*, CustomElementReaction*);
private:
using ElementReactionQueueMap =
HeapHashMap<Member<Element>, Member<CustomElementReactionQueue>>;
ElementReactionQueueMap m_map;
using ElementQueue = HeapVector<Member<Element>, 1>;
HeapVector<Member<ElementQueue>> m_stack;
};
} // namespace blink
#endif // CustomElementReactionStack_h
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/dom/custom/CustomElementReactionStack.h"
#include "core/dom/custom/CustomElementReaction.h"
#include "core/dom/custom/CustomElementReactionTestHelpers.h"
#include "core/dom/custom/CustomElementTestHelpers.h"
#include "core/html/HTMLDocument.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "wtf/text/AtomicString.h"
#include <initializer_list>
#include <vector>
namespace blink {
TEST(CustomElementReactionStackTest, one)
{
std::vector<char> log;
CustomElementReactionStack* stack = new CustomElementReactionStack();
stack->push();
stack->enqueue(CreateElement("a"), new TestReaction({new Log('a', log)}));
stack->popInvokingReactions();
EXPECT_EQ(log, std::vector<char>({'a'}))
<< "popping the reaction stack should run reactions";
}
TEST(CustomElementReactionStackTest, multipleElements)
{
std::vector<char> log;
CustomElementReactionStack* stack = new CustomElementReactionStack();
stack->push();
stack->enqueue(CreateElement("a"), new TestReaction({new Log('a', log)}));
stack->enqueue(CreateElement("a"), new TestReaction({new Log('b', log)}));
stack->popInvokingReactions();
EXPECT_EQ(log, std::vector<char>({'a', 'b'}))
<< "reactions should run in the order the elements queued";
}
TEST(CustomElementReactionStackTest, popTopEmpty)
{
std::vector<char> log;
CustomElementReactionStack* stack = new CustomElementReactionStack();
stack->push();
stack->enqueue(CreateElement("a"), new TestReaction({new Log('a', log)}));
stack->push();
stack->popInvokingReactions();
EXPECT_EQ(log, std::vector<char>())
<< "popping the empty top-of-stack should not run any reactions";
}
TEST(CustomElementReactionStackTest, popTop)
{
std::vector<char> log;
CustomElementReactionStack* stack = new CustomElementReactionStack();
stack->push();
stack->enqueue(CreateElement("a"), new TestReaction({new Log('a', log)}));
stack->push();
stack->enqueue(CreateElement("a"), new TestReaction({new Log('b', log)}));
stack->popInvokingReactions();
EXPECT_EQ(log, std::vector<char>({'b'}))
<< "popping the top-of-stack should only run top-of-stack reactions";
}
TEST(CustomElementReactionStackTest, requeueingDoesNotReorderElements)
{
std::vector<char> log;
Element* element = CreateElement("a");
CustomElementReactionStack* stack = new CustomElementReactionStack();
stack->push();
stack->enqueue(element, new TestReaction({new Log('a', log)}));
stack->enqueue(CreateElement("a"), new TestReaction({new Log('z', log)}));
stack->enqueue(element, new TestReaction({new Log('b', log)}));
stack->popInvokingReactions();
EXPECT_EQ(log, std::vector<char>({'a', 'b', 'z'}))
<< "reactions should run together in the order elements were queued";
}
TEST(CustomElementReactionStackTest, oneReactionQueuePerElement)
{
std::vector<char> log;
Element* element = CreateElement("a");
CustomElementReactionStack* stack = new CustomElementReactionStack();
stack->push();
stack->enqueue(element, new TestReaction({new Log('a', log)}));
stack->enqueue(CreateElement("a"), new TestReaction({new Log('z', log)}));
stack->push();
stack->enqueue(CreateElement("a"), new TestReaction({new Log('y', log)}));
stack->enqueue(element, new TestReaction({new Log('b', log)}));
stack->popInvokingReactions();
EXPECT_EQ(log, std::vector<char>({'y', 'a', 'b'}))
<< "reactions should run together in the order elements were queued";
log.clear();
stack->popInvokingReactions();
EXPECT_EQ(log, std::vector<char>({'z'})) << "reactions should be run once";
}
} // namespace blink
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/dom/custom/CustomElementReaction.h"
#include "core/dom/custom/CustomElementReactionQueue.h"
#include "platform/heap/Handle.h"
#include "wtf/Noncopyable.h"
#include <initializer_list>
#include <vector>
namespace blink {
class Element;
class Command : public GarbageCollectedFinalized<Command> {
WTF_MAKE_NONCOPYABLE(Command);
public:
Command() { }
virtual ~Command() { }
DEFINE_INLINE_VIRTUAL_TRACE() { }
virtual void run(Element*) = 0;
};
class Log : public Command {
WTF_MAKE_NONCOPYABLE(Log);
public:
Log(char what, std::vector<char>& where) : m_what(what), m_where(where) { }
virtual ~Log() { }
void run(Element*) override { m_where.push_back(m_what); }
private:
char m_what;
std::vector<char>& m_where;
};
class Recurse : public Command {
WTF_MAKE_NONCOPYABLE(Recurse);
public:
Recurse(CustomElementReactionQueue* queue) : m_queue(queue) { }
virtual ~Recurse() { }
DEFINE_INLINE_VIRTUAL_TRACE()
{
Command::trace(visitor);
visitor->trace(m_queue);
}
void run(Element* element) override { m_queue->invokeReactions(element); }
private:
Member<CustomElementReactionQueue> m_queue;
};
class Enqueue : public Command {
WTF_MAKE_NONCOPYABLE(Enqueue);
public:
Enqueue(CustomElementReactionQueue* queue, CustomElementReaction* reaction)
: m_queue(queue)
, m_reaction(reaction)
{
}
virtual ~Enqueue() { }
DEFINE_INLINE_VIRTUAL_TRACE()
{
Command::trace(visitor);
visitor->trace(m_queue);
visitor->trace(m_reaction);
}
void run(Element*) override
{
m_queue->add(m_reaction);
}
private:
Member<CustomElementReactionQueue> m_queue;
Member<CustomElementReaction> m_reaction;
};
class TestReaction : public CustomElementReaction {
WTF_MAKE_NONCOPYABLE(TestReaction);
public:
TestReaction(std::initializer_list<Command*> commands)
{
// TODO(dominicc): Simply pass the initializer list when
// HeapVector supports initializer lists like Vector.
for (auto& command : commands)
m_commands.append(command);
}
virtual ~TestReaction() = default;
DEFINE_INLINE_VIRTUAL_TRACE()
{
CustomElementReaction::trace(visitor);
visitor->trace(m_commands);
}
void invoke(Element* element) override
{
for (auto& command : m_commands)
command->run(element);
}
private:
HeapVector<Member<Command>> m_commands;
};
} // namespace blink
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CustomElementTestHelpers_h
#define CustomElementTestHelpers_h
#include "bindings/core/v8/ExceptionState.h"
#include "core/dom/Document.h"
#include "core/dom/Element.h"
#include "core/dom/QualifiedName.h"
#include "core/html/HTMLDocument.h"
#include "platform/heap/Handle.h"
#include "wtf/text/AtomicString.h"
#include <utility>
#include <vector>
namespace blink {
class CreateElement {
STACK_ALLOCATED()
public:
CreateElement(const AtomicString& localName)
: m_namespaceURI(HTMLNames::xhtmlNamespaceURI)
, m_localName(localName)
{
}
CreateElement& inDocument(Document* document)
{
m_document = document;
return *this;
}
CreateElement& inNamespace(const AtomicString& uri)
{
m_namespaceURI = uri;
return *this;
}
CreateElement& withId(const AtomicString& id)
{
m_attributes.push_back(std::make_pair(HTMLNames::idAttr, id));
return *this;
}
CreateElement& withIsAttribute(const AtomicString& value)
{
m_attributes.push_back(std::make_pair(HTMLNames::isAttr, value));
return *this;
}
operator Element*() const
{
Document* document = m_document.get();
if (!document)
document = HTMLDocument::create();
NonThrowableExceptionState noExceptions;
Element* element = document->createElementNS(
m_namespaceURI,
m_localName,
noExceptions);
for (const auto& attribute : m_attributes)
element->setAttribute(attribute.first, attribute.second);
return element;
}
private:
Member<Document> m_document;
AtomicString m_namespaceURI;
AtomicString m_localName;
std::vector<std::pair<QualifiedName, AtomicString>> m_attributes;
};
} // namespace blink
#endif // CustomElementTestHelpers_h
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment