Add a minor text member to ui::MenuModel.
[chromium-blink-merge.git] / chrome / browser / ui / views / select_file_dialog_extension.cc
blob1f74de0681d9c84c33be71c4d53d4eb236a2afbe
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.
5 #include "chrome/browser/ui/views/select_file_dialog_extension.h"
7 #include "apps/native_app_window.h"
8 #include "apps/shell_window.h"
9 #include "apps/shell_window_registry.h"
10 #include "base/bind.h"
11 #include "base/callback.h"
12 #include "base/logging.h"
13 #include "base/memory/ref_counted.h"
14 #include "base/memory/singleton.h"
15 #include "base/message_loop/message_loop.h"
16 #include "chrome/browser/chromeos/extensions/file_manager/app_id.h"
17 #include "chrome/browser/chromeos/extensions/file_manager/fileapi_util.h"
18 #include "chrome/browser/chromeos/extensions/file_manager/select_file_dialog_util.h"
19 #include "chrome/browser/chromeos/extensions/file_manager/url_util.h"
20 #include "chrome/browser/extensions/extension_host.h"
21 #include "chrome/browser/extensions/extension_service.h"
22 #include "chrome/browser/extensions/extension_system.h"
23 #include "chrome/browser/profiles/profile.h"
24 #include "chrome/browser/sessions/session_tab_helper.h"
25 #include "chrome/browser/ui/browser.h"
26 #include "chrome/browser/ui/browser_finder.h"
27 #include "chrome/browser/ui/browser_list.h"
28 #include "chrome/browser/ui/browser_window.h"
29 #include "chrome/browser/ui/chrome_select_file_policy.h"
30 #include "chrome/browser/ui/host_desktop.h"
31 #include "chrome/browser/ui/tabs/tab_strip_model.h"
32 #include "chrome/browser/ui/views/extensions/extension_dialog.h"
33 #include "chrome/common/pref_names.h"
34 #include "content/public/browser/browser_thread.h"
35 #include "ui/base/base_window.h"
36 #include "ui/shell_dialogs/selected_file_info.h"
37 #include "ui/views/widget/widget.h"
39 using apps::ShellWindow;
40 using content::BrowserThread;
42 namespace {
44 const int kFileManagerWidth = 972; // pixels
45 const int kFileManagerHeight = 640; // pixels
47 // Holds references to file manager dialogs that have callbacks pending
48 // to their listeners.
49 class PendingDialog {
50 public:
51 static PendingDialog* GetInstance();
52 void Add(int32 tab_id, scoped_refptr<SelectFileDialogExtension> dialog);
53 void Remove(int32 tab_id);
54 scoped_refptr<SelectFileDialogExtension> Find(int32 tab_id);
56 private:
57 friend struct DefaultSingletonTraits<PendingDialog>;
58 typedef std::map<int32, scoped_refptr<SelectFileDialogExtension> > Map;
59 Map map_;
62 // static
63 PendingDialog* PendingDialog::GetInstance() {
64 return Singleton<PendingDialog>::get();
67 void PendingDialog::Add(int32 tab_id,
68 scoped_refptr<SelectFileDialogExtension> dialog) {
69 DCHECK(dialog.get());
70 if (map_.find(tab_id) == map_.end())
71 map_.insert(std::make_pair(tab_id, dialog));
72 else
73 DLOG(WARNING) << "Duplicate pending dialog " << tab_id;
76 void PendingDialog::Remove(int32 tab_id) {
77 map_.erase(tab_id);
80 scoped_refptr<SelectFileDialogExtension> PendingDialog::Find(int32 tab_id) {
81 Map::const_iterator it = map_.find(tab_id);
82 if (it == map_.end())
83 return NULL;
84 return it->second;
87 } // namespace
89 /////////////////////////////////////////////////////////////////////////////
91 // TODO(jamescook): Move this into a new file shell_dialogs_chromeos.cc
92 // static
93 SelectFileDialogExtension* SelectFileDialogExtension::Create(
94 Listener* listener,
95 ui::SelectFilePolicy* policy) {
96 return new SelectFileDialogExtension(listener, policy);
99 SelectFileDialogExtension::SelectFileDialogExtension(
100 Listener* listener,
101 ui::SelectFilePolicy* policy)
102 : SelectFileDialog(listener, policy),
103 has_multiple_file_type_choices_(false),
104 tab_id_(0),
105 profile_(NULL),
106 owner_window_(NULL),
107 selection_type_(CANCEL),
108 selection_index_(0),
109 params_(NULL) {
112 SelectFileDialogExtension::~SelectFileDialogExtension() {
113 if (extension_dialog_.get())
114 extension_dialog_->ObserverDestroyed();
117 bool SelectFileDialogExtension::IsRunning(
118 gfx::NativeWindow owner_window) const {
119 return owner_window_ == owner_window;
122 void SelectFileDialogExtension::ListenerDestroyed() {
123 listener_ = NULL;
124 params_ = NULL;
125 PendingDialog::GetInstance()->Remove(tab_id_);
128 void SelectFileDialogExtension::ExtensionDialogClosing(
129 ExtensionDialog* /*dialog*/) {
130 profile_ = NULL;
131 owner_window_ = NULL;
132 // Release our reference to the underlying dialog to allow it to close.
133 extension_dialog_ = NULL;
134 PendingDialog::GetInstance()->Remove(tab_id_);
135 // Actually invoke the appropriate callback on our listener.
136 NotifyListener();
139 void SelectFileDialogExtension::ExtensionTerminated(
140 ExtensionDialog* dialog) {
141 // The extension would have been unloaded because of the termination,
142 // reload it.
143 std::string extension_id = dialog->host()->extension()->id();
144 // Reload the extension after a bit; the extension may not have been unloaded
145 // yet. We don't want to try to reload the extension only to have the Unload
146 // code execute after us and re-unload the extension.
148 // TODO(rkc): This is ugly. The ideal solution is that we shouldn't need to
149 // reload the extension at all - when we try to open the extension the next
150 // time, the extension subsystem would automatically reload it for us. At
151 // this time though this is broken because of some faulty wiring in
152 // ExtensionProcessManager::CreateViewHost. Once that is fixed, remove this.
153 if (profile_) {
154 base::MessageLoop::current()->PostTask(
155 FROM_HERE,
156 base::Bind(&ExtensionService::ReloadExtension,
157 base::Unretained(extensions::ExtensionSystem::Get(profile_)
158 ->extension_service()),
159 extension_id));
162 dialog->GetWidget()->Close();
165 // static
166 void SelectFileDialogExtension::OnFileSelected(
167 int32 tab_id,
168 const ui::SelectedFileInfo& file,
169 int index) {
170 scoped_refptr<SelectFileDialogExtension> dialog =
171 PendingDialog::GetInstance()->Find(tab_id);
172 if (!dialog.get())
173 return;
174 dialog->selection_type_ = SINGLE_FILE;
175 dialog->selection_files_.clear();
176 dialog->selection_files_.push_back(file);
177 dialog->selection_index_ = index;
180 // static
181 void SelectFileDialogExtension::OnMultiFilesSelected(
182 int32 tab_id,
183 const std::vector<ui::SelectedFileInfo>& files) {
184 scoped_refptr<SelectFileDialogExtension> dialog =
185 PendingDialog::GetInstance()->Find(tab_id);
186 if (!dialog.get())
187 return;
188 dialog->selection_type_ = MULTIPLE_FILES;
189 dialog->selection_files_ = files;
190 dialog->selection_index_ = 0;
193 // static
194 void SelectFileDialogExtension::OnFileSelectionCanceled(int32 tab_id) {
195 scoped_refptr<SelectFileDialogExtension> dialog =
196 PendingDialog::GetInstance()->Find(tab_id);
197 if (!dialog.get())
198 return;
199 dialog->selection_type_ = CANCEL;
200 dialog->selection_files_.clear();
201 dialog->selection_index_ = 0;
204 content::RenderViewHost* SelectFileDialogExtension::GetRenderViewHost() {
205 if (extension_dialog_.get())
206 return extension_dialog_->host()->render_view_host();
207 return NULL;
210 void SelectFileDialogExtension::NotifyListener() {
211 if (!listener_)
212 return;
213 switch (selection_type_) {
214 case CANCEL:
215 listener_->FileSelectionCanceled(params_);
216 break;
217 case SINGLE_FILE:
218 listener_->FileSelectedWithExtraInfo(selection_files_[0],
219 selection_index_,
220 params_);
221 break;
222 case MULTIPLE_FILES:
223 listener_->MultiFilesSelectedWithExtraInfo(selection_files_, params_);
224 break;
225 default:
226 NOTREACHED();
227 break;
231 void SelectFileDialogExtension::AddPending(int32 tab_id) {
232 PendingDialog::GetInstance()->Add(tab_id, this);
235 // static
236 bool SelectFileDialogExtension::PendingExists(int32 tab_id) {
237 return PendingDialog::GetInstance()->Find(tab_id).get() != NULL;
240 bool SelectFileDialogExtension::HasMultipleFileTypeChoicesImpl() {
241 return has_multiple_file_type_choices_;
244 void SelectFileDialogExtension::SelectFileImpl(
245 Type type,
246 const string16& title,
247 const base::FilePath& default_path,
248 const FileTypeInfo* file_types,
249 int file_type_index,
250 const base::FilePath::StringType& default_extension,
251 gfx::NativeWindow owner_window,
252 void* params) {
253 if (owner_window_) {
254 LOG(ERROR) << "File dialog already in use!";
255 return;
258 // The base window to associate the dialog with.
259 ui::BaseWindow* base_window = NULL;
261 // The web contents to associate the dialog with.
262 content::WebContents* web_contents = NULL;
264 // To get the base_window and profile, either a Browser or ShellWindow is
265 // needed.
266 Browser* owner_browser = NULL;
267 ShellWindow* shell_window = NULL;
269 // If owner_window is supplied, use that to find a browser or a shell window.
270 if (owner_window) {
271 owner_browser = chrome::FindBrowserWithWindow(owner_window);
272 if (!owner_browser) {
273 // If an owner_window was supplied but we couldn't find a browser, this
274 // could be for a shell window.
275 shell_window = apps::ShellWindowRegistry::
276 GetShellWindowForNativeWindowAnyProfile(owner_window);
280 if (shell_window) {
281 if (shell_window->window_type_is_panel()) {
282 NOTREACHED() << "File dialog opened by panel.";
283 return;
285 base_window = shell_window->GetBaseWindow();
286 web_contents = shell_window->web_contents();
287 } else {
288 // If the owning window is still unknown, this could be a background page or
289 // and extension popup. Use the last active browser.
290 if (!owner_browser) {
291 owner_browser =
292 chrome::FindLastActiveWithHostDesktopType(chrome::GetActiveDesktop());
294 DCHECK(owner_browser);
295 if (!owner_browser) {
296 LOG(ERROR) << "Could not find browser or shell window for popup.";
297 return;
299 base_window = owner_browser->window();
300 web_contents = owner_browser->tab_strip_model()->GetActiveWebContents();
303 DCHECK(base_window);
304 DCHECK(web_contents);
305 profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext());
306 DCHECK(profile_);
308 // Check if we have another dialog opened for the contents. It's unlikely, but
309 // possible. If there is no web contents use a tab_id of -1. A dialog without
310 // an associated web contents will be shown full-screen; only one at a time
311 // is allowed in this state.
312 int32 tab_id = SessionID::IdForTab(web_contents);
313 if (PendingExists(tab_id)) {
314 DLOG(WARNING) << "Pending dialog exists with id " << tab_id;
315 return;
318 base::FilePath default_dialog_path;
320 const PrefService* pref_service = profile_->GetPrefs();
322 if (default_path.empty() && pref_service) {
323 default_dialog_path =
324 pref_service->GetFilePath(prefs::kDownloadDefaultDirectory);
325 } else {
326 default_dialog_path = default_path;
329 base::FilePath virtual_path;
330 base::FilePath fallback_path = profile_->last_selected_directory().Append(
331 default_dialog_path.BaseName());
332 // If an absolute path is specified as the default path, convert it to the
333 // virtual path in the file browser extension. Due to the current design,
334 // an invalid temporal cache file path may passed as |default_dialog_path|
335 // (crbug.com/178013 #9-#11). In such a case, we use the last selected
336 // directory as a workaround. Real fix is tracked at crbug.com/110119.
337 using file_manager::kFileManagerAppId;
338 if (default_dialog_path.IsAbsolute() &&
339 (file_manager::util::ConvertAbsoluteFilePathToRelativeFileSystemPath(
340 profile_, kFileManagerAppId, default_dialog_path, &virtual_path) ||
341 file_manager::util::ConvertAbsoluteFilePathToRelativeFileSystemPath(
342 profile_, kFileManagerAppId, fallback_path, &virtual_path))) {
343 virtual_path = base::FilePath("/").Append(virtual_path);
344 } else {
345 // If the path was relative, or failed to convert, just use the base name,
346 virtual_path = default_dialog_path.BaseName();
349 has_multiple_file_type_choices_ =
350 file_types ? file_types->extensions.size() > 1 : true;
352 GURL file_manager_url =
353 file_manager::util::GetFileManagerMainPageUrlWithParams(
354 type, title, virtual_path, file_types, file_type_index,
355 default_extension);
357 ExtensionDialog* dialog = ExtensionDialog::Show(file_manager_url,
358 base_window, profile_, web_contents,
359 kFileManagerWidth, kFileManagerHeight,
360 kFileManagerWidth,
361 kFileManagerHeight,
362 #if defined(USE_AURA)
363 file_manager::util::GetSelectFileDialogTitle(type),
364 #else
365 // HTML-based header used.
366 string16(),
367 #endif
368 this /* ExtensionDialog::Observer */);
369 if (!dialog) {
370 LOG(ERROR) << "Unable to create extension dialog";
371 return;
374 // Connect our listener to FileDialogFunction's per-tab callbacks.
375 AddPending(tab_id);
377 extension_dialog_ = dialog;
378 params_ = params;
379 tab_id_ = tab_id;
380 owner_window_ = owner_window;