blob: 31493b1ed8e088bb9d8e448dc3e3e4434c843248 [file] [log] [blame]
// Copyright 2013 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 "content/browser/loader/detachable_resource_handler.h"
#include <utility>
#include "base/logging.h"
#include "base/time/time.h"
#include "content/browser/loader/null_resource_controller.h"
#include "content/browser/loader/resource_controller.h"
#include "content/browser/loader/resource_request_info_impl.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_status.h"
namespace {
// This matches the maximum allocation size of AsyncResourceHandler.
const int kReadBufSize = 32 * 1024;
}
namespace content {
// ResourceController that, when invoked, runs the corresponding method on
// ResourceHandler.
class DetachableResourceHandler::Controller : public ResourceController {
public:
explicit Controller(DetachableResourceHandler* detachable_handler)
: detachable_handler_(detachable_handler) {}
~Controller() override {}
// ResourceController implementation:
void Resume() override {
MarkAsUsed();
detachable_handler_->ResumeInternal();
}
void ResumeForRedirect(
const std::vector<std::string>& removed_headers,
const net::HttpRequestHeaders& modified_headers) override {
MarkAsUsed();
detachable_handler_->ResumeForRedirect(removed_headers, modified_headers);
}
void Cancel() override {
MarkAsUsed();
detachable_handler_->Cancel();
}
void CancelWithError(int error_code) override {
MarkAsUsed();
detachable_handler_->CancelWithError(error_code);
}
private:
void MarkAsUsed() {
#if DCHECK_IS_ON()
DCHECK(!used_);
used_ = true;
#endif
}
#if DCHECK_IS_ON()
bool used_ = false;
#endif
DetachableResourceHandler* detachable_handler_;
DISALLOW_COPY_AND_ASSIGN(Controller);
};
DetachableResourceHandler::DetachableResourceHandler(
net::URLRequest* request,
base::TimeDelta cancel_delay,
std::unique_ptr<ResourceHandler> next_handler)
: ResourceHandler(request),
next_handler_(std::move(next_handler)),
cancel_delay_(cancel_delay),
parent_read_buffer_(nullptr),
parent_read_buffer_size_(nullptr),
is_finished_(false) {
GetRequestInfo()->set_detachable_handler(this);
}
DetachableResourceHandler::~DetachableResourceHandler() {
// Cleanup back-pointer stored on the request info.
GetRequestInfo()->set_detachable_handler(nullptr);
}
void DetachableResourceHandler::SetDelegate(Delegate* delegate) {
ResourceHandler::SetDelegate(delegate);
if (next_handler_)
next_handler_->SetDelegate(delegate);
}
void DetachableResourceHandler::Detach() {
if (is_detached())
return;
if (!is_finished_) {
// Simulate a cancel on the next handler before destroying it.
net::URLRequestStatus status(net::URLRequestStatus::CANCELED,
net::ERR_ABORTED);
bool was_resumed;
// TODO(mmenke): Get rid of NullResourceController and do something more
// reasonable.
next_handler_->OnResponseCompleted(
status, std::make_unique<NullResourceController>(&was_resumed));
DCHECK(was_resumed);
// If |next_handler_| were to defer its shutdown in OnResponseCompleted,
// this would destroy it anyway. Fortunately, AsyncResourceHandler never
// does this anyway, so DCHECK it. MimeTypeResourceHandler and RVH shutdown
// already ignore deferred ResourceHandler shutdown, but
// DetachableResourceHandler and the detach-on-renderer-cancel logic
// introduces a case where this occurs when the renderer cancels a resource.
}
// A OnWillRead / OnReadCompleted pair may still be in progress, but
// OnWillRead passes back a scoped_refptr, so downstream handler's buffer will
// survive long enough to complete that read. From there, future reads will
// drain into |read_buffer_|. (If |next_handler_| is an AsyncResourceHandler,
// the net::IOBuffer takes a reference to the ResourceBuffer which owns the
// shared memory.)
next_handler_.reset();
// Time the request out if it takes too long.
detached_timer_.reset(new base::OneShotTimer());
detached_timer_->Start(FROM_HERE, cancel_delay_, this,
&DetachableResourceHandler::OnTimedOut);
// Resume if necessary. The request may have been deferred, say, waiting on a
// full buffer in AsyncResourceHandler. Now that it has been detached, resume
// and drain it.
if (has_controller()) {
// The nested ResourceHandler may have logged that it's blocking the
// request. Log it as no longer doing so, to avoid a DCHECK on resume.
request()->LogUnblocked();
// If in the middle of an OnWillRead call, need to allocate the read buffer
// before resuming.
if (parent_read_buffer_) {
DCHECK(parent_read_buffer_size_);
scoped_refptr<net::IOBuffer>* parent_read_buffer = parent_read_buffer_;
int* parent_read_buffer_size = parent_read_buffer_size_;
parent_read_buffer_ = nullptr;
parent_read_buffer_size_ = nullptr;
// Will allocate the buffer and resume the request.
OnWillRead(parent_read_buffer, parent_read_buffer_size,
ReleaseController());
return;
}
Resume();
}
}
void DetachableResourceHandler::OnRequestRedirected(
const net::RedirectInfo& redirect_info,
network::ResourceResponse* response,
std::unique_ptr<ResourceController> controller) {
DCHECK(!has_controller());
if (!next_handler_) {
controller->Resume();
return;
}
HoldController(std::move(controller));
next_handler_->OnRequestRedirected(redirect_info, response,
std::make_unique<Controller>(this));
}
void DetachableResourceHandler::OnResponseStarted(
network::ResourceResponse* response,
std::unique_ptr<ResourceController> controller) {
DCHECK(!has_controller());
if (!next_handler_) {
controller->Resume();
return;
}
HoldController(std::move(controller));
next_handler_->OnResponseStarted(response,
std::make_unique<Controller>(this));
}
void DetachableResourceHandler::OnWillStart(
const GURL& url,
std::unique_ptr<ResourceController> controller) {
DCHECK(!has_controller());
if (!next_handler_) {
controller->Resume();
return;
}
HoldController(std::move(controller));
next_handler_->OnWillStart(url, std::make_unique<Controller>(this));
}
void DetachableResourceHandler::OnWillRead(
scoped_refptr<net::IOBuffer>* buf,
int* buf_size,
std::unique_ptr<ResourceController> controller) {
if (!next_handler_) {
if (!read_buffer_.get())
read_buffer_ = base::MakeRefCounted<net::IOBuffer>(kReadBufSize);
*buf = read_buffer_;
*buf_size = kReadBufSize;
controller->Resume();
return;
}
parent_read_buffer_ = buf;
parent_read_buffer_size_ = buf_size;
HoldController(std::move(controller));
next_handler_->OnWillRead(buf, buf_size, std::make_unique<Controller>(this));
}
void DetachableResourceHandler::OnReadCompleted(
int bytes_read,
std::unique_ptr<ResourceController> controller) {
DCHECK(!has_controller());
if (!next_handler_) {
controller->Resume();
return;
}
HoldController(std::move(controller));
next_handler_->OnReadCompleted(bytes_read,
std::make_unique<Controller>(this));
}
void DetachableResourceHandler::OnResponseCompleted(
const net::URLRequestStatus& status,
std::unique_ptr<ResourceController> controller) {
// No DCHECK(!is_deferred_) as the request may have been cancelled while
// deferred.
if (!next_handler_) {
controller->Resume();
return;
}
is_finished_ = true;
HoldController(std::move(controller));
next_handler_->OnResponseCompleted(status,
std::make_unique<Controller>(this));
}
void DetachableResourceHandler::ResumeInternal() {
parent_read_buffer_ = nullptr;
parent_read_buffer_size_ = nullptr;
Resume();
}
void DetachableResourceHandler::OnTimedOut() {
// Requests are only timed out after being detached, and shouldn't be deferred
// once detached.
DCHECK(!next_handler_);
DCHECK(!has_controller());
OutOfBandCancel(net::ERR_ABORTED, true /* tell_renderer */);
}
} // namespace content