blob: 79bbbdcc7d7abb4a253dcdd1210eedaef4f384d3 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/shell/browser/shell_file_select_helper.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/utf_string_conversions.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "net/base/mime_util.h"
#include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/shell_dialogs/select_file_policy.h"
#include "ui/shell_dialogs/selected_file_info.h"
namespace content {
namespace {
// Helper function 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 (.).
std::unique_ptr<ui::SelectFileDialog::FileTypeInfo> GetFileTypesFromAcceptType(
const std::vector<std::u16string>& accept_types) {
auto base_file_type = std::make_unique<ui::SelectFileDialog::FileTypeInfo>();
if (accept_types.empty()) {
return base_file_type;
}
// Create FileTypeInfo and pre-allocate for the first extension list.
auto file_type =
std::make_unique<ui::SelectFileDialog::FileTypeInfo>(*base_file_type);
file_type->extensions.resize(1);
std::vector<base::FilePath::StringType>* extensions =
&file_type->extensions.back();
// Find the corresponding extensions.
size_t valid_type_count = 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);
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;
}
return file_type;
}
} // namespace
// static
void ShellFileSelectHelper::RunFileChooser(
content::RenderFrameHost* render_frame_host,
scoped_refptr<FileSelectListener> listener,
const blink::mojom::FileChooserParams& params) {
// ShellFileSelectHelper will keep itself alive until it sends the result
// message.
scoped_refptr<ShellFileSelectHelper> file_select_helper(
new ShellFileSelectHelper());
file_select_helper->RunFileChooser(render_frame_host, std::move(listener),
params.Clone());
}
ShellFileSelectHelper::ShellFileSelectHelper() = default;
ShellFileSelectHelper::~ShellFileSelectHelper() {
// 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_) {
select_file_dialog_->ListenerDestroyed();
}
}
void ShellFileSelectHelper::RunFileChooser(
content::RenderFrameHost* render_frame_host,
scoped_refptr<FileSelectListener> listener,
blink::mojom::FileChooserParamsPtr params) {
DCHECK(!web_contents_);
DCHECK(listener);
DCHECK(!listener_);
DCHECK(!select_file_dialog_);
listener_ = std::move(listener);
web_contents_ = content::WebContents::FromRenderFrameHost(render_frame_host)
->GetWeakPtr();
select_file_dialog_ = ui::SelectFileDialog::Create(this, nullptr);
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;
// 1-based index of default extension to show.
int file_type_index =
select_file_types_ && !select_file_types_->extensions.empty() ? 1 : 0;
dialog_mode_ = params->mode;
switch (params->mode) {
case blink::mojom::FileChooserParams::Mode::kOpen:
dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE;
break;
case blink::mojom::FileChooserParams::Mode::kOpenMultiple:
dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE;
break;
case blink::mojom::FileChooserParams::Mode::kUploadFolder:
dialog_type_ = ui::SelectFileDialog::SELECT_UPLOAD_FOLDER;
break;
case blink::mojom::FileChooserParams::Mode::kSave:
dialog_type_ = ui::SelectFileDialog::SELECT_SAVEAS_FILE;
break;
default:
// Prevent warning.
dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE;
NOTREACHED();
}
gfx::NativeWindow owning_window = web_contents_->GetTopLevelNativeWindow();
select_file_dialog_->SelectFile(dialog_type_, std::u16string(),
base::FilePath(), select_file_types_.get(),
file_type_index, base::FilePath::StringType(),
owning_window, nullptr);
// 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 ShellFileSelectHelper::RunFileChooserEnd() {
if (listener_) {
listener_->FileSelectionCanceled();
}
select_file_dialog_->ListenerDestroyed();
select_file_dialog_.reset();
Release();
}
void ShellFileSelectHelper::FileSelected(const base::FilePath& path,
int index,
void* params) {
FileSelectedWithExtraInfo(ui::SelectedFileInfo(path, path), index, params);
}
void ShellFileSelectHelper::FileSelectedWithExtraInfo(
const ui::SelectedFileInfo& file,
int index,
void* params) {
ConvertToFileChooserFileInfoList({file});
}
void ShellFileSelectHelper::MultiFilesSelected(
const std::vector<base::FilePath>& files,
void* params) {
std::vector<ui::SelectedFileInfo> selected_files =
ui::FilePathListToSelectedFileInfoList(files);
MultiFilesSelectedWithExtraInfo(selected_files, params);
}
void ShellFileSelectHelper::MultiFilesSelectedWithExtraInfo(
const std::vector<ui::SelectedFileInfo>& files,
void* params) {
ConvertToFileChooserFileInfoList(files);
}
void ShellFileSelectHelper::FileSelectionCanceled(void* params) {
RunFileChooserEnd();
}
void ShellFileSelectHelper::ConvertToFileChooserFileInfoList(
const std::vector<ui::SelectedFileInfo>& files) {
if (!web_contents_) {
RunFileChooserEnd();
return;
}
std::vector<blink::mojom::FileChooserFileInfoPtr> chooser_files;
for (const auto& file : files) {
chooser_files.push_back(blink::mojom::FileChooserFileInfo::NewNativeFile(
blink::mojom::NativeFileInfo::New(
file.local_path,
base::FilePath(file.display_name).AsUTF16Unsafe())));
}
listener_->FileSelected(std::move(chooser_files), base::FilePath(),
dialog_mode_);
listener_ = nullptr;
// No members should be accessed from here on.
RunFileChooserEnd();
}
} // namespace content