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.
5 #include "apps/app_shim/extension_app_shim_handler_mac.h"
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 "base/files/file_path.h"
14 #include "base/logging.h"
15 #include "chrome/browser/browser_process.h"
16 #include "chrome/browser/chrome_notification_types.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/profiles/profile_manager.h"
19 #include "chrome/browser/ui/extensions/extension_enable_flow.h"
20 #include "chrome/browser/ui/extensions/extension_enable_flow_delegate.h"
21 #include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h"
22 #include "chrome/browser/web_applications/web_app_mac.h"
23 #include "chrome/common/extensions/extension_constants.h"
24 #include "components/crx_file/id_util.h"
25 #include "content/public/browser/notification_details.h"
26 #include "content/public/browser/notification_service.h"
27 #include "content/public/browser/notification_source.h"
28 #include "extensions/browser/app_window/native_app_window.h"
29 #include "extensions/browser/extension_host.h"
30 #include "extensions/browser/extension_registry.h"
31 #include "ui/base/cocoa/focus_window_set.h"
33 using extensions::ExtensionRegistry
;
37 typedef apps::AppWindowRegistry::AppWindowList AppWindowList
;
39 void ProfileLoadedCallback(base::Callback
<void(Profile
*)> callback
,
41 Profile::CreateStatus status
) {
42 if (status
== Profile::CREATE_STATUS_INITIALIZED
) {
43 callback
.Run(profile
);
47 // This should never get an error since it only loads existing profiles.
48 DCHECK_EQ(Profile::CREATE_STATUS_CREATED
, status
);
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();
58 (*it
)->GetBaseWindow()->HideWithApp();
60 (*it
)->GetBaseWindow()->ShowWithApp();
64 bool FocusWindows(const AppWindowList
& windows
) {
68 std::set
<gfx::NativeWindow
> native_windows
;
69 for (AppWindowList::const_iterator it
= windows
.begin(); it
!= windows
.end();
71 native_windows
.insert((*it
)->GetNativeWindow());
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
);
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
{
85 EnableViaPrompt(Profile
* profile
,
86 const std::string
& extension_id
,
87 const base::Callback
<void()>& callback
)
89 extension_id_(extension_id
),
93 virtual ~EnableViaPrompt() {
97 flow_
.reset(new ExtensionEnableFlow(profile_
, extension_id_
, this));
98 flow_
->StartForCurrentlyNonexistentWindow(
99 base::Callback
<gfx::NativeWindow(void)>());
103 // ExtensionEnableFlowDelegate overrides.
104 virtual void ExtensionEnableFlowFinished() OVERRIDE
{
109 virtual void ExtensionEnableFlowAborted(bool user_initiated
) OVERRIDE
{
115 std::string extension_id_
;
116 base::Callback
<void()> callback_
;
117 scoped_ptr
<ExtensionEnableFlow
> flow_
;
119 DISALLOW_COPY_AND_ASSIGN(EnableViaPrompt
);
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
;
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
);
142 // Use IsValidProfile to check if the profile has been created.
143 return profile
&& profile_manager
->IsValidProfile(profile
) ? profile
: NULL
;
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(
153 base::Bind(&ProfileLoadedCallback
, callback
),
154 base::string16(), base::string16(), std::string());
157 AppWindowList
ExtensionAppShimHandler::Delegate::GetWindows(
159 const std::string
& extension_id
) {
160 return AppWindowRegistry::Get(profile
)->GetAppWindowsForApp(extension_id
);
163 const extensions::Extension
*
164 ExtensionAppShimHandler::Delegate::GetAppExtension(
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
;
173 void ExtensionAppShimHandler::Delegate::EnableExtension(
175 const std::string
& extension_id
,
176 const base::Callback
<void()>& callback
) {
177 (new EnableViaPrompt(profile
, extension_id
, callback
))->Run();
180 void ExtensionAppShimHandler::Delegate::LaunchApp(
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());
187 apps::LaunchPlatformApp(profile
, extension
);
189 for (std::vector
<base::FilePath
>::const_iterator it
= files
.begin();
190 it
!= files
.end(); ++it
) {
191 apps::LaunchPlatformAppWithPath(profile
, extension
, *it
);
196 void ExtensionAppShimHandler::Delegate::LaunchShim(
198 const extensions::Extension
* extension
) {
199 web_app::MaybeLaunchShortcut(
200 web_app::ShortcutInfoForExtensionAndProfile(extension
, profile
));
203 void ExtensionAppShimHandler::Delegate::MaybeTerminate() {
204 AppShimHandler::MaybeTerminate();
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());
220 ExtensionAppShimHandler::~ExtensionAppShimHandler() {}
222 AppShimHandler::Host
* ExtensionAppShimHandler::FindHost(
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
;
230 void ExtensionAppShimHandler::QuitAppForWindow(AppWindow
* app_window
) {
231 ExtensionAppShimHandler
* handler
= GetInstance();
232 Host
* host
= handler
->FindHost(
233 Profile::FromBrowserContext(app_window
->browser_context()),
234 app_window
->extension_id());
236 handler
->OnShimQuit(host
);
238 // App shims might be disabled or the shim is still starting up.
239 AppWindowRegistry::Get(
240 Profile::FromBrowserContext(app_window
->browser_context()))
241 ->CloseAllAppWindowsForApp(app_window
->extension_id());
245 void ExtensionAppShimHandler::HideAppForWindow(AppWindow
* app_window
) {
246 ExtensionAppShimHandler
* handler
= GetInstance();
247 Profile
* profile
= Profile::FromBrowserContext(app_window
->browser_context());
248 Host
* host
= handler
->FindHost(profile
, app_window
->extension_id());
252 SetAppHidden(profile
, app_window
->extension_id(), true);
255 void ExtensionAppShimHandler::FocusAppForWindow(AppWindow
* app_window
) {
256 ExtensionAppShimHandler
* handler
= GetInstance();
257 Profile
* profile
= Profile::FromBrowserContext(app_window
->browser_context());
258 const std::string
& app_id
= app_window
->extension_id();
259 Host
* host
= handler
->FindHost(profile
, app_id
);
261 handler
->OnShimFocus(host
,
262 APP_SHIM_FOCUS_NORMAL
,
263 std::vector
<base::FilePath
>());
266 apps::AppWindowRegistry::Get(profile
)->GetAppWindowsForApp(app_id
));
271 bool ExtensionAppShimHandler::ActivateAndRequestUserAttentionForWindow(
272 AppWindow
* app_window
) {
273 ExtensionAppShimHandler
* handler
= GetInstance();
274 Profile
* profile
= Profile::FromBrowserContext(app_window
->browser_context());
275 Host
* host
= handler
->FindHost(profile
, app_window
->extension_id());
277 // Bring the window to the front without showing it.
278 AppWindowRegistry::Get(profile
)->AppWindowActivated(app_window
);
279 host
->OnAppRequestUserAttention(APP_SHIM_ATTENTION_INFORMATIONAL
);
282 // Just show the app.
283 SetAppHidden(profile
, app_window
->extension_id(), false);
289 void ExtensionAppShimHandler::RequestUserAttentionForWindow(
290 AppWindow
* app_window
,
291 AppShimAttentionType attention_type
) {
292 ExtensionAppShimHandler
* handler
= GetInstance();
293 Profile
* profile
= Profile::FromBrowserContext(app_window
->browser_context());
294 Host
* host
= handler
->FindHost(profile
, app_window
->extension_id());
296 host
->OnAppRequestUserAttention(attention_type
);
300 void ExtensionAppShimHandler::OnChromeWillHide() {
301 // Send OnAppHide to all the shims so that they go into the hidden state.
302 // This is necessary so that when the shim is next focused, it will know to
304 ExtensionAppShimHandler
* handler
= GetInstance();
305 for (HostMap::iterator it
= handler
->hosts_
.begin();
306 it
!= handler
->hosts_
.end();
308 it
->second
->OnAppHide();
312 void ExtensionAppShimHandler::OnShimLaunch(
314 AppShimLaunchType launch_type
,
315 const std::vector
<base::FilePath
>& files
) {
316 const std::string
& app_id
= host
->GetAppId();
317 DCHECK(crx_file::id_util::IdIsValid(app_id
));
319 const base::FilePath
& profile_path
= host
->GetProfilePath();
320 DCHECK(!profile_path
.empty());
322 if (!delegate_
->ProfileExistsForPath(profile_path
)) {
323 // User may have deleted the profile this shim was originally created for.
324 // TODO(jackhou): Add some UI for this case and remove the LOG.
325 LOG(ERROR
) << "Requested directory is not a known profile '"
326 << profile_path
.value() << "'.";
327 host
->OnAppLaunchComplete(APP_SHIM_LAUNCH_PROFILE_NOT_FOUND
);
331 Profile
* profile
= delegate_
->ProfileForPath(profile_path
);
334 OnProfileLoaded(host
, launch_type
, files
, profile
);
338 // If the profile is not loaded, this must have been a launch by the shim.
339 // Load the profile asynchronously, the host will be registered in
341 DCHECK_EQ(APP_SHIM_LAUNCH_NORMAL
, launch_type
);
342 delegate_
->LoadProfileAsync(
344 base::Bind(&ExtensionAppShimHandler::OnProfileLoaded
,
345 weak_factory_
.GetWeakPtr(),
346 host
, launch_type
, files
));
348 // Return now. OnAppLaunchComplete will be called when the app is activated.
352 ExtensionAppShimHandler
* ExtensionAppShimHandler::GetInstance() {
353 return g_browser_process
->platform_part()
354 ->app_shim_host_manager()
355 ->extension_app_shim_handler();
358 void ExtensionAppShimHandler::OnProfileLoaded(
360 AppShimLaunchType launch_type
,
361 const std::vector
<base::FilePath
>& files
,
363 const std::string
& app_id
= host
->GetAppId();
365 // The first host to claim this (profile, app_id) becomes the main host.
366 // For any others, focus or relaunch the app.
367 if (!hosts_
.insert(make_pair(make_pair(profile
, app_id
), host
)).second
) {
369 launch_type
== APP_SHIM_LAUNCH_NORMAL
?
370 APP_SHIM_FOCUS_REOPEN
: APP_SHIM_FOCUS_NORMAL
,
372 host
->OnAppLaunchComplete(APP_SHIM_LAUNCH_DUPLICATE_HOST
);
376 if (launch_type
!= APP_SHIM_LAUNCH_NORMAL
) {
377 host
->OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS
);
381 // TODO(jeremya): Handle the case that launching the app fails. Probably we
382 // need to watch for 'app successfully launched' or at least 'background page
383 // exists/was created' and time out with failure if we don't see that sign of
384 // life within a certain window.
385 const extensions::Extension
* extension
=
386 delegate_
->GetAppExtension(profile
, app_id
);
388 delegate_
->LaunchApp(profile
, extension
, files
);
392 delegate_
->EnableExtension(
394 base::Bind(&ExtensionAppShimHandler::OnExtensionEnabled
,
395 weak_factory_
.GetWeakPtr(),
396 host
->GetProfilePath(), app_id
, files
));
399 void ExtensionAppShimHandler::OnExtensionEnabled(
400 const base::FilePath
& profile_path
,
401 const std::string
& app_id
,
402 const std::vector
<base::FilePath
>& files
) {
403 Profile
* profile
= delegate_
->ProfileForPath(profile_path
);
407 const extensions::Extension
* extension
=
408 delegate_
->GetAppExtension(profile
, app_id
);
409 if (!extension
|| !delegate_
->ProfileExistsForPath(profile_path
)) {
410 // If !extension, the extension doesn't exist, or was not re-enabled.
411 // If the profile doesn't exist, it may have been deleted during the enable
412 // prompt. In this case, NOTIFICATION_PROFILE_DESTROYED may not be fired
413 // until later, so respond to the host now.
414 Host
* host
= FindHost(profile
, app_id
);
416 host
->OnAppLaunchComplete(APP_SHIM_LAUNCH_APP_NOT_FOUND
);
420 delegate_
->LaunchApp(profile
, extension
, files
);
424 void ExtensionAppShimHandler::OnShimClose(Host
* host
) {
425 // This might be called when shutting down. Don't try to look up the profile
426 // since profile_manager might not be around.
427 for (HostMap::iterator it
= hosts_
.begin(); it
!= hosts_
.end(); ) {
428 HostMap::iterator current
= it
++;
429 if (current
->second
== host
)
430 hosts_
.erase(current
);
434 void ExtensionAppShimHandler::OnShimFocus(
436 AppShimFocusType focus_type
,
437 const std::vector
<base::FilePath
>& files
) {
438 DCHECK(delegate_
->ProfileExistsForPath(host
->GetProfilePath()));
439 Profile
* profile
= delegate_
->ProfileForPath(host
->GetProfilePath());
441 const AppWindowList windows
=
442 delegate_
->GetWindows(profile
, host
->GetAppId());
443 bool windows_focused
= FocusWindows(windows
);
445 if (focus_type
== APP_SHIM_FOCUS_NORMAL
||
446 (focus_type
== APP_SHIM_FOCUS_REOPEN
&& windows_focused
)) {
450 const extensions::Extension
* extension
=
451 delegate_
->GetAppExtension(profile
, host
->GetAppId());
453 delegate_
->LaunchApp(profile
, extension
, files
);
455 // Extensions may have been uninstalled or disabled since the shim
461 void ExtensionAppShimHandler::OnShimSetHidden(Host
* host
, bool hidden
) {
462 DCHECK(delegate_
->ProfileExistsForPath(host
->GetProfilePath()));
463 Profile
* profile
= delegate_
->ProfileForPath(host
->GetProfilePath());
465 SetAppHidden(profile
, host
->GetAppId(), hidden
);
468 void ExtensionAppShimHandler::OnShimQuit(Host
* host
) {
469 DCHECK(delegate_
->ProfileExistsForPath(host
->GetProfilePath()));
470 Profile
* profile
= delegate_
->ProfileForPath(host
->GetProfilePath());
472 const std::string
& app_id
= host
->GetAppId();
473 const AppWindowList windows
= delegate_
->GetWindows(profile
, app_id
);
474 for (AppWindowRegistry::const_iterator it
= windows
.begin();
477 (*it
)->GetBaseWindow()->Close();
479 // Once the last window closes, flow will end up in OnAppDeactivated via
480 // AppLifetimeMonitor.
483 void ExtensionAppShimHandler::set_delegate(Delegate
* delegate
) {
484 delegate_
.reset(delegate
);
487 void ExtensionAppShimHandler::Observe(
489 const content::NotificationSource
& source
,
490 const content::NotificationDetails
& details
) {
491 Profile
* profile
= content::Source
<Profile
>(source
).ptr();
492 if (profile
->IsOffTheRecord())
496 case chrome::NOTIFICATION_PROFILE_CREATED
: {
497 AppLifetimeMonitorFactory::GetForProfile(profile
)->AddObserver(this);
500 case chrome::NOTIFICATION_PROFILE_DESTROYED
: {
501 AppLifetimeMonitorFactory::GetForProfile(profile
)->RemoveObserver(this);
502 // Shut down every shim associated with this profile.
503 for (HostMap::iterator it
= hosts_
.begin(); it
!= hosts_
.end(); ) {
504 // Increment the iterator first as OnAppClosed may call back to
505 // OnShimClose and invalidate the iterator.
506 HostMap::iterator current
= it
++;
507 if (profile
->IsSameProfile(current
->first
.first
)) {
508 Host
* host
= current
->second
;
515 NOTREACHED(); // Unexpected notification.
521 void ExtensionAppShimHandler::OnAppStart(Profile
* profile
,
522 const std::string
& app_id
) {}
524 void ExtensionAppShimHandler::OnAppActivated(Profile
* profile
,
525 const std::string
& app_id
) {
526 const extensions::Extension
* extension
=
527 delegate_
->GetAppExtension(profile
, app_id
);
531 Host
* host
= FindHost(profile
, app_id
);
533 host
->OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS
);
534 OnShimFocus(host
, APP_SHIM_FOCUS_NORMAL
, std::vector
<base::FilePath
>());
538 delegate_
->LaunchShim(profile
, extension
);
541 void ExtensionAppShimHandler::OnAppDeactivated(Profile
* profile
,
542 const std::string
& app_id
) {
543 Host
* host
= FindHost(profile
, app_id
);
548 delegate_
->MaybeTerminate();
551 void ExtensionAppShimHandler::OnAppStop(Profile
* profile
,
552 const std::string
& app_id
) {}
554 void ExtensionAppShimHandler::OnChromeTerminating() {}