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 "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/profiles/profile.h"
19 #include "chrome/browser/profiles/profile_manager.h"
20 #include "chrome/browser/ui/extensions/extension_enable_flow.h"
21 #include "chrome/browser/ui/extensions/extension_enable_flow_delegate.h"
22 #include "chrome/browser/ui/web_applications/web_app_ui.h"
23 #include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h"
24 #include "chrome/browser/web_applications/web_app_mac.h"
25 #include "chrome/common/extensions/extension_constants.h"
26 #include "content/public/browser/notification_details.h"
27 #include "content/public/browser/notification_service.h"
28 #include "content/public/browser/notification_source.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
, true);
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
=
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());
238 handler
->OnShimQuit(host
);
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());
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());
256 SetAppHidden(profile
, app_window
->extension_id(), true);
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
);
267 handler
->OnShimFocus(host
,
268 APP_SHIM_FOCUS_NORMAL
,
269 std::vector
<base::FilePath
>());
272 apps::AppWindowRegistry::Get(profile
)->GetAppWindowsForApp(app_id
));
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());
285 // Bring the window to the front without showing it.
286 AppWindowRegistry::Get(profile
)->AppWindowActivated(app_window
);
287 host
->OnAppRequestUserAttention();
290 // Just show the app.
291 SetAppHidden(profile
, app_window
->extension_id(), false);
296 void ExtensionAppShimHandler::OnShimLaunch(
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
));
303 const base::FilePath
& profile_path
= host
->GetProfilePath();
304 DCHECK(!profile_path
.empty());
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
);
315 Profile
* profile
= delegate_
->ProfileForPath(profile_path
);
318 OnProfileLoaded(host
, launch_type
, files
, profile
);
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
325 DCHECK_EQ(APP_SHIM_LAUNCH_NORMAL
, launch_type
);
326 delegate_
->LoadProfileAsync(
328 base::Bind(&ExtensionAppShimHandler::OnProfileLoaded
,
329 weak_factory_
.GetWeakPtr(),
330 host
, launch_type
, files
));
332 // Return now. OnAppLaunchComplete will be called when the app is activated.
335 void ExtensionAppShimHandler::OnProfileLoaded(
337 AppShimLaunchType launch_type
,
338 const std::vector
<base::FilePath
>& files
,
340 const std::string
& app_id
= host
->GetAppId();
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
) {
346 launch_type
== APP_SHIM_LAUNCH_NORMAL
?
347 APP_SHIM_FOCUS_REOPEN
: APP_SHIM_FOCUS_NORMAL
,
349 host
->OnAppLaunchComplete(APP_SHIM_LAUNCH_DUPLICATE_HOST
);
353 if (launch_type
!= APP_SHIM_LAUNCH_NORMAL
) {
354 host
->OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS
);
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
);
365 delegate_
->LaunchApp(profile
, extension
, files
);
369 delegate_
->EnableExtension(
371 base::Bind(&ExtensionAppShimHandler::OnExtensionEnabled
,
372 weak_factory_
.GetWeakPtr(),
373 host
->GetProfilePath(), app_id
, files
));
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
);
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
);
393 host
->OnAppLaunchComplete(APP_SHIM_LAUNCH_APP_NOT_FOUND
);
397 delegate_
->LaunchApp(profile
, extension
, files
);
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
);
411 void ExtensionAppShimHandler::OnShimFocus(
413 AppShimFocusType focus_type
,
414 const std::vector
<base::FilePath
>& files
) {
415 DCHECK(delegate_
->ProfileExistsForPath(host
->GetProfilePath()));
416 Profile
* profile
= delegate_
->ProfileForPath(host
->GetProfilePath());
418 const AppWindowList windows
=
419 delegate_
->GetWindows(profile
, host
->GetAppId());
420 bool windows_focused
= FocusWindows(windows
);
422 if (focus_type
== APP_SHIM_FOCUS_NORMAL
||
423 (focus_type
== APP_SHIM_FOCUS_REOPEN
&& windows_focused
)) {
427 const extensions::Extension
* extension
=
428 delegate_
->GetAppExtension(profile
, host
->GetAppId());
430 delegate_
->LaunchApp(profile
, extension
, files
);
432 // Extensions may have been uninstalled or disabled since the shim
438 void ExtensionAppShimHandler::OnShimSetHidden(Host
* host
, bool hidden
) {
439 DCHECK(delegate_
->ProfileExistsForPath(host
->GetProfilePath()));
440 Profile
* profile
= delegate_
->ProfileForPath(host
->GetProfilePath());
442 SetAppHidden(profile
, host
->GetAppId(), hidden
);
445 void ExtensionAppShimHandler::OnShimQuit(Host
* host
) {
446 DCHECK(delegate_
->ProfileExistsForPath(host
->GetProfilePath()));
447 Profile
* profile
= delegate_
->ProfileForPath(host
->GetProfilePath());
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();
454 (*it
)->GetBaseWindow()->Close();
456 // Once the last window closes, flow will end up in OnAppDeactivated via
457 // AppLifetimeMonitor.
460 void ExtensionAppShimHandler::set_delegate(Delegate
* delegate
) {
461 delegate_
.reset(delegate
);
464 void ExtensionAppShimHandler::Observe(
466 const content::NotificationSource
& source
,
467 const content::NotificationDetails
& details
) {
468 Profile
* profile
= content::Source
<Profile
>(source
).ptr();
469 if (profile
->IsOffTheRecord())
473 case chrome::NOTIFICATION_PROFILE_CREATED
: {
474 AppLifetimeMonitorFactory::GetForProfile(profile
)->AddObserver(this);
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
;
492 NOTREACHED(); // Unexpected notification.
498 void ExtensionAppShimHandler::OnAppStart(Profile
* profile
,
499 const std::string
& app_id
) {}
501 void ExtensionAppShimHandler::OnAppActivated(Profile
* profile
,
502 const std::string
& app_id
) {
503 const extensions::Extension
* extension
=
504 delegate_
->GetAppExtension(profile
, app_id
);
508 Host
* host
= FindHost(profile
, app_id
);
510 host
->OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS
);
511 OnShimFocus(host
, APP_SHIM_FOCUS_NORMAL
, std::vector
<base::FilePath
>());
515 delegate_
->LaunchShim(profile
, extension
);
518 void ExtensionAppShimHandler::OnAppDeactivated(Profile
* profile
,
519 const std::string
& app_id
) {
520 Host
* host
= FindHost(profile
, app_id
);
525 delegate_
->MaybeTerminate();
528 void ExtensionAppShimHandler::OnAppStop(Profile
* profile
,
529 const std::string
& app_id
) {}
531 void ExtensionAppShimHandler::OnChromeTerminating() {}