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/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"
39 using apps::AppWindow;
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 AppWindow is
283 Browser* owner_browser = NULL;
284 AppWindow* app_window = NULL;
286 // If owner_window is supplied, use that to find a browser or a app 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.
293 apps::AppWindowRegistry::GetAppWindowForNativeWindowAnyProfile(
299 DCHECK(!app_window->window_type_is_panel());
300 base_window = app_window->GetBaseWindow();
301 web_contents = app_window->web_contents();
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) {
307 chrome::FindLastActiveWithHostDesktopType(chrome::GetActiveDesktop());
309 DCHECK(owner_browser);
310 base_window = owner_browser->window();
311 web_contents = owner_browser->tab_strip_model()->GetActiveWebContents();
315 DCHECK(web_contents);
316 profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext());
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))
325 const PrefService* pref_service = profile_->GetPrefs();
326 DCHECK(pref_service);
328 base::FilePath download_default_path(
329 pref_service->GetFilePath(prefs::kDownloadDefaultDirectory));
331 base::FilePath selection_path = default_path.IsAbsolute() ?
332 default_path : download_default_path.Append(default_path.BaseName());
334 base::FilePath fallback_path = profile_->last_selected_directory().empty() ?
335 download_default_path : profile_->last_selected_directory();
337 // Convert the above absolute paths to file system URLs.
339 if (!file_manager::util::ConvertAbsoluteFilePathToFileSystemUrl(
342 file_manager::kFileManagerAppId,
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
348 if (!file_manager::util::ConvertAbsoluteFilePathToFileSystemUrl(
350 fallback_path.Append(default_path.BaseName()),
351 file_manager::kFileManagerAppId,
353 DVLOG(1) << "Unable to resolve the selection URL.";
357 GURL current_directory_url;
358 base::FilePath current_directory_path = selection_path.DirName();
359 if (!file_manager::util::ConvertAbsoluteFilePathToFileSystemUrl(
361 current_directory_path,
362 file_manager::kFileManagerAppId,
363 ¤t_directory_url)) {
364 // Fallback if necessary, see the comment above.
365 if (!file_manager::util::ConvertAbsoluteFilePathToFileSystemUrl(
368 file_manager::kFileManagerAppId,
369 ¤t_directory_url)) {
370 DVLOG(1) << "Unable to resolve the current directory URL for: "
371 << fallback_path.value();
375 has_multiple_file_type_choices_ =
376 !file_types || (file_types->extensions.size() > 1);
378 GURL file_manager_url =
379 file_manager::util::GetFileManagerMainPageUrlWithParams(
382 current_directory_url,
384 default_path.BaseName().value(),
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),
397 // HTML-based header used.
400 this /* ExtensionDialog::Observer */);
402 LOG(ERROR) << "Unable to create extension dialog";
406 // Connect our listener to FileDialogFunction's per-tab callbacks.
407 AddPending(routing_id);
409 extension_dialog_ = dialog;
411 routing_id_ = routing_id;
412 owner_window_ = owner_window;