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