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