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