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.
5 #include "chrome/browser/ui/views/select_file_dialog_extension.h"
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"
39 using apps::ShellWindow
;
40 using content::BrowserThread
;
44 const int kFileManagerWidth
= 972; // pixels
45 const int kFileManagerHeight
= 640; // pixels
46 const int kFileManagerMinimumWidth
= 320; // pixels
47 const int kFileManagerMinimumHeight
= 240; // pixels
49 // Holds references to file manager dialogs that have callbacks pending
50 // to their listeners.
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
);
61 friend struct DefaultSingletonTraits
<PendingDialog
>;
62 typedef std::map
<SelectFileDialogExtension::RoutingID
,
63 scoped_refptr
<SelectFileDialogExtension
> > Map
;
68 PendingDialog
* PendingDialog::GetInstance() {
69 return Singleton
<PendingDialog
>::get();
72 void PendingDialog::Add(SelectFileDialogExtension::RoutingID id
,
73 scoped_refptr
<SelectFileDialogExtension
> dialog
) {
75 if (map_
.find(id
) == map_
.end())
76 map_
.insert(std::make_pair(id
, dialog
));
78 DLOG(WARNING
) << "Duplicate pending dialog " << id
;
81 void PendingDialog::Remove(SelectFileDialogExtension::RoutingID id
) {
85 scoped_refptr
<SelectFileDialogExtension
> PendingDialog::Find(
86 SelectFileDialogExtension::RoutingID id
) {
87 Map::const_iterator it
= map_
.find(id
);
95 /////////////////////////////////////////////////////////////////////////////
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.
107 // TODO(jamescook): Move this into a new file shell_dialogs_chromeos.cc
109 SelectFileDialogExtension
* SelectFileDialogExtension::Create(
111 ui::SelectFilePolicy
* policy
) {
112 return new SelectFileDialogExtension(listener
, policy
);
115 SelectFileDialogExtension::SelectFileDialogExtension(
117 ui::SelectFilePolicy
* policy
)
118 : SelectFileDialog(listener
, policy
),
119 has_multiple_file_type_choices_(false),
123 selection_type_(CANCEL
),
128 SelectFileDialogExtension::~SelectFileDialogExtension() {
129 if (extension_dialog_
.get())
130 extension_dialog_
->ObserverDestroyed();
133 bool SelectFileDialogExtension::IsRunning(
134 gfx::NativeWindow owner_window
) const {
135 return owner_window_
== owner_window
;
138 void SelectFileDialogExtension::ListenerDestroyed() {
141 PendingDialog::GetInstance()->Remove(routing_id_
);
144 void SelectFileDialogExtension::ExtensionDialogClosing(
145 ExtensionDialog
* /*dialog*/) {
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.
155 void SelectFileDialogExtension::ExtensionTerminated(
156 ExtensionDialog
* dialog
) {
157 // The extension would have been unloaded because of the termination,
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.
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
171 base::MessageLoop::current()->PostTask(
173 base::Bind(&ExtensionService::ReloadExtension
,
174 base::Unretained(extensions::ExtensionSystem::Get(profile_
)
175 ->extension_service()),
179 dialog
->GetWidget()->Close();
183 void SelectFileDialogExtension::OnFileSelected(
184 RoutingID routing_id
,
185 const ui::SelectedFileInfo
& file
,
187 scoped_refptr
<SelectFileDialogExtension
> dialog
=
188 PendingDialog::GetInstance()->Find(routing_id
);
191 dialog
->selection_type_
= SINGLE_FILE
;
192 dialog
->selection_files_
.clear();
193 dialog
->selection_files_
.push_back(file
);
194 dialog
->selection_index_
= index
;
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
);
205 dialog
->selection_type_
= MULTIPLE_FILES
;
206 dialog
->selection_files_
= files
;
207 dialog
->selection_index_
= 0;
211 void SelectFileDialogExtension::OnFileSelectionCanceled(RoutingID routing_id
) {
212 scoped_refptr
<SelectFileDialogExtension
> dialog
=
213 PendingDialog::GetInstance()->Find(routing_id
);
216 dialog
->selection_type_
= CANCEL
;
217 dialog
->selection_files_
.clear();
218 dialog
->selection_index_
= 0;
221 content::RenderViewHost
* SelectFileDialogExtension::GetRenderViewHost() {
222 if (extension_dialog_
.get())
223 return extension_dialog_
->host()->render_view_host();
227 void SelectFileDialogExtension::NotifyListener() {
230 switch (selection_type_
) {
232 listener_
->FileSelectionCanceled(params_
);
235 listener_
->FileSelectedWithExtraInfo(selection_files_
[0],
240 listener_
->MultiFilesSelectedWithExtraInfo(selection_files_
, params_
);
248 void SelectFileDialogExtension::AddPending(RoutingID routing_id
) {
249 PendingDialog::GetInstance()->Add(routing_id
, this);
253 bool SelectFileDialogExtension::PendingExists(RoutingID routing_id
) {
254 return PendingDialog::GetInstance()->Find(routing_id
).get() != NULL
;
257 bool SelectFileDialogExtension::HasMultipleFileTypeChoicesImpl() {
258 return has_multiple_file_type_choices_
;
261 void SelectFileDialogExtension::SelectFileImpl(
263 const base::string16
& title
,
264 const base::FilePath
& default_path
,
265 const FileTypeInfo
* file_types
,
267 const base::FilePath::StringType
& default_extension
,
268 gfx::NativeWindow owner_window
,
271 LOG(ERROR
) << "File dialog already in use!";
275 // The base window to associate the dialog with.
276 ui::BaseWindow
* base_window
= NULL
;
278 // The web contents to associate the dialog with.
279 content::WebContents
* web_contents
= NULL
;
281 // To get the base_window and profile, either a Browser or ShellWindow is
283 Browser
* owner_browser
= NULL
;
284 ShellWindow
* shell_window
= NULL
;
286 // If owner_window is supplied, use that to find a browser or a shell 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
);
298 if (shell_window
->window_type_is_panel()) {
299 NOTREACHED() << "File dialog opened by panel.";
302 base_window
= shell_window
->GetBaseWindow();
303 web_contents
= shell_window
->web_contents();
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
) {
309 chrome::FindLastActiveWithHostDesktopType(chrome::GetActiveDesktop());
311 DCHECK(owner_browser
);
312 if (!owner_browser
) {
313 LOG(ERROR
) << "Could not find browser or shell window for popup.";
316 base_window
= owner_browser
->window();
317 web_contents
= owner_browser
->tab_strip_model()->GetActiveWebContents();
321 DCHECK(web_contents
);
322 profile_
= Profile::FromBrowserContext(web_contents
->GetBrowserContext());
325 // Check if we have another dialog opened for the contents. It's unlikely, but
327 RoutingID routing_id
= GetRoutingIDFromWebContents(web_contents
);
328 if (PendingExists(routing_id
)) {
329 DLOG(WARNING
) << "Pending dialog exists with id " << routing_id
;
333 base::FilePath default_dialog_path
;
335 const PrefService
* pref_service
= profile_
->GetPrefs();
337 if (default_path
.empty() && pref_service
) {
338 default_dialog_path
=
339 pref_service
->GetFilePath(prefs::kDownloadDefaultDirectory
);
341 default_dialog_path
= default_path
;
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
);
360 // If the path was relative, or failed to convert, just use the base name,
361 virtual_path
= default_dialog_path
.BaseName();
364 has_multiple_file_type_choices_
=
365 file_types
? file_types
->extensions
.size() > 1 : true;
367 GURL file_manager_url
=
368 file_manager::util::GetFileManagerMainPageUrlWithParams(
369 type
, title
, virtual_path
, file_types
, file_type_index
,
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
),
380 // HTML-based header used.
383 this /* ExtensionDialog::Observer */);
385 LOG(ERROR
) << "Unable to create extension dialog";
389 // Connect our listener to FileDialogFunction's per-tab callbacks.
390 AddPending(routing_id
);
392 extension_dialog_
= dialog
;
394 routing_id_
= routing_id
;
395 owner_window_
= owner_window
;