blob: 79bbbdcc7d7abb4a253dcdd1210eedaef4f384d3 [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 Kimbc022bc2023-04-12 01:40:5411#include "net/base/mime_util.h"
Julie Jeongeun Kim1f306fa42023-03-30 08:10:1712#include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h"
Julie Jeongeun Kimbc022bc2023-04-12 01:40:5413#include "ui/base/l10n/l10n_util.h"
Julie Jeongeun Kim8e989dd2023-03-30 09:27:0614#include "ui/shell_dialogs/select_file_policy.h"
15#include "ui/shell_dialogs/selected_file_info.h"
Julie Jeongeun Kim1f306fa42023-03-30 08:10:1716
17namespace content {
18
Julie Jeongeun Kimbc022bc2023-04-12 01:40:5419namespace {
20
21// Helper function to get allowed extensions for select file dialog from
22// the specified accept types as defined in the spec:
23// http://whatwg.org/html/number-state.html#attr-input-accept
24// |accept_types| contains only valid lowercased MIME types or file extensions
25// beginning with a period (.).
26std::unique_ptr<ui::SelectFileDialog::FileTypeInfo> GetFileTypesFromAcceptType(
27 const std::vector<std::u16string>& accept_types) {
28 auto base_file_type = std::make_unique<ui::SelectFileDialog::FileTypeInfo>();
29 if (accept_types.empty()) {
30 return base_file_type;
31 }
32
33 // Create FileTypeInfo and pre-allocate for the first extension list.
34 auto file_type =
35 std::make_unique<ui::SelectFileDialog::FileTypeInfo>(*base_file_type);
36 file_type->extensions.resize(1);
37 std::vector<base::FilePath::StringType>* extensions =
38 &file_type->extensions.back();
39
40 // Find the corresponding extensions.
41 size_t valid_type_count = 0;
42 for (const auto& accept_type : accept_types) {
43 size_t old_extension_size = extensions->size();
44 if (accept_type[0] == '.') {
45 // If the type starts with a period it is assumed to be a file extension
46 // so we just have to add it to the list.
47 base::FilePath::StringType ext =
48 base::FilePath::FromUTF16Unsafe(accept_type).value();
49 extensions->push_back(ext.substr(1));
50 } else {
51 if (!base::IsStringASCII(accept_type)) {
52 continue;
53 }
54 std::string ascii_type = base::UTF16ToASCII(accept_type);
55 net::GetExtensionsForMimeType(ascii_type, extensions);
56 }
57
58 if (extensions->size() > old_extension_size) {
59 valid_type_count++;
60 }
61 }
62
63 // If no valid extension is added, bail out.
64 if (valid_type_count == 0) {
65 return base_file_type;
66 }
67
68 return file_type;
69}
70
71} // namespace
72
Julie Jeongeun Kim1f306fa42023-03-30 08:10:1773// static
74void ShellFileSelectHelper::RunFileChooser(
75 content::RenderFrameHost* render_frame_host,
76 scoped_refptr<FileSelectListener> listener,
77 const blink::mojom::FileChooserParams& params) {
78 // ShellFileSelectHelper will keep itself alive until it sends the result
79 // message.
80 scoped_refptr<ShellFileSelectHelper> file_select_helper(
81 new ShellFileSelectHelper());
82 file_select_helper->RunFileChooser(render_frame_host, std::move(listener),
83 params.Clone());
84}
85
86ShellFileSelectHelper::ShellFileSelectHelper() = default;
87
Julie Jeongeun Kim8e989dd2023-03-30 09:27:0688ShellFileSelectHelper::~ShellFileSelectHelper() {
89 // There may be pending file dialogs, we need to tell them that we've gone
90 // away so they don't try and call back to us.
91 if (select_file_dialog_) {
92 select_file_dialog_->ListenerDestroyed();
93 }
94}
Julie Jeongeun Kim1f306fa42023-03-30 08:10:1795
96void ShellFileSelectHelper::RunFileChooser(
97 content::RenderFrameHost* render_frame_host,
98 scoped_refptr<FileSelectListener> listener,
99 blink::mojom::FileChooserParamsPtr params) {
Julie Jeongeun Kim8e989dd2023-03-30 09:27:06100 DCHECK(!web_contents_);
101 DCHECK(listener);
102 DCHECK(!listener_);
103 DCHECK(!select_file_dialog_);
104
105 listener_ = std::move(listener);
106 web_contents_ = content::WebContents::FromRenderFrameHost(render_frame_host)
107 ->GetWeakPtr();
108
109 select_file_dialog_ = ui::SelectFileDialog::Create(this, nullptr);
110
Julie Jeongeun Kimbc022bc2023-04-12 01:40:54111 select_file_types_ = GetFileTypesFromAcceptType(params->accept_types);
112 select_file_types_->allowed_paths =
113 params->need_local_path ? ui::SelectFileDialog::FileTypeInfo::NATIVE_PATH
114 : ui::SelectFileDialog::FileTypeInfo::ANY_PATH;
115 // 1-based index of default extension to show.
116 int file_type_index =
117 select_file_types_ && !select_file_types_->extensions.empty() ? 1 : 0;
118
Julie Jeongeun Kim8e989dd2023-03-30 09:27:06119 dialog_mode_ = params->mode;
120 switch (params->mode) {
121 case blink::mojom::FileChooserParams::Mode::kOpen:
122 dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE;
123 break;
124 case blink::mojom::FileChooserParams::Mode::kOpenMultiple:
125 dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE;
126 break;
127 case blink::mojom::FileChooserParams::Mode::kUploadFolder:
128 dialog_type_ = ui::SelectFileDialog::SELECT_UPLOAD_FOLDER;
129 break;
130 case blink::mojom::FileChooserParams::Mode::kSave:
131 dialog_type_ = ui::SelectFileDialog::SELECT_SAVEAS_FILE;
132 break;
133 default:
134 // Prevent warning.
135 dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE;
136 NOTREACHED();
137 }
138
139 gfx::NativeWindow owning_window = web_contents_->GetTopLevelNativeWindow();
Julie Jeongeun Kimbc022bc2023-04-12 01:40:54140 select_file_dialog_->SelectFile(dialog_type_, std::u16string(),
141 base::FilePath(), select_file_types_.get(),
142 file_type_index, base::FilePath::StringType(),
143 owning_window, nullptr);
Julie Jeongeun Kim8e989dd2023-03-30 09:27:06144
145 // Because this class returns notifications to the RenderViewHost, it is
146 // difficult for callers to know how long to keep a reference to this
147 // instance. We AddRef() here to keep the instance alive after we return
148 // to the caller, until the last callback is received from the file dialog.
149 // At that point, we must call RunFileChooserEnd().
150 AddRef();
151}
152
153void ShellFileSelectHelper::RunFileChooserEnd() {
154 if (listener_) {
155 listener_->FileSelectionCanceled();
156 }
157
158 select_file_dialog_->ListenerDestroyed();
159 select_file_dialog_.reset();
160 Release();
161}
162
163void ShellFileSelectHelper::FileSelected(const base::FilePath& path,
164 int index,
165 void* params) {
166 FileSelectedWithExtraInfo(ui::SelectedFileInfo(path, path), index, params);
167}
168
169void ShellFileSelectHelper::FileSelectedWithExtraInfo(
170 const ui::SelectedFileInfo& file,
171 int index,
172 void* params) {
173 ConvertToFileChooserFileInfoList({file});
174}
175
176void ShellFileSelectHelper::MultiFilesSelected(
177 const std::vector<base::FilePath>& files,
178 void* params) {
179 std::vector<ui::SelectedFileInfo> selected_files =
180 ui::FilePathListToSelectedFileInfoList(files);
181
182 MultiFilesSelectedWithExtraInfo(selected_files, params);
183}
184
185void ShellFileSelectHelper::MultiFilesSelectedWithExtraInfo(
186 const std::vector<ui::SelectedFileInfo>& files,
187 void* params) {
188 ConvertToFileChooserFileInfoList(files);
189}
190
191void ShellFileSelectHelper::FileSelectionCanceled(void* params) {
192 RunFileChooserEnd();
193}
194
195void ShellFileSelectHelper::ConvertToFileChooserFileInfoList(
196 const std::vector<ui::SelectedFileInfo>& files) {
197 if (!web_contents_) {
198 RunFileChooserEnd();
199 return;
200 }
201
202 std::vector<blink::mojom::FileChooserFileInfoPtr> chooser_files;
203 for (const auto& file : files) {
204 chooser_files.push_back(blink::mojom::FileChooserFileInfo::NewNativeFile(
205 blink::mojom::NativeFileInfo::New(
206 file.local_path,
207 base::FilePath(file.display_name).AsUTF16Unsafe())));
208 }
209
210 listener_->FileSelected(std::move(chooser_files), base::FilePath(),
211 dialog_mode_);
212 listener_ = nullptr;
213
214 // No members should be accessed from here on.
215 RunFileChooserEnd();
Julie Jeongeun Kim1f306fa42023-03-30 08:10:17216}
217
218} // namespace content