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 #26217

1 change: 1 addition & 0 deletions BUILD.gn
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
Expand Up @@ -365,6 +365,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
@@ -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));
}

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

if (!lister_)
delete this;
}