Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(777)

Side by Side Diff: apps/app_shim/extension_app_shim_handler_mac.cc

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

Powered by Google App Engine
This is Rietveld 408576698