select_file_dialog_extension.cc revision a02191e04bc25c4935f804f2c080ae28663d096d
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/app_window.h" 8#include "apps/app_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/app_mode/app_mode_utils.h" 17#include "chrome/browser/chromeos/file_manager/app_id.h" 18#include "chrome/browser/chromeos/file_manager/fileapi_util.h" 19#include "chrome/browser/chromeos/file_manager/select_file_dialog_util.h" 20#include "chrome/browser/chromeos/file_manager/url_util.h" 21#include "chrome/browser/chromeos/login/login_web_dialog.h" 22#include "chrome/browser/extensions/extension_service.h" 23#include "chrome/browser/extensions/extension_view_host.h" 24#include "chrome/browser/profiles/profile.h" 25#include "chrome/browser/sessions/session_tab_helper.h" 26#include "chrome/browser/ui/browser.h" 27#include "chrome/browser/ui/browser_finder.h" 28#include "chrome/browser/ui/browser_list.h" 29#include "chrome/browser/ui/browser_window.h" 30#include "chrome/browser/ui/chrome_select_file_policy.h" 31#include "chrome/browser/ui/host_desktop.h" 32#include "chrome/browser/ui/tabs/tab_strip_model.h" 33#include "chrome/browser/ui/views/extensions/extension_dialog.h" 34#include "chrome/common/pref_names.h" 35#include "content/public/browser/browser_thread.h" 36#include "extensions/browser/extension_system.h" 37#include "ui/base/base_window.h" 38#include "ui/shell_dialogs/selected_file_info.h" 39#include "ui/views/widget/widget.h" 40 41using apps::AppWindow; 42using content::BrowserThread; 43 44namespace { 45 46const int kFileManagerWidth = 972; // pixels 47const int kFileManagerHeight = 640; // pixels 48const int kFileManagerMinimumWidth = 640; // pixels 49const int kFileManagerMinimumHeight = 240; // pixels 50 51// Holds references to file manager dialogs that have callbacks pending 52// to their listeners. 53class PendingDialog { 54 public: 55 static PendingDialog* GetInstance(); 56 void Add(SelectFileDialogExtension::RoutingID id, 57 scoped_refptr<SelectFileDialogExtension> dialog); 58 void Remove(SelectFileDialogExtension::RoutingID id); 59 scoped_refptr<SelectFileDialogExtension> Find( 60 SelectFileDialogExtension::RoutingID id); 61 62 private: 63 friend struct DefaultSingletonTraits<PendingDialog>; 64 typedef std::map<SelectFileDialogExtension::RoutingID, 65 scoped_refptr<SelectFileDialogExtension> > Map; 66 Map map_; 67}; 68 69// static 70PendingDialog* PendingDialog::GetInstance() { 71 return Singleton<PendingDialog>::get(); 72} 73 74void PendingDialog::Add(SelectFileDialogExtension::RoutingID id, 75 scoped_refptr<SelectFileDialogExtension> dialog) { 76 DCHECK(dialog.get()); 77 if (map_.find(id) == map_.end()) 78 map_.insert(std::make_pair(id, dialog)); 79 else 80 DLOG(WARNING) << "Duplicate pending dialog " << id; 81} 82 83void PendingDialog::Remove(SelectFileDialogExtension::RoutingID id) { 84 map_.erase(id); 85} 86 87scoped_refptr<SelectFileDialogExtension> PendingDialog::Find( 88 SelectFileDialogExtension::RoutingID id) { 89 Map::const_iterator it = map_.find(id); 90 if (it == map_.end()) 91 return NULL; 92 return it->second; 93} 94 95 96// Given |owner_window| finds corresponding |base_window|, it's associated 97// |web_contents| and |profile|. 98void FindRuntimeContext( 99 gfx::NativeWindow owner_window, 100 ui::BaseWindow** base_window, 101 content::WebContents** web_contents, 102 Profile** profile) { 103 *base_window = NULL; 104 *web_contents = NULL; 105 *profile = NULL; 106 // To get the base_window and profile, either a Browser or AppWindow is 107 // needed. 108 Browser* owner_browser = NULL; 109 AppWindow* app_window = NULL; 110 111 // If owner_window is supplied, use that to find a browser or a app window. 112 if (owner_window) { 113 owner_browser = chrome::FindBrowserWithWindow(owner_window); 114 if (!owner_browser) { 115 // If an owner_window was supplied but we couldn't find a browser, this 116 // could be for a app window. 117 app_window = 118 apps::AppWindowRegistry::GetAppWindowForNativeWindowAnyProfile( 119 owner_window); 120 } 121 } 122 123 if (app_window) { 124 DCHECK(!app_window->window_type_is_panel()); 125 *base_window = app_window->GetBaseWindow(); 126 *web_contents = app_window->web_contents(); 127 } else { 128 // If the owning window is still unknown, this could be a background page or 129 // and extension popup. Use the last active browser. 130 if (!owner_browser) { 131 owner_browser = 132 chrome::FindLastActiveWithHostDesktopType(chrome::GetActiveDesktop()); 133 } 134 if (owner_browser) { 135 *base_window = owner_browser->window(); 136 *web_contents = owner_browser->tab_strip_model()->GetActiveWebContents(); 137 } 138 } 139 140 // In ChromeOS kiosk launch mode, we can still show file picker for 141 // certificate manager dialog. There are no browser or webapp window 142 // instances present in this case. 143 if (chrome::IsRunningInForcedAppMode() && !(*web_contents)) 144 *web_contents = chromeos::LoginWebDialog::GetCurrentWebContents(); 145 146 CHECK(web_contents); 147 *profile = Profile::FromBrowserContext((*web_contents)->GetBrowserContext()); 148} 149 150} // namespace 151 152///////////////////////////////////////////////////////////////////////////// 153 154// static 155SelectFileDialogExtension::RoutingID 156SelectFileDialogExtension::GetRoutingIDFromWebContents( 157 const content::WebContents* web_contents) { 158 // Use the raw pointer value as the identifier. Previously we have used the 159 // tab ID for the purpose, but some web_contents, especially those of the 160 // packaged apps, don't have tab IDs assigned. 161 return web_contents; 162} 163 164// TODO(jamescook): Move this into a new file shell_dialogs_chromeos.cc 165// static 166SelectFileDialogExtension* SelectFileDialogExtension::Create( 167 Listener* listener, 168 ui::SelectFilePolicy* policy) { 169 return new SelectFileDialogExtension(listener, policy); 170} 171 172SelectFileDialogExtension::SelectFileDialogExtension( 173 Listener* listener, 174 ui::SelectFilePolicy* policy) 175 : SelectFileDialog(listener, policy), 176 has_multiple_file_type_choices_(false), 177 routing_id_(), 178 profile_(NULL), 179 owner_window_(NULL), 180 selection_type_(CANCEL), 181 selection_index_(0), 182 params_(NULL) { 183} 184 185SelectFileDialogExtension::~SelectFileDialogExtension() { 186 if (extension_dialog_.get()) 187 extension_dialog_->ObserverDestroyed(); 188} 189 190bool SelectFileDialogExtension::IsRunning( 191 gfx::NativeWindow owner_window) const { 192 return owner_window_ == owner_window; 193} 194 195void SelectFileDialogExtension::ListenerDestroyed() { 196 listener_ = NULL; 197 params_ = NULL; 198 PendingDialog::GetInstance()->Remove(routing_id_); 199} 200 201void SelectFileDialogExtension::ExtensionDialogClosing( 202 ExtensionDialog* /*dialog*/) { 203 profile_ = NULL; 204 owner_window_ = NULL; 205 // Release our reference to the underlying dialog to allow it to close. 206 extension_dialog_ = NULL; 207 PendingDialog::GetInstance()->Remove(routing_id_); 208 // Actually invoke the appropriate callback on our listener. 209 NotifyListener(); 210} 211 212void SelectFileDialogExtension::ExtensionTerminated( 213 ExtensionDialog* dialog) { 214 // The extension would have been unloaded because of the termination, 215 // reload it. 216 std::string extension_id = dialog->host()->extension()->id(); 217 // Reload the extension after a bit; the extension may not have been unloaded 218 // yet. We don't want to try to reload the extension only to have the Unload 219 // code execute after us and re-unload the extension. 220 // 221 // TODO(rkc): This is ugly. The ideal solution is that we shouldn't need to 222 // reload the extension at all - when we try to open the extension the next 223 // time, the extension subsystem would automatically reload it for us. At 224 // this time though this is broken because of some faulty wiring in 225 // extensions::ProcessManager::CreateViewHost. Once that is fixed, remove 226 // this. 227 if (profile_) { 228 base::MessageLoop::current()->PostTask( 229 FROM_HERE, 230 base::Bind(&ExtensionService::ReloadExtension, 231 base::Unretained(extensions::ExtensionSystem::Get(profile_) 232 ->extension_service()), 233 extension_id)); 234 } 235 236 dialog->GetWidget()->Close(); 237} 238 239// static 240void SelectFileDialogExtension::OnFileSelected( 241 RoutingID routing_id, 242 const ui::SelectedFileInfo& file, 243 int index) { 244 scoped_refptr<SelectFileDialogExtension> dialog = 245 PendingDialog::GetInstance()->Find(routing_id); 246 if (!dialog.get()) 247 return; 248 dialog->selection_type_ = SINGLE_FILE; 249 dialog->selection_files_.clear(); 250 dialog->selection_files_.push_back(file); 251 dialog->selection_index_ = index; 252} 253 254// static 255void SelectFileDialogExtension::OnMultiFilesSelected( 256 RoutingID routing_id, 257 const std::vector<ui::SelectedFileInfo>& files) { 258 scoped_refptr<SelectFileDialogExtension> dialog = 259 PendingDialog::GetInstance()->Find(routing_id); 260 if (!dialog.get()) 261 return; 262 dialog->selection_type_ = MULTIPLE_FILES; 263 dialog->selection_files_ = files; 264 dialog->selection_index_ = 0; 265} 266 267// static 268void SelectFileDialogExtension::OnFileSelectionCanceled(RoutingID routing_id) { 269 scoped_refptr<SelectFileDialogExtension> dialog = 270 PendingDialog::GetInstance()->Find(routing_id); 271 if (!dialog.get()) 272 return; 273 dialog->selection_type_ = CANCEL; 274 dialog->selection_files_.clear(); 275 dialog->selection_index_ = 0; 276} 277 278content::RenderViewHost* SelectFileDialogExtension::GetRenderViewHost() { 279 if (extension_dialog_.get()) 280 return extension_dialog_->host()->render_view_host(); 281 return NULL; 282} 283 284void SelectFileDialogExtension::NotifyListener() { 285 if (!listener_) 286 return; 287 switch (selection_type_) { 288 case CANCEL: 289 listener_->FileSelectionCanceled(params_); 290 break; 291 case SINGLE_FILE: 292 listener_->FileSelectedWithExtraInfo(selection_files_[0], 293 selection_index_, 294 params_); 295 break; 296 case MULTIPLE_FILES: 297 listener_->MultiFilesSelectedWithExtraInfo(selection_files_, params_); 298 break; 299 default: 300 NOTREACHED(); 301 break; 302 } 303} 304 305void SelectFileDialogExtension::AddPending(RoutingID routing_id) { 306 PendingDialog::GetInstance()->Add(routing_id, this); 307} 308 309// static 310bool SelectFileDialogExtension::PendingExists(RoutingID routing_id) { 311 return PendingDialog::GetInstance()->Find(routing_id).get() != NULL; 312} 313 314bool SelectFileDialogExtension::HasMultipleFileTypeChoicesImpl() { 315 return has_multiple_file_type_choices_; 316} 317 318void SelectFileDialogExtension::SelectFileImpl( 319 Type type, 320 const base::string16& title, 321 const base::FilePath& default_path, 322 const FileTypeInfo* file_types, 323 int file_type_index, 324 const base::FilePath::StringType& default_extension, 325 gfx::NativeWindow owner_window, 326 void* params) { 327 if (owner_window_) { 328 LOG(ERROR) << "File dialog already in use!"; 329 return; 330 } 331 332 // The base window to associate the dialog with. 333 ui::BaseWindow* base_window = NULL; 334 335 // The web contents to associate the dialog with. 336 content::WebContents* web_contents = NULL; 337 338 FindRuntimeContext(owner_window, &base_window, &web_contents, &profile_); 339 CHECK(profile_); 340 341 // Check if we have another dialog opened for the contents. It's unlikely, but 342 // possible. In such situation, discard this request. 343 RoutingID routing_id = GetRoutingIDFromWebContents(web_contents); 344 if (PendingExists(routing_id)) 345 return; 346 347 const PrefService* pref_service = profile_->GetPrefs(); 348 DCHECK(pref_service); 349 350 base::FilePath download_default_path( 351 pref_service->GetFilePath(prefs::kDownloadDefaultDirectory)); 352 353 base::FilePath selection_path = default_path.IsAbsolute() ? 354 default_path : download_default_path.Append(default_path.BaseName()); 355 356 base::FilePath fallback_path = profile_->last_selected_directory().empty() ? 357 download_default_path : profile_->last_selected_directory(); 358 359 // Convert the above absolute paths to file system URLs. 360 GURL selection_url; 361 if (!file_manager::util::ConvertAbsoluteFilePathToFileSystemUrl( 362 profile_, 363 selection_path, 364 file_manager::kFileManagerAppId, 365 &selection_url)) { 366 // Due to the current design, an invalid temporal cache file path may passed 367 // as |default_path| (crbug.com/178013 #9-#11). In such a case, we use the 368 // last selected directory as a workaround. Real fix is tracked at 369 // crbug.com/110119. 370 if (!file_manager::util::ConvertAbsoluteFilePathToFileSystemUrl( 371 profile_, 372 fallback_path.Append(default_path.BaseName()), 373 file_manager::kFileManagerAppId, 374 &selection_url)) { 375 DVLOG(1) << "Unable to resolve the selection URL."; 376 } 377 } 378 379 GURL current_directory_url; 380 base::FilePath current_directory_path = selection_path.DirName(); 381 if (!file_manager::util::ConvertAbsoluteFilePathToFileSystemUrl( 382 profile_, 383 current_directory_path, 384 file_manager::kFileManagerAppId, 385 ¤t_directory_url)) { 386 // Fallback if necessary, see the comment above. 387 if (!file_manager::util::ConvertAbsoluteFilePathToFileSystemUrl( 388 profile_, 389 fallback_path, 390 file_manager::kFileManagerAppId, 391 ¤t_directory_url)) { 392 DVLOG(1) << "Unable to resolve the current directory URL for: " 393 << fallback_path.value(); 394 } 395 } 396 397 has_multiple_file_type_choices_ = 398 !file_types || (file_types->extensions.size() > 1); 399 400 GURL file_manager_url = 401 file_manager::util::GetFileManagerMainPageUrlWithParams( 402 type, 403 title, 404 current_directory_url, 405 selection_url, 406 default_path.BaseName().value(), 407 file_types, 408 file_type_index, 409 default_extension); 410 411 ExtensionDialog* dialog = ExtensionDialog::Show( 412 file_manager_url, 413 base_window ? base_window->GetNativeWindow() : owner_window, 414 profile_, 415 web_contents, 416 kFileManagerWidth, 417 kFileManagerHeight, 418 kFileManagerMinimumWidth, 419 kFileManagerMinimumHeight, 420#if defined(USE_AURA) 421 file_manager::util::GetSelectFileDialogTitle(type), 422#else 423 // HTML-based header used. 424 base::string16(), 425#endif 426 this /* ExtensionDialog::Observer */); 427 if (!dialog) { 428 LOG(ERROR) << "Unable to create extension dialog"; 429 return; 430 } 431 432 // Connect our listener to FileDialogFunction's per-tab callbacks. 433 AddPending(routing_id); 434 435 extension_dialog_ = dialog; 436 params_ = params; 437 routing_id_ = routing_id; 438 owner_window_ = owner_window; 439} 440