mirror of
				https://bitbucket.org/chromiumembedded/cef
				synced 2025-06-05 21:39:12 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			465 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			465 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // Copyright (c) 2012 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 "libcef/browser/download_manager_delegate.h"
 | |
| 
 | |
| #include <tuple>
 | |
| 
 | |
| #include "include/cef_download_handler.h"
 | |
| #include "libcef/browser/alloy/alloy_browser_host_impl.h"
 | |
| #include "libcef/browser/context.h"
 | |
| #include "libcef/browser/download_item_impl.h"
 | |
| #include "libcef/browser/thread_util.h"
 | |
| 
 | |
| #include "base/bind.h"
 | |
| #include "base/files/file_util.h"
 | |
| #include "base/logging.h"
 | |
| #include "base/path_service.h"
 | |
| #include "base/strings/string_util.h"
 | |
| #include "base/strings/utf_string_conversions.h"
 | |
| #include "chrome/common/chrome_constants.h"
 | |
| #include "content/public/browser/browser_context.h"
 | |
| #include "content/public/browser/download_item_utils.h"
 | |
| #include "content/public/browser/web_contents.h"
 | |
| #include "net/base/filename_util.h"
 | |
| #include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h"
 | |
| 
 | |
| using content::DownloadManager;
 | |
| using content::WebContents;
 | |
| using download::DownloadItem;
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| // Helper function to retrieve the CefDownloadHandler.
 | |
| CefRefPtr<CefDownloadHandler> GetDownloadHandler(
 | |
|     CefRefPtr<AlloyBrowserHostImpl> browser) {
 | |
|   CefRefPtr<CefClient> client = browser->GetClient();
 | |
|   if (client.get())
 | |
|     return client->GetDownloadHandler();
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| // CefBeforeDownloadCallback implementation.
 | |
| class CefBeforeDownloadCallbackImpl : public CefBeforeDownloadCallback {
 | |
|  public:
 | |
|   CefBeforeDownloadCallbackImpl(const base::WeakPtr<DownloadManager>& manager,
 | |
|                                 uint32 download_id,
 | |
|                                 const base::FilePath& suggested_name,
 | |
|                                 content::DownloadTargetCallback callback)
 | |
|       : manager_(manager),
 | |
|         download_id_(download_id),
 | |
|         suggested_name_(suggested_name),
 | |
|         callback_(std::move(callback)) {}
 | |
| 
 | |
|   CefBeforeDownloadCallbackImpl(const CefBeforeDownloadCallbackImpl&) = delete;
 | |
|   CefBeforeDownloadCallbackImpl& operator=(
 | |
|       const CefBeforeDownloadCallbackImpl&) = delete;
 | |
| 
 | |
|   void Continue(const CefString& download_path, bool show_dialog) override {
 | |
|     if (CEF_CURRENTLY_ON_UIT()) {
 | |
|       if (download_id_ <= 0)
 | |
|         return;
 | |
| 
 | |
|       if (manager_) {
 | |
|         base::FilePath path = base::FilePath(download_path);
 | |
|         CEF_POST_USER_VISIBLE_TASK(
 | |
|             base::BindOnce(&CefBeforeDownloadCallbackImpl::GenerateFilename,
 | |
|                            manager_, download_id_, suggested_name_, path,
 | |
|                            show_dialog, std::move(callback_)));
 | |
|       }
 | |
| 
 | |
|       download_id_ = 0;
 | |
|     } else {
 | |
|       CEF_POST_TASK(CEF_UIT,
 | |
|                     base::BindOnce(&CefBeforeDownloadCallbackImpl::Continue,
 | |
|                                    this, download_path, show_dialog));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   static void GenerateFilename(base::WeakPtr<DownloadManager> manager,
 | |
|                                uint32 download_id,
 | |
|                                const base::FilePath& suggested_name,
 | |
|                                const base::FilePath& download_path,
 | |
|                                bool show_dialog,
 | |
|                                content::DownloadTargetCallback callback) {
 | |
|     CEF_REQUIRE_BLOCKING();
 | |
| 
 | |
|     base::FilePath suggested_path = download_path;
 | |
|     if (!suggested_path.empty()) {
 | |
|       // Create the directory if necessary.
 | |
|       base::FilePath dir_path = suggested_path.DirName();
 | |
|       if (!base::DirectoryExists(dir_path) &&
 | |
|           !base::CreateDirectory(dir_path)) {
 | |
|         NOTREACHED() << "failed to create the download directory";
 | |
|         suggested_path.clear();
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (suggested_path.empty()) {
 | |
|       if (base::PathService::Get(base::DIR_TEMP, &suggested_path)) {
 | |
|         // Use the temp directory.
 | |
|         suggested_path = suggested_path.Append(suggested_name);
 | |
|       } else {
 | |
|         // Use the current working directory.
 | |
|         suggested_path = suggested_name;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     CEF_POST_TASK(
 | |
|         CEF_UIT,
 | |
|         base::BindOnce(&CefBeforeDownloadCallbackImpl::ChooseDownloadPath,
 | |
|                        manager, download_id, suggested_path, show_dialog,
 | |
|                        std::move(callback)));
 | |
|   }
 | |
| 
 | |
|   static void ChooseDownloadPath(base::WeakPtr<DownloadManager> manager,
 | |
|                                  uint32 download_id,
 | |
|                                  const base::FilePath& suggested_path,
 | |
|                                  bool show_dialog,
 | |
|                                  content::DownloadTargetCallback callback) {
 | |
|     if (!manager)
 | |
|       return;
 | |
| 
 | |
|     DownloadItem* item = manager->GetDownload(download_id);
 | |
|     if (!item || item->GetState() != DownloadItem::IN_PROGRESS)
 | |
|       return;
 | |
| 
 | |
|     bool handled = false;
 | |
| 
 | |
|     if (show_dialog) {
 | |
|       WebContents* web_contents =
 | |
|           content::DownloadItemUtils::GetWebContents(item);
 | |
|       CefRefPtr<AlloyBrowserHostImpl> browser =
 | |
|           AlloyBrowserHostImpl::GetBrowserForContents(web_contents);
 | |
|       if (browser.get()) {
 | |
|         handled = true;
 | |
| 
 | |
|         blink::mojom::FileChooserParams params;
 | |
|         params.mode = blink::mojom::FileChooserParams::Mode::kSave;
 | |
|         if (!suggested_path.empty()) {
 | |
|           params.default_file_name = suggested_path;
 | |
|           if (!suggested_path.Extension().empty()) {
 | |
|             params.accept_types.push_back(
 | |
|                 CefString(suggested_path.Extension()));
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         browser->RunFileChooserForBrowser(
 | |
|             params,
 | |
|             base::BindOnce(
 | |
|                 &CefBeforeDownloadCallbackImpl::ChooseDownloadPathCallback,
 | |
|                 std::move(callback)));
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (!handled) {
 | |
|       std::move(callback).Run(
 | |
|           suggested_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
 | |
|           download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
 | |
|           download::DownloadItem::MixedContentStatus::UNKNOWN, suggested_path,
 | |
|           base::FilePath(), std::string() /*mime_type*/,
 | |
|           download::DOWNLOAD_INTERRUPT_REASON_NONE);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   static void ChooseDownloadPathCallback(
 | |
|       content::DownloadTargetCallback callback,
 | |
|       const std::vector<base::FilePath>& file_paths) {
 | |
|     DCHECK_LE(file_paths.size(), (size_t)1);
 | |
| 
 | |
|     base::FilePath path;
 | |
|     if (file_paths.size() > 0)
 | |
|       path = file_paths.front();
 | |
| 
 | |
|     // The download will be cancelled if |path| is empty.
 | |
|     std::move(callback).Run(path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
 | |
|                             download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
 | |
|                             download::DownloadItem::MixedContentStatus::UNKNOWN,
 | |
|                             path, base::FilePath(), std::string() /*mime_type*/,
 | |
|                             download::DOWNLOAD_INTERRUPT_REASON_NONE);
 | |
|   }
 | |
| 
 | |
|   base::WeakPtr<DownloadManager> manager_;
 | |
|   uint32 download_id_;
 | |
|   base::FilePath suggested_name_;
 | |
|   content::DownloadTargetCallback callback_;
 | |
| 
 | |
|   IMPLEMENT_REFCOUNTING(CefBeforeDownloadCallbackImpl);
 | |
| };
 | |
| 
 | |
| // CefDownloadItemCallback implementation.
 | |
| class CefDownloadItemCallbackImpl : public CefDownloadItemCallback {
 | |
|  public:
 | |
|   explicit CefDownloadItemCallbackImpl(
 | |
|       const base::WeakPtr<DownloadManager>& manager,
 | |
|       uint32 download_id)
 | |
|       : manager_(manager), download_id_(download_id) {}
 | |
| 
 | |
|   CefDownloadItemCallbackImpl(const CefDownloadItemCallbackImpl&) = delete;
 | |
|   CefDownloadItemCallbackImpl& operator=(const CefDownloadItemCallbackImpl&) =
 | |
|       delete;
 | |
| 
 | |
|   void Cancel() override {
 | |
|     CEF_POST_TASK(CEF_UIT,
 | |
|                   base::BindOnce(&CefDownloadItemCallbackImpl::DoCancel, this));
 | |
|   }
 | |
| 
 | |
|   void Pause() override {
 | |
|     CEF_POST_TASK(CEF_UIT,
 | |
|                   base::BindOnce(&CefDownloadItemCallbackImpl::DoPause, this));
 | |
|   }
 | |
| 
 | |
|   void Resume() override {
 | |
|     CEF_POST_TASK(CEF_UIT,
 | |
|                   base::BindOnce(&CefDownloadItemCallbackImpl::DoResume, this));
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   void DoCancel() {
 | |
|     if (download_id_ <= 0)
 | |
|       return;
 | |
| 
 | |
|     if (manager_) {
 | |
|       DownloadItem* item = manager_->GetDownload(download_id_);
 | |
|       if (item && item->GetState() == DownloadItem::IN_PROGRESS)
 | |
|         item->Cancel(true);
 | |
|     }
 | |
| 
 | |
|     download_id_ = 0;
 | |
|   }
 | |
| 
 | |
|   void DoPause() {
 | |
|     if (download_id_ <= 0)
 | |
|       return;
 | |
| 
 | |
|     if (manager_) {
 | |
|       DownloadItem* item = manager_->GetDownload(download_id_);
 | |
|       if (item && item->GetState() == DownloadItem::IN_PROGRESS)
 | |
|         item->Pause();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   void DoResume() {
 | |
|     if (download_id_ <= 0)
 | |
|       return;
 | |
| 
 | |
|     if (manager_) {
 | |
|       DownloadItem* item = manager_->GetDownload(download_id_);
 | |
|       if (item && item->CanResume())
 | |
|         item->Resume(true);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   base::WeakPtr<DownloadManager> manager_;
 | |
|   uint32 download_id_;
 | |
| 
 | |
|   IMPLEMENT_REFCOUNTING(CefDownloadItemCallbackImpl);
 | |
| };
 | |
| 
 | |
| }  // namespace
 | |
| 
 | |
| CefDownloadManagerDelegate::CefDownloadManagerDelegate(DownloadManager* manager)
 | |
|     : manager_(manager), manager_ptr_factory_(manager) {
 | |
|   DCHECK(manager);
 | |
|   manager->AddObserver(this);
 | |
| 
 | |
|   DownloadManager::DownloadVector items;
 | |
|   manager->GetAllDownloads(&items);
 | |
|   DownloadManager::DownloadVector::const_iterator it = items.begin();
 | |
|   for (; it != items.end(); ++it)
 | |
|     OnDownloadCreated(manager, *it);
 | |
| }
 | |
| 
 | |
| CefDownloadManagerDelegate::~CefDownloadManagerDelegate() {
 | |
|   if (manager_) {
 | |
|     manager_->SetDelegate(nullptr);
 | |
|     manager_->RemoveObserver(this);
 | |
|   }
 | |
| 
 | |
|   while (!item_browser_map_.empty())
 | |
|     OnDownloadDestroyed(item_browser_map_.begin()->first);
 | |
| }
 | |
| 
 | |
| void CefDownloadManagerDelegate::OnDownloadUpdated(DownloadItem* download) {
 | |
|   CefRefPtr<AlloyBrowserHostImpl> browser = GetBrowser(download);
 | |
|   CefRefPtr<CefDownloadHandler> handler;
 | |
|   if (browser.get())
 | |
|     handler = GetDownloadHandler(browser);
 | |
| 
 | |
|   if (handler.get()) {
 | |
|     CefRefPtr<CefDownloadItemImpl> download_item(
 | |
|         new CefDownloadItemImpl(download));
 | |
|     CefRefPtr<CefDownloadItemCallback> callback(new CefDownloadItemCallbackImpl(
 | |
|         manager_ptr_factory_.GetWeakPtr(), download->GetId()));
 | |
| 
 | |
|     handler->OnDownloadUpdated(browser.get(), download_item.get(), callback);
 | |
| 
 | |
|     std::ignore = download_item->Detach(nullptr);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void CefDownloadManagerDelegate::OnDownloadDestroyed(DownloadItem* item) {
 | |
|   item->RemoveObserver(this);
 | |
| 
 | |
|   AlloyBrowserHostImpl* browser = nullptr;
 | |
| 
 | |
|   ItemBrowserMap::iterator it = item_browser_map_.find(item);
 | |
|   DCHECK(it != item_browser_map_.end());
 | |
|   if (it != item_browser_map_.end()) {
 | |
|     browser = it->second;
 | |
|     item_browser_map_.erase(it);
 | |
|   }
 | |
| 
 | |
|   if (browser) {
 | |
|     // Determine if any remaining DownloadItems are associated with the same
 | |
|     // browser. If not, then unregister as an observer.
 | |
|     bool has_remaining = false;
 | |
|     ItemBrowserMap::const_iterator it2 = item_browser_map_.begin();
 | |
|     for (; it2 != item_browser_map_.end(); ++it2) {
 | |
|       if (it2->second == browser) {
 | |
|         has_remaining = true;
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (!has_remaining)
 | |
|       browser->RemoveObserver(this);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void CefDownloadManagerDelegate::OnDownloadCreated(DownloadManager* manager,
 | |
|                                                    DownloadItem* item) {
 | |
|   // This callback may arrive after DetermineDownloadTarget, so we allow
 | |
|   // association from either method.
 | |
|   CefRefPtr<AlloyBrowserHostImpl> browser = GetOrAssociateBrowser(item);
 | |
|   if (!browser) {
 | |
|     // If the download is rejected (e.g. ALT+click on an invalid protocol link)
 | |
|     // then an "interrupted" download will be started via DownloadManagerImpl::
 | |
|     // StartDownloadWithId (originating from CreateInterruptedDownload) with no
 | |
|     // associated WebContents and consequently no associated CEF browser. In
 | |
|     // that case DetermineDownloadTarget will be called before this method.
 | |
|     // TODO(cef): Figure out how to expose this via a client callback.
 | |
|     const std::vector<GURL>& url_chain = item->GetUrlChain();
 | |
|     if (!url_chain.empty()) {
 | |
|       LOG(INFO) << "Rejected download of " << url_chain.back().spec();
 | |
|     }
 | |
|     item->Cancel(true);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void CefDownloadManagerDelegate::ManagerGoingDown(DownloadManager* manager) {
 | |
|   DCHECK_EQ(manager, manager_);
 | |
|   manager->SetDelegate(nullptr);
 | |
|   manager->RemoveObserver(this);
 | |
|   manager_ptr_factory_.InvalidateWeakPtrs();
 | |
|   manager_ = nullptr;
 | |
| }
 | |
| 
 | |
| bool CefDownloadManagerDelegate::DetermineDownloadTarget(
 | |
|     DownloadItem* item,
 | |
|     content::DownloadTargetCallback* callback) {
 | |
|   if (!item->GetForcedFilePath().empty()) {
 | |
|     std::move(*callback).Run(
 | |
|         item->GetForcedFilePath(), DownloadItem::TARGET_DISPOSITION_OVERWRITE,
 | |
|         download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
 | |
|         download::DownloadItem::MixedContentStatus::UNKNOWN,
 | |
|         item->GetForcedFilePath(), base::FilePath(),
 | |
|         std::string() /*mime_type*/, download::DOWNLOAD_INTERRUPT_REASON_NONE);
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // This callback may arrive before OnDownloadCreated, so we allow association
 | |
|   // from either method.
 | |
|   CefRefPtr<AlloyBrowserHostImpl> browser = GetOrAssociateBrowser(item);
 | |
|   CefRefPtr<CefDownloadHandler> handler;
 | |
|   if (browser.get())
 | |
|     handler = GetDownloadHandler(browser);
 | |
| 
 | |
|   if (handler.get()) {
 | |
|     base::FilePath suggested_name = net::GenerateFileName(
 | |
|         item->GetURL(), item->GetContentDisposition(), std::string(),
 | |
|         item->GetSuggestedFilename(), item->GetMimeType(), "download");
 | |
| 
 | |
|     CefRefPtr<CefDownloadItemImpl> download_item(new CefDownloadItemImpl(item));
 | |
|     CefRefPtr<CefBeforeDownloadCallback> callbackObj(
 | |
|         new CefBeforeDownloadCallbackImpl(manager_ptr_factory_.GetWeakPtr(),
 | |
|                                           item->GetId(), suggested_name,
 | |
|                                           std::move(*callback)));
 | |
| 
 | |
|     handler->OnBeforeDownload(browser.get(), download_item.get(),
 | |
|                               suggested_name.value(), callbackObj);
 | |
| 
 | |
|     std::ignore = download_item->Detach(nullptr);
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void CefDownloadManagerDelegate::GetNextId(
 | |
|     content::DownloadIdCallback callback) {
 | |
|   static uint32 next_id = DownloadItem::kInvalidId + 1;
 | |
|   std::move(callback).Run(next_id++);
 | |
| }
 | |
| 
 | |
| std::string CefDownloadManagerDelegate::ApplicationClientIdForFileScanning() {
 | |
|   return std::string(chrome::kApplicationClientIDStringForAVScanning);
 | |
| }
 | |
| 
 | |
| void CefDownloadManagerDelegate::OnBrowserDestroyed(
 | |
|     CefBrowserHostBase* browser) {
 | |
|   ItemBrowserMap::iterator it = item_browser_map_.begin();
 | |
|   for (; it != item_browser_map_.end(); ++it) {
 | |
|     if (it->second == browser) {
 | |
|       // Don't call back into browsers that have been destroyed. We're not
 | |
|       // canceling the download so it will continue silently until it completes
 | |
|       // or until the associated browser context is destroyed.
 | |
|       it->second = nullptr;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| AlloyBrowserHostImpl* CefDownloadManagerDelegate::GetOrAssociateBrowser(
 | |
|     download::DownloadItem* item) {
 | |
|   ItemBrowserMap::const_iterator it = item_browser_map_.find(item);
 | |
|   if (it != item_browser_map_.end())
 | |
|     return it->second;
 | |
| 
 | |
|   AlloyBrowserHostImpl* browser = nullptr;
 | |
|   content::WebContents* contents =
 | |
|       content::DownloadItemUtils::GetWebContents(item);
 | |
|   if (contents) {
 | |
|     browser = AlloyBrowserHostImpl::GetBrowserForContents(contents).get();
 | |
|     DCHECK(browser);
 | |
|   }
 | |
|   if (!browser)
 | |
|     return nullptr;
 | |
| 
 | |
|   item->AddObserver(this);
 | |
| 
 | |
|   item_browser_map_.insert(std::make_pair(item, browser));
 | |
| 
 | |
|   // Register as an observer so that we can cancel associated DownloadItems when
 | |
|   // the browser is destroyed.
 | |
|   if (!browser->HasObserver(this))
 | |
|     browser->AddObserver(this);
 | |
| 
 | |
|   return browser;
 | |
| }
 | |
| 
 | |
| AlloyBrowserHostImpl* CefDownloadManagerDelegate::GetBrowser(
 | |
|     DownloadItem* item) {
 | |
|   ItemBrowserMap::const_iterator it = item_browser_map_.find(item);
 | |
|   if (it != item_browser_map_.end())
 | |
|     return it->second;
 | |
| 
 | |
|   // If the download is rejected (e.g. ALT+click on an invalid protocol link)
 | |
|   // then an "interrupted" download will be started via DownloadManagerImpl::
 | |
|   // StartDownloadWithId (originating from CreateInterruptedDownload) with no
 | |
|   // associated WebContents and consequently no associated CEF browser. In that
 | |
|   // case DetermineDownloadTarget will be called before OnDownloadCreated.
 | |
|   DCHECK(!content::DownloadItemUtils::GetWebContents(item));
 | |
|   return nullptr;
 | |
| }
 |