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