[Metrics] Make MetricsStateManager take a callback param to check if UMA is enabled.
[chromium-blink-merge.git] / chrome / browser / ui / views / select_file_dialog_extension.cc
blob465bc260a49b7068a93e55436688f62524d64044
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/app_mode/app_mode_utils.h"
17 #include "chrome/browser/chromeos/file_manager/app_id.h"
18 #include "chrome/browser/chromeos/file_manager/fileapi_util.h"
19 #include "chrome/browser/chromeos/file_manager/select_file_dialog_util.h"
20 #include "chrome/browser/chromeos/file_manager/url_util.h"
21 #include "chrome/browser/chromeos/login/ui/login_web_dialog.h"
22 #include "chrome/browser/extensions/extension_service.h"
23 #include "chrome/browser/extensions/extension_view_host.h"
24 #include "chrome/browser/profiles/profile.h"
25 #include "chrome/browser/sessions/session_tab_helper.h"
26 #include "chrome/browser/ui/browser.h"
27 #include "chrome/browser/ui/browser_finder.h"
28 #include "chrome/browser/ui/browser_list.h"
29 #include "chrome/browser/ui/browser_window.h"
30 #include "chrome/browser/ui/chrome_select_file_policy.h"
31 #include "chrome/browser/ui/host_desktop.h"
32 #include "chrome/browser/ui/tabs/tab_strip_model.h"
33 #include "chrome/browser/ui/views/extensions/extension_dialog.h"
34 #include "chrome/common/pref_names.h"
35 #include "content/public/browser/browser_thread.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 using apps::AppWindow;
42 using content::BrowserThread;
44 namespace {
46 const int kFileManagerWidth = 972; // pixels
47 const int kFileManagerHeight = 640; // pixels
48 const int kFileManagerMinimumWidth = 640; // pixels
49 const int kFileManagerMinimumHeight = 240; // pixels
51 // Holds references to file manager dialogs that have callbacks pending
52 // to their listeners.
53 class PendingDialog {
54 public:
55 static PendingDialog* GetInstance();
56 void Add(SelectFileDialogExtension::RoutingID id,
57 scoped_refptr<SelectFileDialogExtension> dialog);
58 void Remove(SelectFileDialogExtension::RoutingID id);
59 scoped_refptr<SelectFileDialogExtension> Find(
60 SelectFileDialogExtension::RoutingID id);
62 private:
63 friend struct DefaultSingletonTraits<PendingDialog>;
64 typedef std::map<SelectFileDialogExtension::RoutingID,
65 scoped_refptr<SelectFileDialogExtension> > Map;
66 Map map_;
69 // static
70 PendingDialog* PendingDialog::GetInstance() {
71 return Singleton<PendingDialog>::get();
74 void PendingDialog::Add(SelectFileDialogExtension::RoutingID id,
75 scoped_refptr<SelectFileDialogExtension> dialog) {
76 DCHECK(dialog.get());
77 if (map_.find(id) == map_.end())
78 map_.insert(std::make_pair(id, dialog));
79 else
80 DLOG(WARNING) << "Duplicate pending dialog " << id;
83 void PendingDialog::Remove(SelectFileDialogExtension::RoutingID id) {
84 map_.erase(id);
87 scoped_refptr<SelectFileDialogExtension> PendingDialog::Find(
88 SelectFileDialogExtension::RoutingID id) {
89 Map::const_iterator it = map_.find(id);
90 if (it == map_.end())
91 return NULL;
92 return it->second;
96 // Given |owner_window| finds corresponding |base_window|, it's associated
97 // |web_contents| and |profile|.
98 void FindRuntimeContext(
99 gfx::NativeWindow owner_window,
100 ui::BaseWindow** base_window,
101 content::WebContents** web_contents,
102 Profile** profile) {
103 *base_window = NULL;
104 *web_contents = NULL;
105 *profile = NULL;
106 // To get the base_window and profile, either a Browser or AppWindow is
107 // needed.
108 Browser* owner_browser = NULL;
109 AppWindow* app_window = NULL;
111 // If owner_window is supplied, use that to find a browser or a app window.
112 if (owner_window) {
113 owner_browser = chrome::FindBrowserWithWindow(owner_window);
114 if (!owner_browser) {
115 // If an owner_window was supplied but we couldn't find a browser, this
116 // could be for a app window.
117 app_window =
118 apps::AppWindowRegistry::GetAppWindowForNativeWindowAnyProfile(
119 owner_window);
123 if (app_window) {
124 DCHECK(!app_window->window_type_is_panel());
125 *base_window = app_window->GetBaseWindow();
126 *web_contents = app_window->web_contents();
127 } else {
128 // If the owning window is still unknown, this could be a background page or
129 // and extension popup. Use the last active browser.
130 if (!owner_browser) {
131 owner_browser =
132 chrome::FindLastActiveWithHostDesktopType(chrome::GetActiveDesktop());
134 if (owner_browser) {
135 *base_window = owner_browser->window();
136 *web_contents = owner_browser->tab_strip_model()->GetActiveWebContents();
140 // In ChromeOS kiosk launch mode, we can still show file picker for
141 // certificate manager dialog. There are no browser or webapp window
142 // instances present in this case.
143 if (chrome::IsRunningInForcedAppMode() && !(*web_contents))
144 *web_contents = chromeos::LoginWebDialog::GetCurrentWebContents();
146 CHECK(web_contents);
147 *profile = Profile::FromBrowserContext((*web_contents)->GetBrowserContext());
150 } // namespace
152 /////////////////////////////////////////////////////////////////////////////
154 // static
155 SelectFileDialogExtension::RoutingID
156 SelectFileDialogExtension::GetRoutingIDFromWebContents(
157 const content::WebContents* web_contents) {
158 // Use the raw pointer value as the identifier. Previously we have used the
159 // tab ID for the purpose, but some web_contents, especially those of the
160 // packaged apps, don't have tab IDs assigned.
161 return web_contents;
164 // TODO(jamescook): Move this into a new file shell_dialogs_chromeos.cc
165 // static
166 SelectFileDialogExtension* SelectFileDialogExtension::Create(
167 Listener* listener,
168 ui::SelectFilePolicy* policy) {
169 return new SelectFileDialogExtension(listener, policy);
172 SelectFileDialogExtension::SelectFileDialogExtension(
173 Listener* listener,
174 ui::SelectFilePolicy* policy)
175 : SelectFileDialog(listener, policy),
176 has_multiple_file_type_choices_(false),
177 routing_id_(),
178 profile_(NULL),
179 owner_window_(NULL),
180 selection_type_(CANCEL),
181 selection_index_(0),
182 params_(NULL) {
185 SelectFileDialogExtension::~SelectFileDialogExtension() {
186 if (extension_dialog_.get())
187 extension_dialog_->ObserverDestroyed();
190 bool SelectFileDialogExtension::IsRunning(
191 gfx::NativeWindow owner_window) const {
192 return owner_window_ == owner_window;
195 void SelectFileDialogExtension::ListenerDestroyed() {
196 listener_ = NULL;
197 params_ = NULL;
198 PendingDialog::GetInstance()->Remove(routing_id_);
201 void SelectFileDialogExtension::ExtensionDialogClosing(
202 ExtensionDialog* /*dialog*/) {
203 profile_ = NULL;
204 owner_window_ = NULL;
205 // Release our reference to the underlying dialog to allow it to close.
206 extension_dialog_ = NULL;
207 PendingDialog::GetInstance()->Remove(routing_id_);
208 // Actually invoke the appropriate callback on our listener.
209 NotifyListener();
212 void SelectFileDialogExtension::ExtensionTerminated(
213 ExtensionDialog* dialog) {
214 // The extension would have been unloaded because of the termination,
215 // reload it.
216 std::string extension_id = dialog->host()->extension()->id();
217 // Reload the extension after a bit; the extension may not have been unloaded
218 // yet. We don't want to try to reload the extension only to have the Unload
219 // code execute after us and re-unload the extension.
221 // TODO(rkc): This is ugly. The ideal solution is that we shouldn't need to
222 // reload the extension at all - when we try to open the extension the next
223 // time, the extension subsystem would automatically reload it for us. At
224 // this time though this is broken because of some faulty wiring in
225 // extensions::ProcessManager::CreateViewHost. Once that is fixed, remove
226 // this.
227 if (profile_) {
228 base::MessageLoop::current()->PostTask(
229 FROM_HERE,
230 base::Bind(&ExtensionService::ReloadExtension,
231 base::Unretained(extensions::ExtensionSystem::Get(profile_)
232 ->extension_service()),
233 extension_id));
236 dialog->GetWidget()->Close();
239 // static
240 void SelectFileDialogExtension::OnFileSelected(
241 RoutingID routing_id,
242 const ui::SelectedFileInfo& file,
243 int index) {
244 scoped_refptr<SelectFileDialogExtension> dialog =
245 PendingDialog::GetInstance()->Find(routing_id);
246 if (!dialog.get())
247 return;
248 dialog->selection_type_ = SINGLE_FILE;
249 dialog->selection_files_.clear();
250 dialog->selection_files_.push_back(file);
251 dialog->selection_index_ = index;
254 // static
255 void SelectFileDialogExtension::OnMultiFilesSelected(
256 RoutingID routing_id,
257 const std::vector<ui::SelectedFileInfo>& files) {
258 scoped_refptr<SelectFileDialogExtension> dialog =
259 PendingDialog::GetInstance()->Find(routing_id);
260 if (!dialog.get())
261 return;
262 dialog->selection_type_ = MULTIPLE_FILES;
263 dialog->selection_files_ = files;
264 dialog->selection_index_ = 0;
267 // static
268 void SelectFileDialogExtension::OnFileSelectionCanceled(RoutingID routing_id) {
269 scoped_refptr<SelectFileDialogExtension> dialog =
270 PendingDialog::GetInstance()->Find(routing_id);
271 if (!dialog.get())
272 return;
273 dialog->selection_type_ = CANCEL;
274 dialog->selection_files_.clear();
275 dialog->selection_index_ = 0;
278 content::RenderViewHost* SelectFileDialogExtension::GetRenderViewHost() {
279 if (extension_dialog_.get())
280 return extension_dialog_->host()->render_view_host();
281 return NULL;
284 void SelectFileDialogExtension::NotifyListener() {
285 if (!listener_)
286 return;
287 switch (selection_type_) {
288 case CANCEL:
289 listener_->FileSelectionCanceled(params_);
290 break;
291 case SINGLE_FILE:
292 listener_->FileSelectedWithExtraInfo(selection_files_[0],
293 selection_index_,
294 params_);
295 break;
296 case MULTIPLE_FILES:
297 listener_->MultiFilesSelectedWithExtraInfo(selection_files_, params_);
298 break;
299 default:
300 NOTREACHED();
301 break;
305 void SelectFileDialogExtension::AddPending(RoutingID routing_id) {
306 PendingDialog::GetInstance()->Add(routing_id, this);
309 // static
310 bool SelectFileDialogExtension::PendingExists(RoutingID routing_id) {
311 return PendingDialog::GetInstance()->Find(routing_id).get() != NULL;
314 bool SelectFileDialogExtension::HasMultipleFileTypeChoicesImpl() {
315 return has_multiple_file_type_choices_;
318 void SelectFileDialogExtension::SelectFileImpl(
319 Type type,
320 const base::string16& title,
321 const base::FilePath& default_path,
322 const FileTypeInfo* file_types,
323 int file_type_index,
324 const base::FilePath::StringType& default_extension,
325 gfx::NativeWindow owner_window,
326 void* params) {
327 if (owner_window_) {
328 LOG(ERROR) << "File dialog already in use!";
329 return;
332 // The base window to associate the dialog with.
333 ui::BaseWindow* base_window = NULL;
335 // The web contents to associate the dialog with.
336 content::WebContents* web_contents = NULL;
338 FindRuntimeContext(owner_window, &base_window, &web_contents, &profile_);
339 CHECK(profile_);
341 // Check if we have another dialog opened for the contents. It's unlikely, but
342 // possible. In such situation, discard this request.
343 RoutingID routing_id = GetRoutingIDFromWebContents(web_contents);
344 if (PendingExists(routing_id))
345 return;
347 const PrefService* pref_service = profile_->GetPrefs();
348 DCHECK(pref_service);
350 base::FilePath download_default_path(
351 pref_service->GetFilePath(prefs::kDownloadDefaultDirectory));
353 base::FilePath selection_path = default_path.IsAbsolute() ?
354 default_path : download_default_path.Append(default_path.BaseName());
356 base::FilePath fallback_path = profile_->last_selected_directory().empty() ?
357 download_default_path : profile_->last_selected_directory();
359 // Convert the above absolute paths to file system URLs.
360 GURL selection_url;
361 if (!file_manager::util::ConvertAbsoluteFilePathToFileSystemUrl(
362 profile_,
363 selection_path,
364 file_manager::kFileManagerAppId,
365 &selection_url)) {
366 // Due to the current design, an invalid temporal cache file path may passed
367 // as |default_path| (crbug.com/178013 #9-#11). In such a case, we use the
368 // last selected directory as a workaround. Real fix is tracked at
369 // crbug.com/110119.
370 if (!file_manager::util::ConvertAbsoluteFilePathToFileSystemUrl(
371 profile_,
372 fallback_path.Append(default_path.BaseName()),
373 file_manager::kFileManagerAppId,
374 &selection_url)) {
375 DVLOG(1) << "Unable to resolve the selection URL.";
379 GURL current_directory_url;
380 base::FilePath current_directory_path = selection_path.DirName();
381 if (!file_manager::util::ConvertAbsoluteFilePathToFileSystemUrl(
382 profile_,
383 current_directory_path,
384 file_manager::kFileManagerAppId,
385 &current_directory_url)) {
386 // Fallback if necessary, see the comment above.
387 if (!file_manager::util::ConvertAbsoluteFilePathToFileSystemUrl(
388 profile_,
389 fallback_path,
390 file_manager::kFileManagerAppId,
391 &current_directory_url)) {
392 DVLOG(1) << "Unable to resolve the current directory URL for: "
393 << fallback_path.value();
397 has_multiple_file_type_choices_ =
398 !file_types || (file_types->extensions.size() > 1);
400 GURL file_manager_url =
401 file_manager::util::GetFileManagerMainPageUrlWithParams(
402 type,
403 title,
404 current_directory_url,
405 selection_url,
406 default_path.BaseName().value(),
407 file_types,
408 file_type_index,
409 default_extension);
411 ExtensionDialog* dialog = ExtensionDialog::Show(
412 file_manager_url,
413 base_window ? base_window->GetNativeWindow() : owner_window,
414 profile_,
415 web_contents,
416 kFileManagerWidth,
417 kFileManagerHeight,
418 kFileManagerMinimumWidth,
419 kFileManagerMinimumHeight,
420 file_manager::util::GetSelectFileDialogTitle(type),
421 this /* ExtensionDialog::Observer */);
422 if (!dialog) {
423 LOG(ERROR) << "Unable to create extension dialog";
424 return;
427 // Connect our listener to FileDialogFunction's per-tab callbacks.
428 AddPending(routing_id);
430 extension_dialog_ = dialog;
431 params_ = params;
432 routing_id_ = routing_id;
433 owner_window_ = owner_window;