blob: 8e42baa97c6a97bea3687d63f76e4d04fd9af397 [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"
Lei Zhangf40ac9a612025-06-02 22:35:108#include "base/strings/string_util.h"
Julie Jeongeun Kimbc022bc2023-04-12 01:40:549#include "base/strings/utf_string_conversions.h"
Julie Jeongeun Kim8e989dd2023-03-30 09:27:0610#include "content/public/browser/render_frame_host.h"
11#include "content/public/browser/web_contents.h"
Julie Jeongeun Kim1567d9d2023-05-26 10:06:5912#include "net/base/filename_util.h"
Julie Jeongeun Kimbc022bc2023-04-12 01:40:5413#include "net/base/mime_util.h"
Julie Jeongeun Kim1f306fa42023-03-30 08:10:1714#include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h"
Julie Jeongeun Kimbc022bc2023-04-12 01:40:5415#include "ui/base/l10n/l10n_util.h"
Julie Jeongeun Kim8e989dd2023-03-30 09:27:0616#include "ui/shell_dialogs/select_file_policy.h"
17#include "ui/shell_dialogs/selected_file_info.h"
Julie Jeongeun Kim1f306fa42023-03-30 08:10:1718
19namespace content {
20
Julie Jeongeun Kimbc022bc2023-04-12 01:40:5421namespace {
22
23// Helper function to get allowed extensions for select file dialog from
24// the specified accept types as defined in the spec:
25// http://whatwg.org/html/number-state.html#attr-input-accept
26// |accept_types| contains only valid lowercased MIME types or file extensions
27// beginning with a period (.).
28std::unique_ptr<ui::SelectFileDialog::FileTypeInfo> GetFileTypesFromAcceptType(
29 const std::vector<std::u16string>& accept_types) {
30 auto base_file_type = std::make_unique<ui::SelectFileDialog::FileTypeInfo>();
31 if (accept_types.empty()) {
32 return base_file_type;
33 }
34
35 // Create FileTypeInfo and pre-allocate for the first extension list.
36 auto file_type =
37 std::make_unique<ui::SelectFileDialog::FileTypeInfo>(*base_file_type);
38 file_type->extensions.resize(1);
39 std::vector<base::FilePath::StringType>* extensions =
40 &file_type->extensions.back();
41
42 // Find the corresponding extensions.
43 size_t valid_type_count = 0;
44 for (const auto& accept_type : accept_types) {
45 size_t old_extension_size = extensions->size();
46 if (accept_type[0] == '.') {
47 // If the type starts with a period it is assumed to be a file extension
48 // so we just have to add it to the list.
49 base::FilePath::StringType ext =
50 base::FilePath::FromUTF16Unsafe(accept_type).value();
51 extensions->push_back(ext.substr(1));
52 } else {
53 if (!base::IsStringASCII(accept_type)) {
54 continue;
55 }
56 std::string ascii_type = base::UTF16ToASCII(accept_type);
57 net::GetExtensionsForMimeType(ascii_type, extensions);
58 }
59
60 if (extensions->size() > old_extension_size) {
61 valid_type_count++;
62 }
63 }
64
65 // If no valid extension is added, bail out.
66 if (valid_type_count == 0) {
67 return base_file_type;
68 }
69
70 return file_type;
71}
72
73} // namespace
74
Julie Jeongeun Kim1567d9d2023-05-26 10:06:5975struct ShellFileSelectHelper::ActiveDirectoryEnumeration {
76 explicit ActiveDirectoryEnumeration(const base::FilePath& path)
77 : path_(path) {}
78
79 std::unique_ptr<net::DirectoryLister> lister_;
80 const base::FilePath path_;
81 std::vector<base::FilePath> results_;
82};
83
Julie Jeongeun Kim1f306fa42023-03-30 08:10:1784// static
85void ShellFileSelectHelper::RunFileChooser(
86 content::RenderFrameHost* render_frame_host,
87 scoped_refptr<FileSelectListener> listener,
88 const blink::mojom::FileChooserParams& params) {
89 // ShellFileSelectHelper will keep itself alive until it sends the result
90 // message.
91 scoped_refptr<ShellFileSelectHelper> file_select_helper(
92 new ShellFileSelectHelper());
93 file_select_helper->RunFileChooser(render_frame_host, std::move(listener),
94 params.Clone());
95}
96
97ShellFileSelectHelper::ShellFileSelectHelper() = default;
98
Julie Jeongeun Kim8e989dd2023-03-30 09:27:0699ShellFileSelectHelper::~ShellFileSelectHelper() {
100 // There may be pending file dialogs, we need to tell them that we've gone
101 // away so they don't try and call back to us.
102 if (select_file_dialog_) {
103 select_file_dialog_->ListenerDestroyed();
104 }
105}
Julie Jeongeun Kim1f306fa42023-03-30 08:10:17106
107void ShellFileSelectHelper::RunFileChooser(
108 content::RenderFrameHost* render_frame_host,
109 scoped_refptr<FileSelectListener> listener,
110 blink::mojom::FileChooserParamsPtr params) {
Julie Jeongeun Kim8e989dd2023-03-30 09:27:06111 DCHECK(!web_contents_);
112 DCHECK(listener);
113 DCHECK(!listener_);
114 DCHECK(!select_file_dialog_);
115
Gyuyoung Kim15acc122025-06-25 21:27:49116 select_file_dialog_ = ui::SelectFileDialog::Create(this, nullptr);
117 if (!select_file_dialog_) {
118 listener->FileSelectionCanceled();
119 return;
120 }
121
Julie Jeongeun Kim8e989dd2023-03-30 09:27:06122 listener_ = std::move(listener);
123 web_contents_ = content::WebContents::FromRenderFrameHost(render_frame_host)
124 ->GetWeakPtr();
125
Julie Jeongeun Kimbc022bc2023-04-12 01:40:54126 select_file_types_ = GetFileTypesFromAcceptType(params->accept_types);
127 select_file_types_->allowed_paths =
128 params->need_local_path ? ui::SelectFileDialog::FileTypeInfo::NATIVE_PATH
129 : ui::SelectFileDialog::FileTypeInfo::ANY_PATH;
130 // 1-based index of default extension to show.
131 int file_type_index =
132 select_file_types_ && !select_file_types_->extensions.empty() ? 1 : 0;
133
Julie Jeongeun Kim8e989dd2023-03-30 09:27:06134 dialog_mode_ = params->mode;
135 switch (params->mode) {
136 case blink::mojom::FileChooserParams::Mode::kOpen:
137 dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE;
138 break;
139 case blink::mojom::FileChooserParams::Mode::kOpenMultiple:
140 dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE;
141 break;
142 case blink::mojom::FileChooserParams::Mode::kUploadFolder:
143 dialog_type_ = ui::SelectFileDialog::SELECT_UPLOAD_FOLDER;
144 break;
145 case blink::mojom::FileChooserParams::Mode::kSave:
146 dialog_type_ = ui::SelectFileDialog::SELECT_SAVEAS_FILE;
147 break;
148 default:
149 // Prevent warning.
150 dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE;
Peter Boströmfc7ddc182024-10-31 19:37:21151 NOTREACHED();
Julie Jeongeun Kim8e989dd2023-03-30 09:27:06152 }
153
154 gfx::NativeWindow owning_window = web_contents_->GetTopLevelNativeWindow();
Julie Jeongeun Kimbc022bc2023-04-12 01:40:54155 select_file_dialog_->SelectFile(dialog_type_, std::u16string(),
156 base::FilePath(), select_file_types_.get(),
157 file_type_index, base::FilePath::StringType(),
158 owning_window, nullptr);
Julie Jeongeun Kim8e989dd2023-03-30 09:27:06159
160 // Because this class returns notifications to the RenderViewHost, it is
161 // difficult for callers to know how long to keep a reference to this
162 // instance. We AddRef() here to keep the instance alive after we return
163 // to the caller, until the last callback is received from the file dialog.
164 // At that point, we must call RunFileChooserEnd().
165 AddRef();
166}
167
168void ShellFileSelectHelper::RunFileChooserEnd() {
169 if (listener_) {
170 listener_->FileSelectionCanceled();
171 }
172
173 select_file_dialog_->ListenerDestroyed();
174 select_file_dialog_.reset();
175 Release();
176}
177
Avi Drissmand0b88a2b2023-12-28 22:49:38178void ShellFileSelectHelper::FileSelected(const ui::SelectedFileInfo& file,
Gyuyoung Kime69e6b102024-07-16 15:32:51179 int index) {
Julie Jeongeun Kim1567d9d2023-05-26 10:06:59180 if (dialog_type_ == ui::SelectFileDialog::SELECT_UPLOAD_FOLDER) {
181 StartNewEnumeration(file.local_path);
182 return;
183 }
Julie Jeongeun Kim8e989dd2023-03-30 09:27:06184 ConvertToFileChooserFileInfoList({file});
185}
186
187void ShellFileSelectHelper::MultiFilesSelected(
Gyuyoung Kime69e6b102024-07-16 15:32:51188 const std::vector<ui::SelectedFileInfo>& files) {
Julie Jeongeun Kim8e989dd2023-03-30 09:27:06189 ConvertToFileChooserFileInfoList(files);
190}
191
Gyuyoung Kime69e6b102024-07-16 15:32:51192void ShellFileSelectHelper::FileSelectionCanceled() {
Julie Jeongeun Kim8e989dd2023-03-30 09:27:06193 RunFileChooserEnd();
194}
195
Julie Jeongeun Kim1567d9d2023-05-26 10:06:59196void ShellFileSelectHelper::StartNewEnumeration(const base::FilePath& path) {
197 base_dir_ = path;
198 auto entry = std::make_unique<ActiveDirectoryEnumeration>(path);
199 entry->lister_ = base::WrapUnique(new net::DirectoryLister(
200 path, net::DirectoryLister::NO_SORT_RECURSIVE, this));
201 entry->lister_->Start();
202 directory_enumeration_ = std::move(entry);
203}
204
205void ShellFileSelectHelper::OnListFile(
206 const net::DirectoryLister::DirectoryListerData& data) {
207 // Directory upload only cares about files.
208 if (data.info.IsDirectory()) {
209 return;
210 }
211
212 directory_enumeration_->results_.push_back(data.path);
213}
214
215void ShellFileSelectHelper::OnListDone(int error) {
216 if (!web_contents_) {
217 // Web contents was destroyed under us (probably by closing the tab). We
218 // must notify |listener_| and release our reference to
219 // ourself. RunFileChooserEnd() performs this.
220 RunFileChooserEnd();
221 return;
222 }
223
224 // This entry needs to be cleaned up when this function is done.
225 std::unique_ptr<ActiveDirectoryEnumeration> entry =
226 std::move(directory_enumeration_);
227 if (error) {
Gyuyoung Kime69e6b102024-07-16 15:32:51228 FileSelectionCanceled();
Julie Jeongeun Kim1567d9d2023-05-26 10:06:59229 return;
230 }
231
232 std::vector<ui::SelectedFileInfo> selected_files =
233 ui::FilePathListToSelectedFileInfoList(entry->results_);
234
235 std::vector<blink::mojom::FileChooserFileInfoPtr> chooser_files;
236 for (const auto& file_path : entry->results_) {
237 chooser_files.push_back(blink::mojom::FileChooserFileInfo::NewNativeFile(
Dave Tapuskacd1a01a2024-11-14 20:46:18238 blink::mojom::NativeFileInfo::New(file_path, std::u16string(),
239 std::vector<std::u16string>())));
Julie Jeongeun Kim1567d9d2023-05-26 10:06:59240 }
241
242 listener_->FileSelected(std::move(chooser_files), base_dir_,
243 blink::mojom::FileChooserParams::Mode::kUploadFolder);
244 listener_.reset();
245 // No members should be accessed from here on.
246 RunFileChooserEnd();
247}
248
Julie Jeongeun Kim8e989dd2023-03-30 09:27:06249void ShellFileSelectHelper::ConvertToFileChooserFileInfoList(
250 const std::vector<ui::SelectedFileInfo>& files) {
251 if (!web_contents_) {
252 RunFileChooserEnd();
253 return;
254 }
255
256 std::vector<blink::mojom::FileChooserFileInfoPtr> chooser_files;
257 for (const auto& file : files) {
258 chooser_files.push_back(blink::mojom::FileChooserFileInfo::NewNativeFile(
259 blink::mojom::NativeFileInfo::New(
Dave Tapuskacd1a01a2024-11-14 20:46:18260 file.local_path, base::FilePath(file.display_name).AsUTF16Unsafe(),
261 std::vector<std::u16string>())));
Julie Jeongeun Kim8e989dd2023-03-30 09:27:06262 }
263
264 listener_->FileSelected(std::move(chooser_files), base::FilePath(),
265 dialog_mode_);
266 listener_ = nullptr;
267
268 // No members should be accessed from here on.
269 RunFileChooserEnd();
Julie Jeongeun Kim1f306fa42023-03-30 08:10:17270}
271
272} // namespace content