| // 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/common/throttling_url_loader.h" |
| #include "base/logging.h" |
| #include "base/macros.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/run_loop.h" |
| #include "content/common/url_loader.mojom.h" |
| #include "content/common/url_loader_factory.mojom.h" |
| #include "content/public/common/url_loader_throttle.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace content { |
| namespace { |
| |
| class TestURLLoaderFactory : public mojom::URLLoaderFactory { |
| public: |
| TestURLLoaderFactory() : binding_(this) { |
| binding_.Bind(mojo::MakeRequest(&factory_ptr_)); |
| } |
| |
| mojom::URLLoaderFactoryPtr& factory_ptr() { return factory_ptr_; } |
| mojom::URLLoaderClientPtr& client_ptr() { return client_ptr_; } |
| |
| size_t create_loader_and_start_called() const { |
| return create_loader_and_start_called_; |
| } |
| |
| void NotifyClientOnReceiveResponse() { |
| client_ptr_->OnReceiveResponse(ResourceResponseHead(), base::nullopt, |
| nullptr); |
| } |
| |
| void NotifyClientOnReceiveRedirect() { |
| client_ptr_->OnReceiveRedirect(net::RedirectInfo(), ResourceResponseHead()); |
| } |
| |
| void NotifyClientOnComplete(int error_code) { |
| ResourceRequestCompletionStatus data; |
| data.error_code = error_code; |
| client_ptr_->OnComplete(data); |
| } |
| |
| private: |
| // mojom::URLLoaderFactory implementation. |
| void CreateLoaderAndStart(mojom::URLLoaderAssociatedRequest request, |
| int32_t routing_id, |
| int32_t request_id, |
| uint32_t options, |
| const ResourceRequest& url_request, |
| mojom::URLLoaderClientPtr client) override { |
| create_loader_and_start_called_++; |
| |
| client_ptr_ = std::move(client); |
| } |
| |
| void SyncLoad(int32_t routing_id, |
| int32_t request_id, |
| const ResourceRequest& request, |
| SyncLoadCallback callback) override { |
| NOTREACHED(); |
| } |
| |
| size_t create_loader_and_start_called_ = 0; |
| |
| mojo::Binding<mojom::URLLoaderFactory> binding_; |
| mojom::URLLoaderFactoryPtr factory_ptr_; |
| mojom::URLLoaderClientPtr client_ptr_; |
| DISALLOW_COPY_AND_ASSIGN(TestURLLoaderFactory); |
| }; |
| |
| class TestURLLoaderClient : public mojom::URLLoaderClient { |
| public: |
| TestURLLoaderClient() {} |
| |
| size_t on_received_response_called() const { |
| return on_received_response_called_; |
| } |
| |
| size_t on_received_redirect_called() const { |
| return on_received_redirect_called_; |
| } |
| |
| size_t on_complete_called() const { return on_complete_called_; } |
| |
| using OnCompleteCallback = base::Callback<void(int error_code)>; |
| void set_on_complete_callback(const OnCompleteCallback& callback) { |
| on_complete_callback_ = callback; |
| } |
| |
| private: |
| // mojom::URLLoaderClient implementation: |
| void OnReceiveResponse( |
| const ResourceResponseHead& response_head, |
| const base::Optional<net::SSLInfo>& ssl_info, |
| mojom::DownloadedTempFilePtr downloaded_file) override { |
| on_received_response_called_++; |
| } |
| void OnReceiveRedirect(const net::RedirectInfo& redirect_info, |
| const ResourceResponseHead& response_head) override { |
| on_received_redirect_called_++; |
| } |
| void OnDataDownloaded(int64_t data_len, int64_t encoded_data_len) override {} |
| void OnUploadProgress(int64_t current_position, |
| int64_t total_size, |
| OnUploadProgressCallback ack_callback) override {} |
| void OnReceiveCachedMetadata(const std::vector<uint8_t>& data) override {} |
| void OnTransferSizeUpdated(int32_t transfer_size_diff) override {} |
| void OnStartLoadingResponseBody( |
| mojo::ScopedDataPipeConsumerHandle body) override {} |
| void OnComplete(const ResourceRequestCompletionStatus& status) override { |
| on_complete_called_++; |
| if (on_complete_callback_) |
| on_complete_callback_.Run(status.error_code); |
| } |
| |
| size_t on_received_response_called_ = 0; |
| size_t on_received_redirect_called_ = 0; |
| size_t on_complete_called_ = 0; |
| |
| OnCompleteCallback on_complete_callback_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestURLLoaderClient); |
| }; |
| |
| class TestURLLoaderThrottle : public URLLoaderThrottle { |
| public: |
| TestURLLoaderThrottle() {} |
| |
| using ThrottleCallback = |
| base::Callback<void(URLLoaderThrottle::Delegate* delegate, bool* defer)>; |
| |
| size_t will_start_request_called() const { |
| return will_start_request_called_; |
| } |
| size_t will_redirect_request_called() const { |
| return will_redirect_request_called_; |
| } |
| size_t will_process_response_called() const { |
| return will_process_response_called_; |
| } |
| |
| void set_will_start_request_callback(const ThrottleCallback& callback) { |
| will_start_request_callback_ = callback; |
| } |
| |
| void set_will_redirect_request_callback(const ThrottleCallback& callback) { |
| will_redirect_request_callback_ = callback; |
| } |
| |
| void set_will_process_response_callback(const ThrottleCallback& callback) { |
| will_process_response_callback_ = callback; |
| } |
| |
| Delegate* delegate() const { return delegate_; } |
| |
| private: |
| // URLLoaderThrottle implementation. |
| void WillStartRequest(const GURL& url, |
| int load_flags, |
| ResourceType resource_type, |
| bool* defer) override { |
| will_start_request_called_++; |
| if (will_start_request_callback_) |
| will_start_request_callback_.Run(delegate_, defer); |
| } |
| |
| void WillRedirectRequest(const net::RedirectInfo& redirect_info, |
| bool* defer) override { |
| will_redirect_request_called_++; |
| if (will_redirect_request_callback_) |
| will_redirect_request_callback_.Run(delegate_, defer); |
| } |
| |
| void WillProcessResponse(bool* defer) override { |
| will_process_response_called_++; |
| if (will_process_response_callback_) |
| will_process_response_callback_.Run(delegate_, defer); |
| } |
| |
| size_t will_start_request_called_ = 0; |
| size_t will_redirect_request_called_ = 0; |
| size_t will_process_response_called_ = 0; |
| |
| ThrottleCallback will_start_request_callback_; |
| ThrottleCallback will_redirect_request_callback_; |
| ThrottleCallback will_process_response_callback_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestURLLoaderThrottle); |
| }; |
| |
| class ThrottlingURLLoaderTest : public testing::Test { |
| public: |
| ThrottlingURLLoaderTest() {} |
| |
| protected: |
| // testing::Test implementation. |
| void SetUp() override { |
| auto throttle = base::MakeUnique<TestURLLoaderThrottle>(); |
| throttle_ = throttle.get(); |
| |
| throttles_.push_back(std::move(throttle)); |
| } |
| |
| void CreateLoaderAndStart() { |
| auto request = base::MakeUnique<ResourceRequest>(); |
| request->url = GURL("http://example.org"); |
| loader_ = ThrottlingURLLoader::CreateLoaderAndStart( |
| factory_.factory_ptr().get(), std::move(throttles_), 0, 0, 0, |
| std::move(request), &client_); |
| factory_.factory_ptr().FlushForTesting(); |
| } |
| |
| // Be the first member so it is destroyed last. |
| base::MessageLoop message_loop_; |
| |
| std::unique_ptr<ThrottlingURLLoader> loader_; |
| std::vector<std::unique_ptr<URLLoaderThrottle>> throttles_; |
| |
| TestURLLoaderFactory factory_; |
| TestURLLoaderClient client_; |
| |
| // Owned by |throttles_| or |loader_|. |
| TestURLLoaderThrottle* throttle_ = nullptr; |
| |
| DISALLOW_COPY_AND_ASSIGN(ThrottlingURLLoaderTest); |
| }; |
| |
| TEST_F(ThrottlingURLLoaderTest, CancelBeforeStart) { |
| throttle_->set_will_start_request_callback( |
| base::Bind([](URLLoaderThrottle::Delegate* delegate, bool* defer) { |
| delegate->CancelWithError(net::ERR_ACCESS_DENIED); |
| })); |
| |
| base::RunLoop run_loop; |
| client_.set_on_complete_callback(base::Bind( |
| [](const base::Closure& quit_closure, int error) { |
| EXPECT_EQ(net::ERR_ACCESS_DENIED, error); |
| quit_closure.Run(); |
| }, |
| run_loop.QuitClosure())); |
| |
| CreateLoaderAndStart(); |
| run_loop.Run(); |
| |
| EXPECT_EQ(1u, throttle_->will_start_request_called()); |
| EXPECT_EQ(0u, throttle_->will_redirect_request_called()); |
| EXPECT_EQ(0u, throttle_->will_process_response_called()); |
| |
| EXPECT_EQ(0u, factory_.create_loader_and_start_called()); |
| |
| EXPECT_EQ(0u, client_.on_received_response_called()); |
| EXPECT_EQ(0u, client_.on_received_redirect_called()); |
| EXPECT_EQ(1u, client_.on_complete_called()); |
| } |
| |
| TEST_F(ThrottlingURLLoaderTest, DeferBeforeStart) { |
| throttle_->set_will_start_request_callback( |
| base::Bind([](URLLoaderThrottle::Delegate* delegate, bool* defer) { |
| *defer = true; |
| })); |
| |
| base::RunLoop run_loop; |
| client_.set_on_complete_callback(base::Bind( |
| [](const base::Closure& quit_closure, int error) { |
| EXPECT_EQ(net::OK, error); |
| quit_closure.Run(); |
| }, |
| run_loop.QuitClosure())); |
| |
| CreateLoaderAndStart(); |
| |
| EXPECT_EQ(1u, throttle_->will_start_request_called()); |
| EXPECT_EQ(0u, throttle_->will_redirect_request_called()); |
| EXPECT_EQ(0u, throttle_->will_process_response_called()); |
| |
| EXPECT_EQ(0u, factory_.create_loader_and_start_called()); |
| |
| EXPECT_EQ(0u, client_.on_received_response_called()); |
| EXPECT_EQ(0u, client_.on_received_redirect_called()); |
| EXPECT_EQ(0u, client_.on_complete_called()); |
| |
| throttle_->delegate()->Resume(); |
| factory_.factory_ptr().FlushForTesting(); |
| |
| EXPECT_EQ(1u, factory_.create_loader_and_start_called()); |
| |
| factory_.NotifyClientOnReceiveResponse(); |
| factory_.NotifyClientOnComplete(net::OK); |
| |
| run_loop.Run(); |
| |
| EXPECT_EQ(1u, throttle_->will_start_request_called()); |
| EXPECT_EQ(0u, throttle_->will_redirect_request_called()); |
| EXPECT_EQ(1u, throttle_->will_process_response_called()); |
| |
| EXPECT_EQ(1u, client_.on_received_response_called()); |
| EXPECT_EQ(0u, client_.on_received_redirect_called()); |
| EXPECT_EQ(1u, client_.on_complete_called()); |
| } |
| |
| TEST_F(ThrottlingURLLoaderTest, CancelBeforeRedirect) { |
| throttle_->set_will_redirect_request_callback( |
| base::Bind([](URLLoaderThrottle::Delegate* delegate, bool* defer) { |
| delegate->CancelWithError(net::ERR_ACCESS_DENIED); |
| })); |
| |
| base::RunLoop run_loop; |
| client_.set_on_complete_callback(base::Bind( |
| [](const base::Closure& quit_closure, int error) { |
| EXPECT_EQ(net::ERR_ACCESS_DENIED, error); |
| quit_closure.Run(); |
| }, |
| run_loop.QuitClosure())); |
| |
| CreateLoaderAndStart(); |
| |
| factory_.NotifyClientOnReceiveRedirect(); |
| |
| run_loop.Run(); |
| |
| EXPECT_EQ(1u, throttle_->will_start_request_called()); |
| EXPECT_EQ(1u, throttle_->will_redirect_request_called()); |
| EXPECT_EQ(0u, throttle_->will_process_response_called()); |
| |
| EXPECT_EQ(0u, client_.on_received_response_called()); |
| EXPECT_EQ(0u, client_.on_received_redirect_called()); |
| EXPECT_EQ(1u, client_.on_complete_called()); |
| } |
| |
| TEST_F(ThrottlingURLLoaderTest, DeferBeforeRedirect) { |
| base::RunLoop run_loop1; |
| throttle_->set_will_redirect_request_callback(base::Bind( |
| [](const base::Closure& quit_closure, |
| URLLoaderThrottle::Delegate* delegate, bool* defer) { |
| *defer = true; |
| quit_closure.Run(); |
| }, |
| run_loop1.QuitClosure())); |
| |
| base::RunLoop run_loop2; |
| client_.set_on_complete_callback(base::Bind( |
| [](const base::Closure& quit_closure, int error) { |
| EXPECT_EQ(net::ERR_UNEXPECTED, error); |
| quit_closure.Run(); |
| }, |
| run_loop2.QuitClosure())); |
| |
| CreateLoaderAndStart(); |
| |
| factory_.NotifyClientOnReceiveRedirect(); |
| |
| run_loop1.Run(); |
| |
| EXPECT_EQ(1u, throttle_->will_start_request_called()); |
| EXPECT_EQ(1u, throttle_->will_redirect_request_called()); |
| EXPECT_EQ(0u, throttle_->will_process_response_called()); |
| |
| factory_.NotifyClientOnComplete(net::ERR_UNEXPECTED); |
| |
| base::RunLoop run_loop3; |
| run_loop3.RunUntilIdle(); |
| |
| EXPECT_EQ(0u, client_.on_received_response_called()); |
| EXPECT_EQ(0u, client_.on_received_redirect_called()); |
| EXPECT_EQ(0u, client_.on_complete_called()); |
| |
| throttle_->delegate()->Resume(); |
| run_loop2.Run(); |
| |
| EXPECT_EQ(1u, throttle_->will_start_request_called()); |
| EXPECT_EQ(1u, throttle_->will_redirect_request_called()); |
| EXPECT_EQ(0u, throttle_->will_process_response_called()); |
| |
| EXPECT_EQ(0u, client_.on_received_response_called()); |
| EXPECT_EQ(1u, client_.on_received_redirect_called()); |
| EXPECT_EQ(1u, client_.on_complete_called()); |
| } |
| |
| TEST_F(ThrottlingURLLoaderTest, CancelBeforeResponse) { |
| throttle_->set_will_process_response_callback( |
| base::Bind([](URLLoaderThrottle::Delegate* delegate, bool* defer) { |
| delegate->CancelWithError(net::ERR_ACCESS_DENIED); |
| })); |
| |
| base::RunLoop run_loop; |
| client_.set_on_complete_callback(base::Bind( |
| [](const base::Closure& quit_closure, int error) { |
| EXPECT_EQ(net::ERR_ACCESS_DENIED, error); |
| quit_closure.Run(); |
| }, |
| run_loop.QuitClosure())); |
| |
| CreateLoaderAndStart(); |
| |
| factory_.NotifyClientOnReceiveResponse(); |
| |
| run_loop.Run(); |
| |
| EXPECT_EQ(1u, throttle_->will_start_request_called()); |
| EXPECT_EQ(0u, throttle_->will_redirect_request_called()); |
| EXPECT_EQ(1u, throttle_->will_process_response_called()); |
| |
| EXPECT_EQ(0u, client_.on_received_response_called()); |
| EXPECT_EQ(0u, client_.on_received_redirect_called()); |
| EXPECT_EQ(1u, client_.on_complete_called()); |
| } |
| |
| TEST_F(ThrottlingURLLoaderTest, DeferBeforeResponse) { |
| base::RunLoop run_loop1; |
| throttle_->set_will_process_response_callback(base::Bind( |
| [](const base::Closure& quit_closure, |
| URLLoaderThrottle::Delegate* delegate, bool* defer) { |
| *defer = true; |
| quit_closure.Run(); |
| }, |
| run_loop1.QuitClosure())); |
| |
| base::RunLoop run_loop2; |
| client_.set_on_complete_callback(base::Bind( |
| [](const base::Closure& quit_closure, int error) { |
| EXPECT_EQ(net::ERR_UNEXPECTED, error); |
| quit_closure.Run(); |
| }, |
| run_loop2.QuitClosure())); |
| |
| CreateLoaderAndStart(); |
| |
| factory_.NotifyClientOnReceiveResponse(); |
| |
| run_loop1.Run(); |
| |
| EXPECT_EQ(1u, throttle_->will_start_request_called()); |
| EXPECT_EQ(0u, throttle_->will_redirect_request_called()); |
| EXPECT_EQ(1u, throttle_->will_process_response_called()); |
| |
| factory_.NotifyClientOnComplete(net::ERR_UNEXPECTED); |
| |
| base::RunLoop run_loop3; |
| run_loop3.RunUntilIdle(); |
| |
| EXPECT_EQ(0u, client_.on_received_response_called()); |
| EXPECT_EQ(0u, client_.on_received_redirect_called()); |
| EXPECT_EQ(0u, client_.on_complete_called()); |
| |
| throttle_->delegate()->Resume(); |
| run_loop2.Run(); |
| |
| EXPECT_EQ(1u, throttle_->will_start_request_called()); |
| EXPECT_EQ(0u, throttle_->will_redirect_request_called()); |
| EXPECT_EQ(1u, throttle_->will_process_response_called()); |
| |
| EXPECT_EQ(1u, client_.on_received_response_called()); |
| EXPECT_EQ(0u, client_.on_received_redirect_called()); |
| EXPECT_EQ(1u, client_.on_complete_called()); |
| } |
| |
| TEST_F(ThrottlingURLLoaderTest, ResumeNoOpIfNotDeferred) { |
| auto resume_callback = |
| base::Bind([](URLLoaderThrottle::Delegate* delegate, bool* defer) { |
| delegate->Resume(); |
| delegate->Resume(); |
| }); |
| throttle_->set_will_start_request_callback(resume_callback); |
| throttle_->set_will_redirect_request_callback(resume_callback); |
| throttle_->set_will_process_response_callback(resume_callback); |
| |
| base::RunLoop run_loop; |
| client_.set_on_complete_callback(base::Bind( |
| [](const base::Closure& quit_closure, int error) { |
| EXPECT_EQ(net::OK, error); |
| quit_closure.Run(); |
| }, |
| run_loop.QuitClosure())); |
| |
| CreateLoaderAndStart(); |
| factory_.NotifyClientOnReceiveRedirect(); |
| factory_.NotifyClientOnReceiveResponse(); |
| factory_.NotifyClientOnComplete(net::OK); |
| |
| run_loop.Run(); |
| |
| EXPECT_EQ(1u, throttle_->will_start_request_called()); |
| EXPECT_EQ(1u, throttle_->will_redirect_request_called()); |
| EXPECT_EQ(1u, throttle_->will_process_response_called()); |
| |
| EXPECT_EQ(1u, client_.on_received_response_called()); |
| EXPECT_EQ(1u, client_.on_received_redirect_called()); |
| EXPECT_EQ(1u, client_.on_complete_called()); |
| } |
| |
| TEST_F(ThrottlingURLLoaderTest, CancelNoOpIfAlreadyCanceled) { |
| throttle_->set_will_start_request_callback( |
| base::Bind([](URLLoaderThrottle::Delegate* delegate, bool* defer) { |
| delegate->CancelWithError(net::ERR_ACCESS_DENIED); |
| delegate->CancelWithError(net::ERR_UNEXPECTED); |
| })); |
| |
| base::RunLoop run_loop; |
| client_.set_on_complete_callback(base::Bind( |
| [](const base::Closure& quit_closure, int error) { |
| EXPECT_EQ(net::ERR_ACCESS_DENIED, error); |
| quit_closure.Run(); |
| }, |
| run_loop.QuitClosure())); |
| |
| CreateLoaderAndStart(); |
| throttle_->delegate()->CancelWithError(net::ERR_INVALID_ARGUMENT); |
| run_loop.Run(); |
| |
| EXPECT_EQ(1u, throttle_->will_start_request_called()); |
| EXPECT_EQ(0u, throttle_->will_redirect_request_called()); |
| EXPECT_EQ(0u, throttle_->will_process_response_called()); |
| |
| EXPECT_EQ(0u, factory_.create_loader_and_start_called()); |
| |
| EXPECT_EQ(0u, client_.on_received_response_called()); |
| EXPECT_EQ(0u, client_.on_received_redirect_called()); |
| EXPECT_EQ(1u, client_.on_complete_called()); |
| } |
| |
| TEST_F(ThrottlingURLLoaderTest, ResumeNoOpIfAlreadyCanceled) { |
| throttle_->set_will_process_response_callback( |
| base::Bind([](URLLoaderThrottle::Delegate* delegate, bool* defer) { |
| delegate->CancelWithError(net::ERR_ACCESS_DENIED); |
| delegate->Resume(); |
| })); |
| |
| base::RunLoop run_loop1; |
| client_.set_on_complete_callback(base::Bind( |
| [](const base::Closure& quit_closure, int error) { |
| EXPECT_EQ(net::ERR_ACCESS_DENIED, error); |
| quit_closure.Run(); |
| }, |
| run_loop1.QuitClosure())); |
| |
| CreateLoaderAndStart(); |
| |
| factory_.NotifyClientOnReceiveResponse(); |
| |
| run_loop1.Run(); |
| |
| throttle_->delegate()->Resume(); |
| |
| base::RunLoop run_loop2; |
| run_loop2.RunUntilIdle(); |
| |
| EXPECT_EQ(1u, throttle_->will_start_request_called()); |
| EXPECT_EQ(0u, throttle_->will_redirect_request_called()); |
| EXPECT_EQ(1u, throttle_->will_process_response_called()); |
| |
| EXPECT_EQ(0u, client_.on_received_response_called()); |
| EXPECT_EQ(0u, client_.on_received_redirect_called()); |
| EXPECT_EQ(1u, client_.on_complete_called()); |
| } |
| |
| } // namespace |
| } // namespace content |