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