| // 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_delegate_proxy.h" |
| |
| #include <utility> |
| |
| #include "base/task/post_task.h" |
| #include "components/download/public/common/download_item.h" |
| #include "components/download/public/common/download_url_parameters.h" |
| #include "content/browser/background_fetch/background_fetch_job_controller.h" |
| #include "content/public/browser/background_fetch_description.h" |
| #include "content/public/browser/background_fetch_response.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/download_manager.h" |
| #include "third_party/blink/public/mojom/blob/serialized_blob.mojom.h" |
| #include "ui/gfx/geometry/size.h" |
| |
| class SkBitmap; |
| |
| namespace content { |
| |
| // Internal functionality of the BackgroundFetchDelegateProxy that lives on the |
| // UI thread, where all interaction with the download manager must happen. |
| class BackgroundFetchDelegateProxy::Core |
| : public BackgroundFetchDelegate::Client { |
| public: |
| Core(const base::WeakPtr<BackgroundFetchDelegateProxy>& io_parent, |
| BrowserContext* browser_context) |
| : io_parent_(io_parent), |
| browser_context_(browser_context), |
| weak_ptr_factory_(this) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(browser_context_); |
| } |
| |
| ~Core() override { DCHECK_CURRENTLY_ON(BrowserThread::UI); } |
| |
| base::WeakPtr<Core> GetWeakPtrOnUI() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| void ForwardGetPermissionForOriginCallbackToIO( |
| BackgroundFetchDelegate::GetPermissionForOriginCallback callback, |
| BackgroundFetchPermission permission) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| base::PostTaskWithTraits(FROM_HERE, {BrowserThread::IO}, |
| base::BindOnce(std::move(callback), permission)); |
| } |
| |
| void GetPermissionForOrigin( |
| const url::Origin& origin, |
| const ResourceRequestInfo::WebContentsGetter& wc_getter, |
| BackgroundFetchDelegate::GetPermissionForOriginCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (auto* delegate = browser_context_->GetBackgroundFetchDelegate()) { |
| delegate->GetPermissionForOrigin( |
| origin, wc_getter, |
| base::BindOnce(&Core::ForwardGetPermissionForOriginCallbackToIO, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } else { |
| std::move(callback).Run(BackgroundFetchPermission::BLOCKED); |
| } |
| } |
| |
| void ForwardGetIconDisplaySizeCallbackToIO( |
| BackgroundFetchDelegate::GetIconDisplaySizeCallback callback, |
| const gfx::Size& display_size) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| base::PostTaskWithTraits(FROM_HERE, {BrowserThread::IO}, |
| base::BindOnce(std::move(callback), display_size)); |
| } |
| |
| void GetIconDisplaySize( |
| BackgroundFetchDelegate::GetIconDisplaySizeCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (auto* delegate = browser_context_->GetBackgroundFetchDelegate()) { |
| delegate->GetIconDisplaySize( |
| base::BindOnce(&Core::ForwardGetIconDisplaySizeCallbackToIO, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } else { |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::IO}, |
| base::BindOnce(std::move(callback), gfx::Size(0, 0))); |
| } |
| } |
| |
| void CreateDownloadJob( |
| std::unique_ptr<BackgroundFetchDescription> fetch_description) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| auto* delegate = browser_context_->GetBackgroundFetchDelegate(); |
| if (delegate) |
| delegate->CreateDownloadJob(GetWeakPtrOnUI(), |
| std::move(fetch_description)); |
| } |
| |
| void StartRequest(const std::string& job_unique_id, |
| const url::Origin& origin, |
| scoped_refptr<BackgroundFetchRequestInfo> request) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(request); |
| |
| // TODO(crbug/757760): This can be nullptr, if the delegate has shut down, |
| // in which case we need to make sure this is retried when the browser |
| // restarts. |
| auto* delegate = browser_context_->GetBackgroundFetchDelegate(); |
| if (!delegate) |
| return; |
| |
| const blink::mojom::FetchAPIRequestPtr& fetch_request = |
| request->fetch_request(); |
| |
| const net::NetworkTrafficAnnotationTag traffic_annotation( |
| net::DefineNetworkTrafficAnnotation("background_fetch_context", |
| R"( |
| semantics { |
| sender: "Background Fetch API" |
| description: |
| "The Background Fetch API enables developers to upload or " |
| "download files on behalf of the user. Such fetches will yield " |
| "a user visible notification to inform the user of the " |
| "operation, through which it can be suspended, resumed and/or " |
| "cancelled. The developer retains control of the file once the " |
| "fetch is completed, similar to XMLHttpRequest and other " |
| "mechanisms for fetching resources using JavaScript." |
| trigger: |
| "When the website uses the Background Fetch API to request " |
| "fetching a file and/or a list of files. This is a Web " |
| "Platform API for which no express user permission is required." |
| data: |
| "The request headers and data as set by the website's " |
| "developer." |
| destination: WEBSITE |
| } |
| policy { |
| cookies_allowed: YES |
| cookies_store: "user" |
| setting: "This feature cannot be disabled in settings." |
| policy_exception_justification: "Not implemented." |
| })")); |
| |
| // TODO(peter): The |headers| should be populated with all the properties |
| // set in the |fetch_request| structure. |
| net::HttpRequestHeaders headers; |
| for (const auto& pair : fetch_request->headers) |
| headers.SetHeader(pair.first, pair.second); |
| |
| // Append the Origin header for requests whose CORS flag is set, or whose |
| // request method is not GET or HEAD. See section 3.1 of the standard: |
| // https://fetch.spec.whatwg.org/#origin-header |
| if (fetch_request->mode == network::mojom::FetchRequestMode::kCors || |
| fetch_request->mode == |
| network::mojom::FetchRequestMode::kCorsWithForcedPreflight || |
| (fetch_request->method != "GET" && fetch_request->method != "HEAD")) { |
| headers.SetHeader("Origin", origin.Serialize()); |
| } |
| |
| delegate->DownloadUrl( |
| job_unique_id, request->download_guid(), fetch_request->method, |
| fetch_request->url, traffic_annotation, headers, |
| /* has_request_body= */ request->request_body_size() > 0u); |
| } |
| |
| void Abort(const std::string& job_unique_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (auto* delegate = browser_context_->GetBackgroundFetchDelegate()) |
| delegate->Abort(job_unique_id); |
| } |
| |
| void MarkJobComplete(const std::string& job_unique_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (auto* delegate = browser_context_->GetBackgroundFetchDelegate()) |
| delegate->MarkJobComplete(job_unique_id); |
| } |
| |
| void UpdateUI(const std::string& job_unique_id, |
| const base::Optional<std::string>& title, |
| const base::Optional<SkBitmap>& icon) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (auto* delegate = browser_context_->GetBackgroundFetchDelegate()) |
| delegate->UpdateUI(job_unique_id, title, icon); |
| } |
| |
| // BackgroundFetchDelegate::Client implementation: |
| void OnJobCancelled( |
| const std::string& job_unique_id, |
| blink::mojom::BackgroundFetchFailureReason reason_to_abort) override; |
| void OnDownloadUpdated(const std::string& job_unique_id, |
| const std::string& guid, |
| uint64_t bytes_uploaded, |
| uint64_t bytes_downloaded) override; |
| void OnDownloadComplete( |
| const std::string& job_unique_id, |
| const std::string& guid, |
| std::unique_ptr<BackgroundFetchResult> result) override; |
| void OnDownloadStarted( |
| const std::string& job_unique_id, |
| const std::string& guid, |
| std::unique_ptr<content::BackgroundFetchResponse> response) override; |
| void OnUIActivated(const std::string& unique_id) override; |
| void GetUploadData( |
| const std::string& job_unique_id, |
| const std::string& download_guid, |
| BackgroundFetchDelegate::GetUploadDataCallback callback) override; |
| |
| private: |
| // Weak reference to the IO thread outer class that owns us. |
| base::WeakPtr<BackgroundFetchDelegateProxy> io_parent_; |
| |
| BrowserContext* browser_context_; |
| |
| base::WeakPtrFactory<Core> weak_ptr_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(Core); |
| }; |
| |
| void BackgroundFetchDelegateProxy::Core::OnJobCancelled( |
| const std::string& job_unique_id, |
| blink::mojom::BackgroundFetchFailureReason reason_to_abort) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::IO}, |
| base::BindOnce(&BackgroundFetchDelegateProxy::OnJobCancelled, io_parent_, |
| job_unique_id, reason_to_abort)); |
| } |
| |
| void BackgroundFetchDelegateProxy::Core::OnDownloadUpdated( |
| const std::string& job_unique_id, |
| const std::string& guid, |
| uint64_t bytes_uploaded, |
| uint64_t bytes_downloaded) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::IO}, |
| base::BindOnce(&BackgroundFetchDelegateProxy::OnDownloadUpdated, |
| io_parent_, job_unique_id, guid, bytes_uploaded, |
| bytes_downloaded)); |
| } |
| |
| void BackgroundFetchDelegateProxy::Core::OnDownloadComplete( |
| const std::string& job_unique_id, |
| const std::string& guid, |
| std::unique_ptr<BackgroundFetchResult> result) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::IO}, |
| base::BindOnce(&BackgroundFetchDelegateProxy::OnDownloadComplete, |
| io_parent_, job_unique_id, guid, std::move(result))); |
| } |
| |
| void BackgroundFetchDelegateProxy::Core::OnDownloadStarted( |
| const std::string& job_unique_id, |
| const std::string& guid, |
| std::unique_ptr<content::BackgroundFetchResponse> response) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::IO}, |
| base::BindOnce(&BackgroundFetchDelegateProxy::DidStartRequest, io_parent_, |
| job_unique_id, guid, std::move(response))); |
| } |
| |
| void BackgroundFetchDelegateProxy::Core::OnUIActivated( |
| const std::string& job_unique_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::IO}, |
| base::BindOnce(&BackgroundFetchDelegateProxy::DidActivateUI, io_parent_, |
| job_unique_id)); |
| } |
| |
| void BackgroundFetchDelegateProxy::Core::GetUploadData( |
| const std::string& job_unique_id, |
| const std::string& download_guid, |
| BackgroundFetchDelegate::GetUploadDataCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| // Pass this to the IO thread for processing, but wrap |callback| |
| // to be posted back to the UI thread when executed. |
| BackgroundFetchDelegate::GetUploadDataCallback wrapped_callback = |
| base::BindOnce( |
| [](BackgroundFetchDelegate::GetUploadDataCallback callback, |
| blink::mojom::SerializedBlobPtr blob) { |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(std::move(callback), std::move(blob))); |
| }, |
| std::move(callback)); |
| |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::IO}, |
| base::BindOnce(&BackgroundFetchDelegateProxy::GetUploadData, io_parent_, |
| job_unique_id, download_guid, |
| std::move(wrapped_callback))); |
| } |
| |
| BackgroundFetchDelegateProxy::JobDetails::JobDetails( |
| base::WeakPtr<Controller> controller, |
| std::vector<scoped_refptr<BackgroundFetchRequestInfo>> |
| active_fetch_requests) |
| : controller(controller) { |
| for (auto& request_info : active_fetch_requests) { |
| DCHECK(request_info); |
| std::string download_guid = request_info->download_guid(); |
| current_request_map[std::move(download_guid)] = std::move(request_info); |
| } |
| } |
| |
| BackgroundFetchDelegateProxy::JobDetails::JobDetails(JobDetails&& details) = |
| default; |
| |
| BackgroundFetchDelegateProxy::JobDetails::~JobDetails() = default; |
| |
| BackgroundFetchDelegateProxy::BackgroundFetchDelegateProxy( |
| BrowserContext* browser_context) |
| : weak_ptr_factory_(this) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| // Normally it would be unsafe to obtain a weak pointer on the UI thread from |
| // a factory that lives on the IO thread, but it's ok in the constructor as |
| // |this| can't be destroyed before the constructor finishes. |
| ui_core_.reset(new Core(weak_ptr_factory_.GetWeakPtr(), browser_context)); |
| |
| // Since this constructor runs on the UI thread, a WeakPtr can be safely |
| // obtained from the Core. |
| ui_core_ptr_ = ui_core_->GetWeakPtrOnUI(); |
| } |
| |
| BackgroundFetchDelegateProxy::~BackgroundFetchDelegateProxy() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| } |
| |
| void BackgroundFetchDelegateProxy::SetClickEventDispatcher( |
| const DispatchClickEventCallback callback) { |
| click_event_dispatcher_callback_ = std::move(callback); |
| } |
| |
| void BackgroundFetchDelegateProxy::GetIconDisplaySize( |
| BackgroundFetchDelegate::GetIconDisplaySizeCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(&Core::GetIconDisplaySize, |
| ui_core_ptr_, std::move(callback))); |
| } |
| |
| void BackgroundFetchDelegateProxy::GetPermissionForOrigin( |
| const url::Origin& origin, |
| const ResourceRequestInfo::WebContentsGetter& wc_getter, |
| BackgroundFetchDelegate::GetPermissionForOriginCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(&Core::GetPermissionForOrigin, ui_core_ptr_, origin, |
| wc_getter, std::move(callback))); |
| } |
| |
| void BackgroundFetchDelegateProxy::CreateDownloadJob( |
| base::WeakPtr<Controller> controller, |
| std::unique_ptr<BackgroundFetchDescription> fetch_description, |
| std::vector<scoped_refptr<BackgroundFetchRequestInfo>> |
| active_fetch_requests) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| DCHECK(!job_details_map_.count(fetch_description->job_unique_id)); |
| job_details_map_.emplace( |
| fetch_description->job_unique_id, |
| JobDetails(controller, std::move(active_fetch_requests))); |
| |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(&Core::CreateDownloadJob, ui_core_ptr_, |
| std::move(fetch_description))); |
| } |
| |
| void BackgroundFetchDelegateProxy::StartRequest( |
| const std::string& job_unique_id, |
| const url::Origin& origin, |
| scoped_refptr<BackgroundFetchRequestInfo> request) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| DCHECK(job_details_map_.count(job_unique_id)); |
| JobDetails& job_details = job_details_map_.find(job_unique_id)->second; |
| DCHECK(job_details.controller); |
| |
| std::string download_guid = request->download_guid(); |
| DCHECK(!download_guid.empty()); |
| |
| job_details.current_request_map[download_guid] = request; |
| |
| base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(&Core::StartRequest, ui_core_ptr_, |
| job_unique_id, origin, request)); |
| } |
| |
| void BackgroundFetchDelegateProxy::UpdateUI( |
| const std::string& job_unique_id, |
| const base::Optional<std::string>& title, |
| const base::Optional<SkBitmap>& icon) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(&Core::UpdateUI, ui_core_ptr_, |
| job_unique_id, title, icon)); |
| } |
| |
| void BackgroundFetchDelegateProxy::Abort(const std::string& job_unique_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(&Core::Abort, ui_core_ptr_, job_unique_id)); |
| } |
| |
| void BackgroundFetchDelegateProxy::MarkJobComplete( |
| const std::string& job_unique_id) { |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(&Core::MarkJobComplete, ui_core_ptr_, job_unique_id)); |
| job_details_map_.erase(job_unique_id); |
| } |
| |
| void BackgroundFetchDelegateProxy::OnJobCancelled( |
| const std::string& job_unique_id, |
| blink::mojom::BackgroundFetchFailureReason reason_to_abort) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK( |
| reason_to_abort == |
| blink::mojom::BackgroundFetchFailureReason::CANCELLED_FROM_UI || |
| reason_to_abort == |
| blink::mojom::BackgroundFetchFailureReason::DOWNLOAD_TOTAL_EXCEEDED); |
| |
| // TODO(delphick): The controller may not exist as persistence is not yet |
| // implemented. |
| auto job_details_iter = job_details_map_.find(job_unique_id); |
| if (job_details_iter == job_details_map_.end()) |
| return; |
| |
| JobDetails& job_details = job_details_iter->second; |
| if (job_details.controller) |
| job_details.controller->AbortFromDelegate(reason_to_abort); |
| } |
| |
| void BackgroundFetchDelegateProxy::DidStartRequest( |
| const std::string& job_unique_id, |
| const std::string& guid, |
| std::unique_ptr<BackgroundFetchResponse> response) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| // TODO(delphick): The controller may not exist as persistence is not yet |
| // implemented. |
| auto job_details_iter = job_details_map_.find(job_unique_id); |
| if (job_details_iter == job_details_map_.end()) |
| return; |
| |
| JobDetails& job_details = job_details_iter->second; |
| |
| const scoped_refptr<BackgroundFetchRequestInfo>& request_info = |
| job_details.current_request_map[guid]; |
| DCHECK(request_info); |
| DCHECK_EQ(guid, request_info->download_guid()); |
| |
| request_info->PopulateWithResponse(std::move(response)); |
| |
| if (job_details.controller) |
| job_details.controller->DidStartRequest(request_info); |
| } |
| |
| void BackgroundFetchDelegateProxy::DidActivateUI( |
| const std::string& job_unique_id) { |
| DCHECK(click_event_dispatcher_callback_); |
| click_event_dispatcher_callback_.Run(job_unique_id); |
| } |
| |
| void BackgroundFetchDelegateProxy::OnDownloadUpdated( |
| const std::string& job_unique_id, |
| const std::string& guid, |
| uint64_t bytes_uploaded, |
| uint64_t bytes_downloaded) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| // TODO(delphick): The controller may not exist as persistence is not yet |
| // implemented. |
| auto job_details_iter = job_details_map_.find(job_unique_id); |
| if (job_details_iter == job_details_map_.end()) |
| return; |
| |
| JobDetails& job_details = job_details_iter->second; |
| |
| // TODO(peter): Should we update the |request_info| with the progress? |
| if (job_details.controller) { |
| const scoped_refptr<BackgroundFetchRequestInfo>& request_info = |
| job_details.current_request_map[guid]; |
| DCHECK(request_info); |
| DCHECK_EQ(guid, request_info->download_guid()); |
| job_details.controller->DidUpdateRequest(request_info, bytes_uploaded, |
| bytes_downloaded); |
| } |
| } |
| |
| void BackgroundFetchDelegateProxy::OnDownloadComplete( |
| const std::string& job_unique_id, |
| const std::string& guid, |
| std::unique_ptr<BackgroundFetchResult> result) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| // TODO(delphick): The controller may not exist as persistence is not yet |
| // implemented. |
| auto job_details_iter = job_details_map_.find(job_unique_id); |
| if (job_details_iter == job_details_map_.end()) |
| return; |
| |
| JobDetails& job_details = job_details_iter->second; |
| |
| const scoped_refptr<BackgroundFetchRequestInfo>& request_info = |
| job_details.current_request_map[guid]; |
| DCHECK(request_info); |
| DCHECK_EQ(guid, request_info->download_guid()); |
| request_info->SetResult(std::move(result)); |
| |
| if (job_details.controller) |
| job_details.controller->DidCompleteRequest(request_info); |
| } |
| |
| void BackgroundFetchDelegateProxy::GetUploadData( |
| const std::string& job_unique_id, |
| const std::string& download_guid, |
| BackgroundFetchDelegate::GetUploadDataCallback callback) { |
| auto& job_details = job_details_map_.find(job_unique_id)->second; |
| DCHECK(job_details.controller); |
| |
| const auto& request = |
| job_details.current_request_map[download_guid]->fetch_request_ptr(); |
| job_details.controller->GetUploadData( |
| BackgroundFetchSettledFetch::CloneRequest(request), std::move(callback)); |
| } |
| |
| } // namespace content |