2f51e431637da386842fc1302bfa8d6497856e1e
[platform/framework/web/crosswalk.git] / src / apps / app_shim / extension_app_shim_handler_mac.cc
1 // Copyright 2013 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.
4
5 #include "apps/app_shim/extension_app_shim_handler_mac.h"
6
7 #include "apps/app_lifetime_monitor_factory.h"
8 #include "apps/app_shim/app_shim_host_manager_mac.h"
9 #include "apps/app_shim/app_shim_messages.h"
10 #include "apps/app_window.h"
11 #include "apps/app_window_registry.h"
12 #include "apps/launcher.h"
13 #include "apps/ui/native_app_window.h"
14 #include "base/files/file_path.h"
15 #include "base/logging.h"
16 #include "chrome/browser/browser_process.h"
17 #include "chrome/browser/chrome_notification_types.h"
18 #include "chrome/browser/extensions/extension_host.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/browser/profiles/profile_manager.h"
21 #include "chrome/browser/ui/extensions/extension_enable_flow.h"
22 #include "chrome/browser/ui/extensions/extension_enable_flow_delegate.h"
23 #include "chrome/browser/ui/web_applications/web_app_ui.h"
24 #include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h"
25 #include "chrome/browser/web_applications/web_app_mac.h"
26 #include "chrome/common/extensions/extension_constants.h"
27 #include "content/public/browser/notification_details.h"
28 #include "content/public/browser/notification_service.h"
29 #include "content/public/browser/notification_source.h"
30 #include "extensions/browser/extension_registry.h"
31 #include "ui/base/cocoa/focus_window_set.h"
32
33 using extensions::ExtensionRegistry;
34
35 namespace {
36
37 typedef apps::AppWindowRegistry::AppWindowList AppWindowList;
38
39 void ProfileLoadedCallback(base::Callback<void(Profile*)> callback,
40                            Profile* profile,
41                            Profile::CreateStatus status) {
42   if (status == Profile::CREATE_STATUS_INITIALIZED) {
43     callback.Run(profile);
44     return;
45   }
46
47   // This should never get an error since it only loads existing profiles.
48   DCHECK_EQ(Profile::CREATE_STATUS_CREATED, status);
49 }
50
51 void SetAppHidden(Profile* profile, const std::string& app_id, bool hidden) {
52   AppWindowList windows =
53       apps::AppWindowRegistry::Get(profile)->GetAppWindowsForApp(app_id);
54   for (AppWindowList::const_reverse_iterator it = windows.rbegin();
55        it != windows.rend();
56        ++it) {
57     if (hidden)
58       (*it)->GetBaseWindow()->HideWithApp();
59     else
60       (*it)->GetBaseWindow()->ShowWithApp();
61   }
62 }
63
64 bool FocusWindows(const AppWindowList& windows) {
65   if (windows.empty())
66     return false;
67
68   std::set<gfx::NativeWindow> native_windows;
69   for (AppWindowList::const_iterator it = windows.begin(); it != windows.end();
70        ++it) {
71     native_windows.insert((*it)->GetNativeWindow());
72   }
73   // Allow workspace switching. For the browser process, we can reasonably rely
74   // on OS X to switch spaces for us and honor relevant user settings. But shims
75   // don't have windows, so we have to do it ourselves.
76   ui::FocusWindowSet(native_windows, true);
77   return true;
78 }
79
80 // Attempts to launch a packaged app, prompting the user to enable it if
81 // necessary. The prompt is shown in its own window.
82 // This class manages its own lifetime.
83 class EnableViaPrompt : public ExtensionEnableFlowDelegate {
84  public:
85   EnableViaPrompt(Profile* profile,
86                   const std::string& extension_id,
87                   const base::Callback<void()>& callback)
88       : profile_(profile),
89         extension_id_(extension_id),
90         callback_(callback) {
91   }
92
93   virtual ~EnableViaPrompt() {
94   }
95
96   void Run() {
97     flow_.reset(new ExtensionEnableFlow(profile_, extension_id_, this));
98     flow_->StartForCurrentlyNonexistentWindow(
99         base::Callback<gfx::NativeWindow(void)>());
100   }
101
102  private:
103   // ExtensionEnableFlowDelegate overrides.
104   virtual void ExtensionEnableFlowFinished() OVERRIDE {
105     callback_.Run();
106     delete this;
107   }
108
109   virtual void ExtensionEnableFlowAborted(bool user_initiated) OVERRIDE {
110     callback_.Run();
111     delete this;
112   }
113
114   Profile* profile_;
115   std::string extension_id_;
116   base::Callback<void()> callback_;
117   scoped_ptr<ExtensionEnableFlow> flow_;
118
119   DISALLOW_COPY_AND_ASSIGN(EnableViaPrompt);
120 };
121
122 }  // namespace
123
124 namespace apps {
125
126 bool ExtensionAppShimHandler::Delegate::ProfileExistsForPath(
127     const base::FilePath& path) {
128   ProfileManager* profile_manager = g_browser_process->profile_manager();
129   // Check for the profile name in the profile info cache to ensure that we
130   // never access any directory that isn't a known profile.
131   base::FilePath full_path = profile_manager->user_data_dir().Append(path);
132   ProfileInfoCache& cache = profile_manager->GetProfileInfoCache();
133   return cache.GetIndexOfProfileWithPath(full_path) != std::string::npos;
134 }
135
136 Profile* ExtensionAppShimHandler::Delegate::ProfileForPath(
137     const base::FilePath& path) {
138   ProfileManager* profile_manager = g_browser_process->profile_manager();
139   base::FilePath full_path = profile_manager->user_data_dir().Append(path);
140   Profile* profile = profile_manager->GetProfileByPath(full_path);
141
142   // Use IsValidProfile to check if the profile has been created.
143   return profile && profile_manager->IsValidProfile(profile) ? profile : NULL;
144 }
145
146 void ExtensionAppShimHandler::Delegate::LoadProfileAsync(
147     const base::FilePath& path,
148     base::Callback<void(Profile*)> callback) {
149   ProfileManager* profile_manager = g_browser_process->profile_manager();
150   base::FilePath full_path = profile_manager->user_data_dir().Append(path);
151   profile_manager->CreateProfileAsync(
152       full_path,
153       base::Bind(&ProfileLoadedCallback, callback),
154       base::string16(), base::string16(), std::string());
155 }
156
157 AppWindowList ExtensionAppShimHandler::Delegate::GetWindows(
158     Profile* profile,
159     const std::string& extension_id) {
160   return AppWindowRegistry::Get(profile)->GetAppWindowsForApp(extension_id);
161 }
162
163 const extensions::Extension*
164 ExtensionAppShimHandler::Delegate::GetAppExtension(
165     Profile* profile,
166     const std::string& extension_id) {
167   ExtensionRegistry* registry = ExtensionRegistry::Get(profile);
168   const extensions::Extension* extension =
169       registry->GetExtensionById(extension_id, ExtensionRegistry::ENABLED);
170   return extension && extension->is_platform_app() ? extension : NULL;
171 }
172
173 void ExtensionAppShimHandler::Delegate::EnableExtension(
174     Profile* profile,
175     const std::string& extension_id,
176     const base::Callback<void()>& callback) {
177   (new EnableViaPrompt(profile, extension_id, callback))->Run();
178 }
179
180 void ExtensionAppShimHandler::Delegate::LaunchApp(
181     Profile* profile,
182     const extensions::Extension* extension,
183     const std::vector<base::FilePath>& files) {
184   CoreAppLauncherHandler::RecordAppLaunchType(
185       extension_misc::APP_LAUNCH_CMD_LINE_APP, extension->GetType());
186   if (files.empty()) {
187     apps::LaunchPlatformApp(profile, extension);
188   } else {
189     for (std::vector<base::FilePath>::const_iterator it = files.begin();
190          it != files.end(); ++it) {
191       apps::LaunchPlatformAppWithPath(profile, extension, *it);
192     }
193   }
194 }
195
196 void ExtensionAppShimHandler::Delegate::LaunchShim(
197     Profile* profile,
198     const extensions::Extension* extension) {
199   web_app::MaybeLaunchShortcut(
200       web_app::ShortcutInfoForExtensionAndProfile(extension, profile));
201 }
202
203 void ExtensionAppShimHandler::Delegate::MaybeTerminate() {
204   AppShimHandler::MaybeTerminate();
205 }
206
207 ExtensionAppShimHandler::ExtensionAppShimHandler()
208     : delegate_(new Delegate),
209       weak_factory_(this) {
210   // This is instantiated in BrowserProcessImpl::PreMainMessageLoopRun with
211   // AppShimHostManager. Since PROFILE_CREATED is not fired until
212   // ProfileManager::GetLastUsedProfile/GetLastOpenedProfiles, this should catch
213   // notifications for all profiles.
214   registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CREATED,
215                  content::NotificationService::AllBrowserContextsAndSources());
216   registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
217                  content::NotificationService::AllBrowserContextsAndSources());
218 }
219
220 ExtensionAppShimHandler::~ExtensionAppShimHandler() {}
221
222 AppShimHandler::Host* ExtensionAppShimHandler::FindHost(
223     Profile* profile,
224     const std::string& app_id) {
225   HostMap::iterator it = hosts_.find(make_pair(profile, app_id));
226   return it == hosts_.end() ? NULL : it->second;
227 }
228
229 // static
230 void ExtensionAppShimHandler::QuitAppForWindow(AppWindow* app_window) {
231   ExtensionAppShimHandler* handler =
232       g_browser_process->platform_part()->app_shim_host_manager()->
233           extension_app_shim_handler();
234   Host* host = handler->FindHost(
235       Profile::FromBrowserContext(app_window->browser_context()),
236       app_window->extension_id());
237   if (host) {
238     handler->OnShimQuit(host);
239   } else {
240     // App shims might be disabled or the shim is still starting up.
241     AppWindowRegistry::Get(
242         Profile::FromBrowserContext(app_window->browser_context()))
243         ->CloseAllAppWindowsForApp(app_window->extension_id());
244   }
245 }
246
247 void ExtensionAppShimHandler::HideAppForWindow(AppWindow* app_window) {
248   ExtensionAppShimHandler* handler =
249       g_browser_process->platform_part()->app_shim_host_manager()->
250           extension_app_shim_handler();
251   Profile* profile = Profile::FromBrowserContext(app_window->browser_context());
252   Host* host = handler->FindHost(profile, app_window->extension_id());
253   if (host)
254     host->OnAppHide();
255   else
256     SetAppHidden(profile, app_window->extension_id(), true);
257 }
258
259 void ExtensionAppShimHandler::FocusAppForWindow(AppWindow* app_window) {
260   ExtensionAppShimHandler* handler =
261       g_browser_process->platform_part()->app_shim_host_manager()->
262           extension_app_shim_handler();
263   Profile* profile = Profile::FromBrowserContext(app_window->browser_context());
264   const std::string& app_id = app_window->extension_id();
265   Host* host = handler->FindHost(profile, app_id);
266   if (host) {
267     handler->OnShimFocus(host,
268                          APP_SHIM_FOCUS_NORMAL,
269                          std::vector<base::FilePath>());
270   } else {
271     FocusWindows(
272         apps::AppWindowRegistry::Get(profile)->GetAppWindowsForApp(app_id));
273   }
274 }
275
276 // static
277 bool ExtensionAppShimHandler::RequestUserAttentionForWindow(
278     AppWindow* app_window) {
279   ExtensionAppShimHandler* handler =
280       g_browser_process->platform_part()->app_shim_host_manager()->
281           extension_app_shim_handler();
282   Profile* profile = Profile::FromBrowserContext(app_window->browser_context());
283   Host* host = handler->FindHost(profile, app_window->extension_id());
284   if (host) {
285     // Bring the window to the front without showing it.
286     AppWindowRegistry::Get(profile)->AppWindowActivated(app_window);
287     host->OnAppRequestUserAttention();
288     return true;
289   } else {
290     // Just show the app.
291     SetAppHidden(profile, app_window->extension_id(), false);
292     return false;
293   }
294 }
295
296 void ExtensionAppShimHandler::OnShimLaunch(
297     Host* host,
298     AppShimLaunchType launch_type,
299     const std::vector<base::FilePath>& files) {
300   const std::string& app_id = host->GetAppId();
301   DCHECK(extensions::Extension::IdIsValid(app_id));
302
303   const base::FilePath& profile_path = host->GetProfilePath();
304   DCHECK(!profile_path.empty());
305
306   if (!delegate_->ProfileExistsForPath(profile_path)) {
307     // User may have deleted the profile this shim was originally created for.
308     // TODO(jackhou): Add some UI for this case and remove the LOG.
309     LOG(ERROR) << "Requested directory is not a known profile '"
310                << profile_path.value() << "'.";
311     host->OnAppLaunchComplete(APP_SHIM_LAUNCH_PROFILE_NOT_FOUND);
312     return;
313   }
314
315   Profile* profile = delegate_->ProfileForPath(profile_path);
316
317   if (profile) {
318     OnProfileLoaded(host, launch_type, files, profile);
319     return;
320   }
321
322   // If the profile is not loaded, this must have been a launch by the shim.
323   // Load the profile asynchronously, the host will be registered in
324   // OnProfileLoaded.
325   DCHECK_EQ(APP_SHIM_LAUNCH_NORMAL, launch_type);
326   delegate_->LoadProfileAsync(
327       profile_path,
328       base::Bind(&ExtensionAppShimHandler::OnProfileLoaded,
329                  weak_factory_.GetWeakPtr(),
330                  host, launch_type, files));
331
332   // Return now. OnAppLaunchComplete will be called when the app is activated.
333 }
334
335 void ExtensionAppShimHandler::OnProfileLoaded(
336     Host* host,
337     AppShimLaunchType launch_type,
338     const std::vector<base::FilePath>& files,
339     Profile* profile) {
340   const std::string& app_id = host->GetAppId();
341
342   // The first host to claim this (profile, app_id) becomes the main host.
343   // For any others, focus or relaunch the app.
344   if (!hosts_.insert(make_pair(make_pair(profile, app_id), host)).second) {
345     OnShimFocus(host,
346                 launch_type == APP_SHIM_LAUNCH_NORMAL ?
347                     APP_SHIM_FOCUS_REOPEN : APP_SHIM_FOCUS_NORMAL,
348                 files);
349     host->OnAppLaunchComplete(APP_SHIM_LAUNCH_DUPLICATE_HOST);
350     return;
351   }
352
353   if (launch_type != APP_SHIM_LAUNCH_NORMAL) {
354     host->OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS);
355     return;
356   }
357
358   // TODO(jeremya): Handle the case that launching the app fails. Probably we
359   // need to watch for 'app successfully launched' or at least 'background page
360   // exists/was created' and time out with failure if we don't see that sign of
361   // life within a certain window.
362   const extensions::Extension* extension =
363       delegate_->GetAppExtension(profile, app_id);
364   if (extension) {
365     delegate_->LaunchApp(profile, extension, files);
366     return;
367   }
368
369   delegate_->EnableExtension(
370       profile, app_id,
371       base::Bind(&ExtensionAppShimHandler::OnExtensionEnabled,
372                  weak_factory_.GetWeakPtr(),
373                  host->GetProfilePath(), app_id, files));
374 }
375
376 void ExtensionAppShimHandler::OnExtensionEnabled(
377     const base::FilePath& profile_path,
378     const std::string& app_id,
379     const std::vector<base::FilePath>& files) {
380   Profile* profile = delegate_->ProfileForPath(profile_path);
381   if (!profile)
382     return;
383
384   const extensions::Extension* extension =
385       delegate_->GetAppExtension(profile, app_id);
386   if (!extension || !delegate_->ProfileExistsForPath(profile_path)) {
387     // If !extension, the extension doesn't exist, or was not re-enabled.
388     // If the profile doesn't exist, it may have been deleted during the enable
389     // prompt. In this case, NOTIFICATION_PROFILE_DESTROYED may not be fired
390     // until later, so respond to the host now.
391     Host* host = FindHost(profile, app_id);
392     if (host)
393       host->OnAppLaunchComplete(APP_SHIM_LAUNCH_APP_NOT_FOUND);
394     return;
395   }
396
397   delegate_->LaunchApp(profile, extension, files);
398 }
399
400
401 void ExtensionAppShimHandler::OnShimClose(Host* host) {
402   // This might be called when shutting down. Don't try to look up the profile
403   // since profile_manager might not be around.
404   for (HostMap::iterator it = hosts_.begin(); it != hosts_.end(); ) {
405     HostMap::iterator current = it++;
406     if (current->second == host)
407       hosts_.erase(current);
408   }
409 }
410
411 void ExtensionAppShimHandler::OnShimFocus(
412     Host* host,
413     AppShimFocusType focus_type,
414     const std::vector<base::FilePath>& files) {
415   DCHECK(delegate_->ProfileExistsForPath(host->GetProfilePath()));
416   Profile* profile = delegate_->ProfileForPath(host->GetProfilePath());
417
418   const AppWindowList windows =
419       delegate_->GetWindows(profile, host->GetAppId());
420   bool windows_focused = FocusWindows(windows);
421
422   if (focus_type == APP_SHIM_FOCUS_NORMAL ||
423       (focus_type == APP_SHIM_FOCUS_REOPEN && windows_focused)) {
424     return;
425   }
426
427   const extensions::Extension* extension =
428       delegate_->GetAppExtension(profile, host->GetAppId());
429   if (extension) {
430     delegate_->LaunchApp(profile, extension, files);
431   } else {
432     // Extensions may have been uninstalled or disabled since the shim
433     // started.
434     host->OnAppClosed();
435   }
436 }
437
438 void ExtensionAppShimHandler::OnShimSetHidden(Host* host, bool hidden) {
439   DCHECK(delegate_->ProfileExistsForPath(host->GetProfilePath()));
440   Profile* profile = delegate_->ProfileForPath(host->GetProfilePath());
441
442   SetAppHidden(profile, host->GetAppId(), hidden);
443 }
444
445 void ExtensionAppShimHandler::OnShimQuit(Host* host) {
446   DCHECK(delegate_->ProfileExistsForPath(host->GetProfilePath()));
447   Profile* profile = delegate_->ProfileForPath(host->GetProfilePath());
448
449   const std::string& app_id = host->GetAppId();
450   const AppWindowList windows = delegate_->GetWindows(profile, app_id);
451   for (AppWindowRegistry::const_iterator it = windows.begin();
452        it != windows.end();
453        ++it) {
454     (*it)->GetBaseWindow()->Close();
455   }
456   // Once the last window closes, flow will end up in OnAppDeactivated via
457   // AppLifetimeMonitor.
458 }
459
460 void ExtensionAppShimHandler::set_delegate(Delegate* delegate) {
461   delegate_.reset(delegate);
462 }
463
464 void ExtensionAppShimHandler::Observe(
465     int type,
466     const content::NotificationSource& source,
467     const content::NotificationDetails& details) {
468   Profile* profile = content::Source<Profile>(source).ptr();
469   if (profile->IsOffTheRecord())
470     return;
471
472   switch (type) {
473     case chrome::NOTIFICATION_PROFILE_CREATED: {
474       AppLifetimeMonitorFactory::GetForProfile(profile)->AddObserver(this);
475       break;
476     }
477     case chrome::NOTIFICATION_PROFILE_DESTROYED: {
478       AppLifetimeMonitorFactory::GetForProfile(profile)->RemoveObserver(this);
479       // Shut down every shim associated with this profile.
480       for (HostMap::iterator it = hosts_.begin(); it != hosts_.end(); ) {
481         // Increment the iterator first as OnAppClosed may call back to
482         // OnShimClose and invalidate the iterator.
483         HostMap::iterator current = it++;
484         if (profile->IsSameProfile(current->first.first)) {
485           Host* host = current->second;
486           host->OnAppClosed();
487         }
488       }
489       break;
490     }
491     default: {
492       NOTREACHED();  // Unexpected notification.
493       break;
494     }
495   }
496 }
497
498 void ExtensionAppShimHandler::OnAppStart(Profile* profile,
499                                          const std::string& app_id) {}
500
501 void ExtensionAppShimHandler::OnAppActivated(Profile* profile,
502                                              const std::string& app_id) {
503   const extensions::Extension* extension =
504       delegate_->GetAppExtension(profile, app_id);
505   if (!extension)
506     return;
507
508   Host* host = FindHost(profile, app_id);
509   if (host) {
510     host->OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS);
511     OnShimFocus(host, APP_SHIM_FOCUS_NORMAL, std::vector<base::FilePath>());
512     return;
513   }
514
515   delegate_->LaunchShim(profile, extension);
516 }
517
518 void ExtensionAppShimHandler::OnAppDeactivated(Profile* profile,
519                                                const std::string& app_id) {
520   Host* host = FindHost(profile, app_id);
521   if (host)
522     host->OnAppClosed();
523
524   if (hosts_.empty())
525     delegate_->MaybeTerminate();
526 }
527
528 void ExtensionAppShimHandler::OnAppStop(Profile* profile,
529                                         const std::string& app_id) {}
530
531 void ExtensionAppShimHandler::OnChromeTerminating() {}
532
533 }  // namespace apps