Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / app_list / app_list_service_mac.mm
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 #import "chrome/browser/ui/app_list/app_list_service_mac.h"
6
7 #include <ApplicationServices/ApplicationServices.h>
8 #import <Cocoa/Cocoa.h>
9
10 #include "base/bind.h"
11 #include "base/command_line.h"
12 #include "base/file_util.h"
13 #include "base/lazy_instance.h"
14 #include "base/mac/mac_util.h"
15 #include "base/memory/singleton.h"
16 #include "base/message_loop/message_loop.h"
17 #include "base/prefs/pref_service.h"
18 #import "chrome/browser/app_controller_mac.h"
19 #include "chrome/browser/browser_process.h"
20 #include "chrome/browser/extensions/extension_service.h"
21 #include "chrome/browser/profiles/profile_manager.h"
22 #include "chrome/browser/ui/app_list/app_list_controller_delegate_impl.h"
23 #include "chrome/browser/ui/app_list/app_list_positioner.h"
24 #include "chrome/browser/ui/app_list/app_list_service.h"
25 #include "chrome/browser/ui/app_list/app_list_service_impl.h"
26 #include "chrome/browser/ui/app_list/app_list_util.h"
27 #include "chrome/browser/ui/app_list/app_list_view_delegate.h"
28 #include "chrome/browser/ui/browser_commands.h"
29 #include "chrome/browser/ui/extensions/application_launch.h"
30 #include "chrome/browser/web_applications/web_app.h"
31 #include "chrome/browser/web_applications/web_app_mac.h"
32 #include "chrome/common/chrome_switches.h"
33 #include "chrome/common/chrome_version_info.h"
34 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
35 #include "chrome/common/mac/app_mode_common.h"
36 #include "chrome/common/pref_names.h"
37 #include "content/public/browser/browser_thread.h"
38 #include "extensions/browser/extension_system.h"
39 #include "extensions/common/manifest_handlers/file_handler_info.h"
40 #include "grit/chrome_unscaled_resources.h"
41 #include "grit/google_chrome_strings.h"
42 #include "net/base/url_util.h"
43 #import "ui/app_list/cocoa/app_list_view_controller.h"
44 #import "ui/app_list/cocoa/app_list_window_controller.h"
45 #include "ui/app_list/search_box_model.h"
46 #include "ui/base/l10n/l10n_util.h"
47 #include "ui/base/resource/resource_bundle.h"
48 #include "ui/gfx/display.h"
49 #include "ui/gfx/screen.h"
50
51 namespace gfx {
52 class ImageSkia;
53 }
54
55 // Controller for animations that show or hide the app list.
56 @interface AppListAnimationController : NSObject<NSAnimationDelegate> {
57  @private
58   // When closing, the window to close. Retained until the animation ends.
59   base::scoped_nsobject<NSWindow> window_;
60   // The animation started and owned by |self|. Reset when the animation ends.
61   base::scoped_nsobject<NSViewAnimation> animation_;
62 }
63
64 // Returns whether |window_| is scheduled to be closed when the animation ends.
65 - (BOOL)isClosing;
66
67 // Animate |window| to show or close it, after cancelling any current animation.
68 // Translates from the current location to |targetOrigin| and fades in or out.
69 - (void)animateWindow:(NSWindow*)window
70          targetOrigin:(NSPoint)targetOrigin
71               closing:(BOOL)closing;
72
73 // Called on the UI thread once the animation has completed to reset the
74 // animation state, close the window (if it is a close animation), and possibly
75 // terminate Chrome.
76 - (void)cleanupOnUIThread;
77
78 @end
79
80 namespace {
81
82 // Version of the app list shortcut version installed.
83 const int kShortcutVersion = 2;
84
85 // Duration of show and hide animations.
86 const NSTimeInterval kAnimationDuration = 0.2;
87
88 // Distance towards the screen edge that the app list moves from when showing.
89 const CGFloat kDistanceMovedOnShow = 20;
90
91 web_app::ShortcutInfo GetAppListShortcutInfo(
92     const base::FilePath& profile_path) {
93   web_app::ShortcutInfo shortcut_info;
94   chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
95   if (channel == chrome::VersionInfo::CHANNEL_CANARY) {
96     shortcut_info.title =
97         l10n_util::GetStringUTF16(IDS_APP_LIST_SHORTCUT_NAME_CANARY);
98   } else {
99     shortcut_info.title = l10n_util::GetStringUTF16(IDS_APP_LIST_SHORTCUT_NAME);
100   }
101
102   shortcut_info.extension_id = app_mode::kAppListModeId;
103   shortcut_info.description = shortcut_info.title;
104   shortcut_info.profile_path = profile_path;
105
106   return shortcut_info;
107 }
108
109 void CreateAppListShim(const base::FilePath& profile_path) {
110   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
111   WebApplicationInfo web_app_info;
112   web_app::ShortcutInfo shortcut_info =
113       GetAppListShortcutInfo(profile_path);
114
115   ResourceBundle& resource_bundle = ResourceBundle::GetSharedInstance();
116   chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
117   if (channel == chrome::VersionInfo::CHANNEL_CANARY) {
118 #if defined(GOOGLE_CHROME_BUILD)
119     shortcut_info.favicon.Add(
120         *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_CANARY_16));
121     shortcut_info.favicon.Add(
122         *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_CANARY_32));
123     shortcut_info.favicon.Add(
124         *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_CANARY_128));
125     shortcut_info.favicon.Add(
126         *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_CANARY_256));
127 #else
128     NOTREACHED();
129 #endif
130   } else {
131     shortcut_info.favicon.Add(
132         *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_16));
133     shortcut_info.favicon.Add(
134         *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_32));
135     shortcut_info.favicon.Add(
136         *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_128));
137     shortcut_info.favicon.Add(
138         *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_256));
139   }
140
141   web_app::ShortcutLocations shortcut_locations;
142   PrefService* local_state = g_browser_process->local_state();
143   int installed_version =
144       local_state->GetInteger(prefs::kAppLauncherShortcutVersion);
145
146   // If this is a first-time install, add a dock icon. Otherwise just update
147   // the target, and wait for OSX to refresh its icon caches. This might not
148   // occur until a reboot, but OSX does not offer a nicer way. Deleting cache
149   // files on disk and killing processes can easily result in icon corruption.
150   if (installed_version == 0)
151     shortcut_locations.in_quick_launch_bar = true;
152
153   web_app::CreateShortcutsWithInfo(web_app::SHORTCUT_CREATION_AUTOMATED,
154                                    shortcut_locations,
155                                    shortcut_info,
156                                    extensions::FileHandlersInfo());
157
158   local_state->SetInteger(prefs::kAppLauncherShortcutVersion,
159                           kShortcutVersion);
160 }
161
162 NSRunningApplication* ActiveApplicationNotChrome() {
163   NSArray* applications = [[NSWorkspace sharedWorkspace] runningApplications];
164   for (NSRunningApplication* application in applications) {
165     if (![application isActive])
166       continue;
167
168     if ([application isEqual:[NSRunningApplication currentApplication]])
169       return nil;  // Chrome is active.
170
171     return application;
172   }
173
174   return nil;
175 }
176
177 // Determines which screen edge the dock is aligned to.
178 AppListPositioner::ScreenEdge DockLocationInDisplay(
179     const gfx::Display& display) {
180   // Assume the dock occupies part of the work area either on the left, right or
181   // bottom of the display. Note in the autohide case, it is always 4 pixels.
182   const gfx::Rect work_area = display.work_area();
183   const gfx::Rect display_bounds = display.bounds();
184   if (work_area.bottom() != display_bounds.bottom())
185     return AppListPositioner::SCREEN_EDGE_BOTTOM;
186
187   if (work_area.x() != display_bounds.x())
188     return AppListPositioner::SCREEN_EDGE_LEFT;
189
190   if (work_area.right() != display_bounds.right())
191     return AppListPositioner::SCREEN_EDGE_RIGHT;
192
193   return AppListPositioner::SCREEN_EDGE_UNKNOWN;
194 }
195
196 // If |display|'s work area is too close to its boundary on |dock_edge|, adjust
197 // the work area away from the edge by a constant amount to reduce overlap and
198 // ensure the dock icon can still be clicked to dismiss the app list.
199 void AdjustWorkAreaForDock(const gfx::Display& display,
200                            AppListPositioner* positioner,
201                            AppListPositioner::ScreenEdge dock_edge) {
202   const int kAutohideDockThreshold = 10;
203   const int kExtraDistance = 50;  // A dock with 40 items is about this size.
204
205   const gfx::Rect work_area = display.work_area();
206   const gfx::Rect display_bounds = display.bounds();
207
208   switch (dock_edge) {
209     case AppListPositioner::SCREEN_EDGE_LEFT:
210       if (work_area.x() - display_bounds.x() <= kAutohideDockThreshold)
211         positioner->WorkAreaInset(kExtraDistance, 0, 0, 0);
212       break;
213     case AppListPositioner::SCREEN_EDGE_RIGHT:
214       if (display_bounds.right() - work_area.right() <= kAutohideDockThreshold)
215         positioner->WorkAreaInset(0, 0, kExtraDistance, 0);
216       break;
217     case AppListPositioner::SCREEN_EDGE_BOTTOM:
218       if (display_bounds.bottom() - work_area.bottom() <=
219           kAutohideDockThreshold) {
220         positioner->WorkAreaInset(0, 0, 0, kExtraDistance);
221       }
222       break;
223     case AppListPositioner::SCREEN_EDGE_UNKNOWN:
224     case AppListPositioner::SCREEN_EDGE_TOP:
225       NOTREACHED();
226       break;
227   }
228 }
229
230 void GetAppListWindowOrigins(
231     NSWindow* window, NSPoint* target_origin, NSPoint* start_origin) {
232   gfx::Screen* const screen = gfx::Screen::GetScreenFor([window contentView]);
233   // Ensure y coordinates are flipped back into AppKit's coordinate system.
234   bool cursor_is_visible = CGCursorIsVisible();
235   gfx::Display display;
236   gfx::Point cursor;
237   if (!cursor_is_visible) {
238     // If Chrome is the active application, display on the same display as
239     // Chrome's keyWindow since this will catch activations triggered, e.g, via
240     // WebStore install. If another application is active, OSX doesn't provide a
241     // reliable way to get the display in use. Fall back to the primary display
242     // since it has the menu bar and is likely to be correct, e.g., for
243     // activations from Spotlight.
244     const gfx::NativeView key_view = [[NSApp keyWindow] contentView];
245     display = key_view && [NSApp isActive] ?
246         screen->GetDisplayNearestWindow(key_view) :
247         screen->GetPrimaryDisplay();
248   } else {
249     cursor = screen->GetCursorScreenPoint();
250     display = screen->GetDisplayNearestPoint(cursor);
251   }
252
253   const NSSize ns_window_size = [window frame].size;
254   gfx::Size window_size(ns_window_size.width, ns_window_size.height);
255   int primary_display_height =
256       NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]);
257   AppListServiceMac::FindAnchorPoint(window_size,
258                                      display,
259                                      primary_display_height,
260                                      cursor_is_visible,
261                                      cursor,
262                                      target_origin,
263                                      start_origin);
264 }
265
266 }  // namespace
267
268 AppListServiceMac::AppListServiceMac()
269     : profile_(NULL),
270       controller_delegate_(new AppListControllerDelegateImpl(this)) {
271   animation_controller_.reset([[AppListAnimationController alloc] init]);
272 }
273
274 AppListServiceMac::~AppListServiceMac() {}
275
276 // static
277 AppListServiceMac* AppListServiceMac::GetInstance() {
278   return Singleton<AppListServiceMac,
279                    LeakySingletonTraits<AppListServiceMac> >::get();
280 }
281
282 // static
283 void AppListServiceMac::FindAnchorPoint(const gfx::Size& window_size,
284                                         const gfx::Display& display,
285                                         int primary_display_height,
286                                         bool cursor_is_visible,
287                                         const gfx::Point& cursor,
288                                         NSPoint* target_origin,
289                                         NSPoint* start_origin) {
290   AppListPositioner positioner(display, window_size, 0);
291   AppListPositioner::ScreenEdge dock_location = DockLocationInDisplay(display);
292
293   gfx::Point anchor;
294   // Snap to the dock edge. If the cursor is greater than the window
295   // width/height away or not visible, anchor to the center of the dock.
296   // Otherwise, anchor to the cursor position.
297   if (dock_location == AppListPositioner::SCREEN_EDGE_UNKNOWN) {
298     anchor = positioner.GetAnchorPointForScreenCorner(
299         AppListPositioner::SCREEN_CORNER_BOTTOM_LEFT);
300   } else {
301     int snap_distance =
302         dock_location == AppListPositioner::SCREEN_EDGE_BOTTOM ||
303                 dock_location == AppListPositioner::SCREEN_EDGE_TOP ?
304             window_size.height() :
305             window_size.width();
306     // Subtract the dock area since the display's default work_area will not
307     // subtract it if the dock is set to auto-hide, and the app list should
308     // never overlap the dock.
309     AdjustWorkAreaForDock(display, &positioner, dock_location);
310     if (!cursor_is_visible || positioner.GetCursorDistanceFromShelf(
311                                   dock_location, cursor) > snap_distance) {
312       anchor = positioner.GetAnchorPointForShelfCenter(dock_location);
313     } else {
314       anchor = positioner.GetAnchorPointForShelfCursor(dock_location, cursor);
315     }
316   }
317
318   *target_origin = NSMakePoint(
319       anchor.x() - window_size.width() / 2,
320       primary_display_height - anchor.y() - window_size.height() / 2);
321   *start_origin = *target_origin;
322
323   // If the launcher is anchored to the dock (regardless of whether the cursor
324   // is visible), animate in inwards from the edge of screen
325   switch (dock_location) {
326     case AppListPositioner::SCREEN_EDGE_UNKNOWN:
327       break;
328     case AppListPositioner::SCREEN_EDGE_LEFT:
329       start_origin->x -= kDistanceMovedOnShow;
330       break;
331     case AppListPositioner::SCREEN_EDGE_RIGHT:
332       start_origin->x += kDistanceMovedOnShow;
333       break;
334     case AppListPositioner::SCREEN_EDGE_TOP:
335       NOTREACHED();
336       break;
337     case AppListPositioner::SCREEN_EDGE_BOTTOM:
338       start_origin->y -= kDistanceMovedOnShow;
339       break;
340   }
341 }
342
343 void AppListServiceMac::Init(Profile* initial_profile) {
344   // On Mac, Init() is called multiple times for a process: any time there is no
345   // browser window open and a new window is opened, and during process startup
346   // to handle the silent launch case (e.g. for app shims). In the startup case,
347   // a profile has not yet been determined so |initial_profile| will be NULL.
348   static bool init_called_with_profile = false;
349   if (initial_profile && !init_called_with_profile) {
350     init_called_with_profile = true;
351     PerformStartupChecks(initial_profile);
352     PrefService* local_state = g_browser_process->local_state();
353     if (!IsAppLauncherEnabled()) {
354       local_state->SetInteger(prefs::kAppLauncherShortcutVersion, 0);
355     } else {
356       int installed_shortcut_version =
357           local_state->GetInteger(prefs::kAppLauncherShortcutVersion);
358
359       if (kShortcutVersion > installed_shortcut_version)
360         CreateShortcut();
361     }
362   }
363
364   static bool init_called = false;
365   if (init_called)
366     return;
367
368   init_called = true;
369   apps::AppShimHandler::RegisterHandler(app_mode::kAppListModeId,
370                                         AppListServiceMac::GetInstance());
371
372   // Handle the case where Chrome was not running and was started with the app
373   // launcher shim. The profile has not yet been loaded. To improve response
374   // times, start animating an empty window which will be populated via
375   // OnShimLaunch(). Note that if --silent-launch is not also passed, the window
376   // will instead populate via StartupBrowserCreator::Launch(). Shim-initiated
377   // launches will always have --silent-launch.
378   if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kShowAppList))
379     ShowWindowNearDock();
380 }
381
382 Profile* AppListServiceMac::GetCurrentAppListProfile() {
383   return profile_;
384 }
385
386 void AppListServiceMac::CreateForProfile(Profile* requested_profile) {
387   if (profile_ == requested_profile)
388     return;
389
390   profile_ = requested_profile;
391
392   if (!window_controller_)
393     window_controller_.reset([[AppListWindowController alloc] init]);
394
395   scoped_ptr<app_list::AppListViewDelegate> delegate(
396       new AppListViewDelegate(profile_, GetControllerDelegate()));
397   [[window_controller_ appListViewController] setDelegate:delegate.Pass()];
398 }
399
400 void AppListServiceMac::ShowForProfile(Profile* requested_profile) {
401   InvalidatePendingProfileLoads();
402
403   if (requested_profile == profile_) {
404     ShowWindowNearDock();
405     return;
406   }
407
408   SetProfilePath(requested_profile->GetPath());
409   CreateForProfile(requested_profile);
410   ShowWindowNearDock();
411 }
412
413 void AppListServiceMac::DismissAppList() {
414   if (!IsAppListVisible())
415     return;
416
417   // If the app list is currently the main window, it will activate the next
418   // Chrome window when dismissed. But if a different application was active
419   // when the app list was shown, activate that instead.
420   base::scoped_nsobject<NSRunningApplication> prior_app;
421   if ([[window_controller_ window] isMainWindow])
422     prior_app.swap(previously_active_application_);
423   else
424     previously_active_application_.reset();
425
426   // If activation is successful, the app list will lose main status and try to
427   // close itself again. It can't be closed in this runloop iteration without
428   // OSX deciding to raise the next Chrome window, and _then_ activating the
429   // application on top. This also occurs if no activation option is given.
430   if ([prior_app activateWithOptions:NSApplicationActivateIgnoringOtherApps])
431     return;
432
433   [animation_controller_ animateWindow:[window_controller_ window]
434                           targetOrigin:last_start_origin_
435                                closing:YES];
436 }
437
438 bool AppListServiceMac::IsAppListVisible() const {
439   return [[window_controller_ window] isVisible] &&
440       ![animation_controller_ isClosing];
441 }
442
443 void AppListServiceMac::EnableAppList(Profile* initial_profile,
444                                       AppListEnableSource enable_source) {
445   AppListServiceImpl::EnableAppList(initial_profile, enable_source);
446   AppController* controller = [NSApp delegate];
447   [controller initAppShimMenuController];
448 }
449
450 void AppListServiceMac::CreateShortcut() {
451   CreateAppListShim(GetProfilePath(
452       g_browser_process->profile_manager()->user_data_dir()));
453 }
454
455 NSWindow* AppListServiceMac::GetAppListWindow() {
456   return [window_controller_ window];
457 }
458
459 AppListControllerDelegate* AppListServiceMac::GetControllerDelegate() {
460   return controller_delegate_.get();
461 }
462
463 void AppListServiceMac::OnShimLaunch(apps::AppShimHandler::Host* host,
464                                      apps::AppShimLaunchType launch_type,
465                                      const std::vector<base::FilePath>& files) {
466   if (profile_ && IsAppListVisible()) {
467     DismissAppList();
468   } else {
469     // Start by showing a possibly empty window to handle the case where Chrome
470     // is running, but hasn't yet loaded the app launcher profile.
471     ShowWindowNearDock();
472     Show();
473   }
474
475   // Always close the shim process immediately.
476   host->OnAppLaunchComplete(apps::APP_SHIM_LAUNCH_DUPLICATE_HOST);
477 }
478
479 void AppListServiceMac::OnShimClose(apps::AppShimHandler::Host* host) {}
480
481 void AppListServiceMac::OnShimFocus(apps::AppShimHandler::Host* host,
482                                     apps::AppShimFocusType focus_type,
483                                     const std::vector<base::FilePath>& files) {}
484
485 void AppListServiceMac::OnShimSetHidden(apps::AppShimHandler::Host* host,
486                                         bool hidden) {}
487
488 void AppListServiceMac::OnShimQuit(apps::AppShimHandler::Host* host) {}
489
490 void AppListServiceMac::ShowWindowNearDock() {
491   if (IsAppListVisible())
492     return;
493
494   if (!window_controller_) {
495     // Note that this will start showing an unpopulated window, the caller needs
496     // to ensure it will be populated later.
497     window_controller_.reset([[AppListWindowController alloc] init]);
498   }
499
500   NSWindow* window = GetAppListWindow();
501   DCHECK(window);
502   NSPoint target_origin;
503   GetAppListWindowOrigins(window, &target_origin, &last_start_origin_);
504   [window setFrameOrigin:last_start_origin_];
505
506   // Before activating, see if an application other than Chrome is currently the
507   // active application, so that it can be reactivated when dismissing.
508   previously_active_application_.reset([ActiveApplicationNotChrome() retain]);
509
510   [animation_controller_ animateWindow:[window_controller_ window]
511                           targetOrigin:target_origin
512                                closing:NO];
513   [window makeKeyAndOrderFront:nil];
514   [NSApp activateIgnoringOtherApps:YES];
515   RecordAppListLaunch();
516 }
517
518 void AppListServiceMac::WindowAnimationDidEnd() {
519   [animation_controller_ cleanupOnUIThread];
520 }
521
522 // static
523 AppListService* AppListService::Get(chrome::HostDesktopType desktop_type) {
524   return AppListServiceMac::GetInstance();
525 }
526
527 // static
528 void AppListService::InitAll(Profile* initial_profile) {
529   AppListServiceMac::GetInstance()->Init(initial_profile);
530 }
531
532 @implementation AppListAnimationController
533
534 - (BOOL)isClosing {
535   return !!window_;
536 }
537
538 - (void)animateWindow:(NSWindow*)window
539          targetOrigin:(NSPoint)targetOrigin
540               closing:(BOOL)closing {
541   // First, stop the existing animation, if there is one.
542   [animation_ stopAnimation];
543
544   NSRect targetFrame = [window frame];
545   targetFrame.origin = targetOrigin;
546
547   // NSViewAnimation has a quirk when setting the curve to NSAnimationEaseOut
548   // where it attempts to auto-reverse the animation. FadeOut becomes FadeIn
549   // (good), but FrameKey is also switched (bad). So |targetFrame| needs to be
550   // put on the StartFrameKey when using NSAnimationEaseOut for showing.
551   NSArray* animationArray = @[
552     @{
553       NSViewAnimationTargetKey : window,
554       NSViewAnimationEffectKey : NSViewAnimationFadeOutEffect,
555       (closing ? NSViewAnimationEndFrameKey : NSViewAnimationStartFrameKey) :
556           [NSValue valueWithRect:targetFrame]
557     }
558   ];
559   animation_.reset(
560       [[NSViewAnimation alloc] initWithViewAnimations:animationArray]);
561   [animation_ setDuration:kAnimationDuration];
562   [animation_ setDelegate:self];
563
564   if (closing) {
565     [animation_ setAnimationCurve:NSAnimationEaseIn];
566     window_.reset([window retain]);
567   } else {
568     [window setAlphaValue:0.0f];
569     [animation_ setAnimationCurve:NSAnimationEaseOut];
570     window_.reset();
571   }
572   // Threaded animations are buggy on Snow Leopard. See http://crbug.com/335550.
573   // Note that in the non-threaded case, the animation won't start unless the
574   // UI runloop has spun up, so on <= Lion the animation will only animate if
575   // Chrome is already running.
576   if (base::mac::IsOSMountainLionOrLater())
577     [animation_ setAnimationBlockingMode:NSAnimationNonblockingThreaded];
578   else
579     [animation_ setAnimationBlockingMode:NSAnimationNonblocking];
580
581   [animation_ startAnimation];
582 }
583
584 - (void)cleanupOnUIThread {
585   bool closing = [self isClosing];
586   [window_ close];
587   window_.reset();
588   animation_.reset();
589
590   if (closing)
591     apps::AppShimHandler::MaybeTerminate();
592 }
593
594 - (void)animationDidEnd:(NSAnimation*)animation {
595   content::BrowserThread::PostTask(
596       content::BrowserThread::UI,
597       FROM_HERE,
598       base::Bind(&AppListServiceMac::WindowAnimationDidEnd,
599                  base::Unretained(AppListServiceMac::GetInstance())));
600 }
601
602 @end