- Added search icon and search clear button in 1x and 2x. Screenshot: http://i.imgur...
[chromium-blink-merge.git] / chrome / browser / file_select_helper.cc
blob15d63c7f30120c7b8c46ca539446a0bef2ad599f
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 "chrome/browser/file_select_helper.h"
7 #include <string>
8 #include <utility>
10 #include "base/bind.h"
11 #include "base/file_util.h"
12 #include "base/files/file_enumerator.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 "chrome/browser/platform_util.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/ui/browser.h"
19 #include "chrome/browser/ui/browser_list.h"
20 #include "chrome/browser/ui/chrome_select_file_policy.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "content/public/browser/notification_details.h"
23 #include "content/public/browser/notification_source.h"
24 #include "content/public/browser/notification_types.h"
25 #include "content/public/browser/render_view_host.h"
26 #include "content/public/browser/render_widget_host_view.h"
27 #include "content/public/browser/web_contents.h"
28 #include "content/public/common/file_chooser_params.h"
29 #include "grit/generated_resources.h"
30 #include "net/base/mime_util.h"
31 #include "ui/base/l10n/l10n_util.h"
32 #include "ui/shell_dialogs/selected_file_info.h"
34 using content::BrowserThread;
35 using content::FileChooserParams;
36 using content::RenderViewHost;
37 using content::RenderWidgetHost;
38 using content::WebContents;
40 namespace {
42 // There is only one file-selection happening at any given time,
43 // so we allocate an enumeration ID for that purpose. All IDs from
44 // the renderer must start at 0 and increase.
45 const int kFileSelectEnumerationId = -1;
47 void NotifyRenderViewHost(RenderViewHost* render_view_host,
48 const std::vector<ui::SelectedFileInfo>& files,
49 FileChooserParams::Mode dialog_mode) {
50 render_view_host->FilesSelectedInChooser(files, dialog_mode);
53 // Converts a list of FilePaths to a list of ui::SelectedFileInfo.
54 std::vector<ui::SelectedFileInfo> FilePathListToSelectedFileInfoList(
55 const std::vector<base::FilePath>& paths) {
56 std::vector<ui::SelectedFileInfo> selected_files;
57 for (size_t i = 0; i < paths.size(); ++i) {
58 selected_files.push_back(
59 ui::SelectedFileInfo(paths[i], paths[i]));
61 return selected_files;
64 } // namespace
66 struct FileSelectHelper::ActiveDirectoryEnumeration {
67 ActiveDirectoryEnumeration() : rvh_(NULL) {}
69 scoped_ptr<DirectoryListerDispatchDelegate> delegate_;
70 scoped_ptr<net::DirectoryLister> lister_;
71 RenderViewHost* rvh_;
72 std::vector<base::FilePath> results_;
75 FileSelectHelper::FileSelectHelper(Profile* profile)
76 : profile_(profile),
77 render_view_host_(NULL),
78 web_contents_(NULL),
79 select_file_dialog_(),
80 select_file_types_(),
81 dialog_type_(ui::SelectFileDialog::SELECT_OPEN_FILE),
82 dialog_mode_(FileChooserParams::Open) {
85 FileSelectHelper::~FileSelectHelper() {
86 // There may be pending file dialogs, we need to tell them that we've gone
87 // away so they don't try and call back to us.
88 if (select_file_dialog_.get())
89 select_file_dialog_->ListenerDestroyed();
91 // Stop any pending directory enumeration, prevent a callback, and free
92 // allocated memory.
93 std::map<int, ActiveDirectoryEnumeration*>::iterator iter;
94 for (iter = directory_enumerations_.begin();
95 iter != directory_enumerations_.end();
96 ++iter) {
97 iter->second->lister_.reset();
98 delete iter->second;
102 void FileSelectHelper::DirectoryListerDispatchDelegate::OnListFile(
103 const net::DirectoryLister::DirectoryListerData& data) {
104 parent_->OnListFile(id_, data);
107 void FileSelectHelper::DirectoryListerDispatchDelegate::OnListDone(int error) {
108 parent_->OnListDone(id_, error);
111 void FileSelectHelper::FileSelected(const base::FilePath& path,
112 int index, void* params) {
113 FileSelectedWithExtraInfo(ui::SelectedFileInfo(path, path), index, params);
116 void FileSelectHelper::FileSelectedWithExtraInfo(
117 const ui::SelectedFileInfo& file,
118 int index,
119 void* params) {
120 if (!render_view_host_)
121 return;
123 profile_->set_last_selected_directory(file.file_path.DirName());
125 const base::FilePath& path = file.local_path;
126 if (dialog_type_ == ui::SelectFileDialog::SELECT_UPLOAD_FOLDER) {
127 StartNewEnumeration(path, kFileSelectEnumerationId, render_view_host_);
128 return;
131 std::vector<ui::SelectedFileInfo> files;
132 files.push_back(file);
133 NotifyRenderViewHost(render_view_host_, files, dialog_mode_);
135 // No members should be accessed from here on.
136 RunFileChooserEnd();
139 void FileSelectHelper::MultiFilesSelected(
140 const std::vector<base::FilePath>& files,
141 void* params) {
142 std::vector<ui::SelectedFileInfo> selected_files =
143 FilePathListToSelectedFileInfoList(files);
145 MultiFilesSelectedWithExtraInfo(selected_files, params);
148 void FileSelectHelper::MultiFilesSelectedWithExtraInfo(
149 const std::vector<ui::SelectedFileInfo>& files,
150 void* params) {
151 if (!files.empty())
152 profile_->set_last_selected_directory(files[0].file_path.DirName());
153 if (!render_view_host_)
154 return;
156 NotifyRenderViewHost(render_view_host_, files, dialog_mode_);
158 // No members should be accessed from here on.
159 RunFileChooserEnd();
162 void FileSelectHelper::FileSelectionCanceled(void* params) {
163 if (!render_view_host_)
164 return;
166 // If the user cancels choosing a file to upload we pass back an
167 // empty vector.
168 NotifyRenderViewHost(
169 render_view_host_, std::vector<ui::SelectedFileInfo>(),
170 dialog_mode_);
172 // No members should be accessed from here on.
173 RunFileChooserEnd();
176 void FileSelectHelper::StartNewEnumeration(const base::FilePath& path,
177 int request_id,
178 RenderViewHost* render_view_host) {
179 scoped_ptr<ActiveDirectoryEnumeration> entry(new ActiveDirectoryEnumeration);
180 entry->rvh_ = render_view_host;
181 entry->delegate_.reset(new DirectoryListerDispatchDelegate(this, request_id));
182 entry->lister_.reset(new net::DirectoryLister(path,
183 true,
184 net::DirectoryLister::NO_SORT,
185 entry->delegate_.get()));
186 if (!entry->lister_->Start()) {
187 if (request_id == kFileSelectEnumerationId)
188 FileSelectionCanceled(NULL);
189 else
190 render_view_host->DirectoryEnumerationFinished(request_id,
191 entry->results_);
192 } else {
193 directory_enumerations_[request_id] = entry.release();
197 void FileSelectHelper::OnListFile(
198 int id,
199 const net::DirectoryLister::DirectoryListerData& data) {
200 ActiveDirectoryEnumeration* entry = directory_enumerations_[id];
202 // Directory upload returns directories via a "." file, so that
203 // empty directories are included. This util call just checks
204 // the flags in the structure; there's no file I/O going on.
205 if (data.info.IsDirectory())
206 entry->results_.push_back(data.path.Append(FILE_PATH_LITERAL(".")));
207 else
208 entry->results_.push_back(data.path);
211 void FileSelectHelper::OnListDone(int id, int error) {
212 // This entry needs to be cleaned up when this function is done.
213 scoped_ptr<ActiveDirectoryEnumeration> entry(directory_enumerations_[id]);
214 directory_enumerations_.erase(id);
215 if (!entry->rvh_)
216 return;
217 if (error) {
218 FileSelectionCanceled(NULL);
219 return;
222 std::vector<ui::SelectedFileInfo> selected_files =
223 FilePathListToSelectedFileInfoList(entry->results_);
225 if (id == kFileSelectEnumerationId)
226 NotifyRenderViewHost(entry->rvh_, selected_files, dialog_mode_);
227 else
228 entry->rvh_->DirectoryEnumerationFinished(id, entry->results_);
230 EnumerateDirectoryEnd();
233 scoped_ptr<ui::SelectFileDialog::FileTypeInfo>
234 FileSelectHelper::GetFileTypesFromAcceptType(
235 const std::vector<string16>& accept_types) {
236 scoped_ptr<ui::SelectFileDialog::FileTypeInfo> base_file_type(
237 new ui::SelectFileDialog::FileTypeInfo());
238 if (accept_types.empty())
239 return base_file_type.Pass();
241 // Create FileTypeInfo and pre-allocate for the first extension list.
242 scoped_ptr<ui::SelectFileDialog::FileTypeInfo> file_type(
243 new ui::SelectFileDialog::FileTypeInfo(*base_file_type));
244 file_type->include_all_files = true;
245 file_type->extensions.resize(1);
246 std::vector<base::FilePath::StringType>* extensions =
247 &file_type->extensions.back();
249 // Find the corresponding extensions.
250 int valid_type_count = 0;
251 int description_id = 0;
252 for (size_t i = 0; i < accept_types.size(); ++i) {
253 std::string ascii_type = UTF16ToASCII(accept_types[i]);
254 if (!IsAcceptTypeValid(ascii_type))
255 continue;
257 size_t old_extension_size = extensions->size();
258 if (ascii_type[0] == '.') {
259 // If the type starts with a period it is assumed to be a file extension
260 // so we just have to add it to the list.
261 base::FilePath::StringType ext(ascii_type.begin(), ascii_type.end());
262 extensions->push_back(ext.substr(1));
263 } else {
264 if (ascii_type == "image/*")
265 description_id = IDS_IMAGE_FILES;
266 else if (ascii_type == "audio/*")
267 description_id = IDS_AUDIO_FILES;
268 else if (ascii_type == "video/*")
269 description_id = IDS_VIDEO_FILES;
271 net::GetExtensionsForMimeType(ascii_type, extensions);
274 if (extensions->size() > old_extension_size)
275 valid_type_count++;
278 // If no valid extension is added, bail out.
279 if (valid_type_count == 0)
280 return base_file_type.Pass();
282 // Use a generic description "Custom Files" if either of the following is
283 // true:
284 // 1) There're multiple types specified, like "audio/*,video/*"
285 // 2) There're multiple extensions for a MIME type without parameter, like
286 // "ehtml,shtml,htm,html" for "text/html". On Windows, the select file
287 // dialog uses the first extension in the list to form the description,
288 // like "EHTML Files". This is not what we want.
289 if (valid_type_count > 1 ||
290 (valid_type_count == 1 && description_id == 0 && extensions->size() > 1))
291 description_id = IDS_CUSTOM_FILES;
293 if (description_id) {
294 file_type->extension_description_overrides.push_back(
295 l10n_util::GetStringUTF16(description_id));
298 return file_type.Pass();
301 // static
302 void FileSelectHelper::RunFileChooser(content::WebContents* tab,
303 const FileChooserParams& params) {
304 Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
305 // FileSelectHelper will keep itself alive until it sends the result message.
306 scoped_refptr<FileSelectHelper> file_select_helper(
307 new FileSelectHelper(profile));
308 file_select_helper->RunFileChooser(tab->GetRenderViewHost(), tab, params);
311 // static
312 void FileSelectHelper::EnumerateDirectory(content::WebContents* tab,
313 int request_id,
314 const base::FilePath& path) {
315 Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
316 // FileSelectHelper will keep itself alive until it sends the result message.
317 scoped_refptr<FileSelectHelper> file_select_helper(
318 new FileSelectHelper(profile));
319 file_select_helper->EnumerateDirectory(
320 request_id, tab->GetRenderViewHost(), path);
323 void FileSelectHelper::RunFileChooser(RenderViewHost* render_view_host,
324 content::WebContents* web_contents,
325 const FileChooserParams& params) {
326 DCHECK(!render_view_host_);
327 DCHECK(!web_contents_);
328 render_view_host_ = render_view_host;
329 web_contents_ = web_contents;
330 notification_registrar_.RemoveAll();
331 notification_registrar_.Add(
332 this, content::NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
333 content::Source<RenderWidgetHost>(render_view_host_));
334 notification_registrar_.Add(
335 this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
336 content::Source<WebContents>(web_contents_));
338 BrowserThread::PostTask(
339 BrowserThread::FILE, FROM_HERE,
340 base::Bind(&FileSelectHelper::RunFileChooserOnFileThread, this, params));
342 // Because this class returns notifications to the RenderViewHost, it is
343 // difficult for callers to know how long to keep a reference to this
344 // instance. We AddRef() here to keep the instance alive after we return
345 // to the caller, until the last callback is received from the file dialog.
346 // At that point, we must call RunFileChooserEnd().
347 AddRef();
350 void FileSelectHelper::RunFileChooserOnFileThread(
351 const FileChooserParams& params) {
352 select_file_types_ = GetFileTypesFromAcceptType(params.accept_types);
354 BrowserThread::PostTask(
355 BrowserThread::UI, FROM_HERE,
356 base::Bind(&FileSelectHelper::RunFileChooserOnUIThread, this, params));
359 void FileSelectHelper::RunFileChooserOnUIThread(
360 const FileChooserParams& params) {
361 if (!render_view_host_ || !web_contents_) {
362 // If the renderer was destroyed before we started, just cancel the
363 // operation.
364 RunFileChooserEnd();
365 return;
368 select_file_dialog_ = ui::SelectFileDialog::Create(
369 this, new ChromeSelectFilePolicy(web_contents_));
371 dialog_mode_ = params.mode;
372 switch (params.mode) {
373 case FileChooserParams::Open:
374 dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE;
375 break;
376 case FileChooserParams::OpenMultiple:
377 dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE;
378 break;
379 case FileChooserParams::UploadFolder:
380 dialog_type_ = ui::SelectFileDialog::SELECT_UPLOAD_FOLDER;
381 break;
382 case FileChooserParams::Save:
383 dialog_type_ = ui::SelectFileDialog::SELECT_SAVEAS_FILE;
384 break;
385 default:
386 // Prevent warning.
387 dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE;
388 NOTREACHED();
391 base::FilePath default_file_name = params.default_file_name.IsAbsolute() ?
392 params.default_file_name :
393 profile_->last_selected_directory().Append(params.default_file_name);
395 gfx::NativeWindow owning_window =
396 platform_util::GetTopLevel(render_view_host_->GetView()->GetNativeView());
398 #if defined(OS_ANDROID)
399 // Android needs the original MIME types and an additional capture value.
400 std::pair<std::vector<string16>, bool> accept_types =
401 std::make_pair(params.accept_types, params.capture);
402 #endif
404 select_file_dialog_->SelectFile(
405 dialog_type_,
406 params.title,
407 default_file_name,
408 select_file_types_.get(),
409 select_file_types_.get() && !select_file_types_->extensions.empty()
411 : 0, // 1-based index of default extension to show.
412 base::FilePath::StringType(),
413 owning_window,
414 #if defined(OS_ANDROID)
415 &accept_types);
416 #else
417 NULL);
418 #endif
420 select_file_types_.reset();
423 // This method is called when we receive the last callback from the file
424 // chooser dialog. Perform any cleanup and release the reference we added
425 // in RunFileChooser().
426 void FileSelectHelper::RunFileChooserEnd() {
427 render_view_host_ = NULL;
428 web_contents_ = NULL;
429 Release();
432 void FileSelectHelper::EnumerateDirectory(int request_id,
433 RenderViewHost* render_view_host,
434 const base::FilePath& path) {
436 // Because this class returns notifications to the RenderViewHost, it is
437 // difficult for callers to know how long to keep a reference to this
438 // instance. We AddRef() here to keep the instance alive after we return
439 // to the caller, until the last callback is received from the enumeration
440 // code. At that point, we must call EnumerateDirectoryEnd().
441 AddRef();
442 StartNewEnumeration(path, request_id, render_view_host);
445 // This method is called when we receive the last callback from the enumeration
446 // code. Perform any cleanup and release the reference we added in
447 // EnumerateDirectory().
448 void FileSelectHelper::EnumerateDirectoryEnd() {
449 Release();
452 void FileSelectHelper::Observe(int type,
453 const content::NotificationSource& source,
454 const content::NotificationDetails& details) {
455 switch (type) {
456 case content::NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED: {
457 DCHECK(content::Source<RenderWidgetHost>(source).ptr() ==
458 render_view_host_);
459 render_view_host_ = NULL;
460 break;
463 case content::NOTIFICATION_WEB_CONTENTS_DESTROYED: {
464 DCHECK(content::Source<WebContents>(source).ptr() == web_contents_);
465 web_contents_ = NULL;
466 break;
469 default:
470 NOTREACHED();
474 // static
475 bool FileSelectHelper::IsAcceptTypeValid(const std::string& accept_type) {
476 // TODO(raymes): This only does some basic checks, extend to test more cases.
477 // A 1 character accept type will always be invalid (either a "." in the case
478 // of an extension or a "/" in the case of a MIME type).
479 std::string unused;
480 if (accept_type.length() <= 1 ||
481 StringToLowerASCII(accept_type) != accept_type ||
482 TrimWhitespaceASCII(accept_type, TRIM_ALL, &unused) != TRIM_NONE) {
483 return false;
485 return true;