cef/libcef/browser/download_manager_delegate.cc

463 lines
16 KiB
C++
Raw Normal View History

// 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/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)) {}
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;
CefFileDialogRunner::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->RunFileChooser(
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,
absl::nullopt /*download_schedule*/,
download::DOWNLOAD_INTERRUPT_REASON_NONE);
}
}
static void ChooseDownloadPathCallback(
content::DownloadTargetCallback callback,
- Add open folder dialog support (FILE_DIALOG_OPEN_FOLDER mode) for CefBrowserHost::RunFileDialog (issue #1030). - Standardize file dialog behavior across all platforms (issue #1492). -- Show a file type filter list on OS X. -- Show the file extensions as part of the filter list description on all platforms (e.g. "Image Types (*.png;*.gif;*.jpg)"). -- Rename the CefBrowserHost::RunFileDialog |accept_types| argument to |accept_filters| and expand support for filters that will be displayed as-is in addition to the currently supported mime-type and extension-based filters. For example, a filter value of "Supported Image Types|.png;.gif;.jpg" will display "Supported Image Types (*.png;*.gif;*.jpg)" in the filter drop-down list and accept *.png, *.gif and *.jpg files. -- Persist the selected filter item index by passing a new |selected_accept_filter| argument to CefBrowserHost::RunFileDialog and returning the newly selected index via the CefRunFileDialogCallback and CefFileDialogCallback callbacks. -- Rename the CefBrowserHost::RunFileDialog |default_file_name| argument to |default_file_path| and use the directory component, if any, to set the default directory location. If |default_file_path| ends in a trailing path separator it will be treated as a directory without a file name component. -- Add FILE_DIALOG_OVERWRITEPROMPT_FLAG and FILE_DIALOG_HIDEREADONLY_FLAG values to cef_file_dialog_mode_t for controlling those behaviors where possible. git-svn-id: https://chromiumembedded.googlecode.com/svn/trunk@1973 5089003a-bbd8-11dd-ad1f-f1f9622dbc98
2015-01-20 19:24:54 +01:00
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.
std::move(callback).Run(path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
download::DownloadItem::MixedContentStatus::UNKNOWN,
path, absl::nullopt /*download_schedule*/,
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::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);
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(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);
ignore_result(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(), absl::nullopt /*download_schedule*/,
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);
ignore_result(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() {
const CefSettings& settings = CefContext::Get()->settings();
if (settings.application_client_id_for_file_scanning.length > 0) {
return CefString(&settings.application_client_id_for_file_scanning)
.ToString();
}
return std::string();
}
void CefDownloadManagerDelegate::OnBrowserDestroyed(
Create a ChromeBrowserHostImpl for every Chrome tab (see issue #2969) The Browser object represents the top-level Chrome browser window. One or more tabs (WebContents) are then owned by the Browser object via TabStripModel. A new Browser object can be created programmatically using "new Browser" or Browser::Create, or as a result of user action such as dragging a tab out of an existing window. New or existing tabs can also be added to an already existing Browser object. The Browser object acts as the WebContentsDelegate for all attached tabs. CEF integration requires WebContentsDelegate callbacks and notification of tab attach/detach. To support this integration we add a cef::BrowserDelegate (ChromeBrowserDelegate) member that is created in the Browser constructor and receives delegation for the Browser callbacks. ChromeBrowserDelegate creates a new ChromeBrowserHostImpl when a tab is added to a Browser for the first time, and that ChromeBrowserHostImpl continues to exist until the tab's WebContents is destroyed. The associated WebContents object does not change, but the Browser object will change when the tab is dragged between windows. CEF callback logic is shared between the chrome and alloy runtimes where possible. This shared logic has been extracted from CefBrowserHostImpl to create new CefBrowserHostBase and CefBrowserContentsDelegate classes. The CefBrowserHostImpl class is now only used with the alloy runtime and will be renamed to AlloyBrowserHostImpl in a future commit.
2020-09-18 00:24:08 +02:00
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;
}