blob: a477ded464199e33ebdeb6ca9c35b9f3b228487a [file] [log] [blame]
// Copyright 2017 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 <string>
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "content/browser/loader/mock_resource_loader.h"
#include "content/browser/loader/resource_controller.h"
#include "content/browser/loader/test_resource_handler.h"
#include "content/public/browser/resource_request_info.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "net/base/net_errors.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "net/url_request/redirect_info.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_status.h"
#include "net/url_request/url_request_test_util.h"
#include "services/network/public/cpp/resource_response.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace content {
namespace {
// Full response body.
const char kResponseBody[] = "Nifty response body.";
// Two separate reads allow for testing cancellation in the middle of one read,
// and between reads.
const char kFirstBodyRead[] = "Nifty";
const char kSecondBodyRead[] = " response body.";
enum class DetachPhase {
DETACHED_FROM_CREATION,
ON_WILL_START,
REQUEST_REDIRECTED,
ON_RESPONSE_STARTED,
FIRST_ON_WILL_READ,
FIRST_ON_READ_COMPLETED,
SECOND_ON_WILL_READ,
SECOND_ON_READ_COMPLETED,
ON_READ_EOF,
ON_RESPONSE_COMPLETED,
NEVER_DETACH,
};
class DetachableResourceHandlerTest
: public testing::TestWithParam<DetachPhase> {
public:
DetachableResourceHandlerTest()
: request_(context_.CreateRequest(GURL("http://foo/"),
net::DEFAULT_PRIORITY,
nullptr,
TRAFFIC_ANNOTATION_FOR_TESTS)) {
ResourceRequestInfo::AllocateForTesting(request_.get(),
RESOURCE_TYPE_MAIN_FRAME,
nullptr, // context
0, // render_process_id
0, // render_view_id
0, // render_frame_id
true, // is_main_frame
true, // allow_download
true, // is_async
PREVIEWS_OFF, // previews_state
nullptr); // navigation_ui_data
std::unique_ptr<TestResourceHandler> test_handler;
if (GetParam() != DetachPhase::DETACHED_FROM_CREATION) {
test_handler = std::make_unique<TestResourceHandler>();
test_handler_ = test_handler->GetWeakPtr();
}
// TODO(mmenke): This file currently has no timeout tests. Should it?
detachable_handler_ = std::make_unique<DetachableResourceHandler>(
request_.get(), base::TimeDelta::FromMinutes(30),
std::move(test_handler));
mock_loader_ =
std::make_unique<MockResourceLoader>(detachable_handler_.get());
}
// If the DetachableResourceHandler is supposed to detach the next handler at
// |phase|, attempts to detach the request.
void MaybeSyncDetachAtPhase(DetachPhase phase) {
if (GetParam() == phase) {
detachable_handler_->Detach();
EXPECT_FALSE(test_handler_);
}
}
// Returns true if the DetachableResourceHandler should have detached the next
// handler at or before the specified phase. Also checks that |test_handler_|
// is nullptr iff the request should have been detached by the specified
// phase.
bool WasDetachedBy(DetachPhase phase) {
if (GetParam() <= phase) {
EXPECT_FALSE(test_handler_);
return true;
}
EXPECT_TRUE(test_handler_);
return false;
}
// If the DetachableResourceHandler is supposed to detach the next handler at
// |phase|, attempts to detach the request. Expected to be called in sync
// tests after the specified phase has started. Performs additional sanity
// checks based on that assumption.
void MaybeAsyncDetachAt(DetachPhase phase) {
if (GetParam() < phase) {
EXPECT_FALSE(test_handler_);
EXPECT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->status());
return;
}
EXPECT_EQ(MockResourceLoader::Status::CALLBACK_PENDING,
mock_loader_->status());
if (GetParam() == phase) {
detachable_handler_->Detach();
EXPECT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->status());
EXPECT_FALSE(test_handler_);
return;
}
test_handler_->Resume();
}
protected:
TestBrowserThreadBundle thread_bundle_;
net::TestURLRequestContext context_;
std::unique_ptr<net::URLRequest> request_;
base::WeakPtr<TestResourceHandler> test_handler_;
std::unique_ptr<DetachableResourceHandler> detachable_handler_;
std::unique_ptr<MockResourceLoader> mock_loader_;
};
// Tests where ResourceHandler completes synchronously. Handler is detached
// just before the phase indicated by the DetachPhase parameter.
TEST_P(DetachableResourceHandlerTest, Sync) {
MaybeSyncDetachAtPhase(DetachPhase::ON_WILL_START);
ASSERT_EQ(MockResourceLoader::Status::IDLE,
mock_loader_->OnWillStart(request_->url()));
if (!WasDetachedBy(DetachPhase::ON_WILL_START)) {
EXPECT_EQ(1, test_handler_->on_will_start_called());
EXPECT_EQ(0, test_handler_->on_request_redirected_called());
}
MaybeSyncDetachAtPhase(DetachPhase::REQUEST_REDIRECTED);
ASSERT_EQ(MockResourceLoader::Status::IDLE,
mock_loader_->OnRequestRedirected(
net::RedirectInfo(),
base::MakeRefCounted<network::ResourceResponse>()));
if (!WasDetachedBy(DetachPhase::REQUEST_REDIRECTED)) {
EXPECT_EQ(1, test_handler_->on_request_redirected_called());
EXPECT_EQ(0, test_handler_->on_response_started_called());
}
MaybeSyncDetachAtPhase(DetachPhase::ON_RESPONSE_STARTED);
ASSERT_EQ(MockResourceLoader::Status::IDLE,
mock_loader_->OnResponseStarted(
base::MakeRefCounted<network::ResourceResponse>()));
if (!WasDetachedBy(DetachPhase::ON_RESPONSE_STARTED)) {
EXPECT_EQ(1, test_handler_->on_request_redirected_called());
EXPECT_EQ(1, test_handler_->on_response_started_called());
EXPECT_EQ(0, test_handler_->on_will_read_called());
}
MaybeSyncDetachAtPhase(DetachPhase::FIRST_ON_WILL_READ);
ASSERT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->OnWillRead());
if (!WasDetachedBy(DetachPhase::FIRST_ON_WILL_READ)) {
EXPECT_EQ(1, test_handler_->on_will_read_called());
EXPECT_EQ(0, test_handler_->on_read_completed_called());
}
MaybeSyncDetachAtPhase(DetachPhase::FIRST_ON_READ_COMPLETED);
ASSERT_EQ(MockResourceLoader::Status::IDLE,
mock_loader_->OnReadCompleted(kFirstBodyRead));
if (!WasDetachedBy(DetachPhase::FIRST_ON_READ_COMPLETED)) {
EXPECT_EQ(1, test_handler_->on_read_completed_called());
EXPECT_EQ(kFirstBodyRead, test_handler_->body());
}
MaybeSyncDetachAtPhase(DetachPhase::SECOND_ON_WILL_READ);
ASSERT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->OnWillRead());
if (!WasDetachedBy(DetachPhase::SECOND_ON_WILL_READ)) {
EXPECT_EQ(2, test_handler_->on_will_read_called());
EXPECT_EQ(1, test_handler_->on_read_completed_called());
}
MaybeSyncDetachAtPhase(DetachPhase::SECOND_ON_READ_COMPLETED);
ASSERT_EQ(MockResourceLoader::Status::IDLE,
mock_loader_->OnReadCompleted(kSecondBodyRead));
if (!WasDetachedBy(DetachPhase::SECOND_ON_READ_COMPLETED)) {
EXPECT_EQ(2, test_handler_->on_will_read_called());
EXPECT_EQ(2, test_handler_->on_read_completed_called());
EXPECT_EQ(kResponseBody, test_handler_->body());
}
ASSERT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->OnWillRead());
if (!WasDetachedBy(DetachPhase::SECOND_ON_READ_COMPLETED)) {
EXPECT_EQ(3, test_handler_->on_will_read_called());
EXPECT_EQ(2, test_handler_->on_read_completed_called());
EXPECT_EQ(0, test_handler_->on_response_completed_called());
}
MaybeSyncDetachAtPhase(DetachPhase::ON_READ_EOF);
ASSERT_EQ(MockResourceLoader::Status::IDLE,
mock_loader_->OnReadCompleted(""));
if (!WasDetachedBy(DetachPhase::ON_READ_EOF)) {
EXPECT_EQ(3, test_handler_->on_read_completed_called());
EXPECT_EQ(1, test_handler_->on_read_eof_called());
EXPECT_EQ(0, test_handler_->on_response_completed_called());
}
MaybeSyncDetachAtPhase(DetachPhase::ON_RESPONSE_COMPLETED);
ASSERT_EQ(MockResourceLoader::Status::IDLE,
mock_loader_->OnResponseCompleted(
net::URLRequestStatus::FromError(net::OK)));
if (!WasDetachedBy(DetachPhase::ON_RESPONSE_COMPLETED)) {
EXPECT_EQ(1, test_handler_->on_response_completed_called());
EXPECT_EQ(kResponseBody, test_handler_->body());
}
}
// Tests where ResourceHandler completes asynchronously. Handler is detached
// during the phase indicated by the DetachPhase parameter. Async cases where
// the handler is detached between phases are similar enough to the sync tests
// that they wouldn't provide meaningfully better test coverage.
//
// Before the handler is detached, all calls complete asynchronously.
// Afterwards, they all complete synchronously.
TEST_P(DetachableResourceHandlerTest, Async) {
if (GetParam() != DetachPhase::DETACHED_FROM_CREATION) {
test_handler_->set_defer_on_will_start(true);
test_handler_->set_defer_on_request_redirected(true);
test_handler_->set_defer_on_response_started(true);
test_handler_->set_defer_on_will_read(true);
test_handler_->set_defer_on_read_completed(true);
test_handler_->set_defer_on_read_eof(true);
// Note: Can't set |defer_on_response_completed|, since the
// DetachableResourceHandler DCHECKs when the next handler tries to defer
// the ERR_ABORTED message it sends downstream.
}
mock_loader_->OnWillStart(request_->url());
if (test_handler_) {
EXPECT_EQ(1, test_handler_->on_will_start_called());
EXPECT_EQ(0, test_handler_->on_request_redirected_called());
}
MaybeAsyncDetachAt(DetachPhase::ON_WILL_START);
mock_loader_->OnRequestRedirected(
net::RedirectInfo(), base::MakeRefCounted<network::ResourceResponse>());
if (test_handler_) {
EXPECT_EQ(1, test_handler_->on_request_redirected_called());
EXPECT_EQ(0, test_handler_->on_response_started_called());
}
MaybeAsyncDetachAt(DetachPhase::REQUEST_REDIRECTED);
mock_loader_->OnResponseStarted(
base::MakeRefCounted<network::ResourceResponse>());
if (test_handler_) {
EXPECT_EQ(1, test_handler_->on_request_redirected_called());
EXPECT_EQ(1, test_handler_->on_response_started_called());
EXPECT_EQ(0, test_handler_->on_will_read_called());
}
MaybeAsyncDetachAt(DetachPhase::ON_RESPONSE_STARTED);
mock_loader_->OnWillRead();
if (test_handler_) {
EXPECT_EQ(1, test_handler_->on_will_read_called());
EXPECT_EQ(0, test_handler_->on_read_completed_called());
}
MaybeAsyncDetachAt(DetachPhase::FIRST_ON_WILL_READ);
mock_loader_->OnReadCompleted(kFirstBodyRead);
if (test_handler_) {
EXPECT_EQ(1, test_handler_->on_read_completed_called());
EXPECT_EQ(kFirstBodyRead, test_handler_->body());
}
MaybeAsyncDetachAt(DetachPhase::FIRST_ON_READ_COMPLETED);
if (test_handler_)
test_handler_->set_defer_on_will_read(true);
mock_loader_->OnWillRead();
if (test_handler_) {
EXPECT_EQ(2, test_handler_->on_will_read_called());
EXPECT_EQ(1, test_handler_->on_read_completed_called());
}
MaybeAsyncDetachAt(DetachPhase::SECOND_ON_WILL_READ);
if (test_handler_)
test_handler_->set_defer_on_read_completed(true);
mock_loader_->OnReadCompleted(kSecondBodyRead);
if (test_handler_) {
EXPECT_EQ(2, test_handler_->on_will_read_called());
EXPECT_EQ(2, test_handler_->on_read_completed_called());
EXPECT_EQ(kResponseBody, test_handler_->body());
}
MaybeAsyncDetachAt(DetachPhase::SECOND_ON_READ_COMPLETED);
// Test doesn't check detaching on the third OnWillRead call.
ASSERT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->OnWillRead());
if (GetParam() > DetachPhase::SECOND_ON_READ_COMPLETED) {
EXPECT_EQ(3, test_handler_->on_will_read_called());
EXPECT_EQ(2, test_handler_->on_read_completed_called());
EXPECT_EQ(0, test_handler_->on_response_completed_called());
} else {
EXPECT_FALSE(test_handler_);
}
if (test_handler_)
test_handler_->set_defer_on_read_completed(true);
mock_loader_->OnReadCompleted("");
if (test_handler_) {
EXPECT_EQ(3, test_handler_->on_read_completed_called());
EXPECT_EQ(1, test_handler_->on_read_eof_called());
EXPECT_EQ(0, test_handler_->on_response_completed_called());
}
MaybeAsyncDetachAt(DetachPhase::ON_READ_EOF);
if (test_handler_)
test_handler_->set_defer_on_response_completed(true);
mock_loader_->OnResponseCompleted(net::URLRequestStatus::FromError(net::OK));
if (test_handler_) {
EXPECT_EQ(1, test_handler_->on_response_completed_called());
EXPECT_EQ(kResponseBody, test_handler_->body());
}
MaybeAsyncDetachAt(DetachPhase::ON_RESPONSE_COMPLETED);
}
INSTANTIATE_TEST_CASE_P(/* No prefix needed*/,
DetachableResourceHandlerTest,
testing::Values(DetachPhase::DETACHED_FROM_CREATION,
DetachPhase::ON_WILL_START,
DetachPhase::REQUEST_REDIRECTED,
DetachPhase::ON_RESPONSE_STARTED,
DetachPhase::FIRST_ON_WILL_READ,
DetachPhase::FIRST_ON_READ_COMPLETED,
DetachPhase::SECOND_ON_WILL_READ,
DetachPhase::SECOND_ON_READ_COMPLETED,
DetachPhase::ON_READ_EOF,
DetachPhase::ON_RESPONSE_COMPLETED,
DetachPhase::NEVER_DETACH));
} // namespace
} // namespace content