blob: 4e5118de85b80c80b207aeb4754f31a6f2ecf3bd [file] [log] [blame]
[email protected]b3841c502011-03-09 01:21:311// Copyright (c) 2011 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]600ea402011-04-12 00:01:5117#include "content/browser/renderer_host/render_process_host.h"
[email protected]5de634712011-03-02 00:20:1918#include "content/browser/renderer_host/render_view_host.h"
19#include "content/browser/renderer_host/render_widget_host_view.h"
[email protected]aaed2522011-03-11 18:50:5420#include "content/browser/tab_contents/tab_contents.h"
[email protected]d9898912011-04-15 21:10:0021#include "chrome/browser/ui/browser.h"
22#include "chrome/browser/ui/browser_list.h"
[email protected]0aed2f52011-03-23 18:06:3623#include "content/common/view_messages.h"
[email protected]6c2381d2011-10-19 02:52:5324#include "content/public/browser/notification_details.h"
25#include "content/public/browser/notification_source.h"
[email protected]0d6e9bd2011-10-18 04:29:1626#include "content/public/browser/notification_types.h"
[email protected]ba70d082010-09-10 16:54:4927#include "grit/generated_resources.h"
[email protected]b3841c502011-03-09 01:21:3128#include "net/base/mime_util.h"
[email protected]c051a1b2011-01-21 23:30:1729#include "ui/base/l10n/l10n_util.h"
[email protected]ba70d082010-09-10 16:54:4930
[email protected]631bb742011-11-02 11:29:3931using content::BrowserThread;
32
[email protected]600ea402011-04-12 00:01:5133namespace {
34
35// There is only one file-selection happening at any given time,
36// so we allocate an enumeration ID for that purpose. All IDs from
37// the renderer must start at 0 and increase.
[email protected]459fba82011-10-13 02:48:5038const int kFileSelectEnumerationId = -1;
39
40void NotifyRenderViewHost(RenderViewHost* render_view_host,
41 const std::vector<FilePath>& files,
42 SelectFileDialog::Type dialog_type) {
43 const int kReadFilePermissions =
44 base::PLATFORM_FILE_OPEN |
45 base::PLATFORM_FILE_READ |
46 base::PLATFORM_FILE_EXCLUSIVE_READ |
47 base::PLATFORM_FILE_ASYNC;
48
49 const int kWriteFilePermissions =
[email protected]3c688fac2011-10-14 02:29:1450 base::PLATFORM_FILE_CREATE |
51 base::PLATFORM_FILE_CREATE_ALWAYS |
52 base::PLATFORM_FILE_OPEN |
[email protected]459fba82011-10-13 02:48:5053 base::PLATFORM_FILE_OPEN_ALWAYS |
[email protected]3c688fac2011-10-14 02:29:1454 base::PLATFORM_FILE_OPEN_TRUNCATED |
[email protected]459fba82011-10-13 02:48:5055 base::PLATFORM_FILE_WRITE |
56 base::PLATFORM_FILE_WRITE_ATTRIBUTES |
57 base::PLATFORM_FILE_ASYNC;
58
59 int permissions = kReadFilePermissions;
60 if (dialog_type == SelectFileDialog::SELECT_SAVEAS_FILE)
61 permissions = kWriteFilePermissions;
62 render_view_host->FilesSelectedInChooser(files, permissions);
63}
[email protected]600ea402011-04-12 00:01:5164}
65
[email protected]485a5272011-04-12 00:49:2966struct FileSelectHelper::ActiveDirectoryEnumeration {
[email protected]d45f7512011-06-21 21:18:2767 ActiveDirectoryEnumeration() : rvh_(NULL) {}
[email protected]485a5272011-04-12 00:49:2968
69 scoped_ptr<DirectoryListerDispatchDelegate> delegate_;
[email protected]05a814182011-04-27 19:50:3470 scoped_ptr<net::DirectoryLister> lister_;
[email protected]485a5272011-04-12 00:49:2971 RenderViewHost* rvh_;
72 std::vector<FilePath> results_;
73};
74
[email protected]ba70d082010-09-10 16:54:4975FileSelectHelper::FileSelectHelper(Profile* profile)
76 : profile_(profile),
77 render_view_host_(NULL),
[email protected]9f054aa12011-09-29 19:13:4578 tab_contents_(NULL),
[email protected]ba70d082010-09-10 16:54:4979 select_file_dialog_(),
[email protected]9f054aa12011-09-29 19:13:4580 select_file_types_(),
[email protected]ba70d082010-09-10 16:54:4981 dialog_type_(SelectFileDialog::SELECT_OPEN_FILE) {
82}
83
84FileSelectHelper::~FileSelectHelper() {
85 // There may be pending file dialogs, we need to tell them that we've gone
86 // away so they don't try and call back to us.
87 if (select_file_dialog_.get())
88 select_file_dialog_->ListenerDestroyed();
89
[email protected]600ea402011-04-12 00:01:5190 // Stop any pending directory enumeration, prevent a callback, and free
91 // allocated memory.
92 std::map<int, ActiveDirectoryEnumeration*>::iterator iter;
93 for (iter = directory_enumerations_.begin();
94 iter != directory_enumerations_.end();
95 ++iter) {
[email protected]05a814182011-04-27 19:50:3496 iter->second->lister_.reset();
[email protected]600ea402011-04-12 00:01:5197 delete iter->second;
[email protected]ba70d082010-09-10 16:54:4998 }
99}
100
101void FileSelectHelper::FileSelected(const FilePath& path,
102 int index, void* params) {
103 if (!render_view_host_)
104 return;
105
106 profile_->set_last_selected_directory(path.DirName());
107
108 if (dialog_type_ == SelectFileDialog::SELECT_FOLDER) {
[email protected]600ea402011-04-12 00:01:51109 StartNewEnumeration(path, kFileSelectEnumerationId, render_view_host_);
[email protected]ba70d082010-09-10 16:54:49110 return;
111 }
112
113 std::vector<FilePath> files;
114 files.push_back(path);
[email protected]459fba82011-10-13 02:48:50115 NotifyRenderViewHost(render_view_host_, files, dialog_type_);
[email protected]9f054aa12011-09-29 19:13:45116
[email protected]3a29a6e2011-08-24 18:26:21117 // No members should be accessed from here on.
[email protected]9f054aa12011-09-29 19:13:45118 RunFileChooserEnd();
[email protected]ba70d082010-09-10 16:54:49119}
120
121void FileSelectHelper::MultiFilesSelected(const std::vector<FilePath>& files,
122 void* params) {
123 if (!files.empty())
124 profile_->set_last_selected_directory(files[0].DirName());
125 if (!render_view_host_)
126 return;
127
[email protected]459fba82011-10-13 02:48:50128 NotifyRenderViewHost(render_view_host_, files, dialog_type_);
[email protected]9f054aa12011-09-29 19:13:45129
[email protected]3a29a6e2011-08-24 18:26:21130 // No members should be accessed from here on.
[email protected]9f054aa12011-09-29 19:13:45131 RunFileChooserEnd();
[email protected]ba70d082010-09-10 16:54:49132}
133
134void FileSelectHelper::FileSelectionCanceled(void* params) {
135 if (!render_view_host_)
136 return;
137
138 // If the user cancels choosing a file to upload we pass back an
139 // empty vector.
[email protected]459fba82011-10-13 02:48:50140 NotifyRenderViewHost(
141 render_view_host_, std::vector<FilePath>(), dialog_type_);
[email protected]ba70d082010-09-10 16:54:49142
[email protected]3a29a6e2011-08-24 18:26:21143 // No members should be accessed from here on.
[email protected]9f054aa12011-09-29 19:13:45144 RunFileChooserEnd();
[email protected]ba70d082010-09-10 16:54:49145}
146
[email protected]600ea402011-04-12 00:01:51147void FileSelectHelper::StartNewEnumeration(const FilePath& path,
148 int request_id,
149 RenderViewHost* render_view_host) {
150 scoped_ptr<ActiveDirectoryEnumeration> entry(new ActiveDirectoryEnumeration);
151 entry->rvh_ = render_view_host;
152 entry->delegate_.reset(new DirectoryListerDispatchDelegate(this, request_id));
[email protected]05a814182011-04-27 19:50:34153 entry->lister_.reset(new net::DirectoryLister(path,
154 true,
155 net::DirectoryLister::NO_SORT,
156 entry->delegate_.get()));
[email protected]600ea402011-04-12 00:01:51157 if (!entry->lister_->Start()) {
158 if (request_id == kFileSelectEnumerationId)
159 FileSelectionCanceled(NULL);
160 else
161 render_view_host->DirectoryEnumerationFinished(request_id,
162 entry->results_);
163 } else {
164 directory_enumerations_[request_id] = entry.release();
165 }
[email protected]ba70d082010-09-10 16:54:49166}
167
168void FileSelectHelper::OnListFile(
[email protected]600ea402011-04-12 00:01:51169 int id,
[email protected]ba70d082010-09-10 16:54:49170 const net::DirectoryLister::DirectoryListerData& data) {
[email protected]600ea402011-04-12 00:01:51171 ActiveDirectoryEnumeration* entry = directory_enumerations_[id];
172
[email protected]9897e092011-02-04 22:09:11173 // Directory upload returns directories via a "." file, so that
174 // empty directories are included. This util call just checks
[email protected]ba70d082010-09-10 16:54:49175 // the flags in the structure; there's no file I/O going on.
176 if (file_util::FileEnumerator::IsDirectory(data.info))
[email protected]600ea402011-04-12 00:01:51177 entry->results_.push_back(data.path.Append(FILE_PATH_LITERAL(".")));
[email protected]9897e092011-02-04 22:09:11178 else
[email protected]600ea402011-04-12 00:01:51179 entry->results_.push_back(data.path);
[email protected]ba70d082010-09-10 16:54:49180}
181
[email protected]600ea402011-04-12 00:01:51182void FileSelectHelper::OnListDone(int id, int error) {
183 // This entry needs to be cleaned up when this function is done.
184 scoped_ptr<ActiveDirectoryEnumeration> entry(directory_enumerations_[id]);
185 directory_enumerations_.erase(id);
186 if (!entry->rvh_)
[email protected]ba70d082010-09-10 16:54:49187 return;
[email protected]ba70d082010-09-10 16:54:49188 if (error) {
189 FileSelectionCanceled(NULL);
190 return;
191 }
[email protected]600ea402011-04-12 00:01:51192 if (id == kFileSelectEnumerationId)
[email protected]459fba82011-10-13 02:48:50193 NotifyRenderViewHost(entry->rvh_, entry->results_, dialog_type_);
[email protected]600ea402011-04-12 00:01:51194 else
195 entry->rvh_->DirectoryEnumerationFinished(id, entry->results_);
[email protected]9f054aa12011-09-29 19:13:45196
197 EnumerateDirectoryEnd();
[email protected]ba70d082010-09-10 16:54:49198}
199
200SelectFileDialog::FileTypeInfo* FileSelectHelper::GetFileTypesFromAcceptType(
[email protected]3314c2b12011-11-02 08:05:46201 const std::vector<string16>& accept_types) {
[email protected]ba70d082010-09-10 16:54:49202 if (accept_types.empty())
203 return NULL;
204
[email protected]ba70d082010-09-10 16:54:49205 // Create FileTypeInfo and pre-allocate for the first extension list.
206 scoped_ptr<SelectFileDialog::FileTypeInfo> file_type(
207 new SelectFileDialog::FileTypeInfo());
208 file_type->include_all_files = true;
209 file_type->extensions.resize(1);
210 std::vector<FilePath::StringType>* extensions = &file_type->extensions.back();
211
212 // Find the correspondinge extensions.
213 int valid_type_count = 0;
214 int description_id = 0;
[email protected]3314c2b12011-11-02 08:05:46215 for (size_t i = 0; i < accept_types.size(); ++i) {
216 std::string ascii_mime_type = UTF16ToASCII(accept_types[i]);
217 // WebKit normalizes MIME types. See HTMLInputElement::acceptMIMETypes().
218 DCHECK(StringToLowerASCII(ascii_mime_type) == ascii_mime_type)
219 << "A MIME type contains uppercase letter: " << ascii_mime_type;
220 DCHECK(TrimWhitespaceASCII(ascii_mime_type, TRIM_ALL, &ascii_mime_type)
221 == TRIM_NONE)
222 << "A MIME type contains whitespace: '" << ascii_mime_type << "'";
[email protected]ba70d082010-09-10 16:54:49223
224 size_t old_extension_size = extensions->size();
225 if (ascii_mime_type == "image/*") {
226 description_id = IDS_IMAGE_FILES;
227 net::GetImageExtensions(extensions);
228 } else if (ascii_mime_type == "audio/*") {
229 description_id = IDS_AUDIO_FILES;
230 net::GetAudioExtensions(extensions);
231 } else if (ascii_mime_type == "video/*") {
232 description_id = IDS_VIDEO_FILES;
233 net::GetVideoExtensions(extensions);
234 } else {
235 net::GetExtensionsForMimeType(ascii_mime_type, extensions);
236 }
237
238 if (extensions->size() > old_extension_size)
239 valid_type_count++;
240 }
241
[email protected]cbcd12ed2010-12-16 23:42:57242 // If no valid extension is added, bail out.
243 if (valid_type_count == 0)
244 return NULL;
245
[email protected]ba70d082010-09-10 16:54:49246 // Use a generic description "Custom Files" if either of the following is
247 // true:
248 // 1) There're multiple types specified, like "audio/*,video/*"
249 // 2) There're multiple extensions for a MIME type without parameter, like
250 // "ehtml,shtml,htm,html" for "text/html". On Windows, the select file
251 // dialog uses the first extension in the list to form the description,
252 // like "EHTML Files". This is not what we want.
253 if (valid_type_count > 1 ||
254 (valid_type_count == 1 && description_id == 0 && extensions->size() > 1))
255 description_id = IDS_CUSTOM_FILES;
256
257 if (description_id) {
258 file_type->extension_description_overrides.push_back(
259 l10n_util::GetStringUTF16(description_id));
260 }
261
262 return file_type.release();
263}
264
265void FileSelectHelper::RunFileChooser(
266 RenderViewHost* render_view_host,
[email protected]d9898912011-04-15 21:10:00267 TabContents* tab_contents,
[email protected]aaed2522011-03-11 18:50:54268 const ViewHostMsg_RunFileChooser_Params& params) {
[email protected]ba70d082010-09-10 16:54:49269 DCHECK(!render_view_host_);
[email protected]9f054aa12011-09-29 19:13:45270 DCHECK(!tab_contents_);
[email protected]ba70d082010-09-10 16:54:49271 render_view_host_ = render_view_host;
[email protected]9f054aa12011-09-29 19:13:45272 tab_contents_ = tab_contents;
[email protected]ba70d082010-09-10 16:54:49273 notification_registrar_.RemoveAll();
[email protected]432115822011-07-10 15:52:27274 notification_registrar_.Add(
275 this, content::NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
[email protected]6c2381d2011-10-19 02:52:53276 content::Source<RenderWidgetHost>(render_view_host_));
[email protected]9f054aa12011-09-29 19:13:45277 notification_registrar_.Add(
278 this, content::NOTIFICATION_TAB_CONTENTS_DESTROYED,
[email protected]6c2381d2011-10-19 02:52:53279 content::Source<TabContents>(tab_contents_));
[email protected]9f054aa12011-09-29 19:13:45280
281 BrowserThread::PostTask(
282 BrowserThread::FILE, FROM_HERE,
283 base::Bind(&FileSelectHelper::RunFileChooserOnFileThread, this, params));
284
285 // Because this class returns notifications to the RenderViewHost, it is
286 // difficult for callers to know how long to keep a reference to this
287 // instance. We AddRef() here to keep the instance alive after we return
288 // to the caller, until the last callback is received from the file dialog.
289 // At that point, we must call RunFileChooserEnd().
290 AddRef();
291}
292
293void FileSelectHelper::RunFileChooserOnFileThread(
294 const ViewHostMsg_RunFileChooser_Params& params) {
295 select_file_types_.reset(
296 GetFileTypesFromAcceptType(params.accept_types));
297
298 BrowserThread::PostTask(
299 BrowserThread::UI, FROM_HERE,
300 base::Bind(&FileSelectHelper::RunFileChooserOnUIThread, this, params));
301}
302
303void FileSelectHelper::RunFileChooserOnUIThread(
304 const ViewHostMsg_RunFileChooser_Params& params) {
305 if (!render_view_host_ || !tab_contents_)
306 return;
[email protected]ba70d082010-09-10 16:54:49307
308 if (!select_file_dialog_.get())
309 select_file_dialog_ = SelectFileDialog::Create(this);
310
311 switch (params.mode) {
[email protected]0aed2f52011-03-23 18:06:36312 case ViewHostMsg_RunFileChooser_Mode::Open:
[email protected]ba70d082010-09-10 16:54:49313 dialog_type_ = SelectFileDialog::SELECT_OPEN_FILE;
314 break;
[email protected]0aed2f52011-03-23 18:06:36315 case ViewHostMsg_RunFileChooser_Mode::OpenMultiple:
[email protected]ba70d082010-09-10 16:54:49316 dialog_type_ = SelectFileDialog::SELECT_OPEN_MULTI_FILE;
317 break;
[email protected]0aed2f52011-03-23 18:06:36318 case ViewHostMsg_RunFileChooser_Mode::OpenFolder:
[email protected]ba70d082010-09-10 16:54:49319 dialog_type_ = SelectFileDialog::SELECT_FOLDER;
320 break;
[email protected]0aed2f52011-03-23 18:06:36321 case ViewHostMsg_RunFileChooser_Mode::Save:
[email protected]ba70d082010-09-10 16:54:49322 dialog_type_ = SelectFileDialog::SELECT_SAVEAS_FILE;
323 break;
324 default:
325 dialog_type_ = SelectFileDialog::SELECT_OPEN_FILE; // Prevent warning.
326 NOTREACHED();
327 }
[email protected]ba70d082010-09-10 16:54:49328 FilePath default_file_name = params.default_file_name;
329 if (default_file_name.empty())
330 default_file_name = profile_->last_selected_directory();
331
332 gfx::NativeWindow owning_window =
333 platform_util::GetTopLevel(render_view_host_->view()->GetNativeView());
[email protected]d9898912011-04-15 21:10:00334
[email protected]9f054aa12011-09-29 19:13:45335 select_file_dialog_->SelectFile(
336 dialog_type_,
337 params.title,
338 default_file_name,
339 select_file_types_.get(),
340 select_file_types_.get() ? 1 : 0, // 1-based index.
341 FILE_PATH_LITERAL(""),
342 tab_contents_,
343 owning_window,
344 NULL);
345
346 select_file_types_.reset();
347}
348
349// This method is called when we receive the last callback from the file
350// chooser dialog. Perform any cleanup and release the reference we added
351// in RunFileChooser().
352void FileSelectHelper::RunFileChooserEnd() {
353 render_view_host_ = NULL;
354 tab_contents_ = NULL;
355 Release();
[email protected]ba70d082010-09-10 16:54:49356}
357
[email protected]600ea402011-04-12 00:01:51358void FileSelectHelper::EnumerateDirectory(int request_id,
359 RenderViewHost* render_view_host,
360 const FilePath& path) {
361 DCHECK_NE(kFileSelectEnumerationId, request_id);
[email protected]9f054aa12011-09-29 19:13:45362
363 // Because this class returns notifications to the RenderViewHost, it is
364 // difficult for callers to know how long to keep a reference to this
365 // instance. We AddRef() here to keep the instance alive after we return
366 // to the caller, until the last callback is received from the enumeration
367 // code. At that point, we must call EnumerateDirectoryEnd().
368 AddRef();
[email protected]600ea402011-04-12 00:01:51369 StartNewEnumeration(path, request_id, render_view_host);
370}
371
[email protected]9f054aa12011-09-29 19:13:45372// This method is called when we receive the last callback from the enumeration
373// code. Perform any cleanup and release the reference we added in
374// EnumerateDirectory().
375void FileSelectHelper::EnumerateDirectoryEnd() {
376 Release();
377}
378
[email protected]432115822011-07-10 15:52:27379void FileSelectHelper::Observe(int type,
[email protected]6c2381d2011-10-19 02:52:53380 const content::NotificationSource& source,
381 const content::NotificationDetails& details) {
[email protected]9f054aa12011-09-29 19:13:45382 switch (type) {
383 case content::NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED: {
[email protected]6c2381d2011-10-19 02:52:53384 DCHECK(content::Source<RenderWidgetHost>(source).ptr() ==
385 render_view_host_);
[email protected]9f054aa12011-09-29 19:13:45386 render_view_host_ = NULL;
387 break;
388 }
389
390 case content::NOTIFICATION_TAB_CONTENTS_DESTROYED: {
[email protected]6c2381d2011-10-19 02:52:53391 DCHECK(content::Source<TabContents>(source).ptr() == tab_contents_);
[email protected]9f054aa12011-09-29 19:13:45392 tab_contents_ = NULL;
393 break;
394 }
395
396 default:
397 NOTREACHED();
398 }
[email protected]ba70d082010-09-10 16:54:49399}
[email protected]aaed2522011-03-11 18:50:54400