Upstream version 5.34.104.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 "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 "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_view_host.h"
22 #include "chrome/browser/profiles/profile.h"
23 #include "chrome/browser/sessions/session_tab_helper.h"
24 #include "chrome/browser/ui/browser.h"
25 #include "chrome/browser/ui/browser_finder.h"
26 #include "chrome/browser/ui/browser_list.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/extension_system.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::AppWindow;
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 AppWindow is
282   // needed.
283   Browser* owner_browser =  NULL;
284   AppWindow* app_window = NULL;
285
286   // If owner_window is supplied, use that to find a browser or a app 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 app window.
292       app_window =
293           apps::AppWindowRegistry::GetAppWindowForNativeWindowAnyProfile(
294               owner_window);
295     }
296   }
297
298   if (app_window) {
299     DCHECK(!app_window->window_type_is_panel());
300     base_window = app_window->GetBaseWindow();
301     web_contents = app_window->web_contents();
302   } else {
303     // If the owning window is still unknown, this could be a background page or
304     // and extension popup. Use the last active browser.
305     if (!owner_browser) {
306       owner_browser =
307           chrome::FindLastActiveWithHostDesktopType(chrome::GetActiveDesktop());
308     }
309     DCHECK(owner_browser);
310     base_window = owner_browser->window();
311     web_contents = owner_browser->tab_strip_model()->GetActiveWebContents();
312   }
313
314   DCHECK(base_window);
315   DCHECK(web_contents);
316   profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext());
317   DCHECK(profile_);
318
319   // Check if we have another dialog opened for the contents. It's unlikely, but
320   // possible. In such situation, discard this request.
321   RoutingID routing_id = GetRoutingIDFromWebContents(web_contents);
322   if (PendingExists(routing_id))
323     return;
324
325   const PrefService* pref_service = profile_->GetPrefs();
326   DCHECK(pref_service);
327
328   base::FilePath download_default_path(
329       pref_service->GetFilePath(prefs::kDownloadDefaultDirectory));
330
331   base::FilePath selection_path = default_path.IsAbsolute() ?
332       default_path : download_default_path.Append(default_path.BaseName());
333
334   base::FilePath fallback_path = profile_->last_selected_directory().empty() ?
335       download_default_path : profile_->last_selected_directory();
336
337   // Convert the above absolute paths to file system URLs.
338   GURL selection_url;
339   if (!file_manager::util::ConvertAbsoluteFilePathToFileSystemUrl(
340       profile_,
341       selection_path,
342       file_manager::kFileManagerAppId,
343       &selection_url)) {
344     // Due to the current design, an invalid temporal cache file path may passed
345     // as |default_path| (crbug.com/178013 #9-#11). In such a case, we use the
346     // last selected directory as a workaround. Real fix is tracked at
347     // crbug.com/110119.
348     if (!file_manager::util::ConvertAbsoluteFilePathToFileSystemUrl(
349         profile_,
350         fallback_path.Append(default_path.BaseName()),
351         file_manager::kFileManagerAppId,
352         &selection_url)) {
353       DVLOG(1) << "Unable to resolve the selection URL.";
354     }
355   }
356
357   GURL current_directory_url;
358   base::FilePath current_directory_path = selection_path.DirName();
359   if (!file_manager::util::ConvertAbsoluteFilePathToFileSystemUrl(
360       profile_,
361       current_directory_path,
362       file_manager::kFileManagerAppId,
363       &current_directory_url)) {
364     // Fallback if necessary, see the comment above.
365     if (!file_manager::util::ConvertAbsoluteFilePathToFileSystemUrl(
366             profile_,
367             fallback_path,
368             file_manager::kFileManagerAppId,
369             &current_directory_url)) {
370       DVLOG(1) << "Unable to resolve the current directory URL for: "
371                << fallback_path.value();
372     }
373   }
374
375   has_multiple_file_type_choices_ =
376       !file_types || (file_types->extensions.size() > 1);
377
378   GURL file_manager_url =
379       file_manager::util::GetFileManagerMainPageUrlWithParams(
380           type,
381           title,
382           current_directory_url,
383           selection_url,
384           default_path.BaseName().value(),
385           file_types,
386           file_type_index,
387           default_extension);
388
389   ExtensionDialog* dialog = ExtensionDialog::Show(file_manager_url,
390       base_window, profile_, web_contents,
391       kFileManagerWidth, kFileManagerHeight,
392       kFileManagerMinimumWidth,
393       kFileManagerMinimumHeight,
394 #if defined(USE_AURA)
395       file_manager::util::GetSelectFileDialogTitle(type),
396 #else
397       // HTML-based header used.
398       base::string16(),
399 #endif
400       this /* ExtensionDialog::Observer */);
401   if (!dialog) {
402     LOG(ERROR) << "Unable to create extension dialog";
403     return;
404   }
405
406   // Connect our listener to FileDialogFunction's per-tab callbacks.
407   AddPending(routing_id);
408
409   extension_dialog_ = dialog;
410   params_ = params;
411   routing_id_ = routing_id;
412   owner_window_ = owner_window;
413 }