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 |