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