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