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"
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"
41 #if defined(USE_ATHENA)
42 #include "chrome/browser/ui/views/athena/athena_util.h"
45 using extensions::AppWindow
;
46 using content::BrowserThread
;
50 const int kFileManagerWidth
= 972; // pixels
51 const int kFileManagerHeight
= 640; // pixels
52 const int kFileManagerMinimumWidth
= 640; // pixels
53 const int kFileManagerMinimumHeight
= 240; // pixels
55 // Holds references to file manager dialogs that have callbacks pending
56 // to their listeners.
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
);
67 friend struct DefaultSingletonTraits
<PendingDialog
>;
68 typedef std::map
<SelectFileDialogExtension::RoutingID
,
69 scoped_refptr
<SelectFileDialogExtension
> > Map
;
74 PendingDialog
* PendingDialog::GetInstance() {
75 return Singleton
<PendingDialog
>::get();
78 void PendingDialog::Add(SelectFileDialogExtension::RoutingID id
,
79 scoped_refptr
<SelectFileDialogExtension
> dialog
) {
81 if (map_
.find(id
) == map_
.end())
82 map_
.insert(std::make_pair(id
, dialog
));
84 DLOG(WARNING
) << "Duplicate pending dialog " << id
;
87 void PendingDialog::Remove(SelectFileDialogExtension::RoutingID id
) {
91 scoped_refptr
<SelectFileDialogExtension
> PendingDialog::Find(
92 SelectFileDialogExtension::RoutingID id
) {
93 Map::const_iterator it
= map_
.find(id
);
99 #if defined(USE_ATHENA)
100 void FindRuntimeContext(gfx::NativeWindow owner_window
,
101 ui::BaseWindow
** base_window
,
102 content::WebContents
** web_contents
) {
104 *web_contents
= GetWebContentsForWindow(owner_window
);
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
) {
113 *web_contents
= NULL
;
114 // To get the base_window and web contents, either a Browser or AppWindow is
116 Browser
* owner_browser
= NULL
;
117 AppWindow
* app_window
= NULL
;
119 // If owner_window is supplied, use that to find a browser or a app 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.
126 AppWindowRegistryUtil::GetAppWindowForNativeWindowAnyProfile(
132 DCHECK(!app_window
->window_type_is_panel());
133 *base_window
= app_window
->GetBaseWindow();
134 *web_contents
= app_window
->web_contents();
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
) {
140 chrome::FindLastActiveWithHostDesktopType(chrome::GetActiveDesktop());
143 *base_window
= owner_browser
->window();
144 *web_contents
= owner_browser
->tab_strip_model()->GetActiveWebContents();
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();
160 /////////////////////////////////////////////////////////////////////////////
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.
172 // TODO(jamescook): Move this into a new file shell_dialogs_chromeos.cc
174 SelectFileDialogExtension
* SelectFileDialogExtension::Create(
176 ui::SelectFilePolicy
* policy
) {
177 return new SelectFileDialogExtension(listener
, policy
);
180 SelectFileDialogExtension::SelectFileDialogExtension(
182 ui::SelectFilePolicy
* policy
)
183 : SelectFileDialog(listener
, policy
),
184 has_multiple_file_type_choices_(false),
188 selection_type_(CANCEL
),
193 SelectFileDialogExtension::~SelectFileDialogExtension() {
194 if (extension_dialog_
.get())
195 extension_dialog_
->ObserverDestroyed();
198 bool SelectFileDialogExtension::IsRunning(
199 gfx::NativeWindow owner_window
) const {
200 return owner_window_
== owner_window
;
203 void SelectFileDialogExtension::ListenerDestroyed() {
206 PendingDialog::GetInstance()->Remove(routing_id_
);
209 void SelectFileDialogExtension::ExtensionDialogClosing(
210 ExtensionDialog
* /*dialog*/) {
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.
220 void SelectFileDialogExtension::ExtensionTerminated(
221 ExtensionDialog
* dialog
) {
222 // The extension would have been unloaded because of the termination,
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.
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
236 base::MessageLoop::current()->PostTask(
238 base::Bind(&ExtensionService::ReloadExtension
,
239 base::Unretained(extensions::ExtensionSystem::Get(profile_
)
240 ->extension_service()),
244 dialog
->GetWidget()->Close();
248 void SelectFileDialogExtension::OnFileSelected(
249 RoutingID routing_id
,
250 const ui::SelectedFileInfo
& file
,
252 scoped_refptr
<SelectFileDialogExtension
> dialog
=
253 PendingDialog::GetInstance()->Find(routing_id
);
256 dialog
->selection_type_
= SINGLE_FILE
;
257 dialog
->selection_files_
.clear();
258 dialog
->selection_files_
.push_back(file
);
259 dialog
->selection_index_
= index
;
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
);
270 dialog
->selection_type_
= MULTIPLE_FILES
;
271 dialog
->selection_files_
= files
;
272 dialog
->selection_index_
= 0;
276 void SelectFileDialogExtension::OnFileSelectionCanceled(RoutingID routing_id
) {
277 scoped_refptr
<SelectFileDialogExtension
> dialog
=
278 PendingDialog::GetInstance()->Find(routing_id
);
281 dialog
->selection_type_
= CANCEL
;
282 dialog
->selection_files_
.clear();
283 dialog
->selection_index_
= 0;
286 content::RenderViewHost
* SelectFileDialogExtension::GetRenderViewHost() {
287 if (extension_dialog_
.get())
288 return extension_dialog_
->host()->render_view_host();
292 void SelectFileDialogExtension::NotifyListener() {
295 switch (selection_type_
) {
297 listener_
->FileSelectionCanceled(params_
);
300 listener_
->FileSelectedWithExtraInfo(selection_files_
[0],
305 listener_
->MultiFilesSelectedWithExtraInfo(selection_files_
, params_
);
313 void SelectFileDialogExtension::AddPending(RoutingID routing_id
) {
314 PendingDialog::GetInstance()->Add(routing_id
, this);
318 bool SelectFileDialogExtension::PendingExists(RoutingID routing_id
) {
319 return PendingDialog::GetInstance()->Find(routing_id
).get() != NULL
;
322 bool SelectFileDialogExtension::HasMultipleFileTypeChoicesImpl() {
323 return has_multiple_file_type_choices_
;
326 void SelectFileDialogExtension::SelectFileImpl(
328 const base::string16
& title
,
329 const base::FilePath
& default_path
,
330 const FileTypeInfo
* file_types
,
332 const base::FilePath::StringType
& default_extension
,
333 gfx::NativeWindow owner_window
,
336 LOG(ERROR
) << "File dialog already in use!";
340 // The base window to associate the dialog with.
341 ui::BaseWindow
* base_window
= NULL
;
343 // The web contents to associate the dialog with.
344 content::WebContents
* web_contents
= NULL
;
345 FindRuntimeContext(owner_window
, &base_window
, &web_contents
);
347 profile_
= Profile::FromBrowserContext(web_contents
->GetBrowserContext());
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
))
356 const PrefService
* pref_service
= profile_
->GetPrefs();
357 DCHECK(pref_service
);
359 base::FilePath
download_default_path(
360 pref_service
->GetFilePath(prefs::kDownloadDefaultDirectory
));
362 base::FilePath selection_path
= default_path
.IsAbsolute() ?
363 default_path
: download_default_path
.Append(default_path
.BaseName());
365 base::FilePath fallback_path
= profile_
->last_selected_directory().empty() ?
366 download_default_path
: profile_
->last_selected_directory();
368 // Convert the above absolute paths to file system URLs.
370 if (!file_manager::util::ConvertAbsoluteFilePathToFileSystemUrl(
373 file_manager::kFileManagerAppId
,
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
379 if (!file_manager::util::ConvertAbsoluteFilePathToFileSystemUrl(
381 fallback_path
.Append(default_path
.BaseName()),
382 file_manager::kFileManagerAppId
,
384 DVLOG(1) << "Unable to resolve the selection URL.";
388 GURL current_directory_url
;
389 base::FilePath current_directory_path
= selection_path
.DirName();
390 if (!file_manager::util::ConvertAbsoluteFilePathToFileSystemUrl(
392 current_directory_path
,
393 file_manager::kFileManagerAppId
,
394 ¤t_directory_url
)) {
395 // Fallback if necessary, see the comment above.
396 if (!file_manager::util::ConvertAbsoluteFilePathToFileSystemUrl(
399 file_manager::kFileManagerAppId
,
400 ¤t_directory_url
)) {
401 DVLOG(1) << "Unable to resolve the current directory URL for: "
402 << fallback_path
.value();
406 has_multiple_file_type_choices_
=
407 !file_types
|| (file_types
->extensions
.size() > 1);
409 GURL file_manager_url
=
410 file_manager::util::GetFileManagerMainPageUrlWithParams(
413 current_directory_url
,
415 default_path
.BaseName().value(),
420 ExtensionDialog
* dialog
= ExtensionDialog::Show(
422 base_window
? base_window
->GetNativeWindow() : owner_window
,
427 kFileManagerMinimumWidth
,
428 kFileManagerMinimumHeight
,
429 file_manager::util::GetSelectFileDialogTitle(type
),
430 this /* ExtensionDialog::Observer */);
432 LOG(ERROR
) << "Unable to create extension dialog";
436 // Connect our listener to FileDialogFunction's per-tab callbacks.
437 AddPending(routing_id
);
439 extension_dialog_
= dialog
;
441 routing_id_
= routing_id
;
442 owner_window_
= owner_window
;