mirror of
				https://bitbucket.org/chromiumembedded/cef
				synced 2025-06-05 21:39:12 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			428 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			428 lines
		
	
	
		
			14 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 "include/cef_download_handler.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 "content/public/browser/browser_context.h"
 | 
						|
#include "content/public/browser/download_item_utils.h"
 | 
						|
#include "content/public/browser/web_contents.h"
 | 
						|
#include "content/public/common/file_chooser_params.h"
 | 
						|
#include "net/base/filename_util.h"
 | 
						|
 | 
						|
using download::DownloadItem;
 | 
						|
using content::DownloadManager;
 | 
						|
using content::WebContents;
 | 
						|
 | 
						|
namespace {
 | 
						|
 | 
						|
// Helper function to retrieve the CefDownloadHandler.
 | 
						|
CefRefPtr<CefDownloadHandler> GetDownloadHandler(
 | 
						|
    CefRefPtr<CefBrowserHostImpl> browser) {
 | 
						|
  CefRefPtr<CefClient> client = browser->GetClient();
 | 
						|
  if (client.get())
 | 
						|
    return client->GetDownloadHandler();
 | 
						|
  return NULL;
 | 
						|
}
 | 
						|
 | 
						|
// CefBeforeDownloadCallback implementation.
 | 
						|
class CefBeforeDownloadCallbackImpl : public CefBeforeDownloadCallback {
 | 
						|
 public:
 | 
						|
  CefBeforeDownloadCallbackImpl(const base::WeakPtr<DownloadManager>& manager,
 | 
						|
                                uint32 download_id,
 | 
						|
                                const base::FilePath& suggested_name,
 | 
						|
                                const content::DownloadTargetCallback& callback)
 | 
						|
      : manager_(manager),
 | 
						|
        download_id_(download_id),
 | 
						|
        suggested_name_(suggested_name),
 | 
						|
        callback_(callback) {}
 | 
						|
 | 
						|
  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::Bind(
 | 
						|
            &CefBeforeDownloadCallbackImpl::GenerateFilename, manager_,
 | 
						|
            download_id_, suggested_name_, path, show_dialog, callback_));
 | 
						|
      }
 | 
						|
 | 
						|
      download_id_ = 0;
 | 
						|
      callback_.Reset();
 | 
						|
    } else {
 | 
						|
      CEF_POST_TASK(CEF_UIT,
 | 
						|
                    base::Bind(&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,
 | 
						|
      const 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 (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::Bind(&CefBeforeDownloadCallbackImpl::ChooseDownloadPath, manager,
 | 
						|
                   download_id, suggested_path, show_dialog, callback));
 | 
						|
  }
 | 
						|
 | 
						|
  static void ChooseDownloadPath(
 | 
						|
      base::WeakPtr<DownloadManager> manager,
 | 
						|
      uint32 download_id,
 | 
						|
      const base::FilePath& suggested_path,
 | 
						|
      bool show_dialog,
 | 
						|
      const 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<CefBrowserHostImpl> browser =
 | 
						|
          CefBrowserHostImpl::GetBrowserForContents(web_contents);
 | 
						|
      if (browser.get()) {
 | 
						|
        handled = true;
 | 
						|
 | 
						|
        CefFileDialogRunner::FileChooserParams params;
 | 
						|
        params.mode = content::FileChooserParams::Save;
 | 
						|
        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->RunFileChooser(
 | 
						|
            params,
 | 
						|
            base::Bind(
 | 
						|
                &CefBeforeDownloadCallbackImpl::ChooseDownloadPathCallback,
 | 
						|
                callback));
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (!handled) {
 | 
						|
      callback.Run(suggested_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
 | 
						|
                   download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, suggested_path,
 | 
						|
                   download::DOWNLOAD_INTERRUPT_REASON_NONE);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  static void ChooseDownloadPathCallback(
 | 
						|
      const content::DownloadTargetCallback& callback,
 | 
						|
      int selected_accept_filter,
 | 
						|
      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.
 | 
						|
    callback.Run(path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
 | 
						|
                 download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, path,
 | 
						|
                 download::DOWNLOAD_INTERRUPT_REASON_NONE);
 | 
						|
  }
 | 
						|
 | 
						|
  base::WeakPtr<DownloadManager> manager_;
 | 
						|
  uint32 download_id_;
 | 
						|
  base::FilePath suggested_name_;
 | 
						|
  content::DownloadTargetCallback callback_;
 | 
						|
 | 
						|
  IMPLEMENT_REFCOUNTING(CefBeforeDownloadCallbackImpl);
 | 
						|
  DISALLOW_COPY_AND_ASSIGN(CefBeforeDownloadCallbackImpl);
 | 
						|
};
 | 
						|
 | 
						|
// CefDownloadItemCallback implementation.
 | 
						|
class CefDownloadItemCallbackImpl : public CefDownloadItemCallback {
 | 
						|
 public:
 | 
						|
  explicit CefDownloadItemCallbackImpl(
 | 
						|
      const base::WeakPtr<DownloadManager>& manager,
 | 
						|
      uint32 download_id)
 | 
						|
      : manager_(manager), download_id_(download_id) {}
 | 
						|
 | 
						|
  void Cancel() override {
 | 
						|
    CEF_POST_TASK(CEF_UIT,
 | 
						|
                  base::Bind(&CefDownloadItemCallbackImpl::DoCancel, this));
 | 
						|
  }
 | 
						|
 | 
						|
  void Pause() override {
 | 
						|
    CEF_POST_TASK(CEF_UIT,
 | 
						|
                  base::Bind(&CefDownloadItemCallbackImpl::DoPause, this));
 | 
						|
  }
 | 
						|
 | 
						|
  void Resume() override {
 | 
						|
    CEF_POST_TASK(CEF_UIT,
 | 
						|
                  base::Bind(&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();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  base::WeakPtr<DownloadManager> manager_;
 | 
						|
  uint32 download_id_;
 | 
						|
 | 
						|
  IMPLEMENT_REFCOUNTING(CefDownloadItemCallbackImpl);
 | 
						|
  DISALLOW_COPY_AND_ASSIGN(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(NULL);
 | 
						|
    manager_->RemoveObserver(this);
 | 
						|
  }
 | 
						|
 | 
						|
  while (!item_browser_map_.empty())
 | 
						|
    OnDownloadDestroyed(item_browser_map_.begin()->first);
 | 
						|
}
 | 
						|
 | 
						|
void CefDownloadManagerDelegate::OnDownloadUpdated(DownloadItem* download) {
 | 
						|
  CefRefPtr<CefBrowserHostImpl> 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);
 | 
						|
 | 
						|
    download_item->Detach(NULL);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void CefDownloadManagerDelegate::OnDownloadDestroyed(DownloadItem* item) {
 | 
						|
  item->RemoveObserver(this);
 | 
						|
 | 
						|
  CefBrowserHostImpl* 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) {
 | 
						|
  CefBrowserHostImpl* browser = nullptr;
 | 
						|
  content::WebContents* contents =
 | 
						|
      content::DownloadItemUtils::GetWebContents(item);
 | 
						|
  if (contents) {
 | 
						|
    browser = CefBrowserHostImpl::GetBrowserForContents(contents).get();
 | 
						|
    DCHECK(browser);
 | 
						|
  }
 | 
						|
  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);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  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);
 | 
						|
}
 | 
						|
 | 
						|
void CefDownloadManagerDelegate::ManagerGoingDown(DownloadManager* manager) {
 | 
						|
  DCHECK_EQ(manager, manager_);
 | 
						|
  manager->SetDelegate(NULL);
 | 
						|
  manager->RemoveObserver(this);
 | 
						|
  manager_ptr_factory_.InvalidateWeakPtrs();
 | 
						|
  manager_ = NULL;
 | 
						|
}
 | 
						|
 | 
						|
bool CefDownloadManagerDelegate::DetermineDownloadTarget(
 | 
						|
    DownloadItem* item,
 | 
						|
    const content::DownloadTargetCallback& callback) {
 | 
						|
  if (!item->GetForcedFilePath().empty()) {
 | 
						|
    callback.Run(
 | 
						|
        item->GetForcedFilePath(), DownloadItem::TARGET_DISPOSITION_OVERWRITE,
 | 
						|
        download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, item->GetForcedFilePath(),
 | 
						|
        download::DOWNLOAD_INTERRUPT_REASON_NONE);
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  CefRefPtr<CefBrowserHostImpl> browser = GetBrowser(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,
 | 
						|
                                          callback));
 | 
						|
 | 
						|
    handler->OnBeforeDownload(browser.get(), download_item.get(),
 | 
						|
                              suggested_name.value(), callbackObj);
 | 
						|
 | 
						|
    download_item->Detach(NULL);
 | 
						|
  }
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
void CefDownloadManagerDelegate::GetNextId(
 | 
						|
    const content::DownloadIdCallback& callback) {
 | 
						|
  static uint32 next_id = DownloadItem::kInvalidId + 1;
 | 
						|
  callback.Run(next_id++);
 | 
						|
}
 | 
						|
 | 
						|
void CefDownloadManagerDelegate::OnBrowserDestroyed(
 | 
						|
    CefBrowserHostImpl* 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;
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
CefBrowserHostImpl* 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;
 | 
						|
}
 |