| // 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/background_fetch/background_fetch_job_controller.h" |
| |
| #include <map> |
| #include <memory> |
| #include <set> |
| #include <string> |
| #include <unordered_map> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/guid.h" |
| #include "base/macros.h" |
| #include "base/run_loop.h" |
| #include "components/download/public/common/download_item.h" |
| #include "content/browser/background_fetch/background_fetch_constants.h" |
| #include "content/browser/background_fetch/background_fetch_context.h" |
| #include "content/browser/background_fetch/background_fetch_data_manager.h" |
| #include "content/browser/background_fetch/background_fetch_registration_id.h" |
| #include "content/browser/background_fetch/background_fetch_scheduler.h" |
| #include "content/browser/background_fetch/background_fetch_test_base.h" |
| #include "content/browser/service_worker/service_worker_context_wrapper.h" |
| #include "content/browser/storage_partition_impl.h" |
| #include "content/public/browser/background_fetch_delegate.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/test/fake_download_item.h" |
| #include "content/public/test/mock_download_manager.h" |
| #include "content/public/test/test_utils.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "third_party/blink/public/mojom/background_fetch/background_fetch.mojom.h" |
| #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom.h" |
| |
| using blink::FetchAPIRequestHeadersMap; |
| using testing::_; |
| |
| namespace content { |
| namespace { |
| |
| const int64_t kExampleServiceWorkerRegistrationId = 1; |
| const char kExampleDeveloperId[] = "my-example-id"; |
| const char kExampleResponseData[] = "My response data"; |
| |
| enum class JobCompletionStatus { kRunning, kCompleted, kAborted }; |
| |
| } // namespace |
| |
| class BackgroundFetchJobControllerTest : public BackgroundFetchTestBase { |
| public: |
| BackgroundFetchJobControllerTest() = default; |
| ~BackgroundFetchJobControllerTest() override = default; |
| |
| // Returns the status for the active job for |registration_id|. The |
| // registration should only ever exist in |finished_requests_| in case the |
| // request was aborted, given the absence of a scheduler. |
| JobCompletionStatus GetCompletionStatus( |
| const BackgroundFetchRegistrationId& registration_id) { |
| if (finished_requests_.count(registration_id)) { |
| DCHECK_NE(finished_requests_[registration_id], |
| blink::mojom::BackgroundFetchFailureReason::NONE); |
| |
| return JobCompletionStatus::kAborted; |
| } |
| |
| DCHECK(pending_requests_counts_.count(registration_id)); |
| if (!pending_requests_counts_[registration_id]) |
| return JobCompletionStatus::kCompleted; |
| |
| return JobCompletionStatus::kRunning; |
| } |
| |
| // To be called when a request for |registration_id| has finished. |
| void OnRequestFinished( |
| const BackgroundFetchRegistrationId& registration_id, |
| scoped_refptr<content::BackgroundFetchRequestInfo> request_info) { |
| DCHECK(pending_requests_counts_.count(registration_id)); |
| |
| EXPECT_GE(pending_requests_counts_[registration_id], 1); |
| pending_requests_counts_[registration_id]--; |
| } |
| |
| // To be called when a request for |registration_id| has finished. |
| // Moves |request_info| to |out_request_info|. |
| void GetRequestInfoOnRequestFinished( |
| scoped_refptr<content::BackgroundFetchRequestInfo>* out_request_info, |
| const BackgroundFetchRegistrationId& registration_id, |
| scoped_refptr<content::BackgroundFetchRequestInfo> request_info) { |
| DCHECK(pending_requests_counts_.count(registration_id)); |
| DCHECK(out_request_info); |
| |
| EXPECT_GE(pending_requests_counts_[registration_id], 1); |
| pending_requests_counts_[registration_id]--; |
| *out_request_info = request_info; |
| } |
| |
| // Creates a new Background Fetch registration, whose id will be stored in the |
| // |*registration_id|, and registers it with the DataManager for the included |
| // |request_data|. If |auto_complete_requests| is true, the request will |
| // immediately receive a successful response. Should be wrapped in |
| // ASSERT_NO_FATAL_FAILURE(). |
| std::vector<scoped_refptr<BackgroundFetchRequestInfo>> |
| CreateRegistrationForRequests( |
| BackgroundFetchRegistrationId* registration_id, |
| std::map<GURL, /* method= */ std::string> request_data, |
| bool auto_complete_requests) { |
| DCHECK(registration_id); |
| |
| // New |unique_id|, since this is a new Background Fetch registration. |
| *registration_id = BackgroundFetchRegistrationId( |
| kExampleServiceWorkerRegistrationId, origin(), kExampleDeveloperId, |
| base::GenerateGUID()); |
| |
| std::vector<scoped_refptr<BackgroundFetchRequestInfo>> request_infos; |
| int request_counter = 0; |
| for (const auto& pair : request_data) { |
| blink::mojom::FetchAPIRequestPtr request_ptr = CreateFetchAPIRequest( |
| GURL(pair.first), pair.second, FetchAPIRequestHeadersMap(), |
| blink::mojom::Referrer::New(), false); |
| auto request = base::MakeRefCounted<BackgroundFetchRequestInfo>( |
| request_counter++, std::move(request_ptr), |
| /* has_request_body= */ false); |
| request->InitializeDownloadGuid(); |
| request_infos.push_back(request); |
| } |
| |
| pending_requests_counts_[*registration_id] = request_data.size(); |
| |
| if (auto_complete_requests) { |
| // Provide fake responses for the given |request_data| pairs. |
| for (const auto& pair : request_data) { |
| CreateRequestWithProvidedResponse( |
| /* method= */ pair.second, /* url= */ pair.first, |
| TestResponseBuilder(/* response_code= */ 200) |
| .SetResponseData(kExampleResponseData) |
| .Build()); |
| } |
| } |
| |
| return request_infos; |
| } |
| |
| // Creates a new BackgroundFetchJobController instance. |
| std::unique_ptr<BackgroundFetchJobController> CreateJobController( |
| const BackgroundFetchRegistrationId& registration_id, |
| int total_downloads) { |
| auto controller = std::make_unique<BackgroundFetchJobController>( |
| /* data_manager= */ nullptr, delegate_proxy_.get(), registration_id, |
| blink::mojom::BackgroundFetchOptions::New(), SkBitmap(), |
| /* bytes_downloaded= */ 0u, |
| /* bytes_uploaded= */ 0u, |
| /* upload_total= */ 0u, |
| base::BindRepeating( |
| &BackgroundFetchJobControllerTest::DidUpdateProgress, |
| base::Unretained(this)), |
| base::BindOnce(&BackgroundFetchJobControllerTest::DidFinishJob, |
| base::Unretained(this))); |
| |
| controller->InitializeRequestStatus(/* completed_downloads= */ 0, |
| total_downloads, |
| /* outstanding_guids= */ {}, |
| /* start_paused= */ false); |
| |
| return controller; |
| } |
| |
| void AddControllerToSchedulerMap( |
| const std::string& unique_id, |
| std::unique_ptr<BackgroundFetchJobController> controller) { |
| scheduler()->job_controllers_[unique_id] = std::move(controller); |
| } |
| |
| // BackgroundFetchTestBase overrides: |
| void SetUp() override { |
| BackgroundFetchTestBase::SetUp(); |
| |
| StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>( |
| BrowserContext::GetDefaultStoragePartition(browser_context())); |
| |
| delegate_proxy_ = |
| std::make_unique<BackgroundFetchDelegateProxy>(browser_context()); |
| |
| context_ = base::MakeRefCounted<BackgroundFetchContext>( |
| browser_context(), |
| base::WrapRefCounted(embedded_worker_test_helper()->context_wrapper()), |
| base::WrapRefCounted(partition->GetCacheStorageContext()), |
| /* quota_manager_proxy= */ nullptr); |
| } |
| |
| void TearDown() override { |
| BackgroundFetchTestBase::TearDown(); |
| context_ = nullptr; |
| |
| // Give pending shutdown operations a chance to finish. |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| protected: |
| scoped_refptr<BackgroundFetchContext> context_; |
| |
| uint64_t last_downloaded_ = 0; |
| |
| std::map<BackgroundFetchRegistrationId, int> pending_requests_counts_; |
| std::map<BackgroundFetchRegistrationId, |
| blink::mojom::BackgroundFetchFailureReason> |
| finished_requests_; |
| |
| // Closure that will be invoked every time the JobController receives a |
| // progress update from a download. |
| base::RepeatingClosure job_progress_closure_; |
| |
| std::unique_ptr<BackgroundFetchDelegateProxy> delegate_proxy_; |
| BackgroundFetchDelegate* delegate_; |
| |
| BackgroundFetchScheduler* scheduler() { return context_->scheduler_.get(); } |
| |
| private: |
| void DidUpdateProgress( |
| const blink::mojom::BackgroundFetchRegistration& registration) { |
| last_downloaded_ = registration.downloaded; |
| |
| if (job_progress_closure_) |
| job_progress_closure_.Run(); |
| } |
| |
| void DidFinishJob( |
| const BackgroundFetchRegistrationId& registration_id, |
| blink::mojom::BackgroundFetchFailureReason reason_to_abort, |
| base::OnceCallback<void(blink::mojom::BackgroundFetchError)> callback) { |
| auto iter = pending_requests_counts_.find(registration_id); |
| DCHECK(iter != pending_requests_counts_.end()); |
| |
| finished_requests_[registration_id] = reason_to_abort; |
| pending_requests_counts_.erase(iter); |
| } |
| |
| DISALLOW_COPY_AND_ASSIGN(BackgroundFetchJobControllerTest); |
| }; |
| |
| TEST_F(BackgroundFetchJobControllerTest, SingleRequestJob) { |
| BackgroundFetchRegistrationId registration_id; |
| |
| auto requests = CreateRegistrationForRequests( |
| ®istration_id, {{GURL("https://example.com/funny_cat.png"), "GET"}}, |
| /* auto_complete_requests= */ true); |
| |
| EXPECT_EQ(JobCompletionStatus::kRunning, |
| GetCompletionStatus(registration_id)); |
| |
| std::unique_ptr<BackgroundFetchJobController> controller = |
| CreateJobController(registration_id, requests.size()); |
| |
| controller->StartRequest( |
| requests[0], |
| base::BindOnce(&BackgroundFetchJobControllerTest::OnRequestFinished, |
| base::Unretained(this))); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(JobCompletionStatus::kCompleted, |
| GetCompletionStatus(registration_id)); |
| } |
| |
| TEST_F(BackgroundFetchJobControllerTest, SingleRequestJobWithInsecureOrigin) { |
| BackgroundFetchRegistrationId registration_id; |
| |
| auto requests = CreateRegistrationForRequests( |
| ®istration_id, {{GURL("http://example.com/funny_cat.png"), "GET"}}, |
| /* auto_complete_requests= */ true); |
| |
| EXPECT_EQ(JobCompletionStatus::kRunning, |
| GetCompletionStatus(registration_id)); |
| |
| std::unique_ptr<BackgroundFetchJobController> controller = |
| CreateJobController(registration_id, requests.size()); |
| |
| controller->StartRequest( |
| requests[0], |
| base::BindOnce( |
| &BackgroundFetchJobControllerTest::GetRequestInfoOnRequestFinished, |
| base::Unretained(this), &requests[0])); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(JobCompletionStatus::kCompleted, |
| GetCompletionStatus(registration_id)); |
| EXPECT_FALSE(requests[0]->IsResultSuccess()); |
| } |
| |
| TEST_F(BackgroundFetchJobControllerTest, MultipleRequestJob) { |
| BackgroundFetchRegistrationId registration_id; |
| |
| auto requests = CreateRegistrationForRequests( |
| ®istration_id, |
| {{GURL("https://example.com/funny_cat.png"), "GET"}, |
| {GURL("https://example.com/scary_cat.png"), "GET"}, |
| {GURL("https://example.com/crazy_cat.png"), "GET"}}, |
| /* auto_complete_requests= */ true); |
| |
| EXPECT_EQ(JobCompletionStatus::kRunning, |
| GetCompletionStatus(registration_id)); |
| |
| std::unique_ptr<BackgroundFetchJobController> controller = |
| CreateJobController(registration_id, requests.size()); |
| |
| controller->StartRequest( |
| requests[0], |
| base::BindOnce(&BackgroundFetchJobControllerTest::OnRequestFinished, |
| base::Unretained(this))); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(JobCompletionStatus::kRunning, |
| GetCompletionStatus(registration_id)); |
| |
| controller->StartRequest( |
| requests[1], |
| base::BindOnce(&BackgroundFetchJobControllerTest::OnRequestFinished, |
| base::Unretained(this))); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(JobCompletionStatus::kRunning, |
| GetCompletionStatus(registration_id)); |
| |
| controller->StartRequest( |
| requests[2], |
| base::BindOnce(&BackgroundFetchJobControllerTest::OnRequestFinished, |
| base::Unretained(this))); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(JobCompletionStatus::kCompleted, |
| GetCompletionStatus(registration_id)); |
| } |
| |
| TEST_F(BackgroundFetchJobControllerTest, MultipleRequestsJobWithMixedContent) { |
| BackgroundFetchRegistrationId registration_id; |
| |
| auto requests = CreateRegistrationForRequests( |
| ®istration_id, |
| {{GURL("http://example.com/funny_cat.png"), "GET"}, |
| {GURL("https://example.com/scary_cat.png"), "GET"}}, |
| /* auto_complete_requests= */ true); |
| |
| EXPECT_EQ(JobCompletionStatus::kRunning, |
| GetCompletionStatus(registration_id)); |
| |
| std::unique_ptr<BackgroundFetchJobController> controller = |
| CreateJobController(registration_id, requests.size()); |
| |
| controller->StartRequest( |
| requests[0], |
| base::BindOnce( |
| &BackgroundFetchJobControllerTest::GetRequestInfoOnRequestFinished, |
| base::Unretained(this), &requests[0])); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(JobCompletionStatus::kRunning, |
| GetCompletionStatus(registration_id)); |
| EXPECT_FALSE(requests[0]->IsResultSuccess()); |
| |
| controller->StartRequest( |
| requests[1], |
| base::BindOnce( |
| &BackgroundFetchJobControllerTest::GetRequestInfoOnRequestFinished, |
| base::Unretained(this), &requests[1])); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(JobCompletionStatus::kCompleted, |
| GetCompletionStatus(registration_id)); |
| EXPECT_TRUE(requests[1]->IsResultSuccess()); |
| } |
| |
| TEST_F(BackgroundFetchJobControllerTest, Abort) { |
| BackgroundFetchRegistrationId registration_id; |
| |
| auto requests = CreateRegistrationForRequests( |
| ®istration_id, {{GURL("https://example.com/funny_cat.png"), "GET"}}, |
| /* auto_complete_requests= */ true); |
| |
| EXPECT_EQ(JobCompletionStatus::kRunning, |
| GetCompletionStatus(registration_id)); |
| |
| std::unique_ptr<BackgroundFetchJobController> controller = |
| CreateJobController(registration_id, requests.size()); |
| |
| controller->StartRequest( |
| requests[0], |
| base::BindOnce(&BackgroundFetchJobControllerTest::OnRequestFinished, |
| base::Unretained(this))); |
| |
| controller->Abort( |
| blink::mojom::BackgroundFetchFailureReason::CANCELLED_FROM_UI, |
| base::DoNothing()); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(JobCompletionStatus::kAborted, |
| GetCompletionStatus(registration_id)); |
| } |
| |
| TEST_F(BackgroundFetchJobControllerTest, Progress) { |
| BackgroundFetchRegistrationId registration_id; |
| |
| auto requests = CreateRegistrationForRequests( |
| ®istration_id, {{GURL("https://example.com/funny_cat.png"), "GET"}}, |
| /* auto_complete_requests= */ true); |
| |
| EXPECT_EQ(JobCompletionStatus::kRunning, |
| GetCompletionStatus(registration_id)); |
| |
| std::unique_ptr<BackgroundFetchJobController> controller = |
| CreateJobController(registration_id, requests.size()); |
| |
| controller->StartRequest( |
| requests[0], |
| base::BindOnce(&BackgroundFetchJobControllerTest::OnRequestFinished, |
| base::Unretained(this))); |
| |
| { |
| base::RunLoop run_loop; |
| job_progress_closure_ = run_loop.QuitClosure(); |
| run_loop.Run(); |
| } |
| |
| EXPECT_GT(last_downloaded_, 0u); |
| EXPECT_LT(last_downloaded_, strlen(kExampleResponseData)); |
| EXPECT_EQ(JobCompletionStatus::kRunning, |
| GetCompletionStatus(registration_id)); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(JobCompletionStatus::kCompleted, |
| GetCompletionStatus(registration_id)); |
| EXPECT_EQ(last_downloaded_, strlen(kExampleResponseData)); |
| } |
| |
| TEST_F(BackgroundFetchJobControllerTest, ServiceWorkerRegistrationDeleted) { |
| BackgroundFetchRegistrationId registration_id; |
| |
| auto requests = CreateRegistrationForRequests( |
| ®istration_id, {{GURL("https://example.com/funny_cat.png"), "GET"}}, |
| /* auto_complete_requests= */ true); |
| |
| EXPECT_EQ(JobCompletionStatus::kRunning, |
| GetCompletionStatus(registration_id)); |
| |
| std::unique_ptr<BackgroundFetchJobController> controller = |
| CreateJobController(registration_id, requests.size()); |
| |
| AddControllerToSchedulerMap(registration_id.unique_id(), |
| std::move(controller)); |
| scheduler()->OnRegistrationDeleted(kExampleServiceWorkerRegistrationId, |
| GURL("https://example.com/funny_cat.png")); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(JobCompletionStatus::kAborted, |
| GetCompletionStatus(registration_id)); |
| } |
| |
| TEST_F(BackgroundFetchJobControllerTest, ServiceWorkerDatabaseDeleted) { |
| BackgroundFetchRegistrationId registration_id; |
| |
| auto requests = CreateRegistrationForRequests( |
| ®istration_id, {{GURL("https://example.com/funny_cat.png"), "GET"}}, |
| /* auto_complete_requests= */ true); |
| |
| EXPECT_EQ(JobCompletionStatus::kRunning, |
| GetCompletionStatus(registration_id)); |
| |
| std::unique_ptr<BackgroundFetchJobController> controller = |
| CreateJobController(registration_id, requests.size()); |
| |
| AddControllerToSchedulerMap(registration_id.unique_id(), |
| std::move(controller)); |
| |
| scheduler()->OnStorageWiped(); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(JobCompletionStatus::kAborted, |
| GetCompletionStatus(registration_id)); |
| } |
| |
| } // namespace content |