| // Copyright 2012 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/download/drag_download_file.h" |
| |
| #include <utility> |
| |
| #include "base/files/file.h" |
| #include "base/functional/bind.h" |
| #include "base/location.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "build/build_config.h" |
| #include "components/download/public/common/download_item.h" |
| #include "components/download/public/common/download_stats.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/download_request_utils.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "net/traffic_annotation/network_traffic_annotation.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| using OnCompleted = base::OnceCallback<void(bool)>; |
| |
| } // namespace |
| |
| // Both DragDownloadFile and DragDownloadFileUI run on the UI thread. |
| class DragDownloadFile::DragDownloadFileUI |
| : public download::DownloadItem::Observer { |
| public: |
| DragDownloadFileUI(const GURL& url, |
| const Referrer& referrer, |
| const std::string& referrer_encoding, |
| std::optional<url::Origin> initiator_origin, |
| int render_process_id, |
| int render_frame_id, |
| OnCompleted on_completed) |
| : on_completed_(std::move(on_completed)), |
| url_(url), |
| referrer_(referrer), |
| referrer_encoding_(referrer_encoding), |
| initiator_origin_(initiator_origin), |
| render_process_id_(render_process_id), |
| render_frame_id_(render_frame_id) { |
| DCHECK(on_completed_); |
| DCHECK_GE(render_frame_id_, 0); |
| // May be called on any thread. |
| // Do not call weak_ptr_factory_.GetWeakPtr() outside the UI thread. |
| } |
| |
| DragDownloadFileUI(const DragDownloadFileUI&) = delete; |
| DragDownloadFileUI& operator=(const DragDownloadFileUI&) = delete; |
| |
| void InitiateDownload(base::File file, |
| const base::FilePath& file_path) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| RenderFrameHost* host = |
| RenderFrameHost::FromID(render_process_id_, render_frame_id_); |
| if (!host) |
| return; |
| // TODO(crbug.com/40470366) This should use the frame actually |
| // containing the link being dragged rather than the main frame of the tab. |
| net::NetworkTrafficAnnotationTag traffic_annotation = |
| net::DefineNetworkTrafficAnnotation("drag_download_file", R"( |
| semantics { |
| sender: "Drag To Download" |
| description: |
| "Users can download files by dragging them out of browser and into " |
| "a disk related area." |
| trigger: "When user drags a file from the browser." |
| data: "None." |
| destination: WEBSITE |
| } |
| policy { |
| cookies_allowed: YES |
| cookies_store: "user" |
| setting: |
| "This feature cannot be disabled in settings, but it is only " |
| "activated by direct user action." |
| chrome_policy { |
| DownloadRestrictions { |
| DownloadRestrictions: 3 |
| } |
| } |
| })"); |
| auto params = std::make_unique<download::DownloadUrlParameters>( |
| url_, render_process_id_, render_frame_id_, traffic_annotation); |
| params->set_referrer(referrer_.url); |
| params->set_referrer_policy( |
| Referrer::ReferrerPolicyForUrlRequest(referrer_.policy)); |
| params->set_referrer_encoding(referrer_encoding_); |
| params->set_initiator(initiator_origin_); |
| params->set_callback(base::BindOnce(&DragDownloadFileUI::OnDownloadStarted, |
| weak_ptr_factory_.GetWeakPtr())); |
| params->set_file_path(file_path); |
| params->set_file(std::move(file)); // Nulls file. |
| params->set_download_source(download::DownloadSource::DRAG_AND_DROP); |
| host->GetBrowserContext()->GetDownloadManager()->DownloadUrl( |
| std::move(params)); |
| } |
| |
| void Cancel() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (download_item_) |
| download_item_->Cancel(true); |
| } |
| |
| void Delete() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| delete this; |
| } |
| |
| private: |
| ~DragDownloadFileUI() override { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (download_item_) |
| download_item_->RemoveObserver(this); |
| } |
| |
| void OnDownloadStarted(download::DownloadItem* item, |
| download::DownloadInterruptReason interrupt_reason) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (!item || item->GetState() != download::DownloadItem::IN_PROGRESS) { |
| DCHECK(!item || |
| item->GetLastReason() != download::DOWNLOAD_INTERRUPT_REASON_NONE); |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(std::move(on_completed_), false)); |
| return; |
| } |
| DCHECK_EQ(download::DOWNLOAD_INTERRUPT_REASON_NONE, interrupt_reason); |
| download_item_ = item; |
| download_item_->AddObserver(this); |
| } |
| |
| // download::DownloadItem::Observer: |
| void OnDownloadUpdated(download::DownloadItem* item) override { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK_EQ(download_item_, item); |
| download::DownloadItem::DownloadState state = download_item_->GetState(); |
| if (state == download::DownloadItem::COMPLETE || |
| state == download::DownloadItem::CANCELLED || |
| state == download::DownloadItem::INTERRUPTED) { |
| if (on_completed_) { |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(on_completed_), |
| state == download::DownloadItem::COMPLETE)); |
| } |
| download_item_->RemoveObserver(this); |
| download_item_ = nullptr; |
| } |
| // Ignore other states. |
| } |
| |
| void OnDownloadDestroyed(download::DownloadItem* item) override { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK_EQ(download_item_, item); |
| if (on_completed_) { |
| const bool is_complete = |
| download_item_->GetState() == download::DownloadItem::COMPLETE; |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(std::move(on_completed_), is_complete)); |
| } |
| download_item_->RemoveObserver(this); |
| download_item_ = nullptr; |
| } |
| |
| OnCompleted on_completed_; |
| GURL url_; |
| Referrer referrer_; |
| std::string referrer_encoding_; |
| std::optional<url::Origin> initiator_origin_; |
| int render_process_id_; |
| int render_frame_id_; |
| raw_ptr<download::DownloadItem> download_item_ = nullptr; |
| |
| // Only used in the callback from DownloadManager::DownloadUrl(). |
| base::WeakPtrFactory<DragDownloadFileUI> weak_ptr_factory_{this}; |
| }; |
| |
| DragDownloadFile::DragDownloadFile(const base::FilePath& file_path, |
| base::File file, |
| const GURL& url, |
| const Referrer& referrer, |
| const std::string& referrer_encoding, |
| std::optional<url::Origin> initiator_origin, |
| WebContents* web_contents) |
| : file_path_(file_path), file_(std::move(file)) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| RenderFrameHost* host = web_contents->GetPrimaryMainFrame(); |
| drag_ui_ = new DragDownloadFileUI( |
| url, referrer, referrer_encoding, initiator_origin, |
| host->GetProcess()->GetDeprecatedID(), host->GetRoutingID(), |
| base::BindOnce(&DragDownloadFile::DownloadCompleted, |
| weak_ptr_factory_.GetWeakPtr())); |
| DCHECK(!file_path_.empty()); |
| } |
| |
| DragDownloadFile::~DragDownloadFile() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| // This is the only place that drag_ui_ can be deleted from. Post a message to |
| // the UI thread so that it calls RemoveObserver on the right thread, and so |
| // that this task will run after the InitiateDownload task runs on the UI |
| // thread. |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&DragDownloadFileUI::Delete, base::Unretained(drag_ui_))); |
| drag_ui_ = nullptr; |
| } |
| |
| void DragDownloadFile::Start(ui::DownloadFileObserver* observer) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (state_ != INITIALIZED) |
| return; |
| state_ = STARTED; |
| |
| DCHECK(!observer_.get()); |
| observer_ = observer; |
| DCHECK(observer_.get()); |
| |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&DragDownloadFileUI::InitiateDownload, |
| base::Unretained(drag_ui_), std::move(file_), file_path_)); |
| } |
| |
| bool DragDownloadFile::Wait() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| auto weak_ptr = weak_ptr_factory_.GetWeakPtr(); |
| if (state_ == STARTED) |
| nested_loop_.Run(); |
| DCHECK(weak_ptr); |
| return state_ == SUCCESS; |
| } |
| |
| void DragDownloadFile::Stop() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (drag_ui_) { |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(&DragDownloadFileUI::Cancel, |
| base::Unretained(drag_ui_))); |
| } |
| } |
| |
| void DragDownloadFile::DownloadCompleted(bool is_successful) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| state_ = is_successful ? SUCCESS : FAILURE; |
| |
| scoped_refptr<ui::DownloadFileObserver> file_observer = observer_; |
| // Release the observer since we do not need it any more. |
| observer_ = nullptr; |
| if (nested_loop_.running()) |
| nested_loop_.Quit(); |
| |
| if (is_successful) |
| file_observer->OnDownloadCompleted(file_path_); |
| else |
| file_observer->OnDownloadAborted(); |
| } |
| |
| } // namespace content |