Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: transparently package bundles as zip archives #25030

Merged
merged 14 commits into from
Oct 28, 2020
1 change: 1 addition & 0 deletions BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,7 @@ source_set("electron_lib") {
"//third_party/libyuv",
"//third_party/webrtc_overrides:webrtc_component",
"//third_party/widevine/cdm:headers",
"//third_party/zlib/google:zip",
"//ui/base/idle",
"//ui/events:dom_keycode_converter",
"//ui/gl",
Expand Down
3 changes: 3 additions & 0 deletions filenames.gni
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,9 @@ filenames = {
"shell/browser/event_emitter_mixin.h",
"shell/browser/feature_list.cc",
"shell/browser/feature_list.h",
"shell/browser/file_select_helper.cc",
"shell/browser/file_select_helper.h",
"shell/browser/file_select_helper_mac.mm",
"shell/browser/font_defaults.cc",
"shell/browser/font_defaults.h",
"shell/browser/javascript_environment.cc",
Expand Down
270 changes: 270 additions & 0 deletions shell/browser/file_select_helper.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
// 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 <string>
#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 "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_);
observer_.Add(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));
codebytere marked this conversation as resolved.
Show resolved Hide resolved
}

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)));
nornagon marked this conversation as resolved.
Show resolved Hide resolved

file_dialog::ShowSaveDialog(settings, std::move(promise));
codebytere marked this conversation as resolved.
Show resolved Hide resolved
}

// 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;
deepak1556 marked this conversation as resolved.
Show resolved Hide resolved

if (listener_)
listener_->FileSelectionCanceled();

render_frame_host_ = nullptr;
web_contents_ = nullptr;
codebytere marked this conversation as resolved.
Show resolved Hide resolved

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, base::string16())));

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.size() >= 1) {
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;
observer_.Remove(widget_host);
}

// 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();
codebytere marked this conversation as resolved.
Show resolved Hide resolved

if (!lister_)
delete this;
}