| Index: content/browser/loader/navigation_resource_handler.cc
|
| diff --git a/content/browser/loader/navigation_resource_handler.cc b/content/browser/loader/navigation_resource_handler.cc
|
| index 5e6dd692da0c6a3b875e82c340a71b8d5f3e941a..49a9682a6c14350608c4184ba5a1d27c61636b46 100644
|
| --- a/content/browser/loader/navigation_resource_handler.cc
|
| +++ b/content/browser/loader/navigation_resource_handler.cc
|
| @@ -21,11 +21,72 @@
|
| #include "content/public/browser/ssl_status.h"
|
| #include "content/public/browser/stream_handle.h"
|
| #include "content/public/common/resource_response.h"
|
| +#include "net/base/io_buffer.h"
|
| +#include "net/base/mime_sniffer.h"
|
| #include "net/base/net_errors.h"
|
| #include "net/url_request/url_request.h"
|
|
|
| namespace content {
|
|
|
| +namespace {
|
| +
|
| +// TODO(kinuko): Most of the code below is dup'ed from MojoAsyncResourceHandler,
|
| +// we should unify the implementation.
|
| +
|
| +// For MimeSniffingResourceHandler.
|
| +constexpr size_t kMinAllocationSize = 2 * net::kMaxBytesToSniff;
|
| +
|
| +constexpr size_t kMaxChunkSize = 32 * 1024;
|
| +
|
| +constexpr size_t kDefaultAllocationSize = 512 * 1024;
|
| +
|
| +} // namespace
|
| +
|
| +// This class is for sharing the ownership of a ScopedDataPipeProducerHandle
|
| +// between WriterIOBuffer and other class.
|
| +class NavigationResourceHandler::SharedWriter final
|
| + : public base::RefCountedThreadSafe<SharedWriter> {
|
| + public:
|
| + explicit SharedWriter(mojo::ScopedDataPipeProducerHandle writer)
|
| + : writer_(std::move(writer)) {}
|
| + mojo::DataPipeProducerHandle writer() { return writer_.get(); }
|
| +
|
| + private:
|
| + friend class base::RefCountedThreadSafe<SharedWriter>;
|
| + ~SharedWriter() {}
|
| +
|
| + const mojo::ScopedDataPipeProducerHandle writer_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(SharedWriter);
|
| +};
|
| +
|
| +// This class is a IOBuffer subclass for data gotten from a
|
| +// ScopedDataPipeProducerHandle.
|
| +class NavigationResourceHandler::WriterIOBuffer final
|
| + : public net::IOBufferWithSize {
|
| + public:
|
| + // |data| and |size| should be gotten from |writer| via BeginWriteDataRaw.
|
| + // They will be accesible via IOBuffer methods. As |writer| is stored in this
|
| + // instance, |data| will be kept valid as long as the following conditions
|
| + // hold:
|
| + // 1. |data| is not invalidated via EndWriteDataRaw.
|
| + // 2. |this| instance is alive.
|
| + WriterIOBuffer(scoped_refptr<SharedWriter> writer, void* data, size_t size)
|
| + : net::IOBufferWithSize(static_cast<char*>(data), size),
|
| + writer_(std::move(writer)) {}
|
| +
|
| + private:
|
| + ~WriterIOBuffer() override {
|
| + // Avoid deleting |data_| in the IOBuffer destructor.
|
| + data_ = nullptr;
|
| + }
|
| +
|
| + // This member is for keeping the writer alive.
|
| + scoped_refptr<SharedWriter> writer_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(WriterIOBuffer);
|
| +};
|
| +
|
| void NavigationResourceHandler::GetSSLStatusForRequest(
|
| const GURL& url,
|
| const net::SSLInfo& ssl_info,
|
| @@ -41,9 +102,9 @@ NavigationResourceHandler::NavigationResourceHandler(
|
| ResourceDispatcherHostDelegate* resource_dispatcher_host_delegate)
|
| : ResourceHandler(request),
|
| core_(core),
|
| - resource_dispatcher_host_delegate_(resource_dispatcher_host_delegate) {
|
| + resource_dispatcher_host_delegate_(resource_dispatcher_host_delegate),
|
| + handle_watcher_(FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL) {
|
| core_->set_resource_handler(this);
|
| - writer_.set_immediate_mode(true);
|
| }
|
|
|
| NavigationResourceHandler::~NavigationResourceHandler() {
|
| @@ -95,13 +156,8 @@ void NavigationResourceHandler::OnResponseStarted(
|
|
|
| ResourceRequestInfoImpl* info = GetRequestInfo();
|
|
|
| - StreamContext* stream_context =
|
| - GetStreamContextForResourceContext(info->GetContext());
|
| - writer_.InitializeStream(
|
| - stream_context->registry(), request()->url().GetOrigin(),
|
| - base::Bind(&NavigationResourceHandler::OutOfBandCancel,
|
| - base::Unretained(this), net::ERR_ABORTED,
|
| - true /* tell_renderer */));
|
| + mojo::ScopedDataPipeConsumerHandle data_consumer_handle;
|
| + InitializeDataPipe(&data_consumer_handle);
|
|
|
| NetLogObserver::PopulateResponseInfo(request(), response);
|
| response->head.encoded_data_length = request()->raw_header_size();
|
| @@ -123,7 +179,7 @@ void NavigationResourceHandler::OnResponseStarted(
|
| info->GetChildID(), &ssl_status);
|
| }
|
|
|
| - core_->NotifyResponseStarted(response, writer_.stream()->CreateHandle(),
|
| + core_->NotifyResponseStarted(response, std::move(data_consumer_handle),
|
| ssl_status, std::move(cloned_data),
|
| info->GetGlobalRequestID(), info->IsDownload(),
|
| info->is_stream());
|
| @@ -159,7 +215,52 @@ void NavigationResourceHandler::OnWillRead(
|
| int* buf_size,
|
| std::unique_ptr<ResourceController> controller) {
|
| DCHECK(!has_controller());
|
| - writer_.OnWillRead(buf, buf_size, -1);
|
| +
|
| + DCHECK(shared_writer_);
|
| + DCHECK(!buffer_);
|
| + DCHECK_EQ(0u, buffer_offset_);
|
| +
|
| + bool first_call = first_read_;
|
| +
|
| + if (first_read_) {
|
| + first_read_ = false;
|
| + handle_watcher_.Watch(shared_writer_->writer(), MOJO_HANDLE_SIGNAL_WRITABLE,
|
| + base::Bind(&NavigationResourceHandler::OnWritable,
|
| + base::Unretained(this)));
|
| + handle_watcher_.ArmOrNotify();
|
| + }
|
| +
|
| + bool defer = false;
|
| + if (!AllocateWriterIOBuffer(&buffer_, &defer)) {
|
| + controller->CancelWithError(net::ERR_INSUFFICIENT_RESOURCES);
|
| + return;
|
| + }
|
| +
|
| + if (defer) {
|
| + parent_buffer_ = buf;
|
| + parent_buffer_size_ = buf_size;
|
| + HoldController(std::move(controller));
|
| + request()->LogBlockedBy("MojoStreamWriter");
|
| + did_defer_on_will_read_ = true;
|
| + return;
|
| + }
|
| +
|
| + // The first call to OnWillRead must return a buffer of at least
|
| + // kMinAllocationSize. If the Mojo buffer is too small, need to allocate an
|
| + // intermediary buffer.
|
| + if (first_call && static_cast<size_t>(buffer_->size()) < kMinAllocationSize) {
|
| + // The allocated buffer is too small, so need to create an intermediary one.
|
| + if (EndWrite(0) != MOJO_RESULT_OK) {
|
| + controller->CancelWithError(net::ERR_INSUFFICIENT_RESOURCES);
|
| + return;
|
| + }
|
| + DCHECK(!is_using_io_buffer_not_from_writer_);
|
| + is_using_io_buffer_not_from_writer_ = true;
|
| + buffer_ = new net::IOBufferWithSize(kMinAllocationSize);
|
| + }
|
| +
|
| + *buf = buffer_;
|
| + *buf_size = buffer_->size();
|
| controller->Resume();
|
| }
|
|
|
| @@ -167,17 +268,51 @@ void NavigationResourceHandler::OnReadCompleted(
|
| int bytes_read,
|
| std::unique_ptr<ResourceController> controller) {
|
| DCHECK(!has_controller());
|
| - writer_.OnReadCompleted(bytes_read,
|
| - base::Bind(&ResourceController::Resume,
|
| - base::Passed(std::move(controller))));
|
| + DCHECK_GE(bytes_read, 0);
|
| + DCHECK(buffer_);
|
| +
|
| + if (bytes_read == 0) {
|
| + // Note that |buffer_| is not cleared here, which will cause a DCHECK on
|
| + // subsequent OnWillRead calls.
|
| + controller->Resume();
|
| + return;
|
| + }
|
| +
|
| + if (is_using_io_buffer_not_from_writer_) {
|
| + // Couldn't allocate a large enough buffer on the data pipe in OnWillRead.
|
| + DCHECK_EQ(0u, buffer_bytes_read_);
|
| + buffer_bytes_read_ = bytes_read;
|
| + bool defer = false;
|
| + if (!CopyReadDataToDataPipe(&defer)) {
|
| + controller->CancelWithError(net::ERR_INSUFFICIENT_RESOURCES);
|
| + return;
|
| + }
|
| + if (defer) {
|
| + request()->LogBlockedBy("NavigationResourceHandler");
|
| + did_defer_on_writing_ = true;
|
| + HoldController(std::move(controller));
|
| + return;
|
| + }
|
| + controller->Resume();
|
| + return;
|
| + }
|
| +
|
| + if (EndWrite(bytes_read) != MOJO_RESULT_OK) {
|
| + controller->Cancel();
|
| + return;
|
| + }
|
| +
|
| + buffer_ = nullptr;
|
| + controller->Resume();
|
| }
|
|
|
| void NavigationResourceHandler::OnResponseCompleted(
|
| const net::URLRequestStatus& status,
|
| std::unique_ptr<ResourceController> controller) {
|
| - // If the request has already committed, close the stream and leave it as-is.
|
| - if (writer_.stream()) {
|
| - writer_.Finalize(status.error());
|
| + if (shared_writer_) {
|
| + shared_writer_ = nullptr;
|
| + buffer_ = nullptr;
|
| + handle_watcher_.Cancel();
|
| controller->Resume();
|
| return;
|
| }
|
| @@ -201,4 +336,122 @@ void NavigationResourceHandler::DetachFromCore() {
|
| core_ = nullptr;
|
| }
|
|
|
| +void NavigationResourceHandler::InitializeDataPipe(
|
| + mojo::ScopedDataPipeConsumerHandle* consumer_handle) {
|
| + DCHECK(!shared_writer_);
|
| +
|
| + MojoCreateDataPipeOptions options;
|
| + options.struct_size = sizeof(MojoCreateDataPipeOptions);
|
| + options.flags = MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE;
|
| + options.element_num_bytes = 1;
|
| + options.capacity_num_bytes = kDefaultAllocationSize;
|
| + mojo::DataPipe data_pipe(options);
|
| +
|
| + DCHECK(data_pipe.producer_handle.is_valid());
|
| + DCHECK(data_pipe.consumer_handle.is_valid());
|
| +
|
| + *consumer_handle = std::move(data_pipe.consumer_handle);
|
| + shared_writer_ = new SharedWriter(std::move(data_pipe.producer_handle));
|
| +}
|
| +
|
| +MojoResult NavigationResourceHandler::BeginWrite(void** data,
|
| + uint32_t* available) {
|
| + MojoResult result = mojo::BeginWriteDataRaw(
|
| + shared_writer_->writer(), data, available, MOJO_WRITE_DATA_FLAG_NONE);
|
| + if (result == MOJO_RESULT_OK)
|
| + *available = std::min(*available, static_cast<uint32_t>(kMaxChunkSize));
|
| + else if (result == MOJO_RESULT_SHOULD_WAIT)
|
| + handle_watcher_.ArmOrNotify();
|
| + return result;
|
| +}
|
| +
|
| +MojoResult NavigationResourceHandler::EndWrite(uint32_t written) {
|
| + MojoResult result = mojo::EndWriteDataRaw(shared_writer_->writer(), written);
|
| + if (result == MOJO_RESULT_OK)
|
| + handle_watcher_.ArmOrNotify();
|
| + return result;
|
| +}
|
| +
|
| +bool NavigationResourceHandler::CopyReadDataToDataPipe(bool* defer) {
|
| + while (buffer_bytes_read_ > 0) {
|
| + scoped_refptr<net::IOBufferWithSize> dest;
|
| + if (!AllocateWriterIOBuffer(&dest, defer))
|
| + return false;
|
| + if (*defer)
|
| + return true;
|
| +
|
| + size_t copied_size =
|
| + std::min(buffer_bytes_read_, static_cast<size_t>(dest->size()));
|
| + memcpy(dest->data(), buffer_->data() + buffer_offset_, copied_size);
|
| + buffer_offset_ += copied_size;
|
| + buffer_bytes_read_ -= copied_size;
|
| + if (EndWrite(copied_size) != MOJO_RESULT_OK)
|
| + return false;
|
| + }
|
| +
|
| + // All bytes are copied.
|
| + buffer_ = nullptr;
|
| + buffer_offset_ = 0;
|
| + is_using_io_buffer_not_from_writer_ = false;
|
| + return true;
|
| +}
|
| +
|
| +bool NavigationResourceHandler::AllocateWriterIOBuffer(
|
| + scoped_refptr<net::IOBufferWithSize>* buf,
|
| + bool* defer) {
|
| + void* data = nullptr;
|
| + uint32_t available = 0;
|
| + MojoResult result = BeginWrite(&data, &available);
|
| + if (result == MOJO_RESULT_SHOULD_WAIT) {
|
| + *defer = true;
|
| + return true;
|
| + }
|
| + if (result != MOJO_RESULT_OK)
|
| + return false;
|
| + DCHECK_GT(available, 0u);
|
| + *buf = new WriterIOBuffer(shared_writer_, data, available);
|
| + return true;
|
| +}
|
| +
|
| +void NavigationResourceHandler::OnWritable(MojoResult result) {
|
| + if (did_defer_on_will_read_) {
|
| + DCHECK(has_controller());
|
| + DCHECK(!did_defer_on_writing_);
|
| + DCHECK(!did_defer_on_redirect_);
|
| +
|
| + did_defer_on_will_read_ = false;
|
| +
|
| + scoped_refptr<net::IOBuffer>* parent_buffer = parent_buffer_;
|
| + parent_buffer_ = nullptr;
|
| + int* parent_buffer_size = parent_buffer_size_;
|
| + parent_buffer_size_ = nullptr;
|
| +
|
| + request()->LogUnblocked();
|
| + OnWillRead(parent_buffer, parent_buffer_size, ReleaseController());
|
| + return;
|
| + }
|
| +
|
| + if (!did_defer_on_writing_)
|
| + return;
|
| + DCHECK(has_controller());
|
| + DCHECK(!did_defer_on_redirect_);
|
| + did_defer_on_writing_ = false;
|
| +
|
| + DCHECK(is_using_io_buffer_not_from_writer_);
|
| + // |buffer_| is set to a net::IOBufferWithSize. Write the buffer contents
|
| + // to the data pipe.
|
| + DCHECK_GT(buffer_bytes_read_, 0u);
|
| + if (!CopyReadDataToDataPipe(&did_defer_on_writing_)) {
|
| + CancelWithError(net::ERR_INSUFFICIENT_RESOURCES);
|
| + return;
|
| + }
|
| +
|
| + if (did_defer_on_writing_) {
|
| + // Continue waiting.
|
| + return;
|
| + }
|
| + request()->LogUnblocked();
|
| + Resume();
|
| +}
|
| +
|
| } // namespace content
|
|
|