cef/libcef/browser/download_manager_delegate.cc

468 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 <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;
Use Chrome file dialogs on all platforms and runtimes (fixes issue #3314) All file dialogs irrespective of source, platform and runtime will now be routed through CefFileDialogManager and trigger CefDialogHandler callbacks (see issue #3293). Adds Chrome runtime support for CefBrowserHost::RunFileDialog and CefDialogHandler callbacks. Adds Alloy runtime support for internal GTK file and print dialogs on Linux subject to the following limitations: 1. Internal GTK implementation: - Cannot be used with multi-threaded-message-loop because Chromium's internal GTK implementation is not thread-safe (does not use GDK threads). - Dialogs will not be modal to application windows when used with off-screen rendering due to lack of access to the client's top-level GtkWindow. 2. Cefclient CefDialogHandler implementation: - Cannot be used with Views because it requires a top-level GtkWindow. Due to the above limitations no dialog implementation is currently provided for Views + multi-threaded-message-loop on Linux. In cases where both implementations are supported the cefclient version is now behind an optional `--use-client-dialogs` command-line flag. Expressly forbids multiple simultaneous file dialogs with the internal platform implementation which uses modal dialogs. CefDialogHandler will still be notified and can optionally handle each request without a modal dialog (see issue #3154). Removes some RunFileDialog parameters that are not supported by the Chrome file dialog implementation (selected_accept_filter parameter, cef_file_dialog_mode_t overwrite/read-only flags).
2022-04-15 21:55:23 +02:00
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()));
}
}
Use Chrome file dialogs on all platforms and runtimes (fixes issue #3314) All file dialogs irrespective of source, platform and runtime will now be routed through CefFileDialogManager and trigger CefDialogHandler callbacks (see issue #3293). Adds Chrome runtime support for CefBrowserHost::RunFileDialog and CefDialogHandler callbacks. Adds Alloy runtime support for internal GTK file and print dialogs on Linux subject to the following limitations: 1. Internal GTK implementation: - Cannot be used with multi-threaded-message-loop because Chromium's internal GTK implementation is not thread-safe (does not use GDK threads). - Dialogs will not be modal to application windows when used with off-screen rendering due to lack of access to the client's top-level GtkWindow. 2. Cefclient CefDialogHandler implementation: - Cannot be used with Views because it requires a top-level GtkWindow. Due to the above limitations no dialog implementation is currently provided for Views + multi-threaded-message-loop on Linux. In cases where both implementations are supported the cefclient version is now behind an optional `--use-client-dialogs` command-line flag. Expressly forbids multiple simultaneous file dialogs with the internal platform implementation which uses modal dialogs. CefDialogHandler will still be notified and can optionally handle each request without a modal dialog (see issue #3154). Removes some RunFileDialog parameters that are not supported by the Chrome file dialog implementation (selected_accept_filter parameter, cef_file_dialog_mode_t overwrite/read-only flags).
2022-04-15 21:55:23 +02:00
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*/,
absl::nullopt /*download_schedule*/,
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*/,
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);
};
// 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*/, 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);
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(
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;
}