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

Side by Side Diff: chrome/browser/ui/app_list/app_list_service_mac.mm

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

Powered by Google App Engine
This is Rietveld 408576698