select_file_dialog_extension.cc revision 1e9bf3e0803691d0a228da41fc608347b6db4340
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_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(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 // ExtensionProcessManager::CreateViewHost. Once that is fixed, remove this. 167 if (profile_) { 168 base::MessageLoop::current()->PostTask( 169 FROM_HERE, 170 base::Bind(&ExtensionService::ReloadExtension, 171 base::Unretained(extensions::ExtensionSystem::Get(profile_) 172 ->extension_service()), 173 extension_id)); 174 } 175 176 dialog->GetWidget()->Close(); 177} 178 179// static 180void SelectFileDialogExtension::OnFileSelected( 181 RoutingID routing_id, 182 const ui::SelectedFileInfo& file, 183 int index) { 184 scoped_refptr<SelectFileDialogExtension> dialog = 185 PendingDialog::GetInstance()->Find(routing_id); 186 if (!dialog.get()) 187 return; 188 dialog->selection_type_ = SINGLE_FILE; 189 dialog->selection_files_.clear(); 190 dialog->selection_files_.push_back(file); 191 dialog->selection_index_ = index; 192} 193 194// static 195void SelectFileDialogExtension::OnMultiFilesSelected( 196 RoutingID routing_id, 197 const std::vector<ui::SelectedFileInfo>& files) { 198 scoped_refptr<SelectFileDialogExtension> dialog = 199 PendingDialog::GetInstance()->Find(routing_id); 200 if (!dialog.get()) 201 return; 202 dialog->selection_type_ = MULTIPLE_FILES; 203 dialog->selection_files_ = files; 204 dialog->selection_index_ = 0; 205} 206 207// static 208void SelectFileDialogExtension::OnFileSelectionCanceled(RoutingID routing_id) { 209 scoped_refptr<SelectFileDialogExtension> dialog = 210 PendingDialog::GetInstance()->Find(routing_id); 211 if (!dialog.get()) 212 return; 213 dialog->selection_type_ = CANCEL; 214 dialog->selection_files_.clear(); 215 dialog->selection_index_ = 0; 216} 217 218content::RenderViewHost* SelectFileDialogExtension::GetRenderViewHost() { 219 if (extension_dialog_.get()) 220 return extension_dialog_->host()->render_view_host(); 221 return NULL; 222} 223 224void SelectFileDialogExtension::NotifyListener() { 225 if (!listener_) 226 return; 227 switch (selection_type_) { 228 case CANCEL: 229 listener_->FileSelectionCanceled(params_); 230 break; 231 case SINGLE_FILE: 232 listener_->FileSelectedWithExtraInfo(selection_files_[0], 233 selection_index_, 234 params_); 235 break; 236 case MULTIPLE_FILES: 237 listener_->MultiFilesSelectedWithExtraInfo(selection_files_, params_); 238 break; 239 default: 240 NOTREACHED(); 241 break; 242 } 243} 244 245void SelectFileDialogExtension::AddPending(RoutingID routing_id) { 246 PendingDialog::GetInstance()->Add(routing_id, this); 247} 248 249// static 250bool SelectFileDialogExtension::PendingExists(RoutingID routing_id) { 251 return PendingDialog::GetInstance()->Find(routing_id).get() != NULL; 252} 253 254bool SelectFileDialogExtension::HasMultipleFileTypeChoicesImpl() { 255 return has_multiple_file_type_choices_; 256} 257 258void SelectFileDialogExtension::SelectFileImpl( 259 Type type, 260 const string16& title, 261 const base::FilePath& default_path, 262 const FileTypeInfo* file_types, 263 int file_type_index, 264 const base::FilePath::StringType& default_extension, 265 gfx::NativeWindow owner_window, 266 void* params) { 267 if (owner_window_) { 268 LOG(ERROR) << "File dialog already in use!"; 269 return; 270 } 271 272 // The base window to associate the dialog with. 273 ui::BaseWindow* base_window = NULL; 274 275 // The web contents to associate the dialog with. 276 content::WebContents* web_contents = NULL; 277 278 // To get the base_window and profile, either a Browser or ShellWindow is 279 // needed. 280 Browser* owner_browser = NULL; 281 ShellWindow* shell_window = NULL; 282 283 // If owner_window is supplied, use that to find a browser or a shell window. 284 if (owner_window) { 285 owner_browser = chrome::FindBrowserWithWindow(owner_window); 286 if (!owner_browser) { 287 // If an owner_window was supplied but we couldn't find a browser, this 288 // could be for a shell window. 289 shell_window = apps::ShellWindowRegistry:: 290 GetShellWindowForNativeWindowAnyProfile(owner_window); 291 } 292 } 293 294 if (shell_window) { 295 if (shell_window->window_type_is_panel()) { 296 NOTREACHED() << "File dialog opened by panel."; 297 return; 298 } 299 base_window = shell_window->GetBaseWindow(); 300 web_contents = shell_window->web_contents(); 301 } else { 302 // If the owning window is still unknown, this could be a background page or 303 // and extension popup. Use the last active browser. 304 if (!owner_browser) { 305 owner_browser = 306 chrome::FindLastActiveWithHostDesktopType(chrome::GetActiveDesktop()); 307 } 308 DCHECK(owner_browser); 309 if (!owner_browser) { 310 LOG(ERROR) << "Could not find browser or shell window for popup."; 311 return; 312 } 313 base_window = owner_browser->window(); 314 web_contents = owner_browser->tab_strip_model()->GetActiveWebContents(); 315 } 316 317 DCHECK(base_window); 318 DCHECK(web_contents); 319 profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext()); 320 DCHECK(profile_); 321 322 // Check if we have another dialog opened for the contents. It's unlikely, but 323 // possible. 324 RoutingID routing_id = GetRoutingIDFromWebContents(web_contents); 325 if (PendingExists(routing_id)) { 326 DLOG(WARNING) << "Pending dialog exists with id " << routing_id; 327 return; 328 } 329 330 base::FilePath default_dialog_path; 331 332 const PrefService* pref_service = profile_->GetPrefs(); 333 334 if (default_path.empty() && pref_service) { 335 default_dialog_path = 336 pref_service->GetFilePath(prefs::kDownloadDefaultDirectory); 337 } else { 338 default_dialog_path = default_path; 339 } 340 341 base::FilePath virtual_path; 342 base::FilePath fallback_path = profile_->last_selected_directory().Append( 343 default_dialog_path.BaseName()); 344 // If an absolute path is specified as the default path, convert it to the 345 // virtual path in the file browser extension. Due to the current design, 346 // an invalid temporal cache file path may passed as |default_dialog_path| 347 // (crbug.com/178013 #9-#11). In such a case, we use the last selected 348 // directory as a workaround. Real fix is tracked at crbug.com/110119. 349 using file_manager::kFileManagerAppId; 350 if (default_dialog_path.IsAbsolute() && 351 (file_manager::util::ConvertAbsoluteFilePathToRelativeFileSystemPath( 352 profile_, kFileManagerAppId, default_dialog_path, &virtual_path) || 353 file_manager::util::ConvertAbsoluteFilePathToRelativeFileSystemPath( 354 profile_, kFileManagerAppId, fallback_path, &virtual_path))) { 355 virtual_path = base::FilePath("/").Append(virtual_path); 356 } else { 357 // If the path was relative, or failed to convert, just use the base name, 358 virtual_path = default_dialog_path.BaseName(); 359 } 360 361 has_multiple_file_type_choices_ = 362 file_types ? file_types->extensions.size() > 1 : true; 363 364 GURL file_manager_url = 365 file_manager::util::GetFileManagerMainPageUrlWithParams( 366 type, title, virtual_path, file_types, file_type_index, 367 default_extension); 368 369 ExtensionDialog* dialog = ExtensionDialog::Show(file_manager_url, 370 base_window, profile_, web_contents, 371 kFileManagerWidth, kFileManagerHeight, 372 kFileManagerWidth, 373 kFileManagerHeight, 374#if defined(USE_AURA) 375 file_manager::util::GetSelectFileDialogTitle(type), 376#else 377 // HTML-based header used. 378 string16(), 379#endif 380 this /* ExtensionDialog::Observer */); 381 if (!dialog) { 382 LOG(ERROR) << "Unable to create extension dialog"; 383 return; 384 } 385 386 // Connect our listener to FileDialogFunction's per-tab callbacks. 387 AddPending(routing_id); 388 389 extension_dialog_ = dialog; 390 params_ = params; 391 routing_id_ = routing_id; 392 owner_window_ = owner_window; 393} 394