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