background_fetch_delegate_base.cc 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. // Copyright 2021 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/background_fetch/background_fetch_delegate_base.h"
  5. #include <utility>
  6. #include "base/bind.h"
  7. #include "base/callback_helpers.h"
  8. #include "base/check_op.h"
  9. #include "base/notreached.h"
  10. #include "build/build_config.h"
  11. #include "components/background_fetch/job_details.h"
  12. #include "components/content_settings/core/common/content_settings_types.h"
  13. #include "components/download/public/background_service/background_download_service.h"
  14. #include "components/download/public/background_service/blob_context_getter_factory.h"
  15. #include "components/download/public/background_service/download_params.h"
  16. #include "content/public/browser/background_fetch_description.h"
  17. #include "content/public/browser/background_fetch_response.h"
  18. #include "content/public/browser/browser_context.h"
  19. #include "content/public/browser/browser_thread.h"
  20. #include "content/public/browser/download_manager.h"
  21. #include "content/public/browser/download_manager_delegate.h"
  22. #include "content/public/browser/web_contents.h"
  23. #include "mojo/public/cpp/bindings/pending_remote.h"
  24. #include "mojo/public/cpp/bindings/remote.h"
  25. #include "services/network/public/mojom/data_pipe_getter.mojom.h"
  26. #include "third_party/blink/public/mojom/background_fetch/background_fetch.mojom.h"
  27. #include "third_party/blink/public/mojom/blob/blob.mojom.h"
  28. #include "ui/gfx/geometry/size.h"
  29. namespace background_fetch {
  30. BackgroundFetchDelegateBase::BackgroundFetchDelegateBase(
  31. content::BrowserContext* context)
  32. : context_(context) {}
  33. BackgroundFetchDelegateBase::~BackgroundFetchDelegateBase() = default;
  34. void BackgroundFetchDelegateBase::GetIconDisplaySize(
  35. BackgroundFetchDelegate::GetIconDisplaySizeCallback callback) {
  36. DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  37. // If Android, return 192x192, else return 0x0. 0x0 means not loading an
  38. // icon at all, which is returned for all non-Android platforms as the
  39. // icons can't be displayed on the UI yet.
  40. gfx::Size display_size;
  41. #if BUILDFLAG(IS_ANDROID)
  42. display_size = gfx::Size(192, 192);
  43. #endif
  44. std::move(callback).Run(display_size);
  45. }
  46. void BackgroundFetchDelegateBase::CreateDownloadJob(
  47. base::WeakPtr<Client> client,
  48. std::unique_ptr<content::BackgroundFetchDescription> fetch_description) {
  49. DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  50. const std::string job_id = fetch_description->job_unique_id;
  51. auto inserted = job_details_map_.emplace(std::piecewise_construct,
  52. std::forward_as_tuple(job_id),
  53. std::forward_as_tuple());
  54. DCHECK(inserted.second);
  55. JobDetails* job_details = &inserted.first->second;
  56. job_details->client = std::move(client);
  57. job_details->job_state =
  58. fetch_description->start_paused
  59. ? JobDetails::State::kPendingWillStartPaused
  60. : JobDetails::State::kPendingWillStartDownloading;
  61. job_details->fetch_description = std::move(fetch_description);
  62. OnJobDetailsCreated(job_id);
  63. }
  64. void BackgroundFetchDelegateBase::DownloadUrl(
  65. const std::string& job_id,
  66. const std::string& download_guid,
  67. const std::string& method,
  68. const GURL& url,
  69. ::network::mojom::CredentialsMode credentials_mode,
  70. const net::NetworkTrafficAnnotationTag& traffic_annotation,
  71. const net::HttpRequestHeaders& headers,
  72. bool has_request_body) {
  73. DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  74. DCHECK(!download_job_id_map_.count(download_guid));
  75. download_job_id_map_.emplace(download_guid, job_id);
  76. download::DownloadParams params;
  77. params.guid = download_guid;
  78. params.client = download::DownloadClient::BACKGROUND_FETCH;
  79. params.request_params.method = method;
  80. params.request_params.url = url;
  81. params.request_params.request_headers = headers;
  82. params.request_params.credentials_mode = credentials_mode;
  83. params.callback =
  84. base::BindRepeating(&BackgroundFetchDelegateBase::OnDownloadReceived,
  85. weak_ptr_factory_.GetWeakPtr());
  86. params.traffic_annotation =
  87. net::MutableNetworkTrafficAnnotationTag(traffic_annotation);
  88. params.request_params.update_first_party_url_on_redirect = false;
  89. JobDetails* job_details = GetJobDetails(job_id);
  90. if (job_details->job_state == JobDetails::State::kPendingWillStartPaused ||
  91. job_details->job_state ==
  92. JobDetails::State::kPendingWillStartDownloading) {
  93. DoShowUi(job_id);
  94. job_details->MarkJobAsStarted();
  95. }
  96. params.request_params.isolation_info =
  97. job_details->fetch_description->isolation_info;
  98. if (job_details->job_state == JobDetails::State::kStartedButPaused) {
  99. job_details->on_resume = base::BindOnce(
  100. &BackgroundFetchDelegateBase::StartDownload, GetWeakPtr(), job_id,
  101. std::move(params), has_request_body);
  102. } else {
  103. StartDownload(job_id, std::move(params), has_request_body);
  104. }
  105. DoUpdateUi(job_id);
  106. }
  107. void BackgroundFetchDelegateBase::PauseDownload(const std::string& job_id) {
  108. DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  109. JobDetails* job_details = GetJobDetails(job_id, /*allow_null=*/true);
  110. if (!job_details)
  111. return;
  112. if (job_details->job_state == JobDetails::State::kDownloadsComplete ||
  113. job_details->job_state == JobDetails::State::kJobComplete) {
  114. // The pause event arrived after the fetch was complete; ignore it.
  115. return;
  116. }
  117. job_details->job_state = JobDetails::State::kStartedButPaused;
  118. for (auto& download_guid_pair : job_details->current_fetch_guids)
  119. GetDownloadService()->PauseDownload(download_guid_pair.first);
  120. }
  121. void BackgroundFetchDelegateBase::ResumeDownload(const std::string& job_id) {
  122. DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  123. JobDetails* job_details = GetJobDetails(job_id, /*allow_null=*/true);
  124. if (!job_details)
  125. return;
  126. job_details->job_state = JobDetails::State::kStartedAndDownloading;
  127. for (auto& download_guid_pair : job_details->current_fetch_guids)
  128. GetDownloadService()->ResumeDownload(download_guid_pair.first);
  129. if (job_details->on_resume)
  130. std::move(job_details->on_resume).Run();
  131. }
  132. void BackgroundFetchDelegateBase::CancelDownload(std::string job_id) {
  133. DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  134. JobDetails* job_details = GetJobDetails(job_id);
  135. if (!job_details ||
  136. job_details->job_state == JobDetails::State::kDownloadsComplete ||
  137. job_details->job_state == JobDetails::State::kJobComplete) {
  138. // The cancel event arrived after the fetch was complete; ignore it.
  139. return;
  140. }
  141. job_details->cancelled_from_ui = true;
  142. Abort(job_id);
  143. if (auto client = GetClient(job_id)) {
  144. // The |download_guid| is not releavnt here as the job has already
  145. // been aborted and is assumed to have been removed.
  146. client->OnJobCancelled(
  147. job_id, "" /* download_guid */,
  148. blink::mojom::BackgroundFetchFailureReason::CANCELLED_FROM_UI);
  149. }
  150. }
  151. void BackgroundFetchDelegateBase::OnUiFinished(const std::string& job_id) {
  152. DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  153. job_details_map_.erase(job_id);
  154. DoCleanUpUi(job_id);
  155. }
  156. void BackgroundFetchDelegateBase::OnUiActivated(const std::string& job_id) {
  157. DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  158. if (auto client = GetClient(job_id))
  159. client->OnUIActivated(job_id);
  160. }
  161. JobDetails* BackgroundFetchDelegateBase::GetJobDetails(
  162. const std::string& job_id,
  163. bool allow_null) {
  164. DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  165. auto job_details_iter = job_details_map_.find(job_id);
  166. if (job_details_iter == job_details_map_.end()) {
  167. if (!allow_null)
  168. NOTREACHED();
  169. return nullptr;
  170. }
  171. return &job_details_iter->second;
  172. }
  173. void BackgroundFetchDelegateBase::StartDownload(const std::string& job_id,
  174. download::DownloadParams params,
  175. bool has_request_body) {
  176. DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  177. GetJobDetails(job_id)->current_fetch_guids.emplace(params.guid,
  178. has_request_body);
  179. GetDownloadService()->StartDownload(std::move(params));
  180. }
  181. void BackgroundFetchDelegateBase::Abort(const std::string& job_id) {
  182. DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  183. JobDetails* job_details = GetJobDetails(job_id, /*allow_null=*/true);
  184. if (!job_details)
  185. return;
  186. job_details->job_state = JobDetails::State::kCancelled;
  187. for (const auto& download_guid_pair : job_details->current_fetch_guids) {
  188. GetDownloadService()->CancelDownload(download_guid_pair.first);
  189. download_job_id_map_.erase(download_guid_pair.first);
  190. }
  191. DoUpdateUi(job_id);
  192. }
  193. void BackgroundFetchDelegateBase::MarkJobComplete(const std::string& job_id) {
  194. JobDetails* job_details = GetJobDetails(job_id);
  195. if (job_details->job_state == JobDetails::State::kCancelled) {
  196. OnUiFinished(job_id);
  197. return;
  198. }
  199. job_details->job_state = JobDetails::State::kJobComplete;
  200. // Clear the |job_details| internals that are no longer needed.
  201. job_details->current_fetch_guids.clear();
  202. }
  203. void BackgroundFetchDelegateBase::FailFetch(const std::string& job_id,
  204. const std::string& download_guid) {
  205. // Save a copy before Abort() deletes the reference.
  206. const std::string unique_id = job_id;
  207. Abort(job_id);
  208. if (auto client = GetClient(unique_id)) {
  209. client->OnJobCancelled(
  210. download_guid, unique_id,
  211. blink::mojom::BackgroundFetchFailureReason::DOWNLOAD_TOTAL_EXCEEDED);
  212. }
  213. }
  214. void BackgroundFetchDelegateBase::OnDownloadStarted(
  215. const std::string& download_guid,
  216. std::unique_ptr<content::BackgroundFetchResponse> response) {
  217. DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  218. auto download_job_id_iter = download_job_id_map_.find(download_guid);
  219. // TODO(crbug.com/779012): When DownloadService fixes cancelled jobs calling
  220. // OnDownload* methods, then this can be a DCHECK.
  221. if (download_job_id_iter == download_job_id_map_.end())
  222. return;
  223. const std::string& job_id = download_job_id_iter->second;
  224. JobDetails* job_details = GetJobDetails(job_id);
  225. if (job_details->client) {
  226. job_details->client->OnDownloadStarted(job_id, download_guid,
  227. std::move(response));
  228. }
  229. // Update the upload progress.
  230. auto it = job_details->current_fetch_guids.find(download_guid);
  231. DCHECK(it != job_details->current_fetch_guids.end());
  232. job_details->fetch_description->uploaded_bytes += it->second.body_size_bytes;
  233. }
  234. void BackgroundFetchDelegateBase::OnDownloadUpdated(
  235. const std::string& download_guid,
  236. uint64_t bytes_uploaded,
  237. uint64_t bytes_downloaded) {
  238. DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  239. auto download_job_id_iter = download_job_id_map_.find(download_guid);
  240. // TODO(crbug.com/779012): When DownloadService fixes cancelled jobs calling
  241. // OnDownload* methods, then this can be a DCHECK.
  242. if (download_job_id_iter == download_job_id_map_.end())
  243. return;
  244. const std::string job_id = download_job_id_iter->second;
  245. JobDetails* job_details = GetJobDetails(job_id);
  246. job_details->UpdateInProgressBytes(download_guid, bytes_uploaded,
  247. bytes_downloaded);
  248. if (job_details->fetch_description->download_total_bytes &&
  249. job_details->fetch_description->download_total_bytes <
  250. job_details->GetDownloadedBytes()) {
  251. // Fail the fetch if total download size was set too low.
  252. // We only do this if total download size is specified. If not specified,
  253. // this check is skipped. This is to allow for situations when the
  254. // total download size cannot be known when invoking fetch.
  255. FailFetch(job_id, download_guid);
  256. return;
  257. }
  258. DoUpdateUi(job_id);
  259. if (job_details->client) {
  260. job_details->client->OnDownloadUpdated(job_id, download_guid,
  261. bytes_uploaded, bytes_downloaded);
  262. }
  263. }
  264. void BackgroundFetchDelegateBase::OnDownloadFailed(
  265. const std::string& download_guid,
  266. std::unique_ptr<content::BackgroundFetchResult> result) {
  267. DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  268. auto download_job_id_iter = download_job_id_map_.find(download_guid);
  269. // TODO(crbug.com/779012): When DownloadService fixes cancelled jobs
  270. // potentially calling OnDownloadFailed with a reason other than
  271. // CANCELLED/ABORTED, we should add a DCHECK here.
  272. if (download_job_id_iter == download_job_id_map_.end())
  273. return;
  274. const std::string& job_id = download_job_id_iter->second;
  275. JobDetails* job_details = GetJobDetails(job_id);
  276. job_details->UpdateJobOnDownloadComplete(download_guid);
  277. DoUpdateUi(job_id);
  278. // The client cancelled or aborted the download so no need to notify it.
  279. if (result->failure_reason ==
  280. content::BackgroundFetchResult::FailureReason::CANCELLED) {
  281. return;
  282. }
  283. if (job_details->client) {
  284. job_details->client->OnDownloadComplete(job_id, download_guid,
  285. std::move(result));
  286. }
  287. download_job_id_map_.erase(download_guid);
  288. }
  289. void BackgroundFetchDelegateBase::OnDownloadSucceeded(
  290. const std::string& download_guid,
  291. std::unique_ptr<content::BackgroundFetchResult> result) {
  292. DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  293. auto download_job_id_iter = download_job_id_map_.find(download_guid);
  294. // TODO(crbug.com/779012): When DownloadService fixes cancelled jobs calling
  295. // OnDownload* methods, then this can be a DCHECK.
  296. if (download_job_id_iter == download_job_id_map_.end())
  297. return;
  298. const std::string& job_id = download_job_id_iter->second;
  299. JobDetails* job_details = GetJobDetails(job_id);
  300. job_details->UpdateJobOnDownloadComplete(download_guid);
  301. job_details->fetch_description->downloaded_bytes +=
  302. context_->IsOffTheRecord() ? result->blob_handle->size()
  303. : result->file_size;
  304. DoUpdateUi(job_id);
  305. if (job_details->client) {
  306. job_details->client->OnDownloadComplete(job_id, download_guid,
  307. std::move(result));
  308. }
  309. download_job_id_map_.erase(download_guid);
  310. }
  311. void BackgroundFetchDelegateBase::OnDownloadReceived(
  312. const std::string& download_guid,
  313. download::DownloadParams::StartResult result) {
  314. DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  315. using StartResult = download::DownloadParams::StartResult;
  316. switch (result) {
  317. case StartResult::ACCEPTED:
  318. // Nothing to do.
  319. break;
  320. case StartResult::UNEXPECTED_GUID:
  321. // The download started in a previous session. Nothing to do.
  322. break;
  323. case StartResult::BACKOFF:
  324. // TODO(delphick): try again later?
  325. NOTREACHED();
  326. break;
  327. case StartResult::UNEXPECTED_CLIENT:
  328. // This really should never happen since we're supplying the
  329. // DownloadClient.
  330. NOTREACHED();
  331. break;
  332. case StartResult::CLIENT_CANCELLED:
  333. // TODO(delphick): do we need to do anything here, since we will have
  334. // cancelled it?
  335. break;
  336. case StartResult::INTERNAL_ERROR:
  337. // TODO(delphick): We need to handle this gracefully.
  338. NOTREACHED();
  339. break;
  340. case StartResult::COUNT:
  341. NOTREACHED();
  342. break;
  343. }
  344. }
  345. bool BackgroundFetchDelegateBase::IsGuidOutstanding(
  346. const std::string& guid) const {
  347. auto job_id_iter = download_job_id_map_.find(guid);
  348. if (job_id_iter == download_job_id_map_.end())
  349. return false;
  350. auto job_details_iter = job_details_map_.find(job_id_iter->second);
  351. if (job_details_iter == job_details_map_.end())
  352. return false;
  353. const std::vector<std::string>& outstanding_guids =
  354. job_details_iter->second.fetch_description->outstanding_guids;
  355. return std::find(outstanding_guids.begin(), outstanding_guids.end(), guid) !=
  356. outstanding_guids.end();
  357. }
  358. void BackgroundFetchDelegateBase::RestartPausedDownload(
  359. const std::string& download_guid) {
  360. auto job_it = download_job_id_map_.find(download_guid);
  361. if (job_it == download_job_id_map_.end())
  362. return;
  363. const std::string& job_id = job_it->second;
  364. GetJobDetails(job_id)->job_state = JobDetails::State::kStartedButPaused;
  365. DoUpdateUi(job_id);
  366. }
  367. std::set<std::string> BackgroundFetchDelegateBase::TakeOutstandingGuids() {
  368. std::set<std::string> outstanding_guids;
  369. for (auto& job_id_details : job_details_map_) {
  370. auto& job_details = job_id_details.second;
  371. // If the job is loaded at this point, then it already started
  372. // in a previous session.
  373. job_details.MarkJobAsStarted();
  374. std::vector<std::string>& job_outstanding_guids =
  375. job_details.fetch_description->outstanding_guids;
  376. for (std::string& outstanding_guid : job_outstanding_guids)
  377. outstanding_guids.insert(std::move(outstanding_guid));
  378. job_outstanding_guids.clear();
  379. }
  380. return outstanding_guids;
  381. }
  382. void BackgroundFetchDelegateBase::GetUploadData(
  383. const std::string& download_guid,
  384. download::GetUploadDataCallback callback) {
  385. auto job_it = download_job_id_map_.find(download_guid);
  386. // TODO(crbug.com/779012): When DownloadService fixes cancelled jobs calling
  387. // client methods, then this can be a DCHECK.
  388. if (job_it == download_job_id_map_.end()) {
  389. base::SequencedTaskRunnerHandle::Get()->PostTask(
  390. FROM_HERE,
  391. base::BindOnce(std::move(callback), /* request_body= */ nullptr));
  392. return;
  393. }
  394. const std::string& job_id = job_it->second;
  395. JobDetails* job_details = GetJobDetails(job_id);
  396. if (job_details->current_fetch_guids.at(download_guid).status ==
  397. JobDetails::RequestData::Status::kAbsent) {
  398. base::SequencedTaskRunnerHandle::Get()->PostTask(
  399. FROM_HERE,
  400. base::BindOnce(std::move(callback), /* request_body= */ nullptr));
  401. return;
  402. }
  403. if (job_details->client) {
  404. job_details->client->GetUploadData(
  405. job_id, download_guid,
  406. base::BindOnce(&BackgroundFetchDelegateBase::DidGetUploadData,
  407. weak_ptr_factory_.GetWeakPtr(), job_id, download_guid,
  408. std::move(callback)));
  409. }
  410. }
  411. void BackgroundFetchDelegateBase::DidGetUploadData(
  412. const std::string& job_id,
  413. const std::string& download_guid,
  414. download::GetUploadDataCallback callback,
  415. blink::mojom::SerializedBlobPtr blob) {
  416. if (!blob || blob->uuid.empty()) {
  417. std::move(callback).Run(/* request_body= */ nullptr);
  418. return;
  419. }
  420. JobDetails* job_details = GetJobDetails(job_id, /*allow_null=*/true);
  421. if (!job_details) {
  422. std::move(callback).Run(/* request_body= */ nullptr);
  423. return;
  424. }
  425. DCHECK(job_details->current_fetch_guids.count(download_guid));
  426. auto& request_data = job_details->current_fetch_guids.at(download_guid);
  427. request_data.body_size_bytes = blob->size;
  428. // Use a Data Pipe to transfer the blob.
  429. mojo::PendingRemote<network::mojom::DataPipeGetter> data_pipe_getter_remote;
  430. mojo::Remote<blink::mojom::Blob> blob_remote(std::move(blob->blob));
  431. blob_remote->AsDataPipeGetter(
  432. data_pipe_getter_remote.InitWithNewPipeAndPassReceiver());
  433. auto request_body = base::MakeRefCounted<network::ResourceRequestBody>();
  434. request_body->AppendDataPipe(std::move(data_pipe_getter_remote));
  435. std::move(callback).Run(request_body);
  436. }
  437. base::WeakPtr<content::BackgroundFetchDelegate::Client>
  438. BackgroundFetchDelegateBase::GetClient(const std::string& job_id) {
  439. auto it = job_details_map_.find(job_id);
  440. if (it == job_details_map_.end())
  441. return nullptr;
  442. return it->second.client;
  443. }
  444. } // namespace background_fetch