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_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"
39 using apps::ShellWindow;
40 using content::BrowserThread;
44 const int kFileManagerWidth = 972; // pixels
45 const int kFileManagerHeight = 640; // pixels
47 // Holds references to file manager dialogs that have callbacks pending
48 // to their listeners.
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);
59 friend struct DefaultSingletonTraits<PendingDialog>;
60 typedef std::map<SelectFileDialogExtension::RoutingID,
61 scoped_refptr<SelectFileDialogExtension> > Map;
66 PendingDialog* PendingDialog::GetInstance() {
67 return Singleton<PendingDialog>::get();
70 void PendingDialog::Add(SelectFileDialogExtension::RoutingID id,
71 scoped_refptr<SelectFileDialogExtension> dialog) {
73 if (map_.find(id) == map_.end())
74 map_.insert(std::make_pair(id, dialog));
76 DLOG(WARNING) << "Duplicate pending dialog " << id;
79 void PendingDialog::Remove(SelectFileDialogExtension::RoutingID id) {
83 scoped_refptr<SelectFileDialogExtension> PendingDialog::Find(
84 SelectFileDialogExtension::RoutingID id) {
85 Map::const_iterator it = map_.find(id);
93 /////////////////////////////////////////////////////////////////////////////
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.
105 // TODO(jamescook): Move this into a new file shell_dialogs_chromeos.cc
107 SelectFileDialogExtension* SelectFileDialogExtension::Create(
109 ui::SelectFilePolicy* policy) {
110 return new SelectFileDialogExtension(listener, policy);
113 SelectFileDialogExtension::SelectFileDialogExtension(
115 ui::SelectFilePolicy* policy)
116 : SelectFileDialog(listener, policy),
117 has_multiple_file_type_choices_(false),
121 selection_type_(CANCEL),
126 SelectFileDialogExtension::~SelectFileDialogExtension() {
127 if (extension_dialog_.get())
128 extension_dialog_->ObserverDestroyed();
131 bool SelectFileDialogExtension::IsRunning(
132 gfx::NativeWindow owner_window) const {
133 return owner_window_ == owner_window;
136 void SelectFileDialogExtension::ListenerDestroyed() {
139 PendingDialog::GetInstance()->Remove(routing_id_);
142 void SelectFileDialogExtension::ExtensionDialogClosing(
143 ExtensionDialog* /*dialog*/) {
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.
153 void SelectFileDialogExtension::ExtensionTerminated(
154 ExtensionDialog* dialog) {
155 // The extension would have been unloaded because of the termination,
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.
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.
168 base::MessageLoop::current()->PostTask(
170 base::Bind(&ExtensionService::ReloadExtension,
171 base::Unretained(extensions::ExtensionSystem::Get(profile_)
172 ->extension_service()),
176 dialog->GetWidget()->Close();
180 void SelectFileDialogExtension::OnFileSelected(
181 RoutingID routing_id,
182 const ui::SelectedFileInfo& file,
184 scoped_refptr<SelectFileDialogExtension> dialog =
185 PendingDialog::GetInstance()->Find(routing_id);
188 dialog->selection_type_ = SINGLE_FILE;
189 dialog->selection_files_.clear();
190 dialog->selection_files_.push_back(file);
191 dialog->selection_index_ = index;
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);
202 dialog->selection_type_ = MULTIPLE_FILES;
203 dialog->selection_files_ = files;
204 dialog->selection_index_ = 0;
208 void SelectFileDialogExtension::OnFileSelectionCanceled(RoutingID routing_id) {
209 scoped_refptr<SelectFileDialogExtension> dialog =
210 PendingDialog::GetInstance()->Find(routing_id);
213 dialog->selection_type_ = CANCEL;
214 dialog->selection_files_.clear();
215 dialog->selection_index_ = 0;
218 content::RenderViewHost* SelectFileDialogExtension::GetRenderViewHost() {
219 if (extension_dialog_.get())
220 return extension_dialog_->host()->render_view_host();
224 void SelectFileDialogExtension::NotifyListener() {
227 switch (selection_type_) {
229 listener_->FileSelectionCanceled(params_);
232 listener_->FileSelectedWithExtraInfo(selection_files_[0],
237 listener_->MultiFilesSelectedWithExtraInfo(selection_files_, params_);
245 void SelectFileDialogExtension::AddPending(RoutingID routing_id) {
246 PendingDialog::GetInstance()->Add(routing_id, this);
250 bool SelectFileDialogExtension::PendingExists(RoutingID routing_id) {
251 return PendingDialog::GetInstance()->Find(routing_id).get() != NULL;
254 bool SelectFileDialogExtension::HasMultipleFileTypeChoicesImpl() {
255 return has_multiple_file_type_choices_;
258 void SelectFileDialogExtension::SelectFileImpl(
260 const string16& title,
261 const base::FilePath& default_path,
262 const FileTypeInfo* file_types,
264 const base::FilePath::StringType& default_extension,
265 gfx::NativeWindow owner_window,
268 LOG(ERROR) << "File dialog already in use!";
272 // The base window to associate the dialog with.
273 ui::BaseWindow* base_window = NULL;
275 // The web contents to associate the dialog with.
276 content::WebContents* web_contents = NULL;
278 // To get the base_window and profile, either a Browser or ShellWindow is
280 Browser* owner_browser = NULL;
281 ShellWindow* shell_window = NULL;
283 // If owner_window is supplied, use that to find a browser or a shell 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);
295 if (shell_window->window_type_is_panel()) {
296 NOTREACHED() << "File dialog opened by panel.";
299 base_window = shell_window->GetBaseWindow();
300 web_contents = shell_window->web_contents();
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) {
306 chrome::FindLastActiveWithHostDesktopType(chrome::GetActiveDesktop());
308 DCHECK(owner_browser);
309 if (!owner_browser) {
310 LOG(ERROR) << "Could not find browser or shell window for popup.";
313 base_window = owner_browser->window();
314 web_contents = owner_browser->tab_strip_model()->GetActiveWebContents();
318 DCHECK(web_contents);
319 profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext());
322 // Check if we have another dialog opened for the contents. It's unlikely, but
324 RoutingID routing_id = GetRoutingIDFromWebContents(web_contents);
325 if (PendingExists(routing_id)) {
326 DLOG(WARNING) << "Pending dialog exists with id " << routing_id;
330 base::FilePath default_dialog_path;
332 const PrefService* pref_service = profile_->GetPrefs();
334 if (default_path.empty() && pref_service) {
335 default_dialog_path =
336 pref_service->GetFilePath(prefs::kDownloadDefaultDirectory);
338 default_dialog_path = default_path;
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);
357 // If the path was relative, or failed to convert, just use the base name,
358 virtual_path = default_dialog_path.BaseName();
361 has_multiple_file_type_choices_ =
362 file_types ? file_types->extensions.size() > 1 : true;
364 GURL file_manager_url =
365 file_manager::util::GetFileManagerMainPageUrlWithParams(
366 type, title, virtual_path, file_types, file_type_index,
369 ExtensionDialog* dialog = ExtensionDialog::Show(file_manager_url,
370 base_window, profile_, web_contents,
371 kFileManagerWidth, kFileManagerHeight,
374 #if defined(USE_AURA)
375 file_manager::util::GetSelectFileDialogTitle(type),
377 // HTML-based header used.
380 this /* ExtensionDialog::Observer */);
382 LOG(ERROR) << "Unable to create extension dialog";
386 // Connect our listener to FileDialogFunction's per-tab callbacks.
387 AddPending(routing_id);
389 extension_dialog_ = dialog;
391 routing_id_ = routing_id;
392 owner_window_ = owner_window;