blob: 74acdd46bd5b864682c3746e1bbac24d5481478a [file] [log] [blame]
Julie Jeongeun Kim1f306fa42023-03-30 08:10:171// Copyright 2023 The Chromium Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/shell/browser/shell_file_select_helper.h"
6
7#include "base/memory/scoped_refptr.h"
Julie Jeongeun Kimbc022bc2023-04-12 01:40:548#include "base/strings/utf_string_conversions.h"
Julie Jeongeun Kim8e989dd2023-03-30 09:27:069#include "content/public/browser/render_frame_host.h"
10#include "content/public/browser/web_contents.h"
Julie Jeongeun Kim1567d9d2023-05-26 10:06:5911#include "net/base/filename_util.h"
Julie Jeongeun Kimbc022bc2023-04-12 01:40:5412#include "net/base/mime_util.h"
Julie Jeongeun Kim1f306fa42023-03-30 08:10:1713#include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h"
Julie Jeongeun Kimbc022bc2023-04-12 01:40:5414#include "ui/base/l10n/l10n_util.h"
Julie Jeongeun Kim8e989dd2023-03-30 09:27:0615#include "ui/shell_dialogs/select_file_policy.h"
16#include "ui/shell_dialogs/selected_file_info.h"
Julie Jeongeun Kim1f306fa42023-03-30 08:10:1717
18namespace content {
19
Julie Jeongeun Kimbc022bc2023-04-12 01:40:5420namespace {
21
22// Helper function to get allowed extensions for select file dialog from
23// the specified accept types as defined in the spec:
24// http://whatwg.org/html/number-state.html#attr-input-accept
25// |accept_types| contains only valid lowercased MIME types or file extensions
26// beginning with a period (.).
27std::unique_ptr<ui::SelectFileDialog::FileTypeInfo> GetFileTypesFromAcceptType(
28 const std::vector<std::u16string>& accept_types) {
29 auto base_file_type = std::make_unique<ui::SelectFileDialog::FileTypeInfo>();
30 if (accept_types.empty()) {
31 return base_file_type;
32 }
33
34 // Create FileTypeInfo and pre-allocate for the first extension list.
35 auto file_type =
36 std::make_unique<ui::SelectFileDialog::FileTypeInfo>(*base_file_type);
37 file_type->extensions.resize(1);
38 std::vector<base::FilePath::StringType>* extensions =
39 &file_type->extensions.back();
40
41 // Find the corresponding extensions.
42 size_t valid_type_count = 0;
43 for (const auto& accept_type : accept_types) {
44 size_t old_extension_size = extensions->size();
45 if (accept_type[0] == '.') {
46 // If the type starts with a period it is assumed to be a file extension
47 // so we just have to add it to the list.
48 base::FilePath::StringType ext =
49 base::FilePath::FromUTF16Unsafe(accept_type).value();
50 extensions->push_back(ext.substr(1));
51 } else {
52 if (!base::IsStringASCII(accept_type)) {
53 continue;
54 }
55 std::string ascii_type = base::UTF16ToASCII(accept_type);
56 net::GetExtensionsForMimeType(ascii_type, extensions);
57 }
58
59 if (extensions->size() > old_extension_size) {
60 valid_type_count++;
61 }
62 }
63
64 // If no valid extension is added, bail out.
65 if (valid_type_count == 0) {
66 return base_file_type;
67 }
68
69 return file_type;
70}
71
72} // namespace
73
Julie Jeongeun Kim1567d9d2023-05-26 10:06:5974struct ShellFileSelectHelper::ActiveDirectoryEnumeration {
75 explicit ActiveDirectoryEnumeration(const base::FilePath& path)
76 : path_(path) {}
77
78 std::unique_ptr<net::DirectoryLister> lister_;
79 const base::FilePath path_;
80 std::vector<base::FilePath> results_;
81};
82
Julie Jeongeun Kim1f306fa42023-03-30 08:10:1783// static
84void ShellFileSelectHelper::RunFileChooser(
85 content::RenderFrameHost* render_frame_host,
86 scoped_refptr<FileSelectListener> listener,
87 const blink::mojom::FileChooserParams& params) {
88 // ShellFileSelectHelper will keep itself alive until it sends the result
89 // message.
90 scoped_refptr<ShellFileSelectHelper> file_select_helper(
91 new ShellFileSelectHelper());
92 file_select_helper->RunFileChooser(render_frame_host, std::move(listener),
93 params.Clone());
94}
95
96ShellFileSelectHelper::ShellFileSelectHelper() = default;
97
Julie Jeongeun Kim8e989dd2023-03-30 09:27:0698ShellFileSelectHelper::~ShellFileSelectHelper() {
99 // There may be pending file dialogs, we need to tell them that we've gone
100 // away so they don't try and call back to us.
101 if (select_file_dialog_) {
102 select_file_dialog_->ListenerDestroyed();
103 }
104}
Julie Jeongeun Kim1f306fa42023-03-30 08:10:17105
106void ShellFileSelectHelper::RunFileChooser(
107 content::RenderFrameHost* render_frame_host,
108 scoped_refptr<FileSelectListener> listener,
109 blink::mojom::FileChooserParamsPtr params) {
Julie Jeongeun Kim8e989dd2023-03-30 09:27:06110 DCHECK(!web_contents_);
111 DCHECK(listener);
112 DCHECK(!listener_);
113 DCHECK(!select_file_dialog_);
114
115 listener_ = std::move(listener);
116 web_contents_ = content::WebContents::FromRenderFrameHost(render_frame_host)
117 ->GetWeakPtr();
118
119 select_file_dialog_ = ui::SelectFileDialog::Create(this, nullptr);
120
Julie Jeongeun Kimbc022bc2023-04-12 01:40:54121 select_file_types_ = GetFileTypesFromAcceptType(params->accept_types);
122 select_file_types_->allowed_paths =
123 params->need_local_path ? ui::SelectFileDialog::FileTypeInfo::NATIVE_PATH
124 : ui::SelectFileDialog::FileTypeInfo::ANY_PATH;
125 // 1-based index of default extension to show.
126 int file_type_index =
127 select_file_types_ && !select_file_types_->extensions.empty() ? 1 : 0;
128
Julie Jeongeun Kim8e989dd2023-03-30 09:27:06129 dialog_mode_ = params->mode;
130 switch (params->mode) {
131 case blink::mojom::FileChooserParams::Mode::kOpen:
132 dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE;
133 break;
134 case blink::mojom::FileChooserParams::Mode::kOpenMultiple:
135 dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE;
136 break;
137 case blink::mojom::FileChooserParams::Mode::kUploadFolder:
138 dialog_type_ = ui::SelectFileDialog::SELECT_UPLOAD_FOLDER;
139 break;
140 case blink::mojom::FileChooserParams::Mode::kSave:
141 dialog_type_ = ui::SelectFileDialog::SELECT_SAVEAS_FILE;
142 break;
143 default:
144 // Prevent warning.
145 dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE;
146 NOTREACHED();
147 }
148
149 gfx::NativeWindow owning_window = web_contents_->GetTopLevelNativeWindow();
Julie Jeongeun Kimbc022bc2023-04-12 01:40:54150 select_file_dialog_->SelectFile(dialog_type_, std::u16string(),
151 base::FilePath(), select_file_types_.get(),
152 file_type_index, base::FilePath::StringType(),
153 owning_window, nullptr);
Julie Jeongeun Kim8e989dd2023-03-30 09:27:06154
155 // Because this class returns notifications to the RenderViewHost, it is
156 // difficult for callers to know how long to keep a reference to this
157 // instance. We AddRef() here to keep the instance alive after we return
158 // to the caller, until the last callback is received from the file dialog.
159 // At that point, we must call RunFileChooserEnd().
160 AddRef();
161}
162
163void ShellFileSelectHelper::RunFileChooserEnd() {
164 if (listener_) {
165 listener_->FileSelectionCanceled();
166 }
167
168 select_file_dialog_->ListenerDestroyed();
169 select_file_dialog_.reset();
170 Release();
171}
172
173void ShellFileSelectHelper::FileSelected(const base::FilePath& path,
174 int index,
175 void* params) {
176 FileSelectedWithExtraInfo(ui::SelectedFileInfo(path, path), index, params);
177}
178
179void ShellFileSelectHelper::FileSelectedWithExtraInfo(
180 const ui::SelectedFileInfo& file,
181 int index,
182 void* params) {
Julie Jeongeun Kim1567d9d2023-05-26 10:06:59183 if (dialog_type_ == ui::SelectFileDialog::SELECT_UPLOAD_FOLDER) {
184 StartNewEnumeration(file.local_path);
185 return;
186 }
Julie Jeongeun Kim8e989dd2023-03-30 09:27:06187 ConvertToFileChooserFileInfoList({file});
188}
189
190void ShellFileSelectHelper::MultiFilesSelected(
191 const std::vector<base::FilePath>& files,
192 void* params) {
193 std::vector<ui::SelectedFileInfo> selected_files =
194 ui::FilePathListToSelectedFileInfoList(files);
195
196 MultiFilesSelectedWithExtraInfo(selected_files, params);
197}
198
199void ShellFileSelectHelper::MultiFilesSelectedWithExtraInfo(
200 const std::vector<ui::SelectedFileInfo>& files,
201 void* params) {
202 ConvertToFileChooserFileInfoList(files);
203}
204
205void ShellFileSelectHelper::FileSelectionCanceled(void* params) {
206 RunFileChooserEnd();
207}
208
Julie Jeongeun Kim1567d9d2023-05-26 10:06:59209void ShellFileSelectHelper::StartNewEnumeration(const base::FilePath& path) {
210 base_dir_ = path;
211 auto entry = std::make_unique<ActiveDirectoryEnumeration>(path);
212 entry->lister_ = base::WrapUnique(new net::DirectoryLister(
213 path, net::DirectoryLister::NO_SORT_RECURSIVE, this));
214 entry->lister_->Start();
215 directory_enumeration_ = std::move(entry);
216}
217
218void ShellFileSelectHelper::OnListFile(
219 const net::DirectoryLister::DirectoryListerData& data) {
220 // Directory upload only cares about files.
221 if (data.info.IsDirectory()) {
222 return;
223 }
224
225 directory_enumeration_->results_.push_back(data.path);
226}
227
228void ShellFileSelectHelper::OnListDone(int error) {
229 if (!web_contents_) {
230 // Web contents was destroyed under us (probably by closing the tab). We
231 // must notify |listener_| and release our reference to
232 // ourself. RunFileChooserEnd() performs this.
233 RunFileChooserEnd();
234 return;
235 }
236
237 // This entry needs to be cleaned up when this function is done.
238 std::unique_ptr<ActiveDirectoryEnumeration> entry =
239 std::move(directory_enumeration_);
240 if (error) {
241 FileSelectionCanceled(nullptr);
242 return;
243 }
244
245 std::vector<ui::SelectedFileInfo> selected_files =
246 ui::FilePathListToSelectedFileInfoList(entry->results_);
247
248 std::vector<blink::mojom::FileChooserFileInfoPtr> chooser_files;
249 for (const auto& file_path : entry->results_) {
250 chooser_files.push_back(blink::mojom::FileChooserFileInfo::NewNativeFile(
251 blink::mojom::NativeFileInfo::New(file_path, std::u16string())));
252 }
253
254 listener_->FileSelected(std::move(chooser_files), base_dir_,
255 blink::mojom::FileChooserParams::Mode::kUploadFolder);
256 listener_.reset();
257 // No members should be accessed from here on.
258 RunFileChooserEnd();
259}
260
Julie Jeongeun Kim8e989dd2023-03-30 09:27:06261void ShellFileSelectHelper::ConvertToFileChooserFileInfoList(
262 const std::vector<ui::SelectedFileInfo>& files) {
263 if (!web_contents_) {
264 RunFileChooserEnd();
265 return;
266 }
267
268 std::vector<blink::mojom::FileChooserFileInfoPtr> chooser_files;
269 for (const auto& file : files) {
270 chooser_files.push_back(blink::mojom::FileChooserFileInfo::NewNativeFile(
271 blink::mojom::NativeFileInfo::New(
272 file.local_path,
273 base::FilePath(file.display_name).AsUTF16Unsafe())));
274 }
275
276 listener_->FileSelected(std::move(chooser_files), base::FilePath(),
277 dialog_mode_);
278 listener_ = nullptr;
279
280 // No members should be accessed from here on.
281 RunFileChooserEnd();
Julie Jeongeun Kim1f306fa42023-03-30 08:10:17282}
283
284} // namespace content