Report errors from ChromiumEnv::GetChildren in Posix.
[chromium-blink-merge.git] / apps / app_shim / extension_app_shim_handler_mac.cc
blob3c08f2d4c8b2d6ddb76e8f598060dc5b072124cf
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/launcher.h"
11 #include "apps/shell_window.h"
12 #include "apps/shell_window_registry.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/extensions/extension_service.h"
20 #include "chrome/browser/extensions/extension_system.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/profiles/profile_manager.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 "ui/base/cocoa/focus_window_set.h"
32 namespace {
34 typedef apps::ShellWindowRegistry::ShellWindowList ShellWindowList;
36 void ProfileLoadedCallback(base::Callback<void(Profile*)> callback,
37 Profile* profile,
38 Profile::CreateStatus status) {
39 if (status == Profile::CREATE_STATUS_INITIALIZED) {
40 callback.Run(profile);
41 return;
44 // This should never get an error since it only loads existing profiles.
45 DCHECK_EQ(Profile::CREATE_STATUS_CREATED, status);
48 void SetAppHidden(Profile* profile, const std::string& app_id, bool hidden) {
49 ShellWindowList windows =
50 apps::ShellWindowRegistry::Get(profile)->GetShellWindowsForApp(app_id);
51 for (ShellWindowList::const_reverse_iterator it = windows.rbegin();
52 it != windows.rend(); ++it) {
53 if (hidden)
54 (*it)->GetBaseWindow()->HideWithApp();
55 else
56 (*it)->GetBaseWindow()->ShowWithApp();
60 bool FocusWindows(const ShellWindowList& windows) {
61 if (windows.empty())
62 return false;
64 std::set<gfx::NativeWindow> native_windows;
65 for (ShellWindowList::const_iterator it = windows.begin();
66 it != windows.end(); ++it) {
67 native_windows.insert((*it)->GetNativeWindow());
69 // Allow workspace switching. For the browser process, we can reasonably rely
70 // on OS X to switch spaces for us and honor relevant user settings. But shims
71 // don't have windows, so we have to do it ourselves.
72 ui::FocusWindowSet(native_windows, true);
73 return true;
76 } // namespace
78 namespace apps {
80 bool ExtensionAppShimHandler::Delegate::ProfileExistsForPath(
81 const base::FilePath& path) {
82 ProfileManager* profile_manager = g_browser_process->profile_manager();
83 // Check for the profile name in the profile info cache to ensure that we
84 // never access any directory that isn't a known profile.
85 base::FilePath full_path = profile_manager->user_data_dir().Append(path);
86 ProfileInfoCache& cache = profile_manager->GetProfileInfoCache();
87 return cache.GetIndexOfProfileWithPath(full_path) != std::string::npos;
90 Profile* ExtensionAppShimHandler::Delegate::ProfileForPath(
91 const base::FilePath& path) {
92 ProfileManager* profile_manager = g_browser_process->profile_manager();
93 base::FilePath full_path = profile_manager->user_data_dir().Append(path);
94 Profile* profile = profile_manager->GetProfileByPath(full_path);
96 // Use IsValidProfile to check if the profile has been created.
97 return profile && profile_manager->IsValidProfile(profile) ? profile : NULL;
100 void ExtensionAppShimHandler::Delegate::LoadProfileAsync(
101 const base::FilePath& path,
102 base::Callback<void(Profile*)> callback) {
103 ProfileManager* profile_manager = g_browser_process->profile_manager();
104 base::FilePath full_path = profile_manager->user_data_dir().Append(path);
105 profile_manager->CreateProfileAsync(
106 full_path,
107 base::Bind(&ProfileLoadedCallback, callback),
108 string16(), string16(), std::string());
111 ShellWindowList ExtensionAppShimHandler::Delegate::GetWindows(
112 Profile* profile,
113 const std::string& extension_id) {
114 return ShellWindowRegistry::Get(profile)->GetShellWindowsForApp(extension_id);
117 const extensions::Extension*
118 ExtensionAppShimHandler::Delegate::GetAppExtension(
119 Profile* profile,
120 const std::string& extension_id) {
121 ExtensionService* extension_service =
122 extensions::ExtensionSystem::Get(profile)->extension_service();
123 DCHECK(extension_service);
124 const extensions::Extension* extension =
125 extension_service->GetExtensionById(extension_id, false);
126 return extension && extension->is_platform_app() ? extension : NULL;
129 void ExtensionAppShimHandler::Delegate::LaunchApp(
130 Profile* profile,
131 const extensions::Extension* extension,
132 const std::vector<base::FilePath>& files) {
133 CoreAppLauncherHandler::RecordAppLaunchType(
134 extension_misc::APP_LAUNCH_CMD_LINE_APP, extension->GetType());
135 if (files.empty()) {
136 apps::LaunchPlatformApp(profile, extension);
137 } else {
138 for (std::vector<base::FilePath>::const_iterator it = files.begin();
139 it != files.end(); ++it) {
140 apps::LaunchPlatformAppWithPath(profile, extension, *it);
145 void ExtensionAppShimHandler::Delegate::LaunchShim(
146 Profile* profile,
147 const extensions::Extension* extension) {
148 web_app::MaybeLaunchShortcut(
149 web_app::ShortcutInfoForExtensionAndProfile(extension, profile));
152 void ExtensionAppShimHandler::Delegate::MaybeTerminate() {
153 AppShimHandler::MaybeTerminate();
156 ExtensionAppShimHandler::ExtensionAppShimHandler()
157 : delegate_(new Delegate),
158 weak_factory_(this) {
159 // This is instantiated in BrowserProcessImpl::PreMainMessageLoopRun with
160 // AppShimHostManager. Since PROFILE_CREATED is not fired until
161 // ProfileManager::GetLastUsedProfile/GetLastOpenedProfiles, this should catch
162 // notifications for all profiles.
163 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CREATED,
164 content::NotificationService::AllBrowserContextsAndSources());
165 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
166 content::NotificationService::AllBrowserContextsAndSources());
169 ExtensionAppShimHandler::~ExtensionAppShimHandler() {}
171 AppShimHandler::Host* ExtensionAppShimHandler::FindHost(
172 Profile* profile,
173 const std::string& app_id) {
174 HostMap::iterator it = hosts_.find(make_pair(profile, app_id));
175 return it == hosts_.end() ? NULL : it->second;
178 // static
179 void ExtensionAppShimHandler::QuitAppForWindow(ShellWindow* shell_window) {
180 ExtensionAppShimHandler* handler =
181 g_browser_process->platform_part()->app_shim_host_manager()->
182 extension_app_shim_handler();
183 Host* host = handler->FindHost(shell_window->profile(),
184 shell_window->extension_id());
185 if (host) {
186 handler->OnShimQuit(host);
187 } else {
188 // App shims might be disabled or the shim is still starting up.
189 ShellWindowRegistry::Get(shell_window->profile())->
190 CloseAllShellWindowsForApp(shell_window->extension_id());
194 void ExtensionAppShimHandler::HideAppForWindow(ShellWindow* shell_window) {
195 ExtensionAppShimHandler* handler =
196 g_browser_process->platform_part()->app_shim_host_manager()->
197 extension_app_shim_handler();
198 Profile* profile = shell_window->profile();
199 Host* host = handler->FindHost(profile, shell_window->extension_id());
200 if (host)
201 host->OnAppHide();
202 else
203 SetAppHidden(profile, shell_window->extension_id(), true);
207 void ExtensionAppShimHandler::FocusAppForWindow(ShellWindow* shell_window) {
208 ExtensionAppShimHandler* handler =
209 g_browser_process->platform_part()->app_shim_host_manager()->
210 extension_app_shim_handler();
211 Profile* profile = shell_window->profile();
212 const std::string& app_id = shell_window->extension_id();
213 Host* host = handler->FindHost(profile, app_id);
214 if (host) {
215 handler->OnShimFocus(host,
216 APP_SHIM_FOCUS_NORMAL,
217 std::vector<base::FilePath>());
218 } else {
219 FocusWindows(
220 apps::ShellWindowRegistry::Get(profile)->GetShellWindowsForApp(app_id));
224 // static
225 bool ExtensionAppShimHandler::RequestUserAttentionForWindow(
226 ShellWindow* shell_window) {
227 ExtensionAppShimHandler* handler =
228 g_browser_process->platform_part()->app_shim_host_manager()->
229 extension_app_shim_handler();
230 Profile* profile = shell_window->profile();
231 Host* host = handler->FindHost(profile, shell_window->extension_id());
232 if (host) {
233 // Bring the window to the front without showing it.
234 ShellWindowRegistry::Get(profile)->ShellWindowActivated(shell_window);
235 host->OnAppRequestUserAttention();
236 return true;
237 } else {
238 // Just show the app.
239 SetAppHidden(profile, shell_window->extension_id(), false);
240 return false;
244 void ExtensionAppShimHandler::OnShimLaunch(
245 Host* host,
246 AppShimLaunchType launch_type,
247 const std::vector<base::FilePath>& files) {
248 const std::string& app_id = host->GetAppId();
249 DCHECK(extensions::Extension::IdIsValid(app_id));
251 const base::FilePath& profile_path = host->GetProfilePath();
252 DCHECK(!profile_path.empty());
254 if (!delegate_->ProfileExistsForPath(profile_path)) {
255 // User may have deleted the profile this shim was originally created for.
256 // TODO(jackhou): Add some UI for this case and remove the LOG.
257 LOG(ERROR) << "Requested directory is not a known profile '"
258 << profile_path.value() << "'.";
259 host->OnAppLaunchComplete(APP_SHIM_LAUNCH_PROFILE_NOT_FOUND);
260 return;
263 Profile* profile = delegate_->ProfileForPath(profile_path);
265 if (profile) {
266 OnProfileLoaded(host, launch_type, files, profile);
267 return;
270 // If the profile is not loaded, this must have been a launch by the shim.
271 // Load the profile asynchronously, the host will be registered in
272 // OnProfileLoaded.
273 DCHECK_EQ(APP_SHIM_LAUNCH_NORMAL, launch_type);
274 delegate_->LoadProfileAsync(
275 profile_path,
276 base::Bind(&ExtensionAppShimHandler::OnProfileLoaded,
277 weak_factory_.GetWeakPtr(),
278 host, launch_type, files));
280 // Return now. OnAppLaunchComplete will be called when the app is activated.
283 void ExtensionAppShimHandler::OnProfileLoaded(
284 Host* host,
285 AppShimLaunchType launch_type,
286 const std::vector<base::FilePath>& files,
287 Profile* profile) {
288 const std::string& app_id = host->GetAppId();
289 // TODO(jackhou): Add some UI for this case and remove the LOG.
290 const extensions::Extension* extension =
291 delegate_->GetAppExtension(profile, app_id);
292 if (!extension) {
293 LOG(ERROR) << "Attempted to launch nonexistent app with id '"
294 << app_id << "'.";
295 host->OnAppLaunchComplete(APP_SHIM_LAUNCH_APP_NOT_FOUND);
296 return;
299 // The first host to claim this (profile, app_id) becomes the main host.
300 // For any others, focus or relaunch the app.
301 if (!hosts_.insert(make_pair(make_pair(profile, app_id), host)).second) {
302 OnShimFocus(host,
303 launch_type == APP_SHIM_LAUNCH_NORMAL ?
304 APP_SHIM_FOCUS_REOPEN : APP_SHIM_FOCUS_NORMAL,
305 files);
306 host->OnAppLaunchComplete(APP_SHIM_LAUNCH_DUPLICATE_HOST);
307 return;
310 // TODO(jeremya): Handle the case that launching the app fails. Probably we
311 // need to watch for 'app successfully launched' or at least 'background page
312 // exists/was created' and time out with failure if we don't see that sign of
313 // life within a certain window.
314 if (launch_type == APP_SHIM_LAUNCH_NORMAL)
315 delegate_->LaunchApp(profile, extension, files);
316 else
317 host->OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS);
320 void ExtensionAppShimHandler::OnShimClose(Host* host) {
321 // This might be called when shutting down. Don't try to look up the profile
322 // since profile_manager might not be around.
323 for (HostMap::iterator it = hosts_.begin(); it != hosts_.end(); ) {
324 HostMap::iterator current = it++;
325 if (current->second == host)
326 hosts_.erase(current);
330 void ExtensionAppShimHandler::OnShimFocus(
331 Host* host,
332 AppShimFocusType focus_type,
333 const std::vector<base::FilePath>& files) {
334 DCHECK(delegate_->ProfileExistsForPath(host->GetProfilePath()));
335 Profile* profile = delegate_->ProfileForPath(host->GetProfilePath());
337 const ShellWindowList windows =
338 delegate_->GetWindows(profile, host->GetAppId());
339 bool windows_focused = FocusWindows(windows);
341 if (focus_type == APP_SHIM_FOCUS_NORMAL ||
342 (focus_type == APP_SHIM_FOCUS_REOPEN && windows_focused)) {
343 return;
346 const extensions::Extension* extension =
347 delegate_->GetAppExtension(profile, host->GetAppId());
348 if (extension) {
349 delegate_->LaunchApp(profile, extension, files);
350 } else {
351 // Extensions may have been uninstalled or disabled since the shim
352 // started.
353 host->OnAppClosed();
357 void ExtensionAppShimHandler::OnShimSetHidden(Host* host, bool hidden) {
358 DCHECK(delegate_->ProfileExistsForPath(host->GetProfilePath()));
359 Profile* profile = delegate_->ProfileForPath(host->GetProfilePath());
361 SetAppHidden(profile, host->GetAppId(), hidden);
364 void ExtensionAppShimHandler::OnShimQuit(Host* host) {
365 DCHECK(delegate_->ProfileExistsForPath(host->GetProfilePath()));
366 Profile* profile = delegate_->ProfileForPath(host->GetProfilePath());
368 const std::string& app_id = host->GetAppId();
369 const ShellWindowList windows =
370 delegate_->GetWindows(profile, app_id);
371 for (ShellWindowRegistry::const_iterator it = windows.begin();
372 it != windows.end(); ++it) {
373 (*it)->GetBaseWindow()->Close();
375 // Once the last window closes, flow will end up in OnAppDeactivated via
376 // AppLifetimeMonitor.
379 void ExtensionAppShimHandler::set_delegate(Delegate* delegate) {
380 delegate_.reset(delegate);
383 void ExtensionAppShimHandler::Observe(
384 int type,
385 const content::NotificationSource& source,
386 const content::NotificationDetails& details) {
387 Profile* profile = content::Source<Profile>(source).ptr();
388 if (profile->IsOffTheRecord())
389 return;
391 switch (type) {
392 case chrome::NOTIFICATION_PROFILE_CREATED: {
393 AppLifetimeMonitorFactory::GetForProfile(profile)->AddObserver(this);
394 break;
396 case chrome::NOTIFICATION_PROFILE_DESTROYED: {
397 AppLifetimeMonitorFactory::GetForProfile(profile)->RemoveObserver(this);
398 // Shut down every shim associated with this profile.
399 for (HostMap::iterator it = hosts_.begin(); it != hosts_.end(); ) {
400 // Increment the iterator first as OnAppClosed may call back to
401 // OnShimClose and invalidate the iterator.
402 HostMap::iterator current = it++;
403 if (profile->IsSameProfile(current->first.first))
404 current->second->OnAppClosed();
406 break;
408 default: {
409 NOTREACHED(); // Unexpected notification.
410 break;
415 void ExtensionAppShimHandler::OnAppStart(Profile* profile,
416 const std::string& app_id) {}
418 void ExtensionAppShimHandler::OnAppActivated(Profile* profile,
419 const std::string& app_id) {
420 const extensions::Extension* extension =
421 delegate_->GetAppExtension(profile, app_id);
422 if (!extension)
423 return;
425 Host* host = FindHost(profile, app_id);
426 if (host) {
427 host->OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS);
428 OnShimFocus(host, APP_SHIM_FOCUS_NORMAL, std::vector<base::FilePath>());
429 return;
432 delegate_->LaunchShim(profile, extension);
435 void ExtensionAppShimHandler::OnAppDeactivated(Profile* profile,
436 const std::string& app_id) {
437 Host* host = FindHost(profile, app_id);
438 if (host)
439 host->OnAppClosed();
441 if (hosts_.empty())
442 delegate_->MaybeTerminate();
445 void ExtensionAppShimHandler::OnAppStop(Profile* profile,
446 const std::string& app_id) {}
448 void ExtensionAppShimHandler::OnChromeTerminating() {}
450 } // namespace apps