|  | // 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 |