Chromium Code Reviews| Index: third_party/WebKit/Source/modules/fetch/BlobBytesConsumer.cpp |
| diff --git a/third_party/WebKit/Source/modules/fetch/BlobBytesConsumer.cpp b/third_party/WebKit/Source/modules/fetch/BlobBytesConsumer.cpp |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..db9f2b5d255eff8e76d435b5b97457efe209fd93 |
| --- /dev/null |
| +++ b/third_party/WebKit/Source/modules/fetch/BlobBytesConsumer.cpp |
| @@ -0,0 +1,303 @@ |
| +// 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 "modules/fetch/BlobBytesConsumer.h" |
| + |
| +#include "core/fetch/FetchInitiatorTypeNames.h" |
| +#include "core/loader/ThreadableLoader.h" |
| +#include "modules/fetch/BytesConsumerForDataConsumerHandle.h" |
| +#include "modules/fetch/DataConsumerHandleUtil.h" |
| +#include "platform/blob/BlobData.h" |
| +#include "platform/blob/BlobRegistry.h" |
| +#include "platform/blob/BlobURL.h" |
| +#include "platform/network/ResourceError.h" |
| +#include "platform/network/ResourceRequest.h" |
| +#include "platform/weborigin/KURL.h" |
| +#include "platform/weborigin/SecurityOrigin.h" |
| + |
| +namespace blink { |
| + |
| +BlobBytesConsumer::BlobBytesConsumer(ExecutionContext* executionContext, PassRefPtr<BlobDataHandle> blobDataHandle, ThreadableLoader* loader) |
| + : ContextLifecycleObserver(executionContext) |
| + , m_blobDataHandle(blobDataHandle) |
| + , m_loader(loader) |
| +{ |
| + ThreadState::current()->registerPreFinalizer(this); |
| +} |
| + |
| +BlobBytesConsumer::BlobBytesConsumer(ExecutionContext* executionContext, PassRefPtr<BlobDataHandle> blobDataHandle) |
| + : BlobBytesConsumer(executionContext, blobDataHandle, nullptr) |
| +{ |
| +} |
| + |
| +BlobBytesConsumer::~BlobBytesConsumer() |
| +{ |
| +} |
| + |
| +BytesConsumer::Result BlobBytesConsumer::beginRead(const char** buffer, size_t* available) |
| +{ |
| + *buffer = nullptr; |
| + *available = 0; |
| + |
| + if (m_state == PublicState::Closed) { |
| + // It's possible that |cancel| has been called before the first |
| + // |beginRead| call. That's why we need to check this condition |
| + // before checking |isClean()|. |
| + return Result::Done; |
| + } |
| + |
| + if (isClean()) { |
| + KURL m_blobURL = BlobURL::createPublicURL(getExecutionContext()->getSecurityOrigin()); |
| + if (m_blobURL.isEmpty()) { |
| + m_state = PublicState::Errored; |
| + } else { |
| + BlobRegistry::registerPublicBlobURL(getExecutionContext()->getSecurityOrigin(), m_blobURL, m_blobDataHandle); |
| + |
| + // m_loader is non-null only in tests. |
| + if (!m_loader) |
| + m_loader = createLoader(); |
| + |
| + ResourceRequest request(m_blobURL); |
| + request.setRequestContext(WebURLRequest::RequestContextInternal); |
| + request.setUseStreamOnResponse(true); |
| + // We intentionally skip |
| + // 'setExternalRequestStateFromRequestorAddressSpace', as 'blob:' |
| + // can never be external. |
| + m_loader->start(request); |
| + } |
| + m_blobDataHandle = nullptr; |
| + } |
| + |
| + if (m_state == PublicState::Errored) |
| + return Result::Error; |
| + |
| + if (!m_body) { |
| + // The response has not arrived. |
| + return Result::ShouldWait; |
| + } |
| + |
| + auto result = m_body->beginRead(buffer, available); |
| + switch (result) { |
| + case Result::Ok: |
| + case Result::ShouldWait: |
| + break; |
| + case Result::Done: |
| + m_hasSeenEndOfData = true; |
| + if (m_hasFinishedLoading) |
| + m_state = PublicState::Closed; |
| + clearIfNecessary(); |
| + return m_state == PublicState::Closed ? Result::Done : Result::ShouldWait; |
| + case Result::Error: |
| + m_state = PublicState::Errored; |
| + clearIfNecessary(); |
| + break; |
| + } |
| + return result; |
| +} |
| + |
| +BytesConsumer::Result BlobBytesConsumer::endRead(size_t read) |
| +{ |
| + DCHECK(m_body); |
| + auto result = m_body->endRead(read); |
| + clearIfNecessary(); |
| + return result; |
| +} |
| + |
| +PassRefPtr<BlobDataHandle> BlobBytesConsumer::drainAsBlobDataHandle(BlobSizePolicy policy) |
| +{ |
| + if (!isClean()) |
| + return nullptr; |
| + DCHECK(m_blobDataHandle); |
| + if (policy == BlobSizePolicy::DisallowBlobWithInvalidSize && m_blobDataHandle->size() == UINT64_MAX) |
| + return nullptr; |
| + m_state = PublicState::Closed; |
| + return m_blobDataHandle.release(); |
| +} |
| + |
| +PassRefPtr<EncodedFormData> BlobBytesConsumer::drainAsFormData() |
| +{ |
| + RefPtr<BlobDataHandle> handle = drainAsBlobDataHandle(BlobSizePolicy::AllowBlobWithInvalidSize); |
| + if (!handle) |
| + return nullptr; |
| + RefPtr<EncodedFormData> formData = EncodedFormData::create(); |
| + formData->appendBlob(handle->uuid(), handle); |
| + return formData.release(); |
| +} |
| + |
| +void BlobBytesConsumer::setClient(BytesConsumer::Client* client) |
| +{ |
| + DCHECK(!m_client); |
| + DCHECK(client); |
| + m_client = client; |
| +} |
| + |
| +void BlobBytesConsumer::clearClient() |
| +{ |
| + m_client = nullptr; |
| +} |
| + |
| +void BlobBytesConsumer::cancel() |
| +{ |
| + if (m_state == PublicState::Closed || m_state == PublicState::Errored) |
| + return; |
| + m_state = PublicState::Closed; |
| + clearIfNecessary(); |
| + if (m_body) { |
| + m_body->cancel(); |
| + m_body = nullptr; |
| + } |
| + if (!m_blobURL.isEmpty()) { |
| + BlobRegistry::revokePublicBlobURL(m_blobURL); |
| + m_blobURL = KURL(); |
| + } |
| +} |
| + |
| +BytesConsumer::Error BlobBytesConsumer::getError() const |
| +{ |
| + DCHECK_EQ(PublicState::Errored, m_state); |
| + return Error("Failed to load a blob."); |
| +} |
| + |
| +BytesConsumer::PublicState BlobBytesConsumer::getPublicState() const |
| +{ |
| + return m_state; |
| +} |
| + |
| +void BlobBytesConsumer::contextDestroyed() |
| +{ |
| + if (m_loader) { |
|
hiroshige
2016/09/15 05:54:51
Do we need this block?
when |m_state| is set to Er
yhirano
2016/09/15 08:13:32
Done.
|
| + m_loader->cancel(); |
| + m_loader = nullptr; |
| + } |
| + |
| + if (m_state != PublicState::ReadableOrWaiting) |
| + return; |
| + |
| + m_state = PublicState::Errored; |
|
hiroshige
2016/09/15 05:54:51
Can we make a method like
void BlobBytesConsumer:
yhirano
2016/09/15 08:13:32
I'm more comfortable with saving m_client manually
|
| + if (m_client) |
| + m_client->onStateChange(); |
| + clearIfNecessary(); |
| +} |
| + |
| +void BlobBytesConsumer::onStateChange() |
| +{ |
| + if (m_state != PublicState::ReadableOrWaiting) |
| + return; |
| + DCHECK(m_body); |
| + |
| + switch (m_body->getPublicState()) { |
| + case PublicState::ReadableOrWaiting: |
| + break; |
| + case PublicState::Closed: |
| + m_hasSeenEndOfData = true; |
| + if (m_hasFinishedLoading) |
| + m_state = PublicState::Closed; |
| + break; |
| + case PublicState::Errored: |
| + m_state = PublicState::Errored; |
| + break; |
| + } |
| + |
| + if (m_client) |
| + m_client->onStateChange(); |
| + clearIfNecessary(); |
| +} |
| + |
| +void BlobBytesConsumer::didReceiveResponse(unsigned long identifier, const ResourceResponse&, std::unique_ptr<WebDataConsumerHandle> handle) |
| +{ |
| + DCHECK(handle); |
| + DCHECK(!m_body); |
| + DCHECK_EQ(PublicState::ReadableOrWaiting, m_state); |
| + |
| + m_body = new BytesConsumerForDataConsumerHandle(getExecutionContext(), createFetchDataConsumerHandleFromWebHandle(std::move(handle))); |
| + m_body->setClient(this); |
| + |
| + if (isClean()) { |
| + // This function is called synchronously in ThreadableLoader::start. |
| + return; |
| + } |
| + onStateChange(); |
| +} |
| + |
| +void BlobBytesConsumer::didFinishLoading(unsigned long identifier, double finishTime) |
| +{ |
| + DCHECK_EQ(PublicState::ReadableOrWaiting, m_state); |
| + m_hasFinishedLoading = true; |
| + m_loader = nullptr; |
| + if (!m_hasSeenEndOfData) |
| + return; |
| + DCHECK(!isClean()); |
| + m_state = PublicState::Closed; |
| + if (m_client) { |
| + m_client->onStateChange(); |
| + m_client = nullptr; |
| + } |
| +} |
| + |
| +void BlobBytesConsumer::didFail(const ResourceError& error) |
| +{ |
| + if (error.isCancellation()) { |
| + DCHECK_EQ(PublicState::Closed, m_state); |
| + return; |
| + } |
| + DCHECK_EQ(PublicState::ReadableOrWaiting, m_state); |
| + m_state = PublicState::Errored; |
| + m_loader = nullptr; |
| + if (isClean()) { |
| + // This function is called synchronously in ThreadableLoader::start. |
| + return; |
| + } |
| + if (m_client) { |
| + m_client->onStateChange(); |
| + m_client = nullptr; |
| + } |
| +} |
| + |
| +void BlobBytesConsumer::didFailRedirectCheck() |
| +{ |
| + NOTREACHED(); |
| +} |
| + |
| +DEFINE_TRACE(BlobBytesConsumer) |
| +{ |
| + visitor->trace(m_body); |
| + visitor->trace(m_client); |
| + visitor->trace(m_loader); |
| + BytesConsumer::trace(visitor); |
| + BytesConsumer::Client::trace(visitor); |
| + ContextLifecycleObserver::trace(visitor); |
| +} |
| + |
| +BlobBytesConsumer* BlobBytesConsumer::createForTesting(ExecutionContext* executionContext, PassRefPtr<BlobDataHandle> blobDataHandle, ThreadableLoader* loader) |
| +{ |
| + return new BlobBytesConsumer(executionContext, blobDataHandle, loader); |
| +} |
| + |
| +ThreadableLoader* BlobBytesConsumer::createLoader() |
| +{ |
| + ThreadableLoaderOptions options; |
| + options.preflightPolicy = ConsiderPreflight; |
| + options.crossOriginRequestPolicy = DenyCrossOriginRequests; |
| + options.contentSecurityPolicyEnforcement = DoNotEnforceContentSecurityPolicy; |
| + options.initiator = FetchInitiatorTypeNames::internal; |
| + |
| + ResourceLoaderOptions resourceLoaderOptions; |
| + resourceLoaderOptions.dataBufferingPolicy = DoNotBufferData; |
| + |
| + return ThreadableLoader::create(*getExecutionContext(), this, options, resourceLoaderOptions); |
| +} |
| + |
| +void BlobBytesConsumer::clearIfNecessary() |
| +{ |
| + if (m_state == PublicState::ReadableOrWaiting) |
| + return; |
| + |
| + if (m_loader) { |
| + m_loader->cancel(); |
| + m_loader = nullptr; |
| + } |
| + m_client = nullptr; |
| +} |
| + |
| +} // namespace blink |