Chromium Code Reviews| Index: Source/modules/fetch/DataConsumerHandleTestUtil.h |
| diff --git a/Source/modules/fetch/DataConsumerHandleTestUtil.h b/Source/modules/fetch/DataConsumerHandleTestUtil.h |
| index b60d2e6c7b22517ddb79f91d901acb83176d9dcd..6acf0677d6a8ca8d1c85869cc81d3e5d00e439dd 100644 |
| --- a/Source/modules/fetch/DataConsumerHandleTestUtil.h |
| +++ b/Source/modules/fetch/DataConsumerHandleTestUtil.h |
| @@ -10,6 +10,7 @@ |
| #include "gin/public/isolate_holder.h" |
| #include "modules/fetch/DataConsumerHandleUtil.h" |
| #include "modules/fetch/FetchDataConsumerHandle.h" |
| +#include "modules/fetch/FetchDataLoader.h" |
| #include "platform/Task.h" |
| #include "platform/ThreadSafeFunctional.h" |
| #include "platform/WebThreadSupportingGC.h" |
| @@ -211,6 +212,226 @@ public: |
| OwnPtr<WebDataConsumerHandle> m_handle; |
| }; |
| + |
| + class MockFetchDataLoaderClient : public GarbageCollectedFinalized<MockFetchDataLoaderClient>, public FetchDataLoader::Client { |
| + USING_GARBAGE_COLLECTED_MIXIN(MockFetchDataLoaderClient); |
| + public: |
| + static ::testing::StrictMock<MockFetchDataLoaderClient>* create() { return new ::testing::StrictMock<MockFetchDataLoaderClient>; } |
| + |
| + DEFINE_INLINE_VIRTUAL_TRACE() |
| + { |
| + FetchDataLoader::Client::trace(visitor); |
| + } |
| + |
| + MOCK_METHOD1(didFetchDataLoadedBlobHandleMock, void(RefPtr<BlobDataHandle>)); |
| + MOCK_METHOD1(didFetchDataLoadedArrayBufferMock, void(RefPtr<DOMArrayBuffer>)); |
| + MOCK_METHOD1(didFetchDataLoadedString, void(const String&)); |
| + MOCK_METHOD0(didFetchDataLoadFailed, void()); |
| + |
| + // In mock methods we use RefPtr<> rather than PassRefPtr<>. |
| + void didFetchDataLoadedArrayBuffer(PassRefPtr<DOMArrayBuffer> arrayBuffer) override |
| + { |
| + didFetchDataLoadedArrayBufferMock(arrayBuffer); |
| + } |
| + void didFetchDataLoadedBlobHandle(PassRefPtr<BlobDataHandle> blobDataHandle) override |
| + { |
| + didFetchDataLoadedBlobHandleMock(blobDataHandle); |
| + } |
| + }; |
| + |
| + class Command final { |
| + public: |
| + enum Name { |
| + Data, |
| + Done, |
| + Error, |
| + Wait, |
| + }; |
| + |
| + Command(Name name) : m_name(name) { } |
| + Command(Name name, const Vector<char>& body) : m_name(name), m_body(body) { } |
| + Command(Name name, const char* body, size_t size) : m_name(name) |
| + { |
| + m_body.append(body, size); |
| + } |
| + Command(Name name, const char* body) : Command(name, body, strlen(body)) { } |
| + Name name() const { return m_name; } |
| + const Vector<char>& body() const { return m_body; } |
| + |
| + private: |
| + const Name m_name; |
| + Vector<char> m_body; |
| + }; |
| + |
| + // ReplayingHandle stores commands via |add| and replays the stored commends when read. |
| + class ReplayingHandle final : public WebDataConsumerHandle { |
| + public: |
| + static PassOwnPtr<ReplayingHandle> create() { return adoptPtr(new ReplayingHandle()); } |
| + ~ReplayingHandle(); |
| + |
| + // Add a command to this handle. This function must be called on the |
| + // creator thread. This function must be called BEFORE any reader is |
| + // obtained. |
| + void add(const Command&); |
| + |
| + class Context final : public ThreadSafeRefCounted<Context> { |
| + public: |
| + static PassRefPtr<Context> create() { return adoptRef(new Context); } |
| + |
| + // This function cannot be called after creating a tee. |
| + void add(const Command& command) |
|
yhirano
2015/06/25 04:31:03
Please move function definitions to the cpp file.
hiroshige
2015/06/25 05:33:57
Done.
|
| + { |
| + MutexLocker locker(m_mutex); |
| + m_commands.append(command); |
| + } |
| + |
| + void attachReader(WebDataConsumerHandle::Client* client) |
| + { |
| + MutexLocker locker(m_mutex); |
| + ASSERT(!m_readerThread); |
| + ASSERT(!m_client); |
| + m_readerThread = Platform::current()->currentThread(); |
| + m_client = client; |
| + |
| + if (m_client && !(isEmpty() && m_result == ShouldWait)) |
| + notify(); |
| + } |
| + void detachReader() |
| + { |
| + MutexLocker locker(m_mutex); |
| + ASSERT(m_readerThread && m_readerThread->isCurrentThread()); |
| + m_readerThread = nullptr; |
| + m_client = nullptr; |
| + if (!m_isHandleAttached) |
| + m_detached->signal(); |
| + } |
| + |
| + void detachHandle() |
| + { |
| + MutexLocker locker(m_mutex); |
| + m_isHandleAttached = false; |
| + if (!m_readerThread) |
| + m_detached->signal(); |
| + } |
| + |
| + Result beginRead(const void** buffer, Flags, size_t* available) |
| + { |
| + MutexLocker locker(m_mutex); |
| + *buffer = nullptr; |
| + *available = 0; |
| + if (isEmpty()) |
| + return m_result; |
| + |
| + const Command& command = top(); |
| + Result result = Ok; |
| + switch (command.name()) { |
| + case Command::Data: { |
| + auto& body = command.body(); |
| + *available = body.size() - offset(); |
| + *buffer = body.data() + offset(); |
| + result = Ok; |
| + break; |
| + } |
| + case Command::Done: |
| + m_result = result = Done; |
| + consume(0); |
| + break; |
| + case Command::Wait: |
| + consume(0); |
| + result = ShouldWait; |
| + notify(); |
| + break; |
| + case Command::Error: |
| + m_result = result = UnexpectedError; |
| + consume(0); |
| + break; |
| + } |
| + return result; |
| + } |
| + Result endRead(size_t readSize) |
| + { |
| + MutexLocker locker(m_mutex); |
| + consume(readSize); |
| + return Ok; |
| + } |
| + |
| + WebWaitableEvent* detached() { return m_detached.get(); } |
| + |
| + private: |
| + Context() |
| + : m_offset(0) |
| + , m_readerThread(nullptr) |
| + , m_client(nullptr) |
| + , m_result(ShouldWait) |
| + , m_isHandleAttached(true) |
| + , m_detached(adoptPtr(Platform::current()->createWaitableEvent())) |
| + { |
| + } |
| + |
| + bool isEmpty() const { return m_commands.isEmpty(); } |
| + const Command& top() |
| + { |
| + ASSERT(!isEmpty()); |
| + return m_commands.first(); |
| + } |
| + |
| + void consume(size_t size) |
| + { |
| + ASSERT(!isEmpty()); |
| + ASSERT(size + m_offset <= top().body().size()); |
| + bool fullyConsumed = (size + m_offset >= top().body().size()); |
| + if (fullyConsumed) { |
| + m_offset = 0; |
| + m_commands.removeFirst(); |
| + } else { |
| + m_offset += size; |
| + } |
| + } |
| + |
| + size_t offset() const { return m_offset; } |
| + |
| + void notify() |
| + { |
| + if (!m_client) |
| + return; |
| + ASSERT(m_readerThread); |
| + m_readerThread->postTask(FROM_HERE, new Task(threadSafeBind(&Context::notifyInternal, this))); |
| + } |
| + |
| + void notifyInternal() |
| + { |
| + { |
| + MutexLocker locker(m_mutex); |
| + if (!m_client || !m_readerThread->isCurrentThread()) { |
| + // There is no client, or a new reader is attached. |
| + return; |
| + } |
| + } |
| + // The reading thread is the current thread. |
| + m_client->didGetReadable(); |
| + } |
| + |
| + Deque<Command> m_commands; |
| + size_t m_offset; |
| + WebThread* m_readerThread; |
| + Client* m_client; |
| + Result m_result; |
| + bool m_isHandleAttached; |
| + Mutex m_mutex; |
| + OwnPtr<WebWaitableEvent> m_detached; |
| + }; |
| + |
| + Context* context() { return m_context.get(); } |
| + |
| + private: |
| + class ReaderImpl; |
| + |
| + ReplayingHandle(); |
| + Reader* obtainReaderInternal(Client*) override; |
| + |
| + RefPtr<Context> m_context; |
| + }; |
| + |
| }; |
| } // namespace blink |