1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "xwalk/runtime/browser/runtime_file_select_helper.h"
10 #include "base/bind.h"
11 #include "base/file_util.h"
12 #include "base/platform_file.h"
13 #include "base/strings/string_split.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "xwalk/runtime/browser/runtime_platform_util.h"
17 #include "xwalk/runtime/browser/runtime_select_file_policy.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "content/public/browser/notification_details.h"
20 #include "content/public/browser/notification_source.h"
21 #include "content/public/browser/notification_types.h"
22 #include "content/public/browser/render_view_host.h"
23 #include "content/public/browser/render_widget_host_view.h"
24 #include "content/public/browser/web_contents.h"
25 #include "content/public/common/file_chooser_params.h"
26 #include "grit/xwalk_resources.h"
27 #include "net/base/mime_util.h"
28 #include "ui/base/l10n/l10n_util.h"
29 #include "ui/shell_dialogs/selected_file_info.h"
31 using content::BrowserThread;
32 using content::FileChooserParams;
33 using content::RenderViewHost;
34 using content::RenderWidgetHost;
35 using content::WebContents;
39 // There is only one file-selection happening at any given time,
40 // so we allocate an enumeration ID for that purpose. All IDs from
41 // the renderer must start at 0 and increase.
42 const int kFileSelectEnumerationId = -1;
44 void NotifyRenderViewHost(RenderViewHost* render_view_host,
45 const std::vector<ui::SelectedFileInfo>& files,
46 FileChooserParams::Mode dialog_mode) {
47 render_view_host->FilesSelectedInChooser(files, dialog_mode);
50 // Converts a list of FilePaths to a list of ui::SelectedFileInfo.
51 std::vector<ui::SelectedFileInfo> FilePathListToSelectedFileInfoList(
52 const std::vector<base::FilePath>& paths) {
53 std::vector<ui::SelectedFileInfo> selected_files;
54 for (size_t i = 0; i < paths.size(); ++i) {
55 selected_files.push_back(
56 ui::SelectedFileInfo(paths[i], paths[i]));
58 return selected_files;
63 struct RuntimeFileSelectHelper::ActiveDirectoryEnumeration {
64 ActiveDirectoryEnumeration() : render_view_host_(NULL) {}
66 scoped_ptr<DirectoryListerDispatchDelegate> delegate_;
67 scoped_ptr<net::DirectoryLister> lister_;
68 RenderViewHost* render_view_host_;
69 std::vector<base::FilePath> results_;
72 RuntimeFileSelectHelper::RuntimeFileSelectHelper()
73 : render_view_host_(NULL),
75 select_file_dialog_(),
77 dialog_type_(ui::SelectFileDialog::SELECT_OPEN_FILE),
78 dialog_mode_(FileChooserParams::Open) {
81 RuntimeFileSelectHelper::~RuntimeFileSelectHelper() {
82 // There may be pending file dialogs, we need to tell them that we've gone
83 // away so they don't try and call back to us.
84 if (select_file_dialog_.get())
85 select_file_dialog_->ListenerDestroyed();
87 // Stop any pending directory enumeration, prevent a callback, and free
89 std::map<int, ActiveDirectoryEnumeration*>::iterator iter;
90 for (iter = directory_enumerations_.begin();
91 iter != directory_enumerations_.end();
93 iter->second->lister_.reset();
98 void RuntimeFileSelectHelper::DirectoryListerDispatchDelegate::OnListFile(
99 const net::DirectoryLister::DirectoryListerData& data) {
100 parent_->OnListFile(id_, data);
103 void RuntimeFileSelectHelper::DirectoryListerDispatchDelegate::OnListDone(
105 parent_->OnListDone(id_, error);
108 void RuntimeFileSelectHelper::FileSelected(const base::FilePath& path,
109 int index, void* params) {
110 FileSelectedWithExtraInfo(ui::SelectedFileInfo(path, path), index, params);
113 void RuntimeFileSelectHelper::FileSelectedWithExtraInfo(
114 const ui::SelectedFileInfo& file,
117 if (!render_view_host_)
120 // TODO(wang16): Save last select directory here
122 const base::FilePath& path = file.local_path;
123 if (dialog_type_ == ui::SelectFileDialog::SELECT_FOLDER) {
124 StartNewEnumeration(path, kFileSelectEnumerationId, render_view_host_);
128 std::vector<ui::SelectedFileInfo> files;
129 files.push_back(file);
130 NotifyRenderViewHost(render_view_host_, files, dialog_mode_);
132 // No members should be accessed from here on.
136 void RuntimeFileSelectHelper::MultiFilesSelected(
137 const std::vector<base::FilePath>& files,
139 std::vector<ui::SelectedFileInfo> selected_files =
140 FilePathListToSelectedFileInfoList(files);
142 MultiFilesSelectedWithExtraInfo(selected_files, params);
145 void RuntimeFileSelectHelper::MultiFilesSelectedWithExtraInfo(
146 const std::vector<ui::SelectedFileInfo>& files,
149 // TODO(wang16): Save last select directory here
151 if (!render_view_host_)
154 NotifyRenderViewHost(render_view_host_, files, dialog_mode_);
156 // No members should be accessed from here on.
160 void RuntimeFileSelectHelper::FileSelectionCanceled(void* params) {
161 if (!render_view_host_)
164 // If the user cancels choosing a file to upload we pass back an
166 NotifyRenderViewHost(
167 render_view_host_, std::vector<ui::SelectedFileInfo>(),
170 // No members should be accessed from here on.
174 void RuntimeFileSelectHelper::StartNewEnumeration(
175 const base::FilePath& path,
177 RenderViewHost* render_view_host) {
178 scoped_ptr<ActiveDirectoryEnumeration> entry(new ActiveDirectoryEnumeration);
179 entry->render_view_host_ = render_view_host;
180 entry->delegate_.reset(new DirectoryListerDispatchDelegate(this, request_id));
181 entry->lister_.reset(new net::DirectoryLister(path,
183 net::DirectoryLister::NO_SORT,
184 entry->delegate_.get()));
185 if (!entry->lister_->Start()) {
186 if (request_id == kFileSelectEnumerationId)
187 FileSelectionCanceled(NULL);
189 render_view_host->DirectoryEnumerationFinished(request_id,
192 directory_enumerations_[request_id] = entry.release();
196 void RuntimeFileSelectHelper::OnListFile(
198 const net::DirectoryLister::DirectoryListerData& data) {
199 ActiveDirectoryEnumeration* entry = directory_enumerations_[id];
201 // Directory upload returns directories via a "." file, so that
202 // empty directories are included. This util call just checks
203 // the flags in the structure; there's no file I/O going on.
204 if (data.info.IsDirectory())
205 entry->results_.push_back(data.path.Append(FILE_PATH_LITERAL(".")));
207 entry->results_.push_back(data.path);
210 void RuntimeFileSelectHelper::OnListDone(int id, int error) {
211 // This entry needs to be cleaned up when this function is done.
212 scoped_ptr<ActiveDirectoryEnumeration> entry(directory_enumerations_[id]);
213 directory_enumerations_.erase(id);
214 if (!entry->render_view_host_)
217 FileSelectionCanceled(NULL);
221 std::vector<ui::SelectedFileInfo> selected_files =
222 FilePathListToSelectedFileInfoList(entry->results_);
224 if (id == kFileSelectEnumerationId)
225 NotifyRenderViewHost(
226 entry->render_view_host_, selected_files, dialog_mode_);
228 entry->render_view_host_->DirectoryEnumerationFinished(id, entry->results_);
230 EnumerateDirectoryEnd();
233 scoped_ptr<ui::SelectFileDialog::FileTypeInfo>
234 RuntimeFileSelectHelper::GetFileTypesFromAcceptType(
235 const std::vector<base::string16>& accept_types) {
236 scoped_ptr<ui::SelectFileDialog::FileTypeInfo> base_file_type(
237 new ui::SelectFileDialog::FileTypeInfo());
238 base_file_type->support_drive = true;
239 if (accept_types.empty())
240 return base_file_type.Pass();
242 // Create FileTypeInfo and pre-allocate for the first extension list.
243 scoped_ptr<ui::SelectFileDialog::FileTypeInfo> file_type(
244 new ui::SelectFileDialog::FileTypeInfo(*base_file_type));
245 file_type->include_all_files = true;
246 file_type->extensions.resize(1);
247 std::vector<base::FilePath::StringType>* extensions =
248 &file_type->extensions.back();
250 // Find the corresponding extensions.
251 int valid_type_count = 0;
252 int description_id = 0;
253 for (size_t i = 0; i < accept_types.size(); ++i) {
254 std::string ascii_type = base::UTF16ToASCII(accept_types[i]);
255 if (!IsAcceptTypeValid(ascii_type))
258 size_t old_extension_size = extensions->size();
259 if (ascii_type[0] == '.') {
260 // If the type starts with a period it is assumed to be a file extension
261 // so we just have to add it to the list.
262 base::FilePath::StringType ext(ascii_type.begin(), ascii_type.end());
263 extensions->push_back(ext.substr(1));
265 if (ascii_type == "image/*")
266 description_id = IDS_IMAGE_FILES;
267 else if (ascii_type == "audio/*")
268 description_id = IDS_AUDIO_FILES;
269 else if (ascii_type == "video/*")
270 description_id = IDS_VIDEO_FILES;
272 net::GetExtensionsForMimeType(ascii_type, extensions);
275 if (extensions->size() > old_extension_size)
279 // If no valid extension is added, bail out.
280 if (valid_type_count == 0)
281 return base_file_type.Pass();
283 // Use a generic description "Custom Files" if either of the following is
285 // 1) There're multiple types specified, like "audio/*,video/*"
286 // 2) There're multiple extensions for a MIME type without parameter, like
287 // "ehtml,shtml,htm,html" for "text/html". On Windows, the select file
288 // dialog uses the first extension in the list to form the description,
289 // like "EHTML Files". This is not what we want.
290 if (valid_type_count > 1 ||
291 (valid_type_count == 1 && description_id == 0 && extensions->size() > 1))
292 description_id = IDS_CUSTOM_FILES;
294 if (description_id) {
295 file_type->extension_description_overrides.push_back(
296 l10n_util::GetStringUTF16(description_id));
299 return file_type.Pass();
303 void RuntimeFileSelectHelper::RunFileChooser(content::WebContents* tab,
304 const FileChooserParams& params) {
305 // RuntimeFileSelectHelper will keep itself alive until it sends the
307 scoped_refptr<RuntimeFileSelectHelper> file_select_helper(
308 new RuntimeFileSelectHelper());
309 file_select_helper->RunFileChooser(tab->GetRenderViewHost(), tab, params);
313 void RuntimeFileSelectHelper::EnumerateDirectory(content::WebContents* tab,
315 const base::FilePath& path) {
316 // RuntimeFileSelectHelper will keep itself alive until it sends the
318 scoped_refptr<RuntimeFileSelectHelper> file_select_helper(
319 new RuntimeFileSelectHelper());
320 file_select_helper->EnumerateDirectory(
321 request_id, tab->GetRenderViewHost(), path);
324 void RuntimeFileSelectHelper::RunFileChooser(RenderViewHost* render_view_host,
325 content::WebContents* web_contents,
326 const FileChooserParams& params) {
327 DCHECK(!render_view_host_);
328 DCHECK(!web_contents_);
329 render_view_host_ = render_view_host;
330 web_contents_ = web_contents;
331 notification_registrar_.RemoveAll();
332 notification_registrar_.Add(
333 this, content::NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
334 content::Source<RenderWidgetHost>(render_view_host_));
335 notification_registrar_.Add(
336 this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
337 content::Source<WebContents>(web_contents_));
339 BrowserThread::PostTask(
340 BrowserThread::FILE, FROM_HERE,
341 base::Bind(&RuntimeFileSelectHelper::RunFileChooserOnFileThread,
345 // Because this class returns notifications to the RenderViewHost, it is
346 // difficult for callers to know how long to keep a reference to this
347 // instance. We AddRef() here to keep the instance alive after we return
348 // to the caller, until the last callback is received from the file dialog.
349 // At that point, we must call RunFileChooserEnd().
353 void RuntimeFileSelectHelper::RunFileChooserOnFileThread(
354 const FileChooserParams& params) {
355 select_file_types_ = GetFileTypesFromAcceptType(params.accept_types);
357 BrowserThread::PostTask(
358 BrowserThread::UI, FROM_HERE,
359 base::Bind(&RuntimeFileSelectHelper::RunFileChooserOnUIThread,
364 void RuntimeFileSelectHelper::RunFileChooserOnUIThread(
365 const FileChooserParams& params) {
366 dialog_mode_ = params.mode;
367 if (!render_view_host_ || !web_contents_) {
368 // If the renderer was destroyed before we started, just cancel the
374 select_file_dialog_ = ui::SelectFileDialog::Create(
375 this, new RuntimeSelectFilePolicy());
377 switch (params.mode) {
378 case FileChooserParams::Open:
379 dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE;
381 case FileChooserParams::OpenMultiple:
382 dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE;
384 case FileChooserParams::UploadFolder:
385 dialog_type_ = ui::SelectFileDialog::SELECT_UPLOAD_FOLDER;
387 case FileChooserParams::Save:
388 dialog_type_ = ui::SelectFileDialog::SELECT_SAVEAS_FILE;
392 dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE;
396 base::FilePath default_file_name = params.default_file_name.IsAbsolute() ?
397 params.default_file_name :
398 // TODO(wang16): Load last select directory here
401 gfx::NativeWindow owning_window =
402 platform_util::GetTopLevel(render_view_host_->GetView()->GetNativeView());
404 #if defined(OS_ANDROID)
405 // Android needs the original MIME types and an additional capture value.
406 std::pair<std::vector<base::string16>, bool> accept_types =
407 std::make_pair(params.accept_types, params.capture);
410 select_file_dialog_->SelectFile(
414 select_file_types_.get(),
415 select_file_types_.get() && !select_file_types_->extensions.empty()
417 : 0, // 1-based index of default extension to show.
418 base::FilePath::StringType(),
420 #if defined(OS_ANDROID)
426 select_file_types_.reset();
429 // This method is called when we receive the last callback from the file
430 // chooser dialog. Perform any cleanup and release the reference we added
431 // in RunFileChooser().
432 void RuntimeFileSelectHelper::RunFileChooserEnd() {
433 render_view_host_ = NULL;
434 web_contents_ = NULL;
438 void RuntimeFileSelectHelper::EnumerateDirectory(
440 RenderViewHost* render_view_host,
441 const base::FilePath& path) {
443 // Because this class returns notifications to the RenderViewHost, it is
444 // difficult for callers to know how long to keep a reference to this
445 // instance. We AddRef() here to keep the instance alive after we return
446 // to the caller, until the last callback is received from the enumeration
447 // code. At that point, we must call EnumerateDirectoryEnd().
449 StartNewEnumeration(path, request_id, render_view_host);
452 // This method is called when we receive the last callback from the enumeration
453 // code. Perform any cleanup and release the reference we added in
454 // EnumerateDirectory().
455 void RuntimeFileSelectHelper::EnumerateDirectoryEnd() {
459 void RuntimeFileSelectHelper::Observe(
461 const content::NotificationSource& source,
462 const content::NotificationDetails& details) {
464 case content::NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED: {
465 DCHECK(content::Source<RenderWidgetHost>(source).ptr() ==
467 render_view_host_ = NULL;
471 case content::NOTIFICATION_WEB_CONTENTS_DESTROYED: {
472 DCHECK(content::Source<WebContents>(source).ptr() == web_contents_);
473 web_contents_ = NULL;
483 bool RuntimeFileSelectHelper::IsAcceptTypeValid(
484 const std::string& accept_type) {
485 // TODO(raymes): This only does some basic checks, extend to test more cases.
486 // A 1 character accept type will always be invalid (either a "." in the case
487 // of an extension or a "/" in the case of a MIME type).
489 if (accept_type.length() <= 1 ||
490 StringToLowerASCII(accept_type) != accept_type ||
491 base::TrimWhitespaceASCII(
493 base::TRIM_ALL, &unused) != base::TRIM_NONE) {