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