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/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 
     39 using apps::ShellWindow;
     40 using content::BrowserThread;
     41 
     42 namespace {
     43 
     44 const int kFileManagerWidth = 972;  // pixels
     45 const int kFileManagerHeight = 640;  // pixels
     46 const int kFileManagerMinimumWidth = 320;  // pixels
     47 const int kFileManagerMinimumHeight = 240;  // pixels
     48 
     49 // Holds references to file manager dialogs that have callbacks pending
     50 // to their listeners.
     51 class 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
     68 PendingDialog* PendingDialog::GetInstance() {
     69   return Singleton<PendingDialog>::get();
     70 }
     71 
     72 void 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 
     81 void PendingDialog::Remove(SelectFileDialogExtension::RoutingID id) {
     82   map_.erase(id);
     83 }
     84 
     85 scoped_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
     98 SelectFileDialogExtension::RoutingID
     99 SelectFileDialogExtension::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
    109 SelectFileDialogExtension* SelectFileDialogExtension::Create(
    110     Listener* listener,
    111     ui::SelectFilePolicy* policy) {
    112   return new SelectFileDialogExtension(listener, policy);
    113 }
    114 
    115 SelectFileDialogExtension::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 
    128 SelectFileDialogExtension::~SelectFileDialogExtension() {
    129   if (extension_dialog_.get())
    130     extension_dialog_->ObserverDestroyed();
    131 }
    132 
    133 bool SelectFileDialogExtension::IsRunning(
    134     gfx::NativeWindow owner_window) const {
    135   return owner_window_ == owner_window;
    136 }
    137 
    138 void SelectFileDialogExtension::ListenerDestroyed() {
    139   listener_ = NULL;
    140   params_ = NULL;
    141   PendingDialog::GetInstance()->Remove(routing_id_);
    142 }
    143 
    144 void 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 
    155 void 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
    183 void 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
    198 void 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
    211 void 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 
    221 content::RenderViewHost* SelectFileDialogExtension::GetRenderViewHost() {
    222   if (extension_dialog_.get())
    223     return extension_dialog_->host()->render_view_host();
    224   return NULL;
    225 }
    226 
    227 void 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 
    248 void SelectFileDialogExtension::AddPending(RoutingID routing_id) {
    249   PendingDialog::GetInstance()->Add(routing_id, this);
    250 }
    251 
    252 // static
    253 bool SelectFileDialogExtension::PendingExists(RoutingID routing_id) {
    254   return PendingDialog::GetInstance()->Find(routing_id).get() != NULL;
    255 }
    256 
    257 bool SelectFileDialogExtension::HasMultipleFileTypeChoicesImpl() {
    258   return has_multiple_file_type_choices_;
    259 }
    260 
    261 void 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 ShellWindow is
    282   // needed.
    283   Browser* owner_browser =  NULL;
    284   ShellWindow* shell_window = NULL;
    285 
    286   // If owner_window is supplied, use that to find a browser or a shell 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 shell window.
    292       shell_window = apps::ShellWindowRegistry::
    293           GetShellWindowForNativeWindowAnyProfile(owner_window);
    294     }
    295   }
    296 
    297   if (shell_window) {
    298     if (shell_window->window_type_is_panel()) {
    299       NOTREACHED() << "File dialog opened by panel.";
    300       return;
    301     }
    302     base_window = shell_window->GetBaseWindow();
    303     web_contents = shell_window->web_contents();
    304   } else {
    305     // If the owning window is still unknown, this could be a background page or
    306     // and extension popup. Use the last active browser.
    307     if (!owner_browser) {
    308       owner_browser =
    309           chrome::FindLastActiveWithHostDesktopType(chrome::GetActiveDesktop());
    310     }
    311     DCHECK(owner_browser);
    312     if (!owner_browser) {
    313       LOG(ERROR) << "Could not find browser or shell window for popup.";
    314       return;
    315     }
    316     base_window = owner_browser->window();
    317     web_contents = owner_browser->tab_strip_model()->GetActiveWebContents();
    318   }
    319 
    320   DCHECK(base_window);
    321   DCHECK(web_contents);
    322   profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext());
    323   DCHECK(profile_);
    324 
    325   // Check if we have another dialog opened for the contents. It's unlikely, but
    326   // possible.
    327   RoutingID routing_id = GetRoutingIDFromWebContents(web_contents);
    328   if (PendingExists(routing_id)) {
    329     DLOG(WARNING) << "Pending dialog exists with id " << routing_id;
    330     return;
    331   }
    332 
    333   base::FilePath default_dialog_path;
    334 
    335   const PrefService* pref_service = profile_->GetPrefs();
    336 
    337   if (default_path.empty() && pref_service) {
    338     default_dialog_path =
    339         pref_service->GetFilePath(prefs::kDownloadDefaultDirectory);
    340   } else {
    341     default_dialog_path = default_path;
    342   }
    343 
    344   base::FilePath virtual_path;
    345   base::FilePath fallback_path = profile_->last_selected_directory().Append(
    346       default_dialog_path.BaseName());
    347   // If an absolute path is specified as the default path, convert it to the
    348   // virtual path in the file browser extension. Due to the current design,
    349   // an invalid temporal cache file path may passed as |default_dialog_path|
    350   // (crbug.com/178013 #9-#11). In such a case, we use the last selected
    351   // directory as a workaround. Real fix is tracked at crbug.com/110119.
    352   using file_manager::kFileManagerAppId;
    353   if (default_dialog_path.IsAbsolute() &&
    354       (file_manager::util::ConvertAbsoluteFilePathToRelativeFileSystemPath(
    355            profile_, kFileManagerAppId, default_dialog_path, &virtual_path) ||
    356        file_manager::util::ConvertAbsoluteFilePathToRelativeFileSystemPath(
    357            profile_, kFileManagerAppId, fallback_path, &virtual_path))) {
    358     virtual_path = base::FilePath("/").Append(virtual_path);
    359   } else {
    360     // If the path was relative, or failed to convert, just use the base name,
    361     virtual_path = default_dialog_path.BaseName();
    362   }
    363 
    364   has_multiple_file_type_choices_ =
    365       file_types ? file_types->extensions.size() > 1 : true;
    366 
    367   GURL file_manager_url =
    368       file_manager::util::GetFileManagerMainPageUrlWithParams(
    369           type, title, virtual_path, file_types, file_type_index,
    370           default_extension);
    371 
    372   ExtensionDialog* dialog = ExtensionDialog::Show(file_manager_url,
    373       base_window, profile_, web_contents,
    374       kFileManagerWidth, kFileManagerHeight,
    375       kFileManagerMinimumWidth,
    376       kFileManagerMinimumHeight,
    377 #if defined(USE_AURA)
    378       file_manager::util::GetSelectFileDialogTitle(type),
    379 #else
    380       // HTML-based header used.
    381       base::string16(),
    382 #endif
    383       this /* ExtensionDialog::Observer */);
    384   if (!dialog) {
    385     LOG(ERROR) << "Unable to create extension dialog";
    386     return;
    387   }
    388 
    389   // Connect our listener to FileDialogFunction's per-tab callbacks.
    390   AddPending(routing_id);
    391 
    392   extension_dialog_ = dialog;
    393   params_ = params;
    394   routing_id_ = routing_id;
    395   owner_window_ = owner_window;
    396 }
    397