| // Copyright 2017 The Chromium Authors |
| // 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/functional/bind.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/browser/renderer_host/render_frame_host_impl.h" |
| #include "content/browser/storage_partition_impl.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_thread.h" |
| #include "content/public/browser/download_manager.h" |
| #include "content/public/browser/download_manager_delegate.h" |
| #include "content/public/browser/permission_controller.h" |
| #include "content/public/browser/permission_descriptor_util.h" |
| #include "content/public/browser/web_contents.h" |
| #include "third_party/blink/public/common/permissions/permission_utils.h" |
| #include "third_party/blink/public/mojom/blob/serialized_blob.mojom.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "ui/gfx/geometry/size.h" |
| |
| namespace content { |
| |
| BackgroundFetchDelegateProxy::BackgroundFetchDelegateProxy( |
| base::WeakPtr<StoragePartitionImpl> storage_partition) |
| : storage_partition_(std::move(storage_partition)) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| } |
| |
| BackgroundFetchDelegateProxy::~BackgroundFetchDelegateProxy() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| } |
| |
| void BackgroundFetchDelegateProxy::SetClickEventDispatcher( |
| DispatchClickEventCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| click_event_dispatcher_callback_ = std::move(callback); |
| } |
| |
| void BackgroundFetchDelegateProxy::GetIconDisplaySize( |
| BackgroundFetchDelegate::GetIconDisplaySizeCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (auto* delegate = GetDelegate()) { |
| delegate->GetIconDisplaySize(std::move(callback)); |
| } else { |
| std::move(callback).Run(gfx::Size(0, 0)); |
| } |
| } |
| |
| void BackgroundFetchDelegateProxy::GetPermissionForOrigin( |
| const url::Origin& origin, |
| RenderProcessHost* rph, |
| RenderFrameHostImpl* rfh, |
| GetPermissionForOriginCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // Permissions need to go through the DownloadRequestLimiter for top level |
| // frames. (This may be missing in unit tests.) |
| if (rfh && !rfh->GetParent() && |
| rfh->GetBrowserContext()->GetDownloadManager()->GetDelegate()) { |
| WebContents::Getter web_contents_getter(base::BindRepeating( |
| [](GlobalRenderFrameHostId rfh_id) { |
| return WebContents::FromRenderFrameHost( |
| RenderFrameHost::FromID(rfh_id)); |
| }, |
| rfh->GetGlobalId())); |
| rfh->GetBrowserContext() |
| ->GetDownloadManager() |
| ->GetDelegate() |
| ->CheckDownloadAllowed( |
| std::move(web_contents_getter), origin.GetURL(), "GET", |
| std::nullopt, /* from_download_cross_origin_redirect= */ false, |
| /* content_initiated= */ true, /* mime_type= */ std::string(), |
| /* page_transition= */ std::nullopt, |
| base::BindOnce(&BackgroundFetchDelegateProxy:: |
| DidGetPermissionFromDownloadRequestLimiter, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(callback))); |
| return; |
| } |
| |
| BackgroundFetchPermission result = BackgroundFetchPermission::BLOCKED; |
| |
| if (auto* controller = GetPermissionController()) { |
| blink::mojom::PermissionStatus permission_status = |
| blink::mojom::PermissionStatus::DENIED; |
| const auto descriptor = content::PermissionDescriptorUtil:: |
| CreatePermissionDescriptorForPermissionType( |
| blink::PermissionType::BACKGROUND_FETCH); |
| if (rfh) { |
| DCHECK(origin == rfh->GetLastCommittedOrigin()); |
| permission_status = |
| controller->GetPermissionStatusForCurrentDocument(descriptor, rfh); |
| } else if (rph) { |
| permission_status = |
| controller->GetPermissionStatusForWorker(descriptor, rph, origin); |
| } |
| switch (permission_status) { |
| case blink::mojom::PermissionStatus::GRANTED: |
| result = BackgroundFetchPermission::ALLOWED; |
| break; |
| case blink::mojom::PermissionStatus::DENIED: |
| case blink::mojom::PermissionStatus::UNSATISFIED_OPTIONS: |
| result = BackgroundFetchPermission::BLOCKED; |
| break; |
| case blink::mojom::PermissionStatus::ASK: |
| result = BackgroundFetchPermission::ASK; |
| break; |
| } |
| } |
| |
| std::move(callback).Run(result); |
| } |
| |
| void BackgroundFetchDelegateProxy::CreateDownloadJob( |
| base::WeakPtr<Controller> controller, |
| std::unique_ptr<BackgroundFetchDescription> fetch_description) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| DCHECK(!controller_map_.count(fetch_description->job_unique_id)); |
| controller_map_[fetch_description->job_unique_id] = std::move(controller); |
| |
| auto* delegate = GetDelegate(); |
| if (delegate) { |
| delegate->CreateDownloadJob(weak_ptr_factory_.GetWeakPtr(), |
| std::move(fetch_description)); |
| } |
| } |
| |
| void BackgroundFetchDelegateProxy::StartRequest( |
| const std::string& job_unique_id, |
| const url::Origin& origin, |
| const scoped_refptr<BackgroundFetchRequestInfo>& request) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| DCHECK(controller_map_.count(job_unique_id)); |
| DCHECK(request); |
| DCHECK(!request->download_guid().empty()); |
| |
| auto* delegate = GetDelegate(); |
| 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::RequestMode::kCors || |
| fetch_request->mode == |
| network::mojom::RequestMode::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, fetch_request->credentials_mode, traffic_annotation, |
| headers, |
| /* has_request_body= */ request->request_body_size() > 0u); |
| } |
| |
| void BackgroundFetchDelegateProxy::UpdateUI( |
| const std::string& job_unique_id, |
| const std::optional<std::string>& title, |
| const std::optional<SkBitmap>& icon, |
| blink::mojom::BackgroundFetchRegistrationService::UpdateUICallback |
| update_ui_callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| DCHECK(!update_ui_callback_map_.count(job_unique_id)); |
| update_ui_callback_map_.emplace(job_unique_id, std::move(update_ui_callback)); |
| |
| if (auto* delegate = GetDelegate()) |
| delegate->UpdateUI(job_unique_id, title, icon); |
| } |
| |
| void BackgroundFetchDelegateProxy::Abort(const std::string& job_unique_id) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (auto* delegate = GetDelegate()) |
| delegate->Abort(job_unique_id); |
| } |
| |
| void BackgroundFetchDelegateProxy::MarkJobComplete( |
| const std::string& job_unique_id) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (auto* delegate = GetDelegate()) |
| delegate->MarkJobComplete(job_unique_id); |
| |
| controller_map_.erase(job_unique_id); |
| } |
| |
| void BackgroundFetchDelegateProxy::OnJobCancelled( |
| const std::string& job_unique_id, |
| const std::string& download_guid, |
| blink::mojom::BackgroundFetchFailureReason reason_to_abort) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK( |
| reason_to_abort == |
| blink::mojom::BackgroundFetchFailureReason::CANCELLED_FROM_UI || |
| reason_to_abort == |
| blink::mojom::BackgroundFetchFailureReason::DOWNLOAD_TOTAL_EXCEEDED); |
| |
| auto it = controller_map_.find(job_unique_id); |
| if (it == controller_map_.end()) |
| return; |
| |
| if (const auto& controller = it->second) { |
| if (reason_to_abort == |
| blink::mojom::BackgroundFetchFailureReason::DOWNLOAD_TOTAL_EXCEEDED) { |
| // Mark the request as complete and failed to avoid leaking information |
| // about the size of the resource. |
| controller->DidCompleteRequest( |
| download_guid, |
| std::make_unique<BackgroundFetchResult>( |
| nullptr /* response */, base::Time::Now(), |
| BackgroundFetchResult::FailureReason::FETCH_ERROR)); |
| } |
| |
| controller->AbortFromDelegate(reason_to_abort); |
| } |
| } |
| |
| void BackgroundFetchDelegateProxy::OnDownloadStarted( |
| const std::string& job_unique_id, |
| const std::string& guid, |
| std::unique_ptr<BackgroundFetchResponse> response) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| auto it = controller_map_.find(job_unique_id); |
| if (it == controller_map_.end()) |
| return; |
| |
| if (const auto& controller = it->second) |
| controller->DidStartRequest(guid, std::move(response)); |
| } |
| |
| void BackgroundFetchDelegateProxy::OnUIActivated( |
| const std::string& job_unique_id) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(click_event_dispatcher_callback_); |
| click_event_dispatcher_callback_.Run(job_unique_id); |
| } |
| |
| void BackgroundFetchDelegateProxy::OnUIUpdated( |
| const std::string& job_unique_id) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| auto it = update_ui_callback_map_.find(job_unique_id); |
| if (it == update_ui_callback_map_.end()) |
| return; |
| |
| DCHECK(it->second); |
| std::move(it->second).Run(blink::mojom::BackgroundFetchError::NONE); |
| update_ui_callback_map_.erase(it); |
| } |
| |
| void BackgroundFetchDelegateProxy::OnDownloadUpdated( |
| const std::string& job_unique_id, |
| const std::string& guid, |
| uint64_t bytes_uploaded, |
| uint64_t bytes_downloaded) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| auto it = controller_map_.find(job_unique_id); |
| if (it == controller_map_.end()) |
| return; |
| |
| if (const auto& controller = it->second) |
| controller->DidUpdateRequest(guid, bytes_uploaded, bytes_downloaded); |
| } |
| |
| void BackgroundFetchDelegateProxy::OnDownloadComplete( |
| const std::string& job_unique_id, |
| const std::string& guid, |
| std::unique_ptr<BackgroundFetchResult> result) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| auto it = controller_map_.find(job_unique_id); |
| if (it == controller_map_.end()) |
| return; |
| |
| if (const auto& controller = it->second) |
| controller->DidCompleteRequest(guid, std::move(result)); |
| } |
| |
| void BackgroundFetchDelegateProxy::GetUploadData( |
| const std::string& job_unique_id, |
| const std::string& download_guid, |
| BackgroundFetchDelegate::GetUploadDataCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| auto it = controller_map_.find(job_unique_id); |
| if (it == controller_map_.end()) { |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| |
| if (const auto& controller = it->second) |
| controller->GetUploadData(download_guid, std::move(callback)); |
| else |
| std::move(callback).Run(nullptr); |
| } |
| |
| BrowserContext* BackgroundFetchDelegateProxy::GetBrowserContext() { |
| if (!storage_partition_) |
| return nullptr; |
| return storage_partition_->browser_context(); |
| } |
| |
| BackgroundFetchDelegate* BackgroundFetchDelegateProxy::GetDelegate() { |
| auto* browser_context = GetBrowserContext(); |
| if (!browser_context) |
| return nullptr; |
| return browser_context->GetBackgroundFetchDelegate(); |
| } |
| |
| PermissionController* BackgroundFetchDelegateProxy::GetPermissionController() { |
| auto* browser_context = GetBrowserContext(); |
| if (!browser_context) |
| return nullptr; |
| return browser_context->GetPermissionController(); |
| } |
| |
| void BackgroundFetchDelegateProxy::DidGetPermissionFromDownloadRequestLimiter( |
| GetPermissionForOriginCallback callback, |
| bool has_permission) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| std::move(callback).Run(has_permission |
| ? content::BackgroundFetchPermission::ALLOWED |
| : content::BackgroundFetchPermission::BLOCKED); |
| } |
| |
| } // namespace content |