fa0004ccb2edabad900f27e883d9b98fbe83c6a6
[platform/framework/web/crosswalk.git] / src / chrome / browser / file_select_helper.cc
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.
4
5 #include "chrome/browser/file_select_helper.h"
6
7 #include <string>
8 #include <utility>
9
10 #include "base/bind.h"
11 #include "base/files/file_enumerator.h"
12 #include "base/files/file_util.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 "chrome/grit/generated_resources.h"
22 #include "content/public/browser/browser_thread.h"
23 #include "content/public/browser/notification_details.h"
24 #include "content/public/browser/notification_source.h"
25 #include "content/public/browser/notification_types.h"
26 #include "content/public/browser/render_view_host.h"
27 #include "content/public/browser/render_widget_host_view.h"
28 #include "content/public/browser/web_contents.h"
29 #include "content/public/common/file_chooser_params.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"
33
34 using content::BrowserThread;
35 using content::FileChooserParams;
36 using content::RenderViewHost;
37 using content::RenderWidgetHost;
38 using content::WebContents;
39
40 namespace {
41
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;
46
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);
51 }
52
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]));
60   }
61   return selected_files;
62 }
63
64 }  // namespace
65
66 struct FileSelectHelper::ActiveDirectoryEnumeration {
67   ActiveDirectoryEnumeration() : rvh_(NULL) {}
68
69   scoped_ptr<DirectoryListerDispatchDelegate> delegate_;
70   scoped_ptr<net::DirectoryLister> lister_;
71   RenderViewHost* rvh_;
72   std::vector<base::FilePath> results_;
73 };
74
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) {
83 }
84
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();
90
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;
99   }
100 }
101
102 void FileSelectHelper::DirectoryListerDispatchDelegate::OnListFile(
103     const net::DirectoryLister::DirectoryListerData& data) {
104   parent_->OnListFile(id_, data);
105 }
106
107 void FileSelectHelper::DirectoryListerDispatchDelegate::OnListDone(int error) {
108   parent_->OnListDone(id_, error);
109 }
110
111 void FileSelectHelper::FileSelected(const base::FilePath& path,
112                                     int index, void* params) {
113   FileSelectedWithExtraInfo(ui::SelectedFileInfo(path, path), index, params);
114 }
115
116 void FileSelectHelper::FileSelectedWithExtraInfo(
117     const ui::SelectedFileInfo& file,
118     int index,
119     void* params) {
120   if (!render_view_host_)
121     return;
122
123   profile_->set_last_selected_directory(file.file_path.DirName());
124
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;
129   }
130
131   std::vector<ui::SelectedFileInfo> files;
132   files.push_back(file);
133   NotifyRenderViewHost(render_view_host_, files, dialog_mode_);
134
135   // No members should be accessed from here on.
136   RunFileChooserEnd();
137 }
138
139 void FileSelectHelper::MultiFilesSelected(
140     const std::vector<base::FilePath>& files,
141     void* params) {
142   std::vector<ui::SelectedFileInfo> selected_files =
143       FilePathListToSelectedFileInfoList(files);
144
145   MultiFilesSelectedWithExtraInfo(selected_files, params);
146 }
147
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;
155
156   NotifyRenderViewHost(render_view_host_, files, dialog_mode_);
157
158   // No members should be accessed from here on.
159   RunFileChooserEnd();
160 }
161
162 void FileSelectHelper::FileSelectionCanceled(void* params) {
163   if (!render_view_host_)
164     return;
165
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_);
171
172   // No members should be accessed from here on.
173   RunFileChooserEnd();
174 }
175
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();
194   }
195 }
196
197 void FileSelectHelper::OnListFile(
198     int id,
199     const net::DirectoryLister::DirectoryListerData& data) {
200   ActiveDirectoryEnumeration* entry = directory_enumerations_[id];
201
202   // Directory upload only cares about files.
203   if (data.info.IsDirectory())
204     return;
205
206   entry->results_.push_back(data.path);
207 }
208
209 void FileSelectHelper::OnListDone(int id, int error) {
210   // This entry needs to be cleaned up when this function is done.
211   scoped_ptr<ActiveDirectoryEnumeration> entry(directory_enumerations_[id]);
212   directory_enumerations_.erase(id);
213   if (!entry->rvh_)
214     return;
215   if (error) {
216     FileSelectionCanceled(NULL);
217     return;
218   }
219
220   std::vector<ui::SelectedFileInfo> selected_files =
221       FilePathListToSelectedFileInfoList(entry->results_);
222
223   if (id == kFileSelectEnumerationId)
224     NotifyRenderViewHost(entry->rvh_, selected_files, dialog_mode_);
225   else
226     entry->rvh_->DirectoryEnumerationFinished(id, entry->results_);
227
228   EnumerateDirectoryEnd();
229 }
230
231 scoped_ptr<ui::SelectFileDialog::FileTypeInfo>
232 FileSelectHelper::GetFileTypesFromAcceptType(
233     const std::vector<base::string16>& accept_types) {
234   scoped_ptr<ui::SelectFileDialog::FileTypeInfo> base_file_type(
235       new ui::SelectFileDialog::FileTypeInfo());
236   if (accept_types.empty())
237     return base_file_type.Pass();
238
239   // Create FileTypeInfo and pre-allocate for the first extension list.
240   scoped_ptr<ui::SelectFileDialog::FileTypeInfo> file_type(
241       new ui::SelectFileDialog::FileTypeInfo(*base_file_type));
242   file_type->include_all_files = true;
243   file_type->extensions.resize(1);
244   std::vector<base::FilePath::StringType>* extensions =
245       &file_type->extensions.back();
246
247   // Find the corresponding extensions.
248   int valid_type_count = 0;
249   int description_id = 0;
250   for (size_t i = 0; i < accept_types.size(); ++i) {
251     std::string ascii_type = base::UTF16ToASCII(accept_types[i]);
252     if (!IsAcceptTypeValid(ascii_type))
253       continue;
254
255     size_t old_extension_size = extensions->size();
256     if (ascii_type[0] == '.') {
257       // If the type starts with a period it is assumed to be a file extension
258       // so we just have to add it to the list.
259       base::FilePath::StringType ext(ascii_type.begin(), ascii_type.end());
260       extensions->push_back(ext.substr(1));
261     } else {
262       if (ascii_type == "image/*")
263         description_id = IDS_IMAGE_FILES;
264       else if (ascii_type == "audio/*")
265         description_id = IDS_AUDIO_FILES;
266       else if (ascii_type == "video/*")
267         description_id = IDS_VIDEO_FILES;
268
269       net::GetExtensionsForMimeType(ascii_type, extensions);
270     }
271
272     if (extensions->size() > old_extension_size)
273       valid_type_count++;
274   }
275
276   // If no valid extension is added, bail out.
277   if (valid_type_count == 0)
278     return base_file_type.Pass();
279
280   // Use a generic description "Custom Files" if either of the following is
281   // true:
282   // 1) There're multiple types specified, like "audio/*,video/*"
283   // 2) There're multiple extensions for a MIME type without parameter, like
284   //    "ehtml,shtml,htm,html" for "text/html". On Windows, the select file
285   //    dialog uses the first extension in the list to form the description,
286   //    like "EHTML Files". This is not what we want.
287   if (valid_type_count > 1 ||
288       (valid_type_count == 1 && description_id == 0 && extensions->size() > 1))
289     description_id = IDS_CUSTOM_FILES;
290
291   if (description_id) {
292     file_type->extension_description_overrides.push_back(
293         l10n_util::GetStringUTF16(description_id));
294   }
295
296   return file_type.Pass();
297 }
298
299 // static
300 void FileSelectHelper::RunFileChooser(content::WebContents* tab,
301                                       const FileChooserParams& params) {
302   Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
303   // FileSelectHelper will keep itself alive until it sends the result message.
304   scoped_refptr<FileSelectHelper> file_select_helper(
305       new FileSelectHelper(profile));
306   file_select_helper->RunFileChooser(tab->GetRenderViewHost(), tab, params);
307 }
308
309 // static
310 void FileSelectHelper::EnumerateDirectory(content::WebContents* tab,
311                                           int request_id,
312                                           const base::FilePath& path) {
313   Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
314   // FileSelectHelper will keep itself alive until it sends the result message.
315   scoped_refptr<FileSelectHelper> file_select_helper(
316       new FileSelectHelper(profile));
317   file_select_helper->EnumerateDirectory(
318       request_id, tab->GetRenderViewHost(), path);
319 }
320
321 void FileSelectHelper::RunFileChooser(RenderViewHost* render_view_host,
322                                       content::WebContents* web_contents,
323                                       const FileChooserParams& params) {
324   DCHECK(!render_view_host_);
325   DCHECK(!web_contents_);
326   render_view_host_ = render_view_host;
327   web_contents_ = web_contents;
328   notification_registrar_.RemoveAll();
329   notification_registrar_.Add(
330       this, content::NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
331       content::Source<RenderWidgetHost>(render_view_host_));
332   notification_registrar_.Add(
333       this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
334       content::Source<WebContents>(web_contents_));
335
336   BrowserThread::PostTask(
337       BrowserThread::FILE, FROM_HERE,
338       base::Bind(&FileSelectHelper::RunFileChooserOnFileThread, this, params));
339
340   // Because this class returns notifications to the RenderViewHost, it is
341   // difficult for callers to know how long to keep a reference to this
342   // instance. We AddRef() here to keep the instance alive after we return
343   // to the caller, until the last callback is received from the file dialog.
344   // At that point, we must call RunFileChooserEnd().
345   AddRef();
346 }
347
348 void FileSelectHelper::RunFileChooserOnFileThread(
349     const FileChooserParams& params) {
350   select_file_types_ = GetFileTypesFromAcceptType(params.accept_types);
351
352   BrowserThread::PostTask(
353       BrowserThread::UI, FROM_HERE,
354       base::Bind(&FileSelectHelper::RunFileChooserOnUIThread, this, params));
355 }
356
357 void FileSelectHelper::RunFileChooserOnUIThread(
358     const FileChooserParams& params) {
359   if (!render_view_host_ || !web_contents_) {
360     // If the renderer was destroyed before we started, just cancel the
361     // operation.
362     RunFileChooserEnd();
363     return;
364   }
365
366   select_file_dialog_ = ui::SelectFileDialog::Create(
367       this, new ChromeSelectFilePolicy(web_contents_));
368   if (!select_file_dialog_.get())
369     return;
370
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();
389   }
390
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);
394
395   gfx::NativeWindow owning_window =
396       platform_util::GetTopLevel(render_view_host_->GetView()->GetNativeView());
397
398 #if defined(OS_ANDROID)
399   // Android needs the original MIME types and an additional capture value.
400   std::pair<std::vector<base::string16>, bool> accept_types =
401       std::make_pair(params.accept_types, params.capture);
402 #endif
403
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()
410           ? 1
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
419
420   select_file_types_.reset();
421 }
422
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();
430 }
431
432 void FileSelectHelper::EnumerateDirectory(int request_id,
433                                           RenderViewHost* render_view_host,
434                                           const base::FilePath& path) {
435
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);
443 }
444
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();
450 }
451
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;
461     }
462
463     case content::NOTIFICATION_WEB_CONTENTS_DESTROYED: {
464       DCHECK(content::Source<WebContents>(source).ptr() == web_contents_);
465       web_contents_ = NULL;
466       break;
467     }
468
469     default:
470       NOTREACHED();
471   }
472 }
473
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       base::StringToLowerASCII(accept_type) != accept_type ||
482       base::TrimWhitespaceASCII(accept_type, base::TRIM_ALL, &unused) !=
483           base::TRIM_NONE) {
484     return false;
485   }
486   return true;
487 }