select_file_dialog_extension.cc revision 58537e28ecd584eab876aee8be7156509866d23a
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 "chrome/browser/ui/views/select_file_dialog_extension.h" 6 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/file_manager/app_id.h" 17#include "chrome/browser/chromeos/file_manager/fileapi_util.h" 18#include "chrome/browser/chromeos/file_manager/select_file_dialog_util.h" 19#include "chrome/browser/chromeos/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" 38 39using apps::ShellWindow; 40using content::BrowserThread; 41 42namespace { 43 44const int kFileManagerWidth = 972; // pixels 45const int kFileManagerHeight = 640; // pixels 46 47// Holds references to file manager dialogs that have callbacks pending 48// to their listeners. 49class 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); 55 56 private: 57 friend struct DefaultSingletonTraits<PendingDialog>; 58 typedef std::map<int32, scoped_refptr<SelectFileDialogExtension> > Map; 59 Map map_; 60}; 61 62// static 63PendingDialog* PendingDialog::GetInstance() { 64 return Singleton<PendingDialog>::get(); 65} 66 67void 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; 74} 75 76void PendingDialog::Remove(int32 tab_id) { 77 map_.erase(tab_id); 78} 79 80scoped_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; 85} 86 87} // namespace 88 89///////////////////////////////////////////////////////////////////////////// 90 91// TODO(jamescook): Move this into a new file shell_dialogs_chromeos.cc 92// static 93SelectFileDialogExtension* SelectFileDialogExtension::Create( 94 Listener* listener, 95 ui::SelectFilePolicy* policy) { 96 return new SelectFileDialogExtension(listener, policy); 97} 98 99SelectFileDialogExtension::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) { 110} 111 112SelectFileDialogExtension::~SelectFileDialogExtension() { 113 if (extension_dialog_.get()) 114 extension_dialog_->ObserverDestroyed(); 115} 116 117bool SelectFileDialogExtension::IsRunning( 118 gfx::NativeWindow owner_window) const { 119 return owner_window_ == owner_window; 120} 121 122void SelectFileDialogExtension::ListenerDestroyed() { 123 listener_ = NULL; 124 params_ = NULL; 125 PendingDialog::GetInstance()->Remove(tab_id_); 126} 127 128void 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(); 137} 138 139void 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. 147 // 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)); 160 } 161 162 dialog->GetWidget()->Close(); 163} 164 165// static 166void 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; 178} 179 180// static 181void 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; 191} 192 193// static 194void 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; 202} 203 204content::RenderViewHost* SelectFileDialogExtension::GetRenderViewHost() { 205 if (extension_dialog_.get()) 206 return extension_dialog_->host()->render_view_host(); 207 return NULL; 208} 209 210void 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; 228 } 229} 230 231void SelectFileDialogExtension::AddPending(int32 tab_id) { 232 PendingDialog::GetInstance()->Add(tab_id, this); 233} 234 235// static 236bool SelectFileDialogExtension::PendingExists(int32 tab_id) { 237 return PendingDialog::GetInstance()->Find(tab_id).get() != NULL; 238} 239 240bool SelectFileDialogExtension::HasMultipleFileTypeChoicesImpl() { 241 return has_multiple_file_type_choices_; 242} 243 244void 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; 256 } 257 258 // The base window to associate the dialog with. 259 ui::BaseWindow* base_window = NULL; 260 261 // The web contents to associate the dialog with. 262 content::WebContents* web_contents = NULL; 263 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; 268 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); 277 } 278 } 279 280 if (shell_window) { 281 if (shell_window->window_type_is_panel()) { 282 NOTREACHED() << "File dialog opened by panel."; 283 return; 284 } 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()); 293 } 294 DCHECK(owner_browser); 295 if (!owner_browser) { 296 LOG(ERROR) << "Could not find browser or shell window for popup."; 297 return; 298 } 299 base_window = owner_browser->window(); 300 web_contents = owner_browser->tab_strip_model()->GetActiveWebContents(); 301 } 302 303 DCHECK(base_window); 304 DCHECK(web_contents); 305 profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext()); 306 DCHECK(profile_); 307 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; 316 } 317 318 base::FilePath default_dialog_path; 319 320 const PrefService* pref_service = profile_->GetPrefs(); 321 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; 327 } 328 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(); 347 } 348 349 has_multiple_file_type_choices_ = 350 file_types ? file_types->extensions.size() > 1 : true; 351 352 GURL file_manager_url = 353 file_manager::util::GetFileManagerMainPageUrlWithParams( 354 type, title, virtual_path, file_types, file_type_index, 355 default_extension); 356 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; 372 } 373 374 // Connect our listener to FileDialogFunction's per-tab callbacks. 375 AddPending(tab_id); 376 377 extension_dialog_ = dialog; 378 params_ = params; 379 tab_id_ = tab_id; 380 owner_window_ = owner_window; 381} 382