270 lines
8.5 KiB
C++
270 lines
8.5 KiB
C++
// Copyright (c) 2020 Microsoft, Inc. 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 "base/bind.h"
|
|
#include "base/files/file_enumerator.h"
|
|
#include "base/files/file_util.h"
|
|
#include "base/strings/utf_string_conversions.h"
|
|
#include "base/task/thread_pool.h"
|
|
#include "chrome/common/pref_names.h"
|
|
#include "components/prefs/pref_service.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"
|
|
|
|
using blink::mojom::FileChooserFileInfo;
|
|
using blink::mojom::FileChooserFileInfoPtr;
|
|
using blink::mojom::FileChooserParams;
|
|
using blink::mojom::NativeFileInfo;
|
|
|
|
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_);
|
|
|
|
web_contents_ = content::WebContents::FromRenderFrameHost(render_frame_host);
|
|
DCHECK(web_contents_);
|
|
|
|
content::WebContentsObserver::Observe(web_contents_);
|
|
observation_.Observe(render_frame_host_->GetRenderViewHost()->GetWidget());
|
|
}
|
|
|
|
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::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.
|
|
RunFileChooserEnd();
|
|
return;
|
|
}
|
|
// We don't want to return directory paths, only file paths
|
|
if (data.info.IsDirectory())
|
|
return;
|
|
|
|
lister_paths_.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;
|
|
}
|
|
|
|
// net::DirectoryLister::DirectoryListerDelegate
|
|
void FileSelectHelper::OnListDone(int error) {
|
|
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.
|
|
RunFileChooserEnd();
|
|
return;
|
|
}
|
|
|
|
std::vector<FileChooserFileInfoPtr> file_info;
|
|
for (const auto& path : lister_paths_)
|
|
file_info.push_back(FileChooserFileInfo::NewNativeFile(
|
|
NativeFileInfo::New(path, std::u16string())));
|
|
|
|
OnFilesSelected(std::move(file_info), lister_base_dir_);
|
|
}
|
|
|
|
void FileSelectHelper::DeleteTemporaryFiles() {
|
|
base::ThreadPool::PostTask(
|
|
FROM_HERE,
|
|
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
|
|
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
|
|
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());
|
|
|
|
lister_ = std::make_unique<net::DirectoryLister>(
|
|
lister_base_dir_, net::DirectoryLister::NO_SORT_RECURSIVE, this);
|
|
lister_->Start();
|
|
}
|
|
|
|
void FileSelectHelper::OnOpenDialogDone(gin_helper::Dictionary result) {
|
|
bool canceled = true;
|
|
result.Get("canceled", &canceled);
|
|
|
|
if (!render_frame_host_ || canceled) {
|
|
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
|
|
}
|
|
|
|
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());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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_);
|
|
}
|
|
|
|
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());
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
render_frame_host_ = nullptr;
|
|
|
|
delete this;
|
|
}
|
|
|
|
void FileSelectHelper::RenderWidgetHostDestroyed(
|
|
content::RenderWidgetHost* widget_host) {
|
|
render_frame_host_ = nullptr;
|
|
DCHECK(observation_.IsObservingSource(widget_host));
|
|
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.
|
|
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_)
|
|
render_frame_host_ = nullptr;
|
|
}
|
|
|
|
// content::WebContentsObserver:
|
|
void FileSelectHelper::WebContentsDestroyed() {
|
|
render_frame_host_ = nullptr;
|
|
web_contents_ = nullptr;
|
|
|
|
DeleteTemporaryFiles();
|
|
|
|
if (!lister_)
|
|
delete this;
|
|
}
|