blob: 3d09a3104ab7d3d51a5f0f82ab7362849fd7bf84 [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
116 listener_ = std::move(listener);
117 web_contents_ = content::WebContents::FromRenderFrameHost(render_frame_host)
118 ->GetWeakPtr();
119
120 select_file_dialog_ = ui::SelectFileDialog::Create(this, nullptr);
121
Julie Jeongeun Kimbc022bc2023-04-12 01:40:54122 select_file_types_ = GetFileTypesFromAcceptType(params->accept_types);
123 select_file_types_->allowed_paths =
124 params->need_local_path ? ui::SelectFileDialog::FileTypeInfo::NATIVE_PATH
125 : ui::SelectFileDialog::FileTypeInfo::ANY_PATH;
126 // 1-based index of default extension to show.
127 int file_type_index =
128 select_file_types_ && !select_file_types_->extensions.empty() ? 1 : 0;
129
Julie Jeongeun Kim8e989dd2023-03-30 09:27:06130 dialog_mode_ = params->mode;
131 switch (params->mode) {
132 case blink::mojom::FileChooserParams::Mode::kOpen:
133 dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE;
134 break;
135 case blink::mojom::FileChooserParams::Mode::kOpenMultiple:
136 dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE;
137 break;
138 case blink::mojom::FileChooserParams::Mode::kUploadFolder:
139 dialog_type_ = ui::SelectFileDialog::SELECT_UPLOAD_FOLDER;
140 break;
141 case blink::mojom::FileChooserParams::Mode::kSave:
142 dialog_type_ = ui::SelectFileDialog::SELECT_SAVEAS_FILE;
143 break;
144 default:
145 // Prevent warning.
146 dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE;
Peter Boströmfc7ddc182024-10-31 19:37:21147 NOTREACHED();
Julie Jeongeun Kim8e989dd2023-03-30 09:27:06148 }
149
150 gfx::NativeWindow owning_window = web_contents_->GetTopLevelNativeWindow();
Julie Jeongeun Kimbc022bc2023-04-12 01:40:54151 select_file_dialog_->SelectFile(dialog_type_, std::u16string(),
152 base::FilePath(), select_file_types_.get(),
153 file_type_index, base::FilePath::StringType(),
154 owning_window, nullptr);
Julie Jeongeun Kim8e989dd2023-03-30 09:27:06155
156 // Because this class returns notifications to the RenderViewHost, it is
157 // difficult for callers to know how long to keep a reference to this
158 // instance. We AddRef() here to keep the instance alive after we return
159 // to the caller, until the last callback is received from the file dialog.
160 // At that point, we must call RunFileChooserEnd().
161 AddRef();
162}
163
164void ShellFileSelectHelper::RunFileChooserEnd() {
165 if (listener_) {
166 listener_->FileSelectionCanceled();
167 }
168
169 select_file_dialog_->ListenerDestroyed();
170 select_file_dialog_.reset();
171 Release();
172}
173
Avi Drissmand0b88a2b2023-12-28 22:49:38174void ShellFileSelectHelper::FileSelected(const ui::SelectedFileInfo& file,
Gyuyoung Kime69e6b102024-07-16 15:32:51175 int index) {
Julie Jeongeun Kim1567d9d2023-05-26 10:06:59176 if (dialog_type_ == ui::SelectFileDialog::SELECT_UPLOAD_FOLDER) {
177 StartNewEnumeration(file.local_path);
178 return;
179 }
Julie Jeongeun Kim8e989dd2023-03-30 09:27:06180 ConvertToFileChooserFileInfoList({file});
181}
182
183void ShellFileSelectHelper::MultiFilesSelected(
Gyuyoung Kime69e6b102024-07-16 15:32:51184 const std::vector<ui::SelectedFileInfo>& files) {
Julie Jeongeun Kim8e989dd2023-03-30 09:27:06185 ConvertToFileChooserFileInfoList(files);
186}
187
Gyuyoung Kime69e6b102024-07-16 15:32:51188void ShellFileSelectHelper::FileSelectionCanceled() {
Julie Jeongeun Kim8e989dd2023-03-30 09:27:06189 RunFileChooserEnd();
190}
191
Julie Jeongeun Kim1567d9d2023-05-26 10:06:59192void ShellFileSelectHelper::StartNewEnumeration(const base::FilePath& path) {
193 base_dir_ = path;
194 auto entry = std::make_unique<ActiveDirectoryEnumeration>(path);
195 entry->lister_ = base::WrapUnique(new net::DirectoryLister(
196 path, net::DirectoryLister::NO_SORT_RECURSIVE, this));
197 entry->lister_->Start();
198 directory_enumeration_ = std::move(entry);
199}
200
201void ShellFileSelectHelper::OnListFile(
202 const net::DirectoryLister::DirectoryListerData& data) {
203 // Directory upload only cares about files.
204 if (data.info.IsDirectory()) {
205 return;
206 }
207
208 directory_enumeration_->results_.push_back(data.path);
209}
210
211void ShellFileSelectHelper::OnListDone(int error) {
212 if (!web_contents_) {
213 // Web contents was destroyed under us (probably by closing the tab). We
214 // must notify |listener_| and release our reference to
215 // ourself. RunFileChooserEnd() performs this.
216 RunFileChooserEnd();
217 return;
218 }
219
220 // This entry needs to be cleaned up when this function is done.
221 std::unique_ptr<ActiveDirectoryEnumeration> entry =
222 std::move(directory_enumeration_);
223 if (error) {
Gyuyoung Kime69e6b102024-07-16 15:32:51224 FileSelectionCanceled();
Julie Jeongeun Kim1567d9d2023-05-26 10:06:59225 return;
226 }
227
228 std::vector<ui::SelectedFileInfo> selected_files =
229 ui::FilePathListToSelectedFileInfoList(entry->results_);
230
231 std::vector<blink::mojom::FileChooserFileInfoPtr> chooser_files;
232 for (const auto& file_path : entry->results_) {
233 chooser_files.push_back(blink::mojom::FileChooserFileInfo::NewNativeFile(
Dave Tapuskacd1a01a2024-11-14 20:46:18234 blink::mojom::NativeFileInfo::New(file_path, std::u16string(),
235 std::vector<std::u16string>())));
Julie Jeongeun Kim1567d9d2023-05-26 10:06:59236 }
237
238 listener_->FileSelected(std::move(chooser_files), base_dir_,
239 blink::mojom::FileChooserParams::Mode::kUploadFolder);
240 listener_.reset();
241 // No members should be accessed from here on.
242 RunFileChooserEnd();
243}
244
Julie Jeongeun Kim8e989dd2023-03-30 09:27:06245void ShellFileSelectHelper::ConvertToFileChooserFileInfoList(
246 const std::vector<ui::SelectedFileInfo>& files) {
247 if (!web_contents_) {
248 RunFileChooserEnd();
249 return;
250 }
251
252 std::vector<blink::mojom::FileChooserFileInfoPtr> chooser_files;
253 for (const auto& file : files) {
254 chooser_files.push_back(blink::mojom::FileChooserFileInfo::NewNativeFile(
255 blink::mojom::NativeFileInfo::New(
Dave Tapuskacd1a01a2024-11-14 20:46:18256 file.local_path, base::FilePath(file.display_name).AsUTF16Unsafe(),
257 std::vector<std::u16string>())));
Julie Jeongeun Kim8e989dd2023-03-30 09:27:06258 }
259
260 listener_->FileSelected(std::move(chooser_files), base::FilePath(),
261 dialog_mode_);
262 listener_ = nullptr;
263
264 // No members should be accessed from here on.
265 RunFileChooserEnd();
Julie Jeongeun Kim1f306fa42023-03-30 08:10:17266}
267
268} // namespace content