Upstream version 6.35.131.0
[platform/framework/web/crosswalk.git] / src / xwalk / runtime / browser / runtime_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 "xwalk/runtime/browser/runtime_file_select_helper.h"
6
7 #include <string>
8 #include <utility>
9
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"
30
31 using content::BrowserThread;
32 using content::FileChooserParams;
33 using content::RenderViewHost;
34 using content::RenderWidgetHost;
35 using content::WebContents;
36
37 namespace {
38
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;
43
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);
48 }
49
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]));
57   }
58   return selected_files;
59 }
60
61 }  // namespace
62
63 struct RuntimeFileSelectHelper::ActiveDirectoryEnumeration {
64   ActiveDirectoryEnumeration() : render_view_host_(NULL) {}
65
66   scoped_ptr<DirectoryListerDispatchDelegate> delegate_;
67   scoped_ptr<net::DirectoryLister> lister_;
68   RenderViewHost* render_view_host_;
69   std::vector<base::FilePath> results_;
70 };
71
72 RuntimeFileSelectHelper::RuntimeFileSelectHelper()
73     : render_view_host_(NULL),
74       web_contents_(NULL),
75       select_file_dialog_(),
76       select_file_types_(),
77       dialog_type_(ui::SelectFileDialog::SELECT_OPEN_FILE),
78       dialog_mode_(FileChooserParams::Open) {
79 }
80
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();
86
87   // Stop any pending directory enumeration, prevent a callback, and free
88   // allocated memory.
89   std::map<int, ActiveDirectoryEnumeration*>::iterator iter;
90   for (iter = directory_enumerations_.begin();
91        iter != directory_enumerations_.end();
92        ++iter) {
93     iter->second->lister_.reset();
94     delete iter->second;
95   }
96 }
97
98 void RuntimeFileSelectHelper::DirectoryListerDispatchDelegate::OnListFile(
99     const net::DirectoryLister::DirectoryListerData& data) {
100   parent_->OnListFile(id_, data);
101 }
102
103 void RuntimeFileSelectHelper::DirectoryListerDispatchDelegate::OnListDone(
104     int error) {
105   parent_->OnListDone(id_, error);
106 }
107
108 void RuntimeFileSelectHelper::FileSelected(const base::FilePath& path,
109                                            int index, void* params) {
110   FileSelectedWithExtraInfo(ui::SelectedFileInfo(path, path), index, params);
111 }
112
113 void RuntimeFileSelectHelper::FileSelectedWithExtraInfo(
114     const ui::SelectedFileInfo& file,
115     int index,
116     void* params) {
117   if (!render_view_host_)
118     return;
119
120   // TODO(wang16): Save last select directory here
121
122   const base::FilePath& path = file.local_path;
123   if (dialog_type_ == ui::SelectFileDialog::SELECT_FOLDER) {
124     StartNewEnumeration(path, kFileSelectEnumerationId, render_view_host_);
125     return;
126   }
127
128   std::vector<ui::SelectedFileInfo> files;
129   files.push_back(file);
130   NotifyRenderViewHost(render_view_host_, files, dialog_mode_);
131
132   // No members should be accessed from here on.
133   RunFileChooserEnd();
134 }
135
136 void RuntimeFileSelectHelper::MultiFilesSelected(
137     const std::vector<base::FilePath>& files,
138     void* params) {
139   std::vector<ui::SelectedFileInfo> selected_files =
140       FilePathListToSelectedFileInfoList(files);
141
142   MultiFilesSelectedWithExtraInfo(selected_files, params);
143 }
144
145 void RuntimeFileSelectHelper::MultiFilesSelectedWithExtraInfo(
146     const std::vector<ui::SelectedFileInfo>& files,
147     void* params) {
148
149   // TODO(wang16): Save last select directory here
150
151   if (!render_view_host_)
152     return;
153
154   NotifyRenderViewHost(render_view_host_, files, dialog_mode_);
155
156   // No members should be accessed from here on.
157   RunFileChooserEnd();
158 }
159
160 void RuntimeFileSelectHelper::FileSelectionCanceled(void* params) {
161   if (!render_view_host_)
162     return;
163
164   // If the user cancels choosing a file to upload we pass back an
165   // empty vector.
166   NotifyRenderViewHost(
167       render_view_host_, std::vector<ui::SelectedFileInfo>(),
168       dialog_mode_);
169
170   // No members should be accessed from here on.
171   RunFileChooserEnd();
172 }
173
174 void RuntimeFileSelectHelper::StartNewEnumeration(
175     const base::FilePath& path,
176     int request_id,
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,
182                                                 true,
183                                                 net::DirectoryLister::NO_SORT,
184                                                 entry->delegate_.get()));
185   if (!entry->lister_->Start()) {
186     if (request_id == kFileSelectEnumerationId)
187       FileSelectionCanceled(NULL);
188     else
189       render_view_host->DirectoryEnumerationFinished(request_id,
190                                                      entry->results_);
191   } else {
192     directory_enumerations_[request_id] = entry.release();
193   }
194 }
195
196 void RuntimeFileSelectHelper::OnListFile(
197     int id,
198     const net::DirectoryLister::DirectoryListerData& data) {
199   ActiveDirectoryEnumeration* entry = directory_enumerations_[id];
200
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(".")));
206   else
207     entry->results_.push_back(data.path);
208 }
209
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_)
215     return;
216   if (error) {
217     FileSelectionCanceled(NULL);
218     return;
219   }
220
221   std::vector<ui::SelectedFileInfo> selected_files =
222       FilePathListToSelectedFileInfoList(entry->results_);
223
224   if (id == kFileSelectEnumerationId)
225     NotifyRenderViewHost(
226         entry->render_view_host_, selected_files, dialog_mode_);
227   else
228     entry->render_view_host_->DirectoryEnumerationFinished(id, entry->results_);
229
230   EnumerateDirectoryEnd();
231 }
232
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();
241
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();
249
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))
256       continue;
257
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));
264     } else {
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;
271
272       net::GetExtensionsForMimeType(ascii_type, extensions);
273     }
274
275     if (extensions->size() > old_extension_size)
276       valid_type_count++;
277   }
278
279   // If no valid extension is added, bail out.
280   if (valid_type_count == 0)
281     return base_file_type.Pass();
282
283   // Use a generic description "Custom Files" if either of the following is
284   // true:
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;
293
294   if (description_id) {
295     file_type->extension_description_overrides.push_back(
296         l10n_util::GetStringUTF16(description_id));
297   }
298
299   return file_type.Pass();
300 }
301
302 // static
303 void RuntimeFileSelectHelper::RunFileChooser(content::WebContents* tab,
304                                       const FileChooserParams& params) {
305   // RuntimeFileSelectHelper will keep itself alive until it sends the
306   // result message.
307   scoped_refptr<RuntimeFileSelectHelper> file_select_helper(
308       new RuntimeFileSelectHelper());
309   file_select_helper->RunFileChooser(tab->GetRenderViewHost(), tab, params);
310 }
311
312 // static
313 void RuntimeFileSelectHelper::EnumerateDirectory(content::WebContents* tab,
314                                                  int request_id,
315                                                  const base::FilePath& path) {
316   // RuntimeFileSelectHelper will keep itself alive until it sends the
317   // result message.
318   scoped_refptr<RuntimeFileSelectHelper> file_select_helper(
319       new RuntimeFileSelectHelper());
320   file_select_helper->EnumerateDirectory(
321       request_id, tab->GetRenderViewHost(), path);
322 }
323
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_));
338
339   BrowserThread::PostTask(
340       BrowserThread::FILE, FROM_HERE,
341       base::Bind(&RuntimeFileSelectHelper::RunFileChooserOnFileThread,
342                  this,
343                  params));
344
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().
350   AddRef();
351 }
352
353 void RuntimeFileSelectHelper::RunFileChooserOnFileThread(
354     const FileChooserParams& params) {
355   select_file_types_ = GetFileTypesFromAcceptType(params.accept_types);
356
357   BrowserThread::PostTask(
358       BrowserThread::UI, FROM_HERE,
359       base::Bind(&RuntimeFileSelectHelper::RunFileChooserOnUIThread,
360                  this,
361                  params));
362 }
363
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
369     // operation.
370     RunFileChooserEnd();
371     return;
372   }
373
374   select_file_dialog_ = ui::SelectFileDialog::Create(
375       this, new RuntimeSelectFilePolicy());
376
377   switch (params.mode) {
378     case FileChooserParams::Open:
379       dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE;
380       break;
381     case FileChooserParams::OpenMultiple:
382       dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE;
383       break;
384     case FileChooserParams::UploadFolder:
385       dialog_type_ = ui::SelectFileDialog::SELECT_UPLOAD_FOLDER;
386       break;
387     case FileChooserParams::Save:
388       dialog_type_ = ui::SelectFileDialog::SELECT_SAVEAS_FILE;
389       break;
390     default:
391       // Prevent warning.
392       dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE;
393       NOTREACHED();
394   }
395
396   base::FilePath default_file_name = params.default_file_name.IsAbsolute() ?
397       params.default_file_name :
398       // TODO(wang16): Load last select directory here
399       base::FilePath();
400
401   gfx::NativeWindow owning_window =
402       platform_util::GetTopLevel(render_view_host_->GetView()->GetNativeView());
403
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);
408 #endif
409
410   select_file_dialog_->SelectFile(
411       dialog_type_,
412       params.title,
413       default_file_name,
414       select_file_types_.get(),
415       select_file_types_.get() && !select_file_types_->extensions.empty()
416           ? 1
417           : 0,  // 1-based index of default extension to show.
418       base::FilePath::StringType(),
419       owning_window,
420 #if defined(OS_ANDROID)
421       &accept_types);
422 #else
423       NULL);
424 #endif
425
426   select_file_types_.reset();
427 }
428
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;
435   Release();
436 }
437
438 void RuntimeFileSelectHelper::EnumerateDirectory(
439     int request_id,
440     RenderViewHost* render_view_host,
441     const base::FilePath& path) {
442
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().
448   AddRef();
449   StartNewEnumeration(path, request_id, render_view_host);
450 }
451
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() {
456   Release();
457 }
458
459 void RuntimeFileSelectHelper::Observe(
460     int type,
461     const content::NotificationSource& source,
462     const content::NotificationDetails& details) {
463   switch (type) {
464     case content::NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED: {
465       DCHECK(content::Source<RenderWidgetHost>(source).ptr() ==
466              render_view_host_);
467       render_view_host_ = NULL;
468       break;
469     }
470
471     case content::NOTIFICATION_WEB_CONTENTS_DESTROYED: {
472       DCHECK(content::Source<WebContents>(source).ptr() == web_contents_);
473       web_contents_ = NULL;
474       break;
475     }
476
477     default:
478       NOTREACHED();
479   }
480 }
481
482 // static
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).
488   std::string unused;
489   if (accept_type.length() <= 1 ||
490       StringToLowerASCII(accept_type) != accept_type ||
491       base::TrimWhitespaceASCII(
492         accept_type,
493         base::TRIM_ALL, &unused) != base::TRIM_NONE) {
494     return false;
495   }
496   return true;
497 }