blob: 5ed79eb1a196dd0032ef41f6563a24ecc481a4c8 [file] [log] [blame]
[email protected]5626b0892012-02-20 14:46:581// Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]ba70d082010-09-10 16:54:492// 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
[email protected]5ac950b2010-12-09 21:34:257#include <string>
8
[email protected]9f054aa12011-09-29 19:13:459#include "base/bind.h"
[email protected]ba70d082010-09-10 16:54:4910#include "base/file_util.h"
[email protected]459fba82011-10-13 02:48:5011#include "base/platform_file.h"
[email protected]ba70d082010-09-10 16:54:4912#include "base/string_split.h"
13#include "base/string_util.h"
14#include "base/utf_string_conversions.h"
[email protected]ba70d082010-09-10 16:54:4915#include "chrome/browser/platform_util.h"
[email protected]8ecad5e2010-12-02 21:18:3316#include "chrome/browser/profiles/profile.h"
[email protected]d9898912011-04-15 21:10:0017#include "chrome/browser/ui/browser.h"
18#include "chrome/browser/ui/browser_list.h"
[email protected]6c2381d2011-10-19 02:52:5319#include "content/public/browser/notification_details.h"
20#include "content/public/browser/notification_source.h"
[email protected]0d6e9bd2011-10-18 04:29:1621#include "content/public/browser/notification_types.h"
[email protected]9c1662b2012-03-06 15:44:3322#include "content/public/browser/render_view_host.h"
[email protected]5626b0892012-02-20 14:46:5823#include "content/public/browser/render_widget_host_view.h"
[email protected]8caadeb2011-11-22 02:45:2324#include "content/public/common/file_chooser_params.h"
[email protected]ba70d082010-09-10 16:54:4925#include "grit/generated_resources.h"
[email protected]b3841c502011-03-09 01:21:3126#include "net/base/mime_util.h"
[email protected]c051a1b2011-01-21 23:30:1727#include "ui/base/l10n/l10n_util.h"
[email protected]ba70d082010-09-10 16:54:4928
[email protected]631bb742011-11-02 11:29:3929using content::BrowserThread;
[email protected]eaabba22012-03-07 15:02:1130using content::RenderViewHost;
31using content::RenderWidgetHost;
[email protected]ea049a02011-12-25 21:37:0932using content::WebContents;
[email protected]631bb742011-11-02 11:29:3933
[email protected]600ea402011-04-12 00:01:5134namespace {
35
36// There is only one file-selection happening at any given time,
37// so we allocate an enumeration ID for that purpose. All IDs from
38// the renderer must start at 0 and increase.
[email protected]459fba82011-10-13 02:48:5039const int kFileSelectEnumerationId = -1;
40
41void NotifyRenderViewHost(RenderViewHost* render_view_host,
42 const std::vector<FilePath>& files,
43 SelectFileDialog::Type dialog_type) {
44 const int kReadFilePermissions =
45 base::PLATFORM_FILE_OPEN |
46 base::PLATFORM_FILE_READ |
47 base::PLATFORM_FILE_EXCLUSIVE_READ |
48 base::PLATFORM_FILE_ASYNC;
49
50 const int kWriteFilePermissions =
[email protected]3c688fac2011-10-14 02:29:1451 base::PLATFORM_FILE_CREATE |
52 base::PLATFORM_FILE_CREATE_ALWAYS |
53 base::PLATFORM_FILE_OPEN |
[email protected]459fba82011-10-13 02:48:5054 base::PLATFORM_FILE_OPEN_ALWAYS |
[email protected]3c688fac2011-10-14 02:29:1455 base::PLATFORM_FILE_OPEN_TRUNCATED |
[email protected]459fba82011-10-13 02:48:5056 base::PLATFORM_FILE_WRITE |
57 base::PLATFORM_FILE_WRITE_ATTRIBUTES |
58 base::PLATFORM_FILE_ASYNC;
59
60 int permissions = kReadFilePermissions;
61 if (dialog_type == SelectFileDialog::SELECT_SAVEAS_FILE)
62 permissions = kWriteFilePermissions;
63 render_view_host->FilesSelectedInChooser(files, permissions);
64}
[email protected]600ea402011-04-12 00:01:5165}
66
[email protected]485a5272011-04-12 00:49:2967struct FileSelectHelper::ActiveDirectoryEnumeration {
[email protected]d45f7512011-06-21 21:18:2768 ActiveDirectoryEnumeration() : rvh_(NULL) {}
[email protected]485a5272011-04-12 00:49:2969
70 scoped_ptr<DirectoryListerDispatchDelegate> delegate_;
[email protected]05a814182011-04-27 19:50:3471 scoped_ptr<net::DirectoryLister> lister_;
[email protected]485a5272011-04-12 00:49:2972 RenderViewHost* rvh_;
73 std::vector<FilePath> results_;
74};
75
[email protected]ba70d082010-09-10 16:54:4976FileSelectHelper::FileSelectHelper(Profile* profile)
77 : profile_(profile),
78 render_view_host_(NULL),
[email protected]ea049a02011-12-25 21:37:0979 web_contents_(NULL),
[email protected]ba70d082010-09-10 16:54:4980 select_file_dialog_(),
[email protected]9f054aa12011-09-29 19:13:4581 select_file_types_(),
[email protected]ba70d082010-09-10 16:54:4982 dialog_type_(SelectFileDialog::SELECT_OPEN_FILE) {
83}
84
85FileSelectHelper::~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
[email protected]600ea402011-04-12 00:01:5191 // 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) {
[email protected]05a814182011-04-27 19:50:3497 iter->second->lister_.reset();
[email protected]600ea402011-04-12 00:01:5198 delete iter->second;
[email protected]ba70d082010-09-10 16:54:4999 }
100}
101
102void FileSelectHelper::FileSelected(const FilePath& path,
103 int index, void* params) {
104 if (!render_view_host_)
105 return;
106
107 profile_->set_last_selected_directory(path.DirName());
108
109 if (dialog_type_ == SelectFileDialog::SELECT_FOLDER) {
[email protected]600ea402011-04-12 00:01:51110 StartNewEnumeration(path, kFileSelectEnumerationId, render_view_host_);
[email protected]ba70d082010-09-10 16:54:49111 return;
112 }
113
114 std::vector<FilePath> files;
115 files.push_back(path);
[email protected]459fba82011-10-13 02:48:50116 NotifyRenderViewHost(render_view_host_, files, dialog_type_);
[email protected]9f054aa12011-09-29 19:13:45117
[email protected]3a29a6e2011-08-24 18:26:21118 // No members should be accessed from here on.
[email protected]9f054aa12011-09-29 19:13:45119 RunFileChooserEnd();
[email protected]ba70d082010-09-10 16:54:49120}
121
122void FileSelectHelper::MultiFilesSelected(const std::vector<FilePath>& files,
123 void* params) {
124 if (!files.empty())
125 profile_->set_last_selected_directory(files[0].DirName());
126 if (!render_view_host_)
127 return;
128
[email protected]459fba82011-10-13 02:48:50129 NotifyRenderViewHost(render_view_host_, files, dialog_type_);
[email protected]9f054aa12011-09-29 19:13:45130
[email protected]3a29a6e2011-08-24 18:26:21131 // No members should be accessed from here on.
[email protected]9f054aa12011-09-29 19:13:45132 RunFileChooserEnd();
[email protected]ba70d082010-09-10 16:54:49133}
134
135void FileSelectHelper::FileSelectionCanceled(void* params) {
136 if (!render_view_host_)
137 return;
138
139 // If the user cancels choosing a file to upload we pass back an
140 // empty vector.
[email protected]459fba82011-10-13 02:48:50141 NotifyRenderViewHost(
142 render_view_host_, std::vector<FilePath>(), dialog_type_);
[email protected]ba70d082010-09-10 16:54:49143
[email protected]3a29a6e2011-08-24 18:26:21144 // No members should be accessed from here on.
[email protected]9f054aa12011-09-29 19:13:45145 RunFileChooserEnd();
[email protected]ba70d082010-09-10 16:54:49146}
147
[email protected]600ea402011-04-12 00:01:51148void FileSelectHelper::StartNewEnumeration(const FilePath& path,
149 int request_id,
150 RenderViewHost* render_view_host) {
151 scoped_ptr<ActiveDirectoryEnumeration> entry(new ActiveDirectoryEnumeration);
152 entry->rvh_ = render_view_host;
153 entry->delegate_.reset(new DirectoryListerDispatchDelegate(this, request_id));
[email protected]05a814182011-04-27 19:50:34154 entry->lister_.reset(new net::DirectoryLister(path,
155 true,
156 net::DirectoryLister::NO_SORT,
157 entry->delegate_.get()));
[email protected]600ea402011-04-12 00:01:51158 if (!entry->lister_->Start()) {
159 if (request_id == kFileSelectEnumerationId)
160 FileSelectionCanceled(NULL);
161 else
162 render_view_host->DirectoryEnumerationFinished(request_id,
163 entry->results_);
164 } else {
165 directory_enumerations_[request_id] = entry.release();
166 }
[email protected]ba70d082010-09-10 16:54:49167}
168
169void FileSelectHelper::OnListFile(
[email protected]600ea402011-04-12 00:01:51170 int id,
[email protected]ba70d082010-09-10 16:54:49171 const net::DirectoryLister::DirectoryListerData& data) {
[email protected]600ea402011-04-12 00:01:51172 ActiveDirectoryEnumeration* entry = directory_enumerations_[id];
173
[email protected]9897e092011-02-04 22:09:11174 // Directory upload returns directories via a "." file, so that
175 // empty directories are included. This util call just checks
[email protected]ba70d082010-09-10 16:54:49176 // the flags in the structure; there's no file I/O going on.
177 if (file_util::FileEnumerator::IsDirectory(data.info))
[email protected]600ea402011-04-12 00:01:51178 entry->results_.push_back(data.path.Append(FILE_PATH_LITERAL(".")));
[email protected]9897e092011-02-04 22:09:11179 else
[email protected]600ea402011-04-12 00:01:51180 entry->results_.push_back(data.path);
[email protected]ba70d082010-09-10 16:54:49181}
182
[email protected]600ea402011-04-12 00:01:51183void FileSelectHelper::OnListDone(int id, int error) {
184 // This entry needs to be cleaned up when this function is done.
185 scoped_ptr<ActiveDirectoryEnumeration> entry(directory_enumerations_[id]);
186 directory_enumerations_.erase(id);
187 if (!entry->rvh_)
[email protected]ba70d082010-09-10 16:54:49188 return;
[email protected]ba70d082010-09-10 16:54:49189 if (error) {
190 FileSelectionCanceled(NULL);
191 return;
192 }
[email protected]600ea402011-04-12 00:01:51193 if (id == kFileSelectEnumerationId)
[email protected]459fba82011-10-13 02:48:50194 NotifyRenderViewHost(entry->rvh_, entry->results_, dialog_type_);
[email protected]600ea402011-04-12 00:01:51195 else
196 entry->rvh_->DirectoryEnumerationFinished(id, entry->results_);
[email protected]9f054aa12011-09-29 19:13:45197
198 EnumerateDirectoryEnd();
[email protected]ba70d082010-09-10 16:54:49199}
200
201SelectFileDialog::FileTypeInfo* FileSelectHelper::GetFileTypesFromAcceptType(
[email protected]3314c2b12011-11-02 08:05:46202 const std::vector<string16>& accept_types) {
[email protected]ba70d082010-09-10 16:54:49203 if (accept_types.empty())
204 return NULL;
205
[email protected]ba70d082010-09-10 16:54:49206 // Create FileTypeInfo and pre-allocate for the first extension list.
207 scoped_ptr<SelectFileDialog::FileTypeInfo> file_type(
208 new SelectFileDialog::FileTypeInfo());
209 file_type->include_all_files = true;
210 file_type->extensions.resize(1);
211 std::vector<FilePath::StringType>* extensions = &file_type->extensions.back();
212
213 // Find the correspondinge extensions.
214 int valid_type_count = 0;
215 int description_id = 0;
[email protected]3314c2b12011-11-02 08:05:46216 for (size_t i = 0; i < accept_types.size(); ++i) {
217 std::string ascii_mime_type = UTF16ToASCII(accept_types[i]);
218 // WebKit normalizes MIME types. See HTMLInputElement::acceptMIMETypes().
219 DCHECK(StringToLowerASCII(ascii_mime_type) == ascii_mime_type)
220 << "A MIME type contains uppercase letter: " << ascii_mime_type;
221 DCHECK(TrimWhitespaceASCII(ascii_mime_type, TRIM_ALL, &ascii_mime_type)
222 == TRIM_NONE)
223 << "A MIME type contains whitespace: '" << ascii_mime_type << "'";
[email protected]ba70d082010-09-10 16:54:49224
225 size_t old_extension_size = extensions->size();
226 if (ascii_mime_type == "image/*") {
227 description_id = IDS_IMAGE_FILES;
228 net::GetImageExtensions(extensions);
229 } else if (ascii_mime_type == "audio/*") {
230 description_id = IDS_AUDIO_FILES;
231 net::GetAudioExtensions(extensions);
232 } else if (ascii_mime_type == "video/*") {
233 description_id = IDS_VIDEO_FILES;
234 net::GetVideoExtensions(extensions);
235 } else {
236 net::GetExtensionsForMimeType(ascii_mime_type, extensions);
237 }
238
239 if (extensions->size() > old_extension_size)
240 valid_type_count++;
241 }
242
[email protected]cbcd12ed2010-12-16 23:42:57243 // If no valid extension is added, bail out.
244 if (valid_type_count == 0)
245 return NULL;
246
[email protected]ba70d082010-09-10 16:54:49247 // Use a generic description "Custom Files" if either of the following is
248 // true:
249 // 1) There're multiple types specified, like "audio/*,video/*"
250 // 2) There're multiple extensions for a MIME type without parameter, like
251 // "ehtml,shtml,htm,html" for "text/html". On Windows, the select file
252 // dialog uses the first extension in the list to form the description,
253 // like "EHTML Files". This is not what we want.
254 if (valid_type_count > 1 ||
255 (valid_type_count == 1 && description_id == 0 && extensions->size() > 1))
256 description_id = IDS_CUSTOM_FILES;
257
258 if (description_id) {
259 file_type->extension_description_overrides.push_back(
260 l10n_util::GetStringUTF16(description_id));
261 }
262
263 return file_type.release();
264}
265
266void FileSelectHelper::RunFileChooser(
267 RenderViewHost* render_view_host,
[email protected]ea049a02011-12-25 21:37:09268 content::WebContents* web_contents,
[email protected]8caadeb2011-11-22 02:45:23269 const content::FileChooserParams& params) {
[email protected]ba70d082010-09-10 16:54:49270 DCHECK(!render_view_host_);
[email protected]ea049a02011-12-25 21:37:09271 DCHECK(!web_contents_);
[email protected]ba70d082010-09-10 16:54:49272 render_view_host_ = render_view_host;
[email protected]ea049a02011-12-25 21:37:09273 web_contents_ = web_contents;
[email protected]ba70d082010-09-10 16:54:49274 notification_registrar_.RemoveAll();
[email protected]432115822011-07-10 15:52:27275 notification_registrar_.Add(
276 this, content::NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
[email protected]6c2381d2011-10-19 02:52:53277 content::Source<RenderWidgetHost>(render_view_host_));
[email protected]9f054aa12011-09-29 19:13:45278 notification_registrar_.Add(
[email protected]ea049a02011-12-25 21:37:09279 this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
280 content::Source<WebContents>(web_contents_));
[email protected]9f054aa12011-09-29 19:13:45281
282 BrowserThread::PostTask(
283 BrowserThread::FILE, FROM_HERE,
284 base::Bind(&FileSelectHelper::RunFileChooserOnFileThread, this, params));
285
286 // Because this class returns notifications to the RenderViewHost, it is
287 // difficult for callers to know how long to keep a reference to this
288 // instance. We AddRef() here to keep the instance alive after we return
289 // to the caller, until the last callback is received from the file dialog.
290 // At that point, we must call RunFileChooserEnd().
291 AddRef();
292}
293
294void FileSelectHelper::RunFileChooserOnFileThread(
[email protected]8caadeb2011-11-22 02:45:23295 const content::FileChooserParams& params) {
[email protected]9f054aa12011-09-29 19:13:45296 select_file_types_.reset(
297 GetFileTypesFromAcceptType(params.accept_types));
298
299 BrowserThread::PostTask(
300 BrowserThread::UI, FROM_HERE,
301 base::Bind(&FileSelectHelper::RunFileChooserOnUIThread, this, params));
302}
303
304void FileSelectHelper::RunFileChooserOnUIThread(
[email protected]8caadeb2011-11-22 02:45:23305 const content::FileChooserParams& params) {
[email protected]ea049a02011-12-25 21:37:09306 if (!render_view_host_ || !web_contents_) {
[email protected]b95b08d2011-12-15 20:23:16307 // If the renderer was destroyed before we started, just cancel the
308 // operation.
309 RunFileChooserEnd();
[email protected]9f054aa12011-09-29 19:13:45310 return;
[email protected]b95b08d2011-12-15 20:23:16311 }
[email protected]ba70d082010-09-10 16:54:49312
313 if (!select_file_dialog_.get())
314 select_file_dialog_ = SelectFileDialog::Create(this);
315
316 switch (params.mode) {
[email protected]8caadeb2011-11-22 02:45:23317 case content::FileChooserParams::Open:
[email protected]ba70d082010-09-10 16:54:49318 dialog_type_ = SelectFileDialog::SELECT_OPEN_FILE;
319 break;
[email protected]8caadeb2011-11-22 02:45:23320 case content::FileChooserParams::OpenMultiple:
[email protected]ba70d082010-09-10 16:54:49321 dialog_type_ = SelectFileDialog::SELECT_OPEN_MULTI_FILE;
322 break;
[email protected]8caadeb2011-11-22 02:45:23323 case content::FileChooserParams::OpenFolder:
[email protected]ba70d082010-09-10 16:54:49324 dialog_type_ = SelectFileDialog::SELECT_FOLDER;
325 break;
[email protected]8caadeb2011-11-22 02:45:23326 case content::FileChooserParams::Save:
[email protected]ba70d082010-09-10 16:54:49327 dialog_type_ = SelectFileDialog::SELECT_SAVEAS_FILE;
328 break;
329 default:
330 dialog_type_ = SelectFileDialog::SELECT_OPEN_FILE; // Prevent warning.
331 NOTREACHED();
332 }
[email protected]ba70d082010-09-10 16:54:49333 FilePath default_file_name = params.default_file_name;
334 if (default_file_name.empty())
335 default_file_name = profile_->last_selected_directory();
336
337 gfx::NativeWindow owning_window =
[email protected]9f76c1e2012-03-05 15:15:58338 platform_util::GetTopLevel(render_view_host_->GetView()->GetNativeView());
[email protected]d9898912011-04-15 21:10:00339
[email protected]9f054aa12011-09-29 19:13:45340 select_file_dialog_->SelectFile(
341 dialog_type_,
342 params.title,
343 default_file_name,
344 select_file_types_.get(),
345 select_file_types_.get() ? 1 : 0, // 1-based index.
346 FILE_PATH_LITERAL(""),
[email protected]ea049a02011-12-25 21:37:09347 web_contents_,
[email protected]9f054aa12011-09-29 19:13:45348 owning_window,
349 NULL);
350
351 select_file_types_.reset();
352}
353
354// This method is called when we receive the last callback from the file
355// chooser dialog. Perform any cleanup and release the reference we added
356// in RunFileChooser().
357void FileSelectHelper::RunFileChooserEnd() {
358 render_view_host_ = NULL;
[email protected]ea049a02011-12-25 21:37:09359 web_contents_ = NULL;
[email protected]9f054aa12011-09-29 19:13:45360 Release();
[email protected]ba70d082010-09-10 16:54:49361}
362
[email protected]600ea402011-04-12 00:01:51363void FileSelectHelper::EnumerateDirectory(int request_id,
364 RenderViewHost* render_view_host,
365 const FilePath& path) {
366 DCHECK_NE(kFileSelectEnumerationId, request_id);
[email protected]9f054aa12011-09-29 19:13:45367
368 // Because this class returns notifications to the RenderViewHost, it is
369 // difficult for callers to know how long to keep a reference to this
370 // instance. We AddRef() here to keep the instance alive after we return
371 // to the caller, until the last callback is received from the enumeration
372 // code. At that point, we must call EnumerateDirectoryEnd().
373 AddRef();
[email protected]600ea402011-04-12 00:01:51374 StartNewEnumeration(path, request_id, render_view_host);
375}
376
[email protected]9f054aa12011-09-29 19:13:45377// This method is called when we receive the last callback from the enumeration
378// code. Perform any cleanup and release the reference we added in
379// EnumerateDirectory().
380void FileSelectHelper::EnumerateDirectoryEnd() {
381 Release();
382}
383
[email protected]432115822011-07-10 15:52:27384void FileSelectHelper::Observe(int type,
[email protected]6c2381d2011-10-19 02:52:53385 const content::NotificationSource& source,
386 const content::NotificationDetails& details) {
[email protected]9f054aa12011-09-29 19:13:45387 switch (type) {
388 case content::NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED: {
[email protected]6c2381d2011-10-19 02:52:53389 DCHECK(content::Source<RenderWidgetHost>(source).ptr() ==
390 render_view_host_);
[email protected]9f054aa12011-09-29 19:13:45391 render_view_host_ = NULL;
392 break;
393 }
394
[email protected]ea049a02011-12-25 21:37:09395 case content::NOTIFICATION_WEB_CONTENTS_DESTROYED: {
396 DCHECK(content::Source<WebContents>(source).ptr() == web_contents_);
397 web_contents_ = NULL;
[email protected]9f054aa12011-09-29 19:13:45398 break;
399 }
400
401 default:
402 NOTREACHED();
403 }
[email protected]ba70d082010-09-10 16:54:49404}
[email protected]aaed2522011-03-11 18:50:54405