blob: 2bc5b03d31d3d9f922b873e9b16a869f9d14991a [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]fb11b6a42012-03-14 07:25:1225#include "content/public/common/selected_file_info.h"
[email protected]ba70d082010-09-10 16:54:4926#include "grit/generated_resources.h"
[email protected]b3841c502011-03-09 01:21:3127#include "net/base/mime_util.h"
[email protected]c051a1b2011-01-21 23:30:1728#include "ui/base/l10n/l10n_util.h"
[email protected]ba70d082010-09-10 16:54:4929
[email protected]631bb742011-11-02 11:29:3930using content::BrowserThread;
[email protected]eaabba22012-03-07 15:02:1131using content::RenderViewHost;
32using content::RenderWidgetHost;
[email protected]ea049a02011-12-25 21:37:0933using content::WebContents;
[email protected]631bb742011-11-02 11:29:3934
[email protected]600ea402011-04-12 00:01:5135namespace {
36
37// There is only one file-selection happening at any given time,
38// so we allocate an enumeration ID for that purpose. All IDs from
39// the renderer must start at 0 and increase.
[email protected]459fba82011-10-13 02:48:5040const int kFileSelectEnumerationId = -1;
41
42void NotifyRenderViewHost(RenderViewHost* render_view_host,
[email protected]fb11b6a42012-03-14 07:25:1243 const std::vector<content::SelectedFileInfo>& files,
[email protected]459fba82011-10-13 02:48:5044 SelectFileDialog::Type dialog_type) {
45 const int kReadFilePermissions =
46 base::PLATFORM_FILE_OPEN |
47 base::PLATFORM_FILE_READ |
48 base::PLATFORM_FILE_EXCLUSIVE_READ |
49 base::PLATFORM_FILE_ASYNC;
50
51 const int kWriteFilePermissions =
[email protected]3c688fac2011-10-14 02:29:1452 base::PLATFORM_FILE_CREATE |
53 base::PLATFORM_FILE_CREATE_ALWAYS |
54 base::PLATFORM_FILE_OPEN |
[email protected]459fba82011-10-13 02:48:5055 base::PLATFORM_FILE_OPEN_ALWAYS |
[email protected]3c688fac2011-10-14 02:29:1456 base::PLATFORM_FILE_OPEN_TRUNCATED |
[email protected]459fba82011-10-13 02:48:5057 base::PLATFORM_FILE_WRITE |
58 base::PLATFORM_FILE_WRITE_ATTRIBUTES |
59 base::PLATFORM_FILE_ASYNC;
60
61 int permissions = kReadFilePermissions;
62 if (dialog_type == SelectFileDialog::SELECT_SAVEAS_FILE)
63 permissions = kWriteFilePermissions;
64 render_view_host->FilesSelectedInChooser(files, permissions);
65}
[email protected]fb11b6a42012-03-14 07:25:1266
67// Converts a list of FilePaths to a list of SelectedFileInfo, with the
68// display name field left empty.
69std::vector<content::SelectedFileInfo> ConvertToSelectedFileInfoList(
70 std::vector<FilePath> paths) {
71 std::vector<content::SelectedFileInfo> selected_files;
72 for (size_t i = 0; i < paths.size(); ++i) {
73 selected_files.push_back(
74 content::SelectedFileInfo(paths[i], FilePath::StringType()));
75 }
76 return selected_files;
[email protected]600ea402011-04-12 00:01:5177}
78
[email protected]fb11b6a42012-03-14 07:25:1279} // namespace
80
[email protected]485a5272011-04-12 00:49:2981struct FileSelectHelper::ActiveDirectoryEnumeration {
[email protected]d45f7512011-06-21 21:18:2782 ActiveDirectoryEnumeration() : rvh_(NULL) {}
[email protected]485a5272011-04-12 00:49:2983
84 scoped_ptr<DirectoryListerDispatchDelegate> delegate_;
[email protected]05a814182011-04-27 19:50:3485 scoped_ptr<net::DirectoryLister> lister_;
[email protected]485a5272011-04-12 00:49:2986 RenderViewHost* rvh_;
87 std::vector<FilePath> results_;
88};
89
[email protected]ba70d082010-09-10 16:54:4990FileSelectHelper::FileSelectHelper(Profile* profile)
91 : profile_(profile),
92 render_view_host_(NULL),
[email protected]ea049a02011-12-25 21:37:0993 web_contents_(NULL),
[email protected]ba70d082010-09-10 16:54:4994 select_file_dialog_(),
[email protected]9f054aa12011-09-29 19:13:4595 select_file_types_(),
[email protected]ba70d082010-09-10 16:54:4996 dialog_type_(SelectFileDialog::SELECT_OPEN_FILE) {
97}
98
99FileSelectHelper::~FileSelectHelper() {
100 // There may be pending file dialogs, we need to tell them that we've gone
101 // away so they don't try and call back to us.
102 if (select_file_dialog_.get())
103 select_file_dialog_->ListenerDestroyed();
104
[email protected]600ea402011-04-12 00:01:51105 // Stop any pending directory enumeration, prevent a callback, and free
106 // allocated memory.
107 std::map<int, ActiveDirectoryEnumeration*>::iterator iter;
108 for (iter = directory_enumerations_.begin();
109 iter != directory_enumerations_.end();
110 ++iter) {
[email protected]05a814182011-04-27 19:50:34111 iter->second->lister_.reset();
[email protected]600ea402011-04-12 00:01:51112 delete iter->second;
[email protected]ba70d082010-09-10 16:54:49113 }
114}
115
116void FileSelectHelper::FileSelected(const FilePath& path,
117 int index, void* params) {
[email protected]fb11b6a42012-03-14 07:25:12118 FileSelectedWithExtraInfo(
119 content::SelectedFileInfo(path, FilePath::StringType()),
120 index, params);
121}
122
123void FileSelectHelper::FileSelectedWithExtraInfo(
124 const content::SelectedFileInfo& file,
125 int index,
126 void* params) {
[email protected]ba70d082010-09-10 16:54:49127 if (!render_view_host_)
128 return;
129
[email protected]fb11b6a42012-03-14 07:25:12130 const FilePath& path = file.path;
[email protected]ba70d082010-09-10 16:54:49131 profile_->set_last_selected_directory(path.DirName());
132
133 if (dialog_type_ == SelectFileDialog::SELECT_FOLDER) {
[email protected]600ea402011-04-12 00:01:51134 StartNewEnumeration(path, kFileSelectEnumerationId, render_view_host_);
[email protected]ba70d082010-09-10 16:54:49135 return;
136 }
137
[email protected]fb11b6a42012-03-14 07:25:12138 std::vector<content::SelectedFileInfo> files;
139 files.push_back(file);
[email protected]459fba82011-10-13 02:48:50140 NotifyRenderViewHost(render_view_host_, files, dialog_type_);
[email protected]9f054aa12011-09-29 19:13:45141
[email protected]3a29a6e2011-08-24 18:26:21142 // No members should be accessed from here on.
[email protected]9f054aa12011-09-29 19:13:45143 RunFileChooserEnd();
[email protected]ba70d082010-09-10 16:54:49144}
145
146void FileSelectHelper::MultiFilesSelected(const std::vector<FilePath>& files,
147 void* params) {
[email protected]fb11b6a42012-03-14 07:25:12148 std::vector<content::SelectedFileInfo> selected_files =
149 ConvertToSelectedFileInfoList(files);
150 MultiFilesSelectedWithExtraInfo(selected_files, params);
151}
152
153void FileSelectHelper::MultiFilesSelectedWithExtraInfo(
154 const std::vector<content::SelectedFileInfo>& files,
155 void* params) {
[email protected]ba70d082010-09-10 16:54:49156 if (!files.empty())
[email protected]fb11b6a42012-03-14 07:25:12157 profile_->set_last_selected_directory(files[0].path.DirName());
[email protected]ba70d082010-09-10 16:54:49158 if (!render_view_host_)
159 return;
160
[email protected]459fba82011-10-13 02:48:50161 NotifyRenderViewHost(render_view_host_, files, dialog_type_);
[email protected]9f054aa12011-09-29 19:13:45162
[email protected]3a29a6e2011-08-24 18:26:21163 // No members should be accessed from here on.
[email protected]9f054aa12011-09-29 19:13:45164 RunFileChooserEnd();
[email protected]ba70d082010-09-10 16:54:49165}
166
167void FileSelectHelper::FileSelectionCanceled(void* params) {
168 if (!render_view_host_)
169 return;
170
171 // If the user cancels choosing a file to upload we pass back an
172 // empty vector.
[email protected]459fba82011-10-13 02:48:50173 NotifyRenderViewHost(
[email protected]fb11b6a42012-03-14 07:25:12174 render_view_host_, std::vector<content::SelectedFileInfo>(),
175 dialog_type_);
[email protected]ba70d082010-09-10 16:54:49176
[email protected]3a29a6e2011-08-24 18:26:21177 // No members should be accessed from here on.
[email protected]9f054aa12011-09-29 19:13:45178 RunFileChooserEnd();
[email protected]ba70d082010-09-10 16:54:49179}
180
[email protected]600ea402011-04-12 00:01:51181void FileSelectHelper::StartNewEnumeration(const FilePath& path,
182 int request_id,
183 RenderViewHost* render_view_host) {
184 scoped_ptr<ActiveDirectoryEnumeration> entry(new ActiveDirectoryEnumeration);
185 entry->rvh_ = render_view_host;
186 entry->delegate_.reset(new DirectoryListerDispatchDelegate(this, request_id));
[email protected]05a814182011-04-27 19:50:34187 entry->lister_.reset(new net::DirectoryLister(path,
188 true,
189 net::DirectoryLister::NO_SORT,
190 entry->delegate_.get()));
[email protected]600ea402011-04-12 00:01:51191 if (!entry->lister_->Start()) {
192 if (request_id == kFileSelectEnumerationId)
193 FileSelectionCanceled(NULL);
194 else
195 render_view_host->DirectoryEnumerationFinished(request_id,
196 entry->results_);
197 } else {
198 directory_enumerations_[request_id] = entry.release();
199 }
[email protected]ba70d082010-09-10 16:54:49200}
201
202void FileSelectHelper::OnListFile(
[email protected]600ea402011-04-12 00:01:51203 int id,
[email protected]ba70d082010-09-10 16:54:49204 const net::DirectoryLister::DirectoryListerData& data) {
[email protected]600ea402011-04-12 00:01:51205 ActiveDirectoryEnumeration* entry = directory_enumerations_[id];
206
[email protected]9897e092011-02-04 22:09:11207 // Directory upload returns directories via a "." file, so that
208 // empty directories are included. This util call just checks
[email protected]ba70d082010-09-10 16:54:49209 // the flags in the structure; there's no file I/O going on.
210 if (file_util::FileEnumerator::IsDirectory(data.info))
[email protected]600ea402011-04-12 00:01:51211 entry->results_.push_back(data.path.Append(FILE_PATH_LITERAL(".")));
[email protected]9897e092011-02-04 22:09:11212 else
[email protected]600ea402011-04-12 00:01:51213 entry->results_.push_back(data.path);
[email protected]ba70d082010-09-10 16:54:49214}
215
[email protected]600ea402011-04-12 00:01:51216void FileSelectHelper::OnListDone(int id, int error) {
217 // This entry needs to be cleaned up when this function is done.
218 scoped_ptr<ActiveDirectoryEnumeration> entry(directory_enumerations_[id]);
219 directory_enumerations_.erase(id);
220 if (!entry->rvh_)
[email protected]ba70d082010-09-10 16:54:49221 return;
[email protected]ba70d082010-09-10 16:54:49222 if (error) {
223 FileSelectionCanceled(NULL);
224 return;
225 }
[email protected]fb11b6a42012-03-14 07:25:12226
227 std::vector<content::SelectedFileInfo> selected_files =
228 ConvertToSelectedFileInfoList(entry->results_);
229
[email protected]600ea402011-04-12 00:01:51230 if (id == kFileSelectEnumerationId)
[email protected]fb11b6a42012-03-14 07:25:12231 NotifyRenderViewHost(entry->rvh_, selected_files, dialog_type_);
[email protected]600ea402011-04-12 00:01:51232 else
233 entry->rvh_->DirectoryEnumerationFinished(id, entry->results_);
[email protected]9f054aa12011-09-29 19:13:45234
235 EnumerateDirectoryEnd();
[email protected]ba70d082010-09-10 16:54:49236}
237
238SelectFileDialog::FileTypeInfo* FileSelectHelper::GetFileTypesFromAcceptType(
[email protected]3314c2b12011-11-02 08:05:46239 const std::vector<string16>& accept_types) {
[email protected]ba70d082010-09-10 16:54:49240 if (accept_types.empty())
241 return NULL;
242
[email protected]ba70d082010-09-10 16:54:49243 // Create FileTypeInfo and pre-allocate for the first extension list.
244 scoped_ptr<SelectFileDialog::FileTypeInfo> file_type(
245 new SelectFileDialog::FileTypeInfo());
246 file_type->include_all_files = true;
247 file_type->extensions.resize(1);
248 std::vector<FilePath::StringType>* extensions = &file_type->extensions.back();
249
250 // Find the correspondinge extensions.
251 int valid_type_count = 0;
252 int description_id = 0;
[email protected]3314c2b12011-11-02 08:05:46253 for (size_t i = 0; i < accept_types.size(); ++i) {
254 std::string ascii_mime_type = UTF16ToASCII(accept_types[i]);
255 // WebKit normalizes MIME types. See HTMLInputElement::acceptMIMETypes().
256 DCHECK(StringToLowerASCII(ascii_mime_type) == ascii_mime_type)
257 << "A MIME type contains uppercase letter: " << ascii_mime_type;
258 DCHECK(TrimWhitespaceASCII(ascii_mime_type, TRIM_ALL, &ascii_mime_type)
259 == TRIM_NONE)
260 << "A MIME type contains whitespace: '" << ascii_mime_type << "'";
[email protected]ba70d082010-09-10 16:54:49261
262 size_t old_extension_size = extensions->size();
263 if (ascii_mime_type == "image/*") {
264 description_id = IDS_IMAGE_FILES;
265 net::GetImageExtensions(extensions);
266 } else if (ascii_mime_type == "audio/*") {
267 description_id = IDS_AUDIO_FILES;
268 net::GetAudioExtensions(extensions);
269 } else if (ascii_mime_type == "video/*") {
270 description_id = IDS_VIDEO_FILES;
271 net::GetVideoExtensions(extensions);
272 } else {
273 net::GetExtensionsForMimeType(ascii_mime_type, extensions);
274 }
275
276 if (extensions->size() > old_extension_size)
277 valid_type_count++;
278 }
279
[email protected]cbcd12ed2010-12-16 23:42:57280 // If no valid extension is added, bail out.
281 if (valid_type_count == 0)
282 return NULL;
283
[email protected]ba70d082010-09-10 16:54:49284 // Use a generic description "Custom Files" if either of the following is
285 // true:
286 // 1) There're multiple types specified, like "audio/*,video/*"
287 // 2) There're multiple extensions for a MIME type without parameter, like
288 // "ehtml,shtml,htm,html" for "text/html". On Windows, the select file
289 // dialog uses the first extension in the list to form the description,
290 // like "EHTML Files". This is not what we want.
291 if (valid_type_count > 1 ||
292 (valid_type_count == 1 && description_id == 0 && extensions->size() > 1))
293 description_id = IDS_CUSTOM_FILES;
294
295 if (description_id) {
296 file_type->extension_description_overrides.push_back(
297 l10n_util::GetStringUTF16(description_id));
298 }
299
300 return file_type.release();
301}
302
303void FileSelectHelper::RunFileChooser(
304 RenderViewHost* render_view_host,
[email protected]ea049a02011-12-25 21:37:09305 content::WebContents* web_contents,
[email protected]8caadeb2011-11-22 02:45:23306 const content::FileChooserParams& params) {
[email protected]ba70d082010-09-10 16:54:49307 DCHECK(!render_view_host_);
[email protected]ea049a02011-12-25 21:37:09308 DCHECK(!web_contents_);
[email protected]ba70d082010-09-10 16:54:49309 render_view_host_ = render_view_host;
[email protected]ea049a02011-12-25 21:37:09310 web_contents_ = web_contents;
[email protected]ba70d082010-09-10 16:54:49311 notification_registrar_.RemoveAll();
[email protected]432115822011-07-10 15:52:27312 notification_registrar_.Add(
313 this, content::NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
[email protected]6c2381d2011-10-19 02:52:53314 content::Source<RenderWidgetHost>(render_view_host_));
[email protected]9f054aa12011-09-29 19:13:45315 notification_registrar_.Add(
[email protected]ea049a02011-12-25 21:37:09316 this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
317 content::Source<WebContents>(web_contents_));
[email protected]9f054aa12011-09-29 19:13:45318
319 BrowserThread::PostTask(
320 BrowserThread::FILE, FROM_HERE,
321 base::Bind(&FileSelectHelper::RunFileChooserOnFileThread, this, params));
322
323 // Because this class returns notifications to the RenderViewHost, it is
324 // difficult for callers to know how long to keep a reference to this
325 // instance. We AddRef() here to keep the instance alive after we return
326 // to the caller, until the last callback is received from the file dialog.
327 // At that point, we must call RunFileChooserEnd().
328 AddRef();
329}
330
331void FileSelectHelper::RunFileChooserOnFileThread(
[email protected]8caadeb2011-11-22 02:45:23332 const content::FileChooserParams& params) {
[email protected]9f054aa12011-09-29 19:13:45333 select_file_types_.reset(
334 GetFileTypesFromAcceptType(params.accept_types));
335
336 BrowserThread::PostTask(
337 BrowserThread::UI, FROM_HERE,
338 base::Bind(&FileSelectHelper::RunFileChooserOnUIThread, this, params));
339}
340
341void FileSelectHelper::RunFileChooserOnUIThread(
[email protected]8caadeb2011-11-22 02:45:23342 const content::FileChooserParams& params) {
[email protected]ea049a02011-12-25 21:37:09343 if (!render_view_host_ || !web_contents_) {
[email protected]b95b08d2011-12-15 20:23:16344 // If the renderer was destroyed before we started, just cancel the
345 // operation.
346 RunFileChooserEnd();
[email protected]9f054aa12011-09-29 19:13:45347 return;
[email protected]b95b08d2011-12-15 20:23:16348 }
[email protected]ba70d082010-09-10 16:54:49349
350 if (!select_file_dialog_.get())
351 select_file_dialog_ = SelectFileDialog::Create(this);
352
353 switch (params.mode) {
[email protected]8caadeb2011-11-22 02:45:23354 case content::FileChooserParams::Open:
[email protected]ba70d082010-09-10 16:54:49355 dialog_type_ = SelectFileDialog::SELECT_OPEN_FILE;
356 break;
[email protected]8caadeb2011-11-22 02:45:23357 case content::FileChooserParams::OpenMultiple:
[email protected]ba70d082010-09-10 16:54:49358 dialog_type_ = SelectFileDialog::SELECT_OPEN_MULTI_FILE;
359 break;
[email protected]8caadeb2011-11-22 02:45:23360 case content::FileChooserParams::OpenFolder:
[email protected]ba70d082010-09-10 16:54:49361 dialog_type_ = SelectFileDialog::SELECT_FOLDER;
362 break;
[email protected]8caadeb2011-11-22 02:45:23363 case content::FileChooserParams::Save:
[email protected]ba70d082010-09-10 16:54:49364 dialog_type_ = SelectFileDialog::SELECT_SAVEAS_FILE;
365 break;
366 default:
367 dialog_type_ = SelectFileDialog::SELECT_OPEN_FILE; // Prevent warning.
368 NOTREACHED();
369 }
[email protected]ba70d082010-09-10 16:54:49370 FilePath default_file_name = params.default_file_name;
371 if (default_file_name.empty())
372 default_file_name = profile_->last_selected_directory();
373
374 gfx::NativeWindow owning_window =
[email protected]9f76c1e2012-03-05 15:15:58375 platform_util::GetTopLevel(render_view_host_->GetView()->GetNativeView());
[email protected]d9898912011-04-15 21:10:00376
[email protected]9f054aa12011-09-29 19:13:45377 select_file_dialog_->SelectFile(
378 dialog_type_,
379 params.title,
380 default_file_name,
381 select_file_types_.get(),
382 select_file_types_.get() ? 1 : 0, // 1-based index.
383 FILE_PATH_LITERAL(""),
[email protected]ea049a02011-12-25 21:37:09384 web_contents_,
[email protected]9f054aa12011-09-29 19:13:45385 owning_window,
386 NULL);
387
388 select_file_types_.reset();
389}
390
391// This method is called when we receive the last callback from the file
392// chooser dialog. Perform any cleanup and release the reference we added
393// in RunFileChooser().
394void FileSelectHelper::RunFileChooserEnd() {
395 render_view_host_ = NULL;
[email protected]ea049a02011-12-25 21:37:09396 web_contents_ = NULL;
[email protected]9f054aa12011-09-29 19:13:45397 Release();
[email protected]ba70d082010-09-10 16:54:49398}
399
[email protected]600ea402011-04-12 00:01:51400void FileSelectHelper::EnumerateDirectory(int request_id,
401 RenderViewHost* render_view_host,
402 const FilePath& path) {
403 DCHECK_NE(kFileSelectEnumerationId, request_id);
[email protected]9f054aa12011-09-29 19:13:45404
405 // Because this class returns notifications to the RenderViewHost, it is
406 // difficult for callers to know how long to keep a reference to this
407 // instance. We AddRef() here to keep the instance alive after we return
408 // to the caller, until the last callback is received from the enumeration
409 // code. At that point, we must call EnumerateDirectoryEnd().
410 AddRef();
[email protected]600ea402011-04-12 00:01:51411 StartNewEnumeration(path, request_id, render_view_host);
412}
413
[email protected]9f054aa12011-09-29 19:13:45414// This method is called when we receive the last callback from the enumeration
415// code. Perform any cleanup and release the reference we added in
416// EnumerateDirectory().
417void FileSelectHelper::EnumerateDirectoryEnd() {
418 Release();
419}
420
[email protected]432115822011-07-10 15:52:27421void FileSelectHelper::Observe(int type,
[email protected]6c2381d2011-10-19 02:52:53422 const content::NotificationSource& source,
423 const content::NotificationDetails& details) {
[email protected]9f054aa12011-09-29 19:13:45424 switch (type) {
425 case content::NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED: {
[email protected]6c2381d2011-10-19 02:52:53426 DCHECK(content::Source<RenderWidgetHost>(source).ptr() ==
427 render_view_host_);
[email protected]9f054aa12011-09-29 19:13:45428 render_view_host_ = NULL;
429 break;
430 }
431
[email protected]ea049a02011-12-25 21:37:09432 case content::NOTIFICATION_WEB_CONTENTS_DESTROYED: {
433 DCHECK(content::Source<WebContents>(source).ptr() == web_contents_);
434 web_contents_ = NULL;
[email protected]9f054aa12011-09-29 19:13:45435 break;
436 }
437
438 default:
439 NOTREACHED();
440 }
[email protected]ba70d082010-09-10 16:54:49441}