refactor: reuse upstream //shell_dialogs (#30663)
This commit is contained in:
parent
c74b9ff312
commit
00d0265782
8 changed files with 655 additions and 462 deletions
|
@ -48,6 +48,8 @@ static_library("chrome") {
|
|||
"//chrome/browser/predictors/resolve_host_client_impl.cc",
|
||||
"//chrome/browser/predictors/resolve_host_client_impl.h",
|
||||
"//chrome/browser/process_singleton.h",
|
||||
"//chrome/browser/ui/browser_dialogs.cc",
|
||||
"//chrome/browser/ui/browser_dialogs.h",
|
||||
"//chrome/browser/ui/views/autofill/autofill_popup_view_utils.cc",
|
||||
"//chrome/browser/ui/views/autofill/autofill_popup_view_utils.h",
|
||||
"//chrome/browser/ui/views/eye_dropper/eye_dropper.cc",
|
||||
|
|
|
@ -494,8 +494,6 @@ filenames = {
|
|||
"shell/browser/web_contents_preferences.h",
|
||||
"shell/browser/web_contents_zoom_controller.cc",
|
||||
"shell/browser/web_contents_zoom_controller.h",
|
||||
"shell/browser/web_dialog_helper.cc",
|
||||
"shell/browser/web_dialog_helper.h",
|
||||
"shell/browser/web_view_guest_delegate.cc",
|
||||
"shell/browser/web_view_guest_delegate.h",
|
||||
"shell/browser/web_view_manager.cc",
|
||||
|
|
|
@ -86,6 +86,7 @@
|
|||
#include "shell/browser/electron_browser_main_parts.h"
|
||||
#include "shell/browser/electron_javascript_dialog_manager.h"
|
||||
#include "shell/browser/electron_navigation_throttle.h"
|
||||
#include "shell/browser/file_select_helper.h"
|
||||
#include "shell/browser/native_window.h"
|
||||
#include "shell/browser/session_preferences.h"
|
||||
#include "shell/browser/ui/drag_util.h"
|
||||
|
@ -95,7 +96,6 @@
|
|||
#include "shell/browser/web_contents_permission_helper.h"
|
||||
#include "shell/browser/web_contents_preferences.h"
|
||||
#include "shell/browser/web_contents_zoom_controller.h"
|
||||
#include "shell/browser/web_dialog_helper.h"
|
||||
#include "shell/browser/web_view_guest_delegate.h"
|
||||
#include "shell/browser/web_view_manager.h"
|
||||
#include "shell/common/api/electron_api_native_image.h"
|
||||
|
@ -3255,21 +3255,15 @@ void WebContents::RunFileChooser(
|
|||
content::RenderFrameHost* render_frame_host,
|
||||
scoped_refptr<content::FileSelectListener> listener,
|
||||
const blink::mojom::FileChooserParams& params) {
|
||||
if (!web_dialog_helper_)
|
||||
web_dialog_helper_ =
|
||||
std::make_unique<WebDialogHelper>(owner_window(), offscreen_);
|
||||
web_dialog_helper_->RunFileChooser(render_frame_host, std::move(listener),
|
||||
params);
|
||||
FileSelectHelper::RunFileChooser(render_frame_host, std::move(listener),
|
||||
params);
|
||||
}
|
||||
|
||||
void WebContents::EnumerateDirectory(
|
||||
content::WebContents* guest,
|
||||
content::WebContents* web_contents,
|
||||
scoped_refptr<content::FileSelectListener> listener,
|
||||
const base::FilePath& path) {
|
||||
if (!web_dialog_helper_)
|
||||
web_dialog_helper_ =
|
||||
std::make_unique<WebDialogHelper>(owner_window(), offscreen_);
|
||||
web_dialog_helper_->EnumerateDirectory(guest, std::move(listener), path);
|
||||
FileSelectHelper::EnumerateDirectory(web_contents, std::move(listener), path);
|
||||
}
|
||||
|
||||
bool WebContents::IsFullscreenForTabOrPending(
|
||||
|
|
|
@ -731,9 +731,6 @@ class WebContents : public gin::Wrappable<WebContents>,
|
|||
// Whether window is fullscreened by window api.
|
||||
bool native_fullscreen_ = false;
|
||||
|
||||
// UI related helper classes.
|
||||
std::unique_ptr<WebDialogHelper> web_dialog_helper_;
|
||||
|
||||
scoped_refptr<DevToolsFileSystemIndexer> devtools_file_system_indexer_;
|
||||
|
||||
std::unique_ptr<DevToolsEyeDropper> eye_dropper_;
|
||||
|
|
|
@ -1,133 +1,243 @@
|
|||
// Copyright (c) 2020 Microsoft, Inc. All rights reserved.
|
||||
// Copyright (c) 2021 Microsoft. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "shell/browser/file_select_helper.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/files/file_enumerator.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/memory/ptr_util.h"
|
||||
#include "base/strings/string_split.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "base/task/thread_pool.h"
|
||||
#include "base/threading/hang_watcher.h"
|
||||
#include "build/build_config.h"
|
||||
#include "chrome/browser/browser_process.h"
|
||||
#include "chrome/browser/platform_util.h"
|
||||
#include "chrome/browser/ui/browser_dialogs.h"
|
||||
#include "chrome/common/pref_names.h"
|
||||
#include "chrome/grit/generated_resources.h"
|
||||
#include "components/prefs/pref_service.h"
|
||||
#include "content/public/browser/browser_task_traits.h"
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "content/public/browser/file_select_listener.h"
|
||||
#include "content/public/browser/render_frame_host.h"
|
||||
#include "content/public/browser/render_process_host.h"
|
||||
#include "content/public/browser/render_view_host.h"
|
||||
#include "content/public/browser/render_widget_host_view.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
#include "net/base/filename_util.h"
|
||||
#include "net/base/mime_util.h"
|
||||
#include "shell/browser/api/electron_api_web_contents.h"
|
||||
#include "shell/browser/electron_browser_context.h"
|
||||
#include "shell/browser/javascript_environment.h"
|
||||
#include "shell/browser/ui/file_dialog.h"
|
||||
#include "shell/common/gin_converters/callback_converter.h"
|
||||
#include "shell/common/gin_converters/file_path_converter.h"
|
||||
#include "shell/browser/native_window.h"
|
||||
#include "ui/base/l10n/l10n_util.h"
|
||||
#include "ui/shell_dialogs/select_file_policy.h"
|
||||
#include "ui/shell_dialogs/selected_file_info.h"
|
||||
|
||||
using blink::mojom::FileChooserFileInfo;
|
||||
using blink::mojom::FileChooserFileInfoPtr;
|
||||
using blink::mojom::FileChooserParams;
|
||||
using blink::mojom::NativeFileInfo;
|
||||
using blink::mojom::FileChooserParamsPtr;
|
||||
using content::BrowserThread;
|
||||
using content::RenderViewHost;
|
||||
using content::RenderWidgetHost;
|
||||
using content::WebContents;
|
||||
|
||||
namespace {
|
||||
|
||||
void DeleteFiles(std::vector<base::FilePath> paths) {
|
||||
for (auto& file_path : paths)
|
||||
base::DeleteFile(file_path);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
FileSelectHelper::FileSelectHelper(
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
scoped_refptr<content::FileSelectListener> listener,
|
||||
FileChooserParams::Mode mode)
|
||||
: render_frame_host_(render_frame_host),
|
||||
listener_(std::move(listener)),
|
||||
mode_(mode) {
|
||||
DCHECK(render_frame_host_);
|
||||
DCHECK(listener_);
|
||||
struct FileSelectHelper::ActiveDirectoryEnumeration {
|
||||
explicit ActiveDirectoryEnumeration(const base::FilePath& path)
|
||||
: path_(path) {}
|
||||
|
||||
web_contents_ = content::WebContents::FromRenderFrameHost(render_frame_host);
|
||||
DCHECK(web_contents_);
|
||||
std::unique_ptr<net::DirectoryLister> lister_;
|
||||
const base::FilePath path_;
|
||||
std::vector<base::FilePath> results_;
|
||||
};
|
||||
|
||||
content::WebContentsObserver::Observe(web_contents_);
|
||||
observation_.Observe(render_frame_host_->GetRenderViewHost()->GetWidget());
|
||||
FileSelectHelper::FileSelectHelper()
|
||||
: render_frame_host_(nullptr),
|
||||
web_contents_(nullptr),
|
||||
select_file_dialog_(),
|
||||
select_file_types_(),
|
||||
dialog_type_(ui::SelectFileDialog::SELECT_OPEN_FILE),
|
||||
dialog_mode_(FileChooserParams::Mode::kOpen) {}
|
||||
|
||||
FileSelectHelper::~FileSelectHelper() {
|
||||
// There may be pending file dialogs, we need to tell them that we've gone
|
||||
// away so they don't try and call back to us.
|
||||
if (select_file_dialog_.get())
|
||||
select_file_dialog_->ListenerDestroyed();
|
||||
}
|
||||
|
||||
FileSelectHelper::~FileSelectHelper() = default;
|
||||
|
||||
void FileSelectHelper::ShowOpenDialog(
|
||||
const file_dialog::DialogSettings& settings) {
|
||||
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
gin_helper::Promise<gin_helper::Dictionary> promise(isolate);
|
||||
|
||||
auto callback = base::BindOnce(&FileSelectHelper::OnOpenDialogDone,
|
||||
weak_ptr_factory_.GetWeakPtr());
|
||||
ignore_result(promise.Then(std::move(callback)));
|
||||
|
||||
file_dialog::ShowOpenDialog(settings, std::move(promise));
|
||||
void FileSelectHelper::FileSelected(const base::FilePath& path,
|
||||
int index,
|
||||
void* params) {
|
||||
FileSelectedWithExtraInfo(ui::SelectedFileInfo(path, path), index, params);
|
||||
}
|
||||
|
||||
void FileSelectHelper::ShowSaveDialog(
|
||||
const file_dialog::DialogSettings& settings) {
|
||||
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
gin_helper::Promise<gin_helper::Dictionary> promise(isolate);
|
||||
|
||||
auto callback = base::BindOnce(&FileSelectHelper::OnSaveDialogDone,
|
||||
weak_ptr_factory_.GetWeakPtr());
|
||||
ignore_result(promise.Then(std::move(callback)));
|
||||
|
||||
file_dialog::ShowSaveDialog(settings, std::move(promise));
|
||||
}
|
||||
|
||||
// net::DirectoryLister::DirectoryListerDelegate
|
||||
void FileSelectHelper::OnListFile(
|
||||
const net::DirectoryLister::DirectoryListerData& data) {
|
||||
if (!render_frame_host_ || !web_contents_) {
|
||||
// If the frame or webcontents was destroyed under us. We
|
||||
// must notify |listener_| and release our reference to
|
||||
// ourself. RunFileChooserEnd() performs this.
|
||||
void FileSelectHelper::FileSelectedWithExtraInfo(
|
||||
const ui::SelectedFileInfo& file,
|
||||
int index,
|
||||
void* params) {
|
||||
if (!render_frame_host_) {
|
||||
RunFileChooserEnd();
|
||||
return;
|
||||
}
|
||||
// We don't want to return directory paths, only file paths
|
||||
|
||||
const base::FilePath& path = file.local_path;
|
||||
if (dialog_type_ == ui::SelectFileDialog::SELECT_UPLOAD_FOLDER) {
|
||||
StartNewEnumeration(path);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<ui::SelectedFileInfo> files;
|
||||
files.push_back(file);
|
||||
|
||||
#if defined(OS_MAC)
|
||||
base::ThreadPool::PostTask(
|
||||
FROM_HERE,
|
||||
{base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
|
||||
base::BindOnce(&FileSelectHelper::ProcessSelectedFilesMac, this, files));
|
||||
#else
|
||||
ConvertToFileChooserFileInfoList(files);
|
||||
#endif // defined(OS_MAC)
|
||||
}
|
||||
|
||||
void FileSelectHelper::MultiFilesSelected(
|
||||
const std::vector<base::FilePath>& files,
|
||||
void* params) {
|
||||
std::vector<ui::SelectedFileInfo> selected_files =
|
||||
ui::FilePathListToSelectedFileInfoList(files);
|
||||
|
||||
MultiFilesSelectedWithExtraInfo(selected_files, params);
|
||||
}
|
||||
|
||||
void FileSelectHelper::MultiFilesSelectedWithExtraInfo(
|
||||
const std::vector<ui::SelectedFileInfo>& files,
|
||||
void* params) {
|
||||
#if defined(OS_MAC)
|
||||
base::ThreadPool::PostTask(
|
||||
FROM_HERE,
|
||||
{base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
|
||||
base::BindOnce(&FileSelectHelper::ProcessSelectedFilesMac, this, files));
|
||||
#else
|
||||
ConvertToFileChooserFileInfoList(files);
|
||||
#endif // defined(OS_MAC)
|
||||
}
|
||||
|
||||
void FileSelectHelper::FileSelectionCanceled(void* params) {
|
||||
RunFileChooserEnd();
|
||||
}
|
||||
|
||||
void FileSelectHelper::StartNewEnumeration(const base::FilePath& path) {
|
||||
base_dir_ = path;
|
||||
auto entry = std::make_unique<ActiveDirectoryEnumeration>(path);
|
||||
entry->lister_ = base::WrapUnique(new net::DirectoryLister(
|
||||
path, net::DirectoryLister::NO_SORT_RECURSIVE, this));
|
||||
entry->lister_->Start();
|
||||
directory_enumeration_ = std::move(entry);
|
||||
}
|
||||
|
||||
void FileSelectHelper::OnListFile(
|
||||
const net::DirectoryLister::DirectoryListerData& data) {
|
||||
// Directory upload only cares about files.
|
||||
if (data.info.IsDirectory())
|
||||
return;
|
||||
|
||||
lister_paths_.push_back(data.path);
|
||||
directory_enumeration_->results_.push_back(data.path);
|
||||
}
|
||||
|
||||
void FileSelectHelper::RunFileChooserEnd() {
|
||||
// If there are temporary files, then this instance needs to stick around
|
||||
// until web_contents_ is destroyed, so that this instance can delete the
|
||||
// temporary files.
|
||||
if (!temporary_files_.empty())
|
||||
return;
|
||||
|
||||
if (listener_)
|
||||
listener_->FileSelectionCanceled();
|
||||
|
||||
render_frame_host_ = nullptr;
|
||||
web_contents_ = nullptr;
|
||||
|
||||
delete this;
|
||||
void FileSelectHelper::LaunchConfirmationDialog(
|
||||
const base::FilePath& path,
|
||||
std::vector<ui::SelectedFileInfo> selected_files) {
|
||||
ShowFolderUploadConfirmationDialog(
|
||||
path,
|
||||
base::BindOnce(&FileSelectHelper::ConvertToFileChooserFileInfoList, this),
|
||||
std::move(selected_files), web_contents_);
|
||||
}
|
||||
|
||||
// net::DirectoryLister::DirectoryListerDelegate
|
||||
void FileSelectHelper::OnListDone(int error) {
|
||||
if (!render_frame_host_ || !web_contents_) {
|
||||
// If the frame or webcontents was destroyed under us. We
|
||||
if (!web_contents_) {
|
||||
// Web contents was destroyed under us (probably by closing the tab). We
|
||||
// must notify |listener_| and release our reference to
|
||||
// ourself. RunFileChooserEnd() performs this.
|
||||
RunFileChooserEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<FileChooserFileInfoPtr> file_info;
|
||||
for (const auto& path : lister_paths_)
|
||||
file_info.push_back(FileChooserFileInfo::NewNativeFile(
|
||||
NativeFileInfo::New(path, std::u16string())));
|
||||
// This entry needs to be cleaned up when this function is done.
|
||||
std::unique_ptr<ActiveDirectoryEnumeration> entry =
|
||||
std::move(directory_enumeration_);
|
||||
if (error) {
|
||||
FileSelectionCanceled(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
OnFilesSelected(std::move(file_info), lister_base_dir_);
|
||||
std::vector<ui::SelectedFileInfo> selected_files =
|
||||
ui::FilePathListToSelectedFileInfoList(entry->results_);
|
||||
|
||||
if (dialog_type_ == ui::SelectFileDialog::SELECT_UPLOAD_FOLDER) {
|
||||
LaunchConfirmationDialog(entry->path_, std::move(selected_files));
|
||||
} else {
|
||||
std::vector<FileChooserFileInfoPtr> chooser_files;
|
||||
for (const auto& file_path : entry->results_) {
|
||||
chooser_files.push_back(FileChooserFileInfo::NewNativeFile(
|
||||
blink::mojom::NativeFileInfo::New(file_path, std::u16string())));
|
||||
}
|
||||
|
||||
listener_->FileSelected(std::move(chooser_files), base_dir_,
|
||||
FileChooserParams::Mode::kUploadFolder);
|
||||
listener_.reset();
|
||||
EnumerateDirectoryEnd();
|
||||
}
|
||||
}
|
||||
|
||||
void FileSelectHelper::ConvertToFileChooserFileInfoList(
|
||||
const std::vector<ui::SelectedFileInfo>& files) {
|
||||
if (AbortIfWebContentsDestroyed())
|
||||
return;
|
||||
|
||||
std::vector<FileChooserFileInfoPtr> chooser_files;
|
||||
for (const auto& file : files) {
|
||||
chooser_files.push_back(
|
||||
FileChooserFileInfo::NewNativeFile(blink::mojom::NativeFileInfo::New(
|
||||
file.local_path,
|
||||
base::FilePath(file.display_name).AsUTF16Unsafe())));
|
||||
}
|
||||
|
||||
PerformContentAnalysisIfNeeded(std::move(chooser_files));
|
||||
}
|
||||
|
||||
void FileSelectHelper::PerformContentAnalysisIfNeeded(
|
||||
std::vector<FileChooserFileInfoPtr> list) {
|
||||
if (AbortIfWebContentsDestroyed())
|
||||
return;
|
||||
|
||||
NotifyListenerAndEnd(std::move(list));
|
||||
}
|
||||
|
||||
void FileSelectHelper::NotifyListenerAndEnd(
|
||||
std::vector<blink::mojom::FileChooserFileInfoPtr> list) {
|
||||
listener_->FileSelected(std::move(list), base_dir_, dialog_mode_);
|
||||
listener_.reset();
|
||||
|
||||
// No members should be accessed from here on.
|
||||
RunFileChooserEnd();
|
||||
}
|
||||
|
||||
void FileSelectHelper::DeleteTemporaryFiles() {
|
||||
|
@ -138,96 +248,275 @@ void FileSelectHelper::DeleteTemporaryFiles() {
|
|||
base::BindOnce(&DeleteFiles, std::move(temporary_files_)));
|
||||
}
|
||||
|
||||
void FileSelectHelper::EnumerateDirectory() {
|
||||
// Ensure that this fn is only called once
|
||||
DCHECK(!lister_);
|
||||
DCHECK(!lister_base_dir_.empty());
|
||||
DCHECK(lister_paths_.empty());
|
||||
void FileSelectHelper::CleanUp() {
|
||||
if (!temporary_files_.empty()) {
|
||||
DeleteTemporaryFiles();
|
||||
|
||||
lister_ = std::make_unique<net::DirectoryLister>(
|
||||
lister_base_dir_, net::DirectoryLister::NO_SORT_RECURSIVE, this);
|
||||
lister_->Start();
|
||||
// Now that the temporary files have been scheduled for deletion, there
|
||||
// is no longer any reason to keep this instance around.
|
||||
Release();
|
||||
}
|
||||
}
|
||||
|
||||
void FileSelectHelper::OnOpenDialogDone(gin_helper::Dictionary result) {
|
||||
bool canceled = true;
|
||||
result.Get("canceled", &canceled);
|
||||
|
||||
if (!render_frame_host_ || canceled) {
|
||||
bool FileSelectHelper::AbortIfWebContentsDestroyed() {
|
||||
if (render_frame_host_ == nullptr || web_contents_ == nullptr) {
|
||||
RunFileChooserEnd();
|
||||
} else {
|
||||
std::vector<base::FilePath> paths;
|
||||
if (result.Get("filePaths", &paths)) {
|
||||
std::vector<ui::SelectedFileInfo> files =
|
||||
ui::FilePathListToSelectedFileInfoList(paths);
|
||||
// If we are uploading a folder we need to enumerate its contents
|
||||
if (mode_ == FileChooserParams::Mode::kUploadFolder && !paths.empty()) {
|
||||
lister_base_dir_ = paths[0];
|
||||
EnumerateDirectory();
|
||||
} else {
|
||||
#if defined(OS_MAC)
|
||||
base::ThreadPool::PostTask(
|
||||
FROM_HERE,
|
||||
{base::MayBlock(),
|
||||
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
|
||||
base::BindOnce(&FileSelectHelper::ProcessSelectedFilesMac,
|
||||
base::Unretained(this), files));
|
||||
#else
|
||||
ConvertToFileChooserFileInfoList(files);
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (render_frame_host_ && !paths.empty()) {
|
||||
auto* browser_context = static_cast<electron::ElectronBrowserContext*>(
|
||||
render_frame_host_->GetProcess()->GetBrowserContext());
|
||||
browser_context->prefs()->SetFilePath(prefs::kSelectFileLastDirectory,
|
||||
paths[0].DirName());
|
||||
}
|
||||
#if !defined(OS_MAC)
|
||||
RunFileChooserEnd();
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
void FileSelectHelper::SetFileSelectListenerForTesting(
|
||||
scoped_refptr<content::FileSelectListener> listener) {
|
||||
DCHECK(listener);
|
||||
DCHECK(!listener_);
|
||||
listener_ = std::move(listener);
|
||||
}
|
||||
|
||||
std::unique_ptr<ui::SelectFileDialog::FileTypeInfo>
|
||||
FileSelectHelper::GetFileTypesFromAcceptType(
|
||||
const std::vector<std::u16string>& accept_types) {
|
||||
std::unique_ptr<ui::SelectFileDialog::FileTypeInfo> base_file_type(
|
||||
new ui::SelectFileDialog::FileTypeInfo());
|
||||
if (accept_types.empty())
|
||||
return base_file_type;
|
||||
|
||||
// Create FileTypeInfo and pre-allocate for the first extension list.
|
||||
std::unique_ptr<ui::SelectFileDialog::FileTypeInfo> file_type(
|
||||
new ui::SelectFileDialog::FileTypeInfo(*base_file_type));
|
||||
file_type->include_all_files = true;
|
||||
file_type->extensions.resize(1);
|
||||
std::vector<base::FilePath::StringType>* extensions =
|
||||
&file_type->extensions.back();
|
||||
|
||||
// Find the corresponding extensions.
|
||||
int valid_type_count = 0;
|
||||
int description_id = 0;
|
||||
for (const auto& accept_type : accept_types) {
|
||||
size_t old_extension_size = extensions->size();
|
||||
if (accept_type[0] == '.') {
|
||||
// If the type starts with a period it is assumed to be a file extension
|
||||
// so we just have to add it to the list.
|
||||
base::FilePath::StringType ext =
|
||||
base::FilePath::FromUTF16Unsafe(accept_type).value();
|
||||
extensions->push_back(ext.substr(1));
|
||||
} else {
|
||||
if (!base::IsStringASCII(accept_type))
|
||||
continue;
|
||||
std::string ascii_type = base::UTF16ToASCII(accept_type);
|
||||
if (ascii_type == "image/*")
|
||||
description_id = IDS_IMAGE_FILES;
|
||||
else if (ascii_type == "audio/*")
|
||||
description_id = IDS_AUDIO_FILES;
|
||||
else if (ascii_type == "video/*")
|
||||
description_id = IDS_VIDEO_FILES;
|
||||
|
||||
net::GetExtensionsForMimeType(ascii_type, extensions);
|
||||
}
|
||||
|
||||
if (extensions->size() > old_extension_size)
|
||||
valid_type_count++;
|
||||
}
|
||||
|
||||
// If no valid extension is added, bail out.
|
||||
if (valid_type_count == 0)
|
||||
return base_file_type;
|
||||
|
||||
// Use a generic description "Custom Files" if either of the following is
|
||||
// true:
|
||||
// 1) There're multiple types specified, like "audio/*,video/*"
|
||||
// 2) There're multiple extensions for a MIME type without parameter, like
|
||||
// "ehtml,shtml,htm,html" for "text/html". On Windows, the select file
|
||||
// dialog uses the first extension in the list to form the description,
|
||||
// like "EHTML Files". This is not what we want.
|
||||
if (valid_type_count > 1 ||
|
||||
(valid_type_count == 1 && description_id == 0 && extensions->size() > 1))
|
||||
description_id = IDS_CUSTOM_FILES;
|
||||
|
||||
if (description_id) {
|
||||
file_type->extension_description_overrides.push_back(
|
||||
l10n_util::GetStringUTF16(description_id));
|
||||
}
|
||||
|
||||
return file_type;
|
||||
}
|
||||
|
||||
void FileSelectHelper::ConvertToFileChooserFileInfoList(
|
||||
const std::vector<ui::SelectedFileInfo>& files) {
|
||||
std::vector<FileChooserFileInfoPtr> file_info;
|
||||
|
||||
for (const auto& file : files) {
|
||||
file_info.push_back(FileChooserFileInfo::NewNativeFile(NativeFileInfo::New(
|
||||
file.local_path, base::FilePath(file.display_name).AsUTF16Unsafe())));
|
||||
}
|
||||
|
||||
OnFilesSelected(std::move(file_info), lister_base_dir_);
|
||||
// static
|
||||
void FileSelectHelper::RunFileChooser(
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
scoped_refptr<content::FileSelectListener> listener,
|
||||
const FileChooserParams& params) {
|
||||
// FileSelectHelper will keep itself alive until it sends the result
|
||||
// message.
|
||||
scoped_refptr<FileSelectHelper> file_select_helper(new FileSelectHelper());
|
||||
file_select_helper->RunFileChooser(render_frame_host, std::move(listener),
|
||||
params.Clone());
|
||||
}
|
||||
|
||||
void FileSelectHelper::OnSaveDialogDone(gin_helper::Dictionary result) {
|
||||
std::vector<FileChooserFileInfoPtr> file_info;
|
||||
bool canceled = true;
|
||||
result.Get("canceled", &canceled);
|
||||
|
||||
if (!render_frame_host_ || canceled) {
|
||||
RunFileChooserEnd();
|
||||
} else {
|
||||
base::FilePath path;
|
||||
if (result.Get("filePath", &path)) {
|
||||
file_info.push_back(FileChooserFileInfo::NewNativeFile(
|
||||
NativeFileInfo::New(path, path.BaseName().AsUTF16Unsafe())));
|
||||
}
|
||||
// We should only call this if we have not cancelled the dialog.
|
||||
OnFilesSelected(std::move(file_info), base::FilePath());
|
||||
RunFileChooserEnd();
|
||||
}
|
||||
// static
|
||||
void FileSelectHelper::EnumerateDirectory(
|
||||
content::WebContents* tab,
|
||||
scoped_refptr<content::FileSelectListener> listener,
|
||||
const base::FilePath& path) {
|
||||
// FileSelectHelper will keep itself alive until it sends the result
|
||||
// message.
|
||||
scoped_refptr<FileSelectHelper> file_select_helper(new FileSelectHelper());
|
||||
file_select_helper->EnumerateDirectoryImpl(tab, std::move(listener), path);
|
||||
}
|
||||
|
||||
void FileSelectHelper::OnFilesSelected(
|
||||
std::vector<FileChooserFileInfoPtr> file_info,
|
||||
base::FilePath base_dir) {
|
||||
if (listener_) {
|
||||
listener_->FileSelected(std::move(file_info), base_dir, mode_);
|
||||
listener_.reset();
|
||||
void FileSelectHelper::RunFileChooser(
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
scoped_refptr<content::FileSelectListener> listener,
|
||||
FileChooserParamsPtr params) {
|
||||
DCHECK(!render_frame_host_);
|
||||
DCHECK(!web_contents_);
|
||||
DCHECK(listener);
|
||||
DCHECK(!listener_);
|
||||
DCHECK(params->default_file_name.empty() ||
|
||||
params->mode == FileChooserParams::Mode::kSave)
|
||||
<< "The default_file_name parameter should only be specified for Save "
|
||||
"file choosers";
|
||||
DCHECK(params->default_file_name == params->default_file_name.BaseName())
|
||||
<< "The default_file_name parameter should not contain path separators";
|
||||
|
||||
render_frame_host_ = render_frame_host;
|
||||
web_contents_ = WebContents::FromRenderFrameHost(render_frame_host);
|
||||
listener_ = std::move(listener);
|
||||
observation_.Reset();
|
||||
content::WebContentsObserver::Observe(web_contents_);
|
||||
observation_.Observe(render_frame_host_->GetRenderViewHost()->GetWidget());
|
||||
|
||||
base::ThreadPool::PostTask(
|
||||
FROM_HERE, {base::MayBlock()},
|
||||
base::BindOnce(&FileSelectHelper::GetFileTypesInThreadPool, this,
|
||||
std::move(params)));
|
||||
|
||||
// Because this class returns notifications to the RenderViewHost, it is
|
||||
// difficult for callers to know how long to keep a reference to this
|
||||
// instance. We AddRef() here to keep the instance alive after we return
|
||||
// to the caller, until the last callback is received from the file dialog.
|
||||
// At that point, we must call RunFileChooserEnd().
|
||||
AddRef();
|
||||
}
|
||||
|
||||
void FileSelectHelper::GetFileTypesInThreadPool(FileChooserParamsPtr params) {
|
||||
select_file_types_ = GetFileTypesFromAcceptType(params->accept_types);
|
||||
select_file_types_->allowed_paths =
|
||||
params->need_local_path ? ui::SelectFileDialog::FileTypeInfo::NATIVE_PATH
|
||||
: ui::SelectFileDialog::FileTypeInfo::ANY_PATH;
|
||||
|
||||
content::GetUIThreadTaskRunner({})->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(&FileSelectHelper::GetSanitizedFilenameOnUIThread, this,
|
||||
std::move(params)));
|
||||
}
|
||||
|
||||
void FileSelectHelper::GetSanitizedFilenameOnUIThread(
|
||||
FileChooserParamsPtr params) {
|
||||
if (AbortIfWebContentsDestroyed())
|
||||
return;
|
||||
|
||||
auto* browser_context = static_cast<electron::ElectronBrowserContext*>(
|
||||
render_frame_host_->GetProcess()->GetBrowserContext());
|
||||
base::FilePath default_file_path =
|
||||
browser_context->prefs()
|
||||
->GetFilePath(prefs::kSelectFileLastDirectory)
|
||||
.Append(params->default_file_name);
|
||||
|
||||
RunFileChooserOnUIThread(default_file_path, std::move(params));
|
||||
}
|
||||
|
||||
void FileSelectHelper::RunFileChooserOnUIThread(
|
||||
const base::FilePath& default_file_path,
|
||||
FileChooserParamsPtr params) {
|
||||
DCHECK(params);
|
||||
|
||||
select_file_dialog_ = ui::SelectFileDialog::Create(this, nullptr);
|
||||
if (!select_file_dialog_.get())
|
||||
return;
|
||||
|
||||
dialog_mode_ = params->mode;
|
||||
switch (params->mode) {
|
||||
case FileChooserParams::Mode::kOpen:
|
||||
dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE;
|
||||
break;
|
||||
case FileChooserParams::Mode::kOpenMultiple:
|
||||
dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE;
|
||||
break;
|
||||
case FileChooserParams::Mode::kUploadFolder:
|
||||
dialog_type_ = ui::SelectFileDialog::SELECT_UPLOAD_FOLDER;
|
||||
break;
|
||||
case FileChooserParams::Mode::kSave:
|
||||
dialog_type_ = ui::SelectFileDialog::SELECT_SAVEAS_FILE;
|
||||
break;
|
||||
default:
|
||||
// Prevent warning.
|
||||
dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE;
|
||||
NOTREACHED();
|
||||
}
|
||||
|
||||
auto* web_contents = electron::api::WebContents::From(
|
||||
content::WebContents::FromRenderFrameHost(render_frame_host_));
|
||||
if (!web_contents || !web_contents->owner_window())
|
||||
return;
|
||||
|
||||
// Never consider the current scope as hung. The hang watching deadline (if
|
||||
// any) is not valid since the user can take unbounded time to choose the
|
||||
// file.
|
||||
base::HangWatcher::InvalidateActiveExpectations();
|
||||
|
||||
select_file_dialog_->SelectFile(
|
||||
dialog_type_, params->title, default_file_path, select_file_types_.get(),
|
||||
select_file_types_.get() && !select_file_types_->extensions.empty()
|
||||
? 1
|
||||
: 0, // 1-based index of default extension to show.
|
||||
base::FilePath::StringType(),
|
||||
web_contents->owner_window()->GetNativeWindow(), NULL);
|
||||
|
||||
select_file_types_.reset();
|
||||
}
|
||||
|
||||
// This method is called when we receive the last callback from the file chooser
|
||||
// dialog or if the renderer was destroyed. Perform any cleanup and release the
|
||||
// reference we added in RunFileChooser().
|
||||
void FileSelectHelper::RunFileChooserEnd() {
|
||||
// If there are temporary files, then this instance needs to stick around
|
||||
// until web_contents_ is destroyed, so that this instance can delete the
|
||||
// temporary files.
|
||||
if (!temporary_files_.empty())
|
||||
return;
|
||||
|
||||
if (listener_)
|
||||
listener_->FileSelectionCanceled();
|
||||
render_frame_host_ = nullptr;
|
||||
web_contents_ = nullptr;
|
||||
Release();
|
||||
}
|
||||
|
||||
void FileSelectHelper::EnumerateDirectoryImpl(
|
||||
content::WebContents* tab,
|
||||
scoped_refptr<content::FileSelectListener> listener,
|
||||
const base::FilePath& path) {
|
||||
DCHECK(listener);
|
||||
DCHECK(!listener_);
|
||||
dialog_type_ = ui::SelectFileDialog::SELECT_NONE;
|
||||
web_contents_ = tab;
|
||||
listener_ = std::move(listener);
|
||||
// Because this class returns notifications to the RenderViewHost, it is
|
||||
// difficult for callers to know how long to keep a reference to this
|
||||
// instance. We AddRef() here to keep the instance alive after we return
|
||||
// to the caller, until the last callback is received from the enumeration
|
||||
// code. At that point, we must call EnumerateDirectoryEnd().
|
||||
AddRef();
|
||||
StartNewEnumeration(path);
|
||||
}
|
||||
|
||||
// This method is called when we receive the last callback from the enumeration
|
||||
// code. Perform any cleanup and release the reference we added in
|
||||
// EnumerateDirectoryImpl().
|
||||
void FileSelectHelper::EnumerateDirectoryEnd() {
|
||||
Release();
|
||||
}
|
||||
|
||||
void FileSelectHelper::RenderWidgetHostDestroyed(
|
||||
|
@ -237,34 +526,52 @@ void FileSelectHelper::RenderWidgetHostDestroyed(
|
|||
observation_.Reset();
|
||||
}
|
||||
|
||||
// content::WebContentsObserver:
|
||||
void FileSelectHelper::RenderFrameHostChanged(
|
||||
content::RenderFrameHost* old_host,
|
||||
content::RenderFrameHost* new_host) {
|
||||
if (!render_frame_host_)
|
||||
return;
|
||||
// The |old_host| and its children are now pending deletion. Do not give
|
||||
// them file access past this point.
|
||||
// The |old_host| and its children are now pending deletion. Do not give them
|
||||
// file access past this point.
|
||||
if (render_frame_host_ == old_host ||
|
||||
render_frame_host_->IsDescendantOf(old_host)) {
|
||||
render_frame_host_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// content::WebContentsObserver:
|
||||
void FileSelectHelper::RenderFrameDeleted(
|
||||
content::RenderFrameHost* deleted_host) {
|
||||
if (deleted_host == render_frame_host_)
|
||||
content::RenderFrameHost* render_frame_host) {
|
||||
if (render_frame_host == render_frame_host_)
|
||||
render_frame_host_ = nullptr;
|
||||
}
|
||||
|
||||
// content::WebContentsObserver:
|
||||
void FileSelectHelper::WebContentsDestroyed() {
|
||||
render_frame_host_ = nullptr;
|
||||
web_contents_ = nullptr;
|
||||
|
||||
DeleteTemporaryFiles();
|
||||
|
||||
if (!lister_)
|
||||
delete this;
|
||||
CleanUp();
|
||||
}
|
||||
|
||||
// static
|
||||
bool FileSelectHelper::IsAcceptTypeValid(const std::string& accept_type) {
|
||||
// TODO(raymes): This only does some basic checks, extend to test more cases.
|
||||
// A 1 character accept type will always be invalid (either a "." in the case
|
||||
// of an extension or a "/" in the case of a MIME type).
|
||||
std::string unused;
|
||||
if (accept_type.length() <= 1 ||
|
||||
base::ToLowerASCII(accept_type) != accept_type ||
|
||||
base::TrimWhitespaceASCII(accept_type, base::TRIM_ALL, &unused) !=
|
||||
base::TRIM_NONE) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// static
|
||||
base::FilePath FileSelectHelper::GetSanitizedFileName(
|
||||
const base::FilePath& suggested_filename) {
|
||||
if (suggested_filename.empty())
|
||||
return base::FilePath();
|
||||
return net::GenerateFileName(
|
||||
GURL(), std::string(), std::string(), suggested_filename.AsUTF8Unsafe(),
|
||||
std::string(), l10n_util::GetStringUTF8(IDS_DEFAULT_DOWNLOAD_FILENAME));
|
||||
}
|
||||
|
|
|
@ -1,69 +1,130 @@
|
|||
// Copyright (c) 2020 Microsoft, Inc. All rights reserved.
|
||||
// Copyright (c) 2021 Microsoft. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef SHELL_BROWSER_FILE_SELECT_HELPER_H_
|
||||
#define SHELL_BROWSER_FILE_SELECT_HELPER_H_
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/compiler_specific.h"
|
||||
#include "base/macros.h"
|
||||
#include "base/scoped_observation.h"
|
||||
#include "chrome/common/pref_names.h"
|
||||
#include "content/public/browser/file_select_listener.h"
|
||||
#include "content/public/browser/render_frame_host.h"
|
||||
#include "content/public/browser/render_process_host.h"
|
||||
#include "content/public/browser/render_view_host.h"
|
||||
#include "build/build_config.h"
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "content/public/browser/render_widget_host.h"
|
||||
#include "content/public/browser/render_widget_host_observer.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
#include "content/public/browser/web_contents_observer.h"
|
||||
#include "gin/dictionary.h"
|
||||
#include "net/base/directory_lister.h"
|
||||
#include "shell/browser/electron_browser_context.h"
|
||||
#include "shell/browser/ui/file_dialog.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "ui/shell_dialogs/selected_file_info.h"
|
||||
#include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h"
|
||||
#include "ui/shell_dialogs/select_file_dialog.h"
|
||||
|
||||
using blink::mojom::FileChooserParams;
|
||||
namespace content {
|
||||
class FileSelectListener;
|
||||
class WebContents;
|
||||
} // namespace content
|
||||
|
||||
class FileSelectHelper : public content::WebContentsObserver,
|
||||
namespace ui {
|
||||
struct SelectedFileInfo;
|
||||
}
|
||||
|
||||
// This class handles file-selection requests coming from renderer processes.
|
||||
// It implements both the initialisation and listener functions for
|
||||
// file-selection dialogs.
|
||||
//
|
||||
// Since FileSelectHelper listens to observations of a widget, it needs to live
|
||||
// on and be destroyed on the UI thread. References to FileSelectHelper may be
|
||||
// passed on to other threads.
|
||||
class FileSelectHelper : public base::RefCountedThreadSafe<
|
||||
FileSelectHelper,
|
||||
content::BrowserThread::DeleteOnUIThread>,
|
||||
public ui::SelectFileDialog::Listener,
|
||||
public content::WebContentsObserver,
|
||||
public content::RenderWidgetHostObserver,
|
||||
public net::DirectoryLister::DirectoryListerDelegate {
|
||||
private net::DirectoryLister::DirectoryListerDelegate {
|
||||
public:
|
||||
FileSelectHelper(content::RenderFrameHost* render_frame_host,
|
||||
scoped_refptr<content::FileSelectListener> listener,
|
||||
FileChooserParams::Mode mode);
|
||||
~FileSelectHelper() override;
|
||||
// Show the file chooser dialog.
|
||||
static void RunFileChooser(
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
scoped_refptr<content::FileSelectListener> listener,
|
||||
const blink::mojom::FileChooserParams& params);
|
||||
|
||||
// WebDialogHelper::RunFileChooser
|
||||
|
||||
void ShowOpenDialog(const file_dialog::DialogSettings& settings);
|
||||
|
||||
void ShowSaveDialog(const file_dialog::DialogSettings& settings);
|
||||
// Enumerates all the files in directory.
|
||||
static void EnumerateDirectory(
|
||||
content::WebContents* tab,
|
||||
scoped_refptr<content::FileSelectListener> listener,
|
||||
const base::FilePath& path);
|
||||
|
||||
private:
|
||||
friend class base::RefCountedThreadSafe<FileSelectHelper>;
|
||||
friend class base::DeleteHelper<FileSelectHelper>;
|
||||
friend struct content::BrowserThread::DeleteOnThread<
|
||||
content::BrowserThread::UI>;
|
||||
|
||||
FileSelectHelper();
|
||||
~FileSelectHelper() override;
|
||||
|
||||
void RunFileChooser(content::RenderFrameHost* render_frame_host,
|
||||
scoped_refptr<content::FileSelectListener> listener,
|
||||
blink::mojom::FileChooserParamsPtr params);
|
||||
void GetFileTypesInThreadPool(blink::mojom::FileChooserParamsPtr params);
|
||||
void GetSanitizedFilenameOnUIThread(
|
||||
blink::mojom::FileChooserParamsPtr params);
|
||||
|
||||
void RunFileChooserOnUIThread(const base::FilePath& default_path,
|
||||
blink::mojom::FileChooserParamsPtr params);
|
||||
|
||||
// Cleans up and releases this instance. This must be called after the last
|
||||
// callback is received from the file chooser dialog.
|
||||
void RunFileChooserEnd();
|
||||
|
||||
// SelectFileDialog::Listener overrides.
|
||||
void FileSelected(const base::FilePath& path,
|
||||
int index,
|
||||
void* params) override;
|
||||
void FileSelectedWithExtraInfo(const ui::SelectedFileInfo& file,
|
||||
int index,
|
||||
void* params) override;
|
||||
void MultiFilesSelected(const std::vector<base::FilePath>& files,
|
||||
void* params) override;
|
||||
void MultiFilesSelectedWithExtraInfo(
|
||||
const std::vector<ui::SelectedFileInfo>& files,
|
||||
void* params) override;
|
||||
void FileSelectionCanceled(void* params) override;
|
||||
|
||||
// content::RenderWidgetHostObserver overrides.
|
||||
void RenderWidgetHostDestroyed(
|
||||
content::RenderWidgetHost* widget_host) override;
|
||||
|
||||
// content::WebContentsObserver overrides.
|
||||
void RenderFrameHostChanged(content::RenderFrameHost* old_host,
|
||||
content::RenderFrameHost* new_host) override;
|
||||
void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;
|
||||
void WebContentsDestroyed() override;
|
||||
|
||||
void EnumerateDirectoryImpl(
|
||||
content::WebContents* tab,
|
||||
scoped_refptr<content::FileSelectListener> listener,
|
||||
const base::FilePath& path);
|
||||
|
||||
// Kicks off a new directory enumeration.
|
||||
void StartNewEnumeration(const base::FilePath& path);
|
||||
|
||||
// net::DirectoryLister::DirectoryListerDelegate overrides.
|
||||
void OnListFile(
|
||||
const net::DirectoryLister::DirectoryListerData& data) override;
|
||||
void OnListDone(int error) override;
|
||||
|
||||
void DeleteTemporaryFiles();
|
||||
void LaunchConfirmationDialog(
|
||||
const base::FilePath& path,
|
||||
std::vector<ui::SelectedFileInfo> selected_files);
|
||||
|
||||
void EnumerateDirectory();
|
||||
|
||||
void OnOpenDialogDone(gin_helper::Dictionary result);
|
||||
void OnSaveDialogDone(gin_helper::Dictionary result);
|
||||
|
||||
void OnFilesSelected(
|
||||
std::vector<blink::mojom::FileChooserFileInfoPtr> file_info,
|
||||
base::FilePath base_dir);
|
||||
|
||||
void RunFileChooserEnd();
|
||||
|
||||
void ConvertToFileChooserFileInfoList(
|
||||
const std::vector<ui::SelectedFileInfo>& files);
|
||||
// Cleans up and releases this instance. This must be called after the last
|
||||
// callback is received from the enumeration code.
|
||||
void EnumerateDirectoryEnd();
|
||||
|
||||
#if defined(OS_MAC)
|
||||
// Must be called from a MayBlock() task. Each selected file that is a package
|
||||
|
@ -71,11 +132,11 @@ class FileSelectHelper : public content::WebContentsObserver,
|
|||
// of the package.
|
||||
void ProcessSelectedFilesMac(const std::vector<ui::SelectedFileInfo>& files);
|
||||
|
||||
// Saves the paths of |temporary_files| for later deletion. Passes |files| to
|
||||
// the render view host.
|
||||
// Saves the paths of |zipped_files| for later deletion. Passes |files| to the
|
||||
// render view host.
|
||||
void ProcessSelectedFilesMacOnUIThread(
|
||||
const std::vector<ui::SelectedFileInfo>& files,
|
||||
const std::vector<base::FilePath>& temporary_files);
|
||||
const std::vector<base::FilePath>& zipped_files);
|
||||
|
||||
// Zips the package at |path| into a temporary destination. Returns the
|
||||
// temporary destination, if the zip was successful. Otherwise returns an
|
||||
|
@ -83,20 +144,78 @@ class FileSelectHelper : public content::WebContentsObserver,
|
|||
static base::FilePath ZipPackage(const base::FilePath& path);
|
||||
#endif // defined(OS_MAC)
|
||||
|
||||
// content::RenderWidgetHostObserver:
|
||||
void RenderWidgetHostDestroyed(
|
||||
content::RenderWidgetHost* widget_host) override;
|
||||
void ConvertToFileChooserFileInfoList(
|
||||
const std::vector<ui::SelectedFileInfo>& files);
|
||||
|
||||
// content::WebContentsObserver:
|
||||
void RenderFrameHostChanged(content::RenderFrameHost* old_host,
|
||||
content::RenderFrameHost* new_host) override;
|
||||
void RenderFrameDeleted(content::RenderFrameHost* deleted_host) override;
|
||||
void WebContentsDestroyed() override;
|
||||
// Checks to see if scans are required for the specified files.
|
||||
void PerformContentAnalysisIfNeeded(
|
||||
std::vector<blink::mojom::FileChooserFileInfoPtr> list);
|
||||
|
||||
// Finish the PerformContentAnalysisIfNeeded() handling after the
|
||||
// deep scanning checks have been performed. Deep scanning may change the
|
||||
// list of files chosen by the user, so the list of files passed here may be
|
||||
// a subset of of the files passed to PerformContentAnalysisIfNeeded().
|
||||
void NotifyListenerAndEnd(
|
||||
std::vector<blink::mojom::FileChooserFileInfoPtr> list);
|
||||
|
||||
// Schedules the deletion of the files in |temporary_files_| and clears the
|
||||
// vector.
|
||||
void DeleteTemporaryFiles();
|
||||
|
||||
// Cleans up when the initiator of the file chooser is no longer valid.
|
||||
void CleanUp();
|
||||
|
||||
// Calls RunFileChooserEnd() if the webcontents was destroyed. Returns true
|
||||
// if the file chooser operation shouldn't proceed.
|
||||
bool AbortIfWebContentsDestroyed();
|
||||
|
||||
void SetFileSelectListenerForTesting(
|
||||
scoped_refptr<content::FileSelectListener> listener);
|
||||
|
||||
// Helper method to get allowed extensions for select file dialog from
|
||||
// the specified accept types as defined in the spec:
|
||||
// http://whatwg.org/html/number-state.html#attr-input-accept
|
||||
// |accept_types| contains only valid lowercased MIME types or file extensions
|
||||
// beginning with a period (.).
|
||||
static std::unique_ptr<ui::SelectFileDialog::FileTypeInfo>
|
||||
GetFileTypesFromAcceptType(const std::vector<std::u16string>& accept_types);
|
||||
|
||||
// Check the accept type is valid. It is expected to be all lower case with
|
||||
// no whitespace.
|
||||
static bool IsAcceptTypeValid(const std::string& accept_type);
|
||||
|
||||
// Get a sanitized filename suitable for use as a default filename.
|
||||
static base::FilePath GetSanitizedFileName(
|
||||
const base::FilePath& suggested_path);
|
||||
|
||||
// The RenderFrameHost and WebContents for the page showing a file dialog
|
||||
// (may only be one such dialog).
|
||||
content::RenderFrameHost* render_frame_host_;
|
||||
content::WebContents* web_contents_;
|
||||
|
||||
// |listener_| receives the result of the FileSelectHelper.
|
||||
scoped_refptr<content::FileSelectListener> listener_;
|
||||
FileChooserParams::Mode mode_;
|
||||
|
||||
// Dialog box used for choosing files to upload from file form fields.
|
||||
scoped_refptr<ui::SelectFileDialog> select_file_dialog_;
|
||||
std::unique_ptr<ui::SelectFileDialog::FileTypeInfo> select_file_types_;
|
||||
|
||||
// The type of file dialog last shown. This is SELECT_NONE if an
|
||||
// instance is created through the public EnumerateDirectory().
|
||||
ui::SelectFileDialog::Type dialog_type_;
|
||||
|
||||
// The mode of file dialog last shown.
|
||||
blink::mojom::FileChooserParams::Mode dialog_mode_;
|
||||
|
||||
// The enumeration root directory for EnumerateDirectory() and
|
||||
// RunFileChooser with kUploadFolder.
|
||||
base::FilePath base_dir_;
|
||||
|
||||
// Maintain an active directory enumeration. These could come from the file
|
||||
// select dialog or from drag-and-drop of directories. There could not be
|
||||
// more than one going on at a time.
|
||||
struct ActiveDirectoryEnumeration;
|
||||
std::unique_ptr<ActiveDirectoryEnumeration> directory_enumeration_;
|
||||
|
||||
base::ScopedObservation<content::RenderWidgetHost,
|
||||
content::RenderWidgetHostObserver>
|
||||
|
@ -106,12 +225,7 @@ class FileSelectHelper : public content::WebContentsObserver,
|
|||
// these files when they are no longer needed.
|
||||
std::vector<base::FilePath> temporary_files_;
|
||||
|
||||
// DirectoryLister-specific members
|
||||
std::unique_ptr<net::DirectoryLister> lister_;
|
||||
base::FilePath lister_base_dir_;
|
||||
std::vector<base::FilePath> lister_paths_;
|
||||
|
||||
base::WeakPtrFactory<FileSelectHelper> weak_ptr_factory_{this};
|
||||
DISALLOW_COPY_AND_ASSIGN(FileSelectHelper);
|
||||
};
|
||||
|
||||
#endif // SHELL_BROWSER_FILE_SELECT_HELPER_H_
|
||||
|
|
|
@ -1,171 +0,0 @@
|
|||
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/browser/web_dialog_helper.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/files/file_enumerator.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "chrome/common/pref_names.h"
|
||||
#include "components/prefs/pref_service.h"
|
||||
#include "content/public/browser/file_select_listener.h"
|
||||
#include "content/public/browser/render_frame_host.h"
|
||||
#include "content/public/browser/render_process_host.h"
|
||||
#include "content/public/browser/render_view_host.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
#include "net/base/mime_util.h"
|
||||
#include "shell/browser/electron_browser_context.h"
|
||||
#include "shell/browser/file_select_helper.h"
|
||||
#include "shell/browser/native_window.h"
|
||||
#include "shell/browser/ui/file_dialog.h"
|
||||
|
||||
using blink::mojom::FileChooserFileInfo;
|
||||
using blink::mojom::FileChooserFileInfoPtr;
|
||||
using blink::mojom::FileChooserParams;
|
||||
using blink::mojom::NativeFileInfo;
|
||||
|
||||
namespace {
|
||||
|
||||
file_dialog::Filters GetFileTypesFromAcceptType(
|
||||
const std::vector<std::u16string>& accept_types) {
|
||||
file_dialog::Filters filters;
|
||||
if (accept_types.empty())
|
||||
return filters;
|
||||
|
||||
std::vector<base::FilePath::StringType> extensions;
|
||||
|
||||
int valid_type_count = 0;
|
||||
std::string description;
|
||||
|
||||
for (const auto& accept_type : accept_types) {
|
||||
std::string ascii_type = base::UTF16ToASCII(accept_type);
|
||||
auto old_extension_size = extensions.size();
|
||||
|
||||
if (ascii_type[0] == '.') {
|
||||
// If the type starts with a period it is assumed to be a file extension,
|
||||
// like `.txt`, // so we just have to add it to the list.
|
||||
base::FilePath::StringType extension(ascii_type.begin(),
|
||||
ascii_type.end());
|
||||
// Skip the first character.
|
||||
extensions.push_back(extension.substr(1));
|
||||
} else {
|
||||
if (ascii_type == "image/*")
|
||||
description = "Image Files";
|
||||
else if (ascii_type == "audio/*")
|
||||
description = "Audio Files";
|
||||
else if (ascii_type == "video/*")
|
||||
description = "Video Files";
|
||||
|
||||
// For MIME Type, `audio/*, video/*, image/*
|
||||
net::GetExtensionsForMimeType(ascii_type, &extensions);
|
||||
}
|
||||
|
||||
if (extensions.size() > old_extension_size)
|
||||
valid_type_count++;
|
||||
}
|
||||
|
||||
// If no valid extension is added, return empty filters.
|
||||
if (extensions.empty())
|
||||
return filters;
|
||||
|
||||
filters.push_back(file_dialog::Filter());
|
||||
|
||||
if (valid_type_count > 1 || (valid_type_count == 1 && description.empty()))
|
||||
description = "Custom Files";
|
||||
|
||||
DCHECK(!description.empty());
|
||||
filters[0].first = description;
|
||||
|
||||
for (const auto& extension : extensions) {
|
||||
#if defined(OS_WIN)
|
||||
filters[0].second.push_back(base::WideToASCII(extension));
|
||||
#else
|
||||
filters[0].second.push_back(extension);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Allow all files when extension is specified.
|
||||
filters.push_back(file_dialog::Filter());
|
||||
filters.back().first = "All Files";
|
||||
filters.back().second.emplace_back("*");
|
||||
|
||||
return filters;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace electron {
|
||||
|
||||
WebDialogHelper::WebDialogHelper(NativeWindow* window, bool offscreen)
|
||||
: window_(window), offscreen_(offscreen) {}
|
||||
|
||||
WebDialogHelper::~WebDialogHelper() = default;
|
||||
|
||||
void WebDialogHelper::RunFileChooser(
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
scoped_refptr<content::FileSelectListener> listener,
|
||||
const FileChooserParams& params) {
|
||||
file_dialog::DialogSettings settings;
|
||||
settings.force_detached = offscreen_;
|
||||
settings.filters = GetFileTypesFromAcceptType(params.accept_types);
|
||||
settings.parent_window = window_;
|
||||
settings.title = base::UTF16ToUTF8(params.title);
|
||||
|
||||
auto* fsc =
|
||||
new FileSelectHelper(render_frame_host, std::move(listener), params.mode);
|
||||
|
||||
if (params.mode == FileChooserParams::Mode::kSave) {
|
||||
settings.default_path = params.default_file_name;
|
||||
fsc->ShowSaveDialog(settings);
|
||||
} else {
|
||||
int flags = file_dialog::OPEN_DIALOG_CREATE_DIRECTORY;
|
||||
switch (params.mode) {
|
||||
case FileChooserParams::Mode::kOpenMultiple:
|
||||
flags |= file_dialog::OPEN_DIALOG_MULTI_SELECTIONS;
|
||||
FALLTHROUGH;
|
||||
case FileChooserParams::Mode::kOpen:
|
||||
flags |= file_dialog::OPEN_DIALOG_OPEN_FILE;
|
||||
break;
|
||||
case FileChooserParams::Mode::kUploadFolder:
|
||||
flags |= file_dialog::OPEN_DIALOG_OPEN_DIRECTORY;
|
||||
break;
|
||||
default:
|
||||
NOTREACHED();
|
||||
}
|
||||
|
||||
auto* browser_context = static_cast<electron::ElectronBrowserContext*>(
|
||||
render_frame_host->GetProcess()->GetBrowserContext());
|
||||
settings.default_path = browser_context->prefs()
|
||||
->GetFilePath(prefs::kSelectFileLastDirectory)
|
||||
.Append(params.default_file_name);
|
||||
settings.properties = flags;
|
||||
fsc->ShowOpenDialog(settings);
|
||||
}
|
||||
}
|
||||
|
||||
void WebDialogHelper::EnumerateDirectory(
|
||||
content::WebContents* web_contents,
|
||||
scoped_refptr<content::FileSelectListener> listener,
|
||||
const base::FilePath& dir) {
|
||||
int types = base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES |
|
||||
base::FileEnumerator::INCLUDE_DOT_DOT;
|
||||
base::FileEnumerator file_enum(dir, false, types);
|
||||
|
||||
base::FilePath path;
|
||||
std::vector<FileChooserFileInfoPtr> file_info;
|
||||
while (!(path = file_enum.Next()).empty()) {
|
||||
file_info.push_back(FileChooserFileInfo::NewNativeFile(
|
||||
NativeFileInfo::New(path, std::u16string())));
|
||||
}
|
||||
|
||||
listener->FileSelected(std::move(file_info), dir,
|
||||
FileChooserParams::Mode::kUploadFolder);
|
||||
}
|
||||
|
||||
} // namespace electron
|
|
@ -1,48 +0,0 @@
|
|||
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef SHELL_BROWSER_WEB_DIALOG_HELPER_H_
|
||||
#define SHELL_BROWSER_WEB_DIALOG_HELPER_H_
|
||||
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h"
|
||||
|
||||
namespace base {
|
||||
class FilePath;
|
||||
}
|
||||
|
||||
namespace content {
|
||||
class FileSelectListener;
|
||||
class RenderFrameHost;
|
||||
class WebContents;
|
||||
} // namespace content
|
||||
|
||||
namespace electron {
|
||||
|
||||
class NativeWindow;
|
||||
|
||||
class WebDialogHelper {
|
||||
public:
|
||||
WebDialogHelper(NativeWindow* window, bool offscreen);
|
||||
~WebDialogHelper();
|
||||
|
||||
void RunFileChooser(content::RenderFrameHost* render_frame_host,
|
||||
scoped_refptr<content::FileSelectListener> listener,
|
||||
const blink::mojom::FileChooserParams& params);
|
||||
void EnumerateDirectory(content::WebContents* web_contents,
|
||||
scoped_refptr<content::FileSelectListener> listener,
|
||||
const base::FilePath& dir);
|
||||
|
||||
private:
|
||||
NativeWindow* window_;
|
||||
bool offscreen_;
|
||||
|
||||
base::WeakPtrFactory<WebDialogHelper> weak_factory_{this};
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(WebDialogHelper);
|
||||
};
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // SHELL_BROWSER_WEB_DIALOG_HELPER_H_
|
Loading…
Reference in a new issue