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

Side by Side Diff: chrome/browser/ui/cocoa/status_bubble_mac.mm

Issue 164333006: Fix the Mac status bubble. (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: nits Created 6 years, 10 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
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/ui/cocoa/status_bubble_mac.h" 5 #include "chrome/browser/ui/cocoa/status_bubble_mac.h"
6 6
7 #include <limits> 7 #include <limits>
8 8
9 #include "base/bind.h" 9 #include "base/bind.h"
10 #include "base/compiler_specific.h" 10 #include "base/compiler_specific.h"
11 #include "base/mac/mac_util.h" 11 #include "base/mac/mac_util.h"
12 #include "base/mac/scoped_block.h"
13 #include "base/mac/sdk_forward_declarations.h"
12 #include "base/message_loop/message_loop.h" 14 #include "base/message_loop/message_loop.h"
13 #include "base/strings/string_util.h" 15 #include "base/strings/string_util.h"
14 #include "base/strings/sys_string_conversions.h" 16 #include "base/strings/sys_string_conversions.h"
15 #include "base/strings/utf_string_conversions.h" 17 #include "base/strings/utf_string_conversions.h"
16 #import "chrome/browser/ui/cocoa/bubble_view.h" 18 #import "chrome/browser/ui/cocoa/bubble_view.h"
17 #include "chrome/browser/ui/elide_url.h" 19 #include "chrome/browser/ui/elide_url.h"
18 #include "net/base/net_util.h" 20 #include "net/base/net_util.h"
19 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSAnimation+Duration.h " 21 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSAnimation+Duration.h "
20 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSBezierPath+RoundRect .h" 22 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSBezierPath+RoundRect .h"
21 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSColor+Luminance.h" 23 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSColor+Luminance.h"
22 #include "ui/base/cocoa/window_size_constants.h" 24 #include "ui/base/cocoa/window_size_constants.h"
23 #include "ui/gfx/font_list.h" 25 #include "ui/gfx/font_list.h"
24 #include "ui/gfx/point.h" 26 #include "ui/gfx/point.h"
25 #include "ui/gfx/text_elider.h" 27 #include "ui/gfx/text_elider.h"
26 #include "ui/gfx/text_utils.h" 28 #include "ui/gfx/text_utils.h"
27 29
28 namespace { 30 namespace {
29 31
30 const int kWindowHeight = 18; 32 const int kWindowHeight = 18;
31 33
32 // The width of the bubble in relation to the width of the parent window. 34 // The width of the bubble in relation to the width of the parent window.
33 const CGFloat kWindowWidthPercent = 1.0 / 3.0; 35 const CGFloat kWindowWidthPercent = 1.0 / 3.0;
34 36
35 // How close the mouse can get to the infobubble before it starts sliding 37 // How close the mouse can get to the infobubble before it starts sliding
36 // off-screen. 38 // off-screen.
37 const int kMousePadding = 20; 39 const int kMousePadding = 20;
38 40
39 const int kTextPadding = 3; 41 const int kTextPadding = 3;
40 42
41 // The animation key used for fade-in and fade-out transitions.
42 NSString* const kFadeAnimationKey = @"alphaValue";
43
44 // The status bubble's maximum opacity, when fully faded in. 43 // The status bubble's maximum opacity, when fully faded in.
45 const CGFloat kBubbleOpacity = 1.0; 44 const CGFloat kBubbleOpacity = 1.0;
46 45
47 // Delay before showing or hiding the bubble after a SetStatus or SetURL call. 46 // Delay before showing or hiding the bubble after a SetStatus or SetURL call.
48 const int64 kShowDelayMS = 80; 47 const int64 kShowDelayMS = 80;
49 const int64 kHideDelayMS = 250; 48 const int64 kHideDelayMS = 250;
50 49
51 // How long each fade should last. 50 // How long each fade should last.
52 const NSTimeInterval kShowFadeInDurationSeconds = 0.120; 51 const NSTimeInterval kShowFadeInDurationSeconds = 0.120;
53 const NSTimeInterval kHideFadeOutDurationSeconds = 0.200; 52 const NSTimeInterval kHideFadeOutDurationSeconds = 0.200;
54 53
55 // The minimum representable time interval. This can be used as the value 54 // The minimum representable time interval. This can be used as the value
56 // passed to +[NSAnimationContext setDuration:] to stop an in-progress 55 // passed to +[NSAnimationContext setDuration:] to stop an in-progress
57 // animation as quickly as possible. 56 // animation as quickly as possible.
58 const NSTimeInterval kMinimumTimeInterval = 57 const NSTimeInterval kMinimumTimeInterval =
59 std::numeric_limits<NSTimeInterval>::min(); 58 std::numeric_limits<NSTimeInterval>::min();
60 59
61 // How quickly the status bubble should expand. 60 // How quickly the status bubble should expand.
62 const CGFloat kExpansionDurationSeconds = 0.125; 61 const CGFloat kExpansionDurationSeconds = 0.125;
63 62
64 } // namespace 63 } // namespace
65 64
66 @interface StatusBubbleAnimationDelegate : NSObject { 65 @interface StatusBubbleAnimationDelegate : NSObject {
67 @private 66 @private
68 StatusBubbleMac* statusBubble_; // weak; owns us indirectly 67 base::mac::ScopedBlock<void (^)(void)> completionHandler_;
69 } 68 }
70 69
71 - (id)initWithStatusBubble:(StatusBubbleMac*)statusBubble; 70 - (id)initWithCompletionHandler:(void (^)(void))completionHandler;
72
73 // Invalidates this object so that no further calls will be made to
74 // statusBubble_. This should be called when statusBubble_ is released, to
75 // prevent attempts to call into the released object.
76 - (void)invalidate;
77 71
78 // CAAnimation delegate method 72 // CAAnimation delegate method
79 - (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)finished; 73 - (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)finished;
80 @end 74 @end
81 75
82 @implementation StatusBubbleAnimationDelegate 76 @implementation StatusBubbleAnimationDelegate
83 77
84 - (id)initWithStatusBubble:(StatusBubbleMac*)statusBubble { 78 - (id)initWithCompletionHandler:(void (^)(void))completionHandler {
85 if ((self = [super init])) { 79 if ((self = [super init])) {
86 statusBubble_ = statusBubble; 80 completionHandler_.reset(completionHandler, base::scoped_policy::RETAIN);
87 } 81 }
88 82
89 return self; 83 return self;
90 } 84 }
91 85
92 - (void)invalidate {
93 statusBubble_ = NULL;
94 }
95
96 - (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)finished { 86 - (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)finished {
97 if (statusBubble_) 87 completionHandler_.get()();
98 statusBubble_->AnimationDidStop(animation, finished);
99 } 88 }
100 89
101 @end 90 @end
91
92 @interface StatusBubbleWindow : NSWindow {
93 @private
94 void (^completionHandler_)(void);
95 }
96
97 - (id)animationForKey:(NSString *)key;
98 - (void)runAnimationGroup:(void (^)(NSAnimationContext *context))changes
99 completionHandler:(void (^)(void))completionHandler;
100 @end
101
102 @implementation StatusBubbleWindow
103
104 - (id)animationForKey:(NSString *)key {
105 CAAnimation* animation = [super animationForKey:key];
106 // If completionHandler_ isn't nil, then this is the first of (potentially)
107 // multiple animations in a grouping; give it the completion handler. If
108 // completionHandler_ is nil, then some other animation was tagged with the
109 // completion handler.
110 if (completionHandler_) {
111 DCHECK(![NSAnimationContext respondsToSelector:
112 @selector(runAnimationGroup:completionHandler:)]);
113 StatusBubbleAnimationDelegate* animation_delegate =
114 [[StatusBubbleAnimationDelegate alloc]
115 initWithCompletionHandler:completionHandler_];
116 [animation setDelegate:animation_delegate];
117 completionHandler_ = nil;
118 }
119 return animation;
120 }
121
122 - (void)runAnimationGroup:(void (^)(NSAnimationContext *context))changes
123 completionHandler:(void (^)(void))completionHandler {
124 if ([NSAnimationContext respondsToSelector:
125 @selector(runAnimationGroup:completionHandler:)]) {
126 [NSAnimationContext runAnimationGroup:changes
127 completionHandler:completionHandler];
128 } else {
129 // Mac OS 10.6 does not have completion handler callbacks at the Cocoa
130 // level, only at the CoreAnimation level. So intercept calls made to
131 // -animationForKey: and tag one of the animations with a delegate that will
132 // execute the completion handler.
133 completionHandler_ = completionHandler;
134 [NSAnimationContext beginGrouping];
135 changes([NSAnimationContext currentContext]);
136 // At this point, -animationForKey should have been called by CoreAnimation
137 // to set up the animation to run. Verify this.
138 DCHECK(completionHandler_ == nil);
139 [NSAnimationContext endGrouping];
140 }
141 }
142
143 @end
102 144
103 StatusBubbleMac::StatusBubbleMac(NSWindow* parent, id delegate) 145 StatusBubbleMac::StatusBubbleMac(NSWindow* parent, id delegate)
104 : timer_factory_(this), 146 : timer_factory_(this),
105 expand_timer_factory_(this), 147 expand_timer_factory_(this),
148 completion_handler_factory_(this),
106 parent_(parent), 149 parent_(parent),
107 delegate_(delegate), 150 delegate_(delegate),
108 window_(nil), 151 window_(nil),
109 status_text_(nil), 152 status_text_(nil),
110 url_text_(nil), 153 url_text_(nil),
111 state_(kBubbleHidden), 154 state_(kBubbleHidden),
112 immediate_(false), 155 immediate_(false),
113 is_expanded_(false) { 156 is_expanded_(false) {
114 Create(); 157 Create();
115 Attach(); 158 Attach();
116 } 159 }
117 160
118 StatusBubbleMac::~StatusBubbleMac() { 161 StatusBubbleMac::~StatusBubbleMac() {
119 DCHECK(window_); 162 DCHECK(window_);
120 163
121 Hide(); 164 Hide();
122 165
123 [[[window_ animationForKey:kFadeAnimationKey] delegate] invalidate]; 166 completion_handler_factory_.InvalidateWeakPtrs();
124 Detach(); 167 Detach();
125 [window_ release]; 168 [window_ release];
126 window_ = nil; 169 window_ = nil;
127 } 170 }
128 171
129 void StatusBubbleMac::SetStatus(const base::string16& status) { 172 void StatusBubbleMac::SetStatus(const base::string16& status) {
130 SetText(status, false); 173 SetText(status, false);
131 } 174 }
132 175
133 void StatusBubbleMac::SetURL(const GURL& url, const std::string& languages) { 176 void StatusBubbleMac::SetURL(const GURL& url, const std::string& languages) {
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after
230 is_expanded_ = false; 273 is_expanded_ = false;
231 274
232 bool fade_out = false; 275 bool fade_out = false;
233 if (state_ == kBubbleHidingFadeOut || state_ == kBubbleShowingFadeIn) { 276 if (state_ == kBubbleHidingFadeOut || state_ == kBubbleShowingFadeIn) {
234 SetState(kBubbleHidingFadeOut); 277 SetState(kBubbleHidingFadeOut);
235 278
236 if (!immediate_) { 279 if (!immediate_) {
237 // An animation is in progress. Cancel it by starting a new animation. 280 // An animation is in progress. Cancel it by starting a new animation.
238 // Use kMinimumTimeInterval to set the opacity as rapidly as possible. 281 // Use kMinimumTimeInterval to set the opacity as rapidly as possible.
239 fade_out = true; 282 fade_out = true;
240 [NSAnimationContext beginGrouping]; 283 AnimateWindowAlpha(0.0, kMinimumTimeInterval);
241 [[NSAnimationContext currentContext] setDuration:kMinimumTimeInterval];
242 [[window_ animator] setAlphaValue:0.0];
243 [NSAnimationContext endGrouping];
244 } 284 }
245 } 285 }
246 286
247 if (!fade_out) { 287 if (!fade_out) {
248 // No animation is in progress, so the opacity can be set directly. 288 // No animation is in progress, so the opacity can be set directly.
249 [window_ setAlphaValue:0.0]; 289 [window_ setAlphaValue:0.0];
250 SetState(kBubbleHidden); 290 SetState(kBubbleHidden);
251 } 291 }
252 292
253 // Stop any width animation and reset the bubble size. 293 // Stop any width animation and reset the bubble size.
(...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after
357 SetFrameAvoidingMouse([window_ frame], location); 397 SetFrameAvoidingMouse([window_ frame], location);
358 } 398 }
359 399
360 void StatusBubbleMac::UpdateDownloadShelfVisibility(bool visible) { 400 void StatusBubbleMac::UpdateDownloadShelfVisibility(bool visible) {
361 UpdateSizeAndPosition(); 401 UpdateSizeAndPosition();
362 } 402 }
363 403
364 void StatusBubbleMac::Create() { 404 void StatusBubbleMac::Create() {
365 DCHECK(!window_); 405 DCHECK(!window_);
366 406
367 window_ = [[NSWindow alloc] initWithContentRect:ui::kWindowSizeDeterminedLater 407 window_ = [[StatusBubbleWindow alloc]
368 styleMask:NSBorderlessWindowMask 408 initWithContentRect:ui::kWindowSizeDeterminedLater
369 backing:NSBackingStoreBuffered 409 styleMask:NSBorderlessWindowMask
370 defer:YES]; 410 backing:NSBackingStoreBuffered
411 defer:YES];
371 [window_ setMovableByWindowBackground:NO]; 412 [window_ setMovableByWindowBackground:NO];
372 [window_ setBackgroundColor:[NSColor clearColor]]; 413 [window_ setBackgroundColor:[NSColor clearColor]];
373 [window_ setLevel:NSNormalWindowLevel]; 414 [window_ setLevel:NSNormalWindowLevel];
374 [window_ setOpaque:NO]; 415 [window_ setOpaque:NO];
375 [window_ setHasShadow:NO]; 416 [window_ setHasShadow:NO];
376 417
377 // We do not need to worry about the bubble outliving |parent_| because our 418 // We do not need to worry about the bubble outliving |parent_| because our
378 // teardown sequence in BWC guarantees that |parent_| outlives the status 419 // teardown sequence in BWC guarantees that |parent_| outlives the status
379 // bubble and that the StatusBubble is torn down completely prior to the 420 // bubble and that the StatusBubble is torn down completely prior to the
380 // window going away. 421 // window going away.
381 base::scoped_nsobject<BubbleView> view( 422 base::scoped_nsobject<BubbleView> view(
382 [[BubbleView alloc] initWithFrame:NSZeroRect themeProvider:parent_]); 423 [[BubbleView alloc] initWithFrame:NSZeroRect themeProvider:parent_]);
383 [window_ setContentView:view]; 424 [window_ setContentView:view];
384 425
385 [window_ setAlphaValue:0.0]; 426 [window_ setAlphaValue:0.0];
386 427
387 // TODO(dtseng): Ignore until we provide NSAccessibility support. 428 // TODO(dtseng): Ignore until we provide NSAccessibility support.
388 [window_ accessibilitySetOverrideValue:NSAccessibilityUnknownRole 429 [window_ accessibilitySetOverrideValue:NSAccessibilityUnknownRole
389 forAttribute:NSAccessibilityRoleAttribute]; 430 forAttribute:NSAccessibilityRoleAttribute];
390 431
391 // Set a delegate for the fade-in and fade-out transitions to be notified
392 // when fades are complete. The ownership model is for window_ to own
393 // animation_dictionary, which owns animation, which owns
394 // animation_delegate.
395 CAAnimation* animation = [[window_ animationForKey:kFadeAnimationKey] copy];
396 [animation autorelease];
397 StatusBubbleAnimationDelegate* animation_delegate =
398 [[StatusBubbleAnimationDelegate alloc] initWithStatusBubble:this];
399 [animation_delegate autorelease];
400 [animation setDelegate:animation_delegate];
401 NSMutableDictionary* animation_dictionary =
402 [NSMutableDictionary dictionaryWithDictionary:[window_ animations]];
403 [animation_dictionary setObject:animation forKey:kFadeAnimationKey];
404 [window_ setAnimations:animation_dictionary];
405
406 [view setCornerFlags:kRoundedTopRightCorner]; 432 [view setCornerFlags:kRoundedTopRightCorner];
407 MouseMoved(gfx::Point(), false); 433 MouseMoved(gfx::Point(), false);
408 } 434 }
409 435
410 void StatusBubbleMac::Attach() { 436 void StatusBubbleMac::Attach() {
411 DCHECK(!is_attached()); 437 DCHECK(!is_attached());
412 438
413 [window_ orderFront:nil]; 439 [window_ orderFront:nil];
414 [parent_ addChildWindow:window_ ordered:NSWindowAbove]; 440 [parent_ addChildWindow:window_ ordered:NSWindowAbove];
415 441
416 [[window_ contentView] setThemeProvider:parent_]; 442 [[window_ contentView] setThemeProvider:parent_];
417 } 443 }
418 444
419 void StatusBubbleMac::Detach() { 445 void StatusBubbleMac::Detach() {
420 DCHECK(is_attached()); 446 DCHECK(is_attached());
421 447
422 // Magic setFrame: See http://crbug.com/58506 and http://crrev.com/3564021 . 448 // Magic setFrame: See http://crbug.com/58506 and http://crrev.com/3564021 .
423 [window_ setFrame:CalculateWindowFrame(/*expand=*/false) display:NO]; 449 [window_ setFrame:CalculateWindowFrame(/*expand=*/false) display:NO];
424 [parent_ removeChildWindow:window_]; // See crbug.com/28107 ... 450 [parent_ removeChildWindow:window_]; // See crbug.com/28107 ...
425 [window_ orderOut:nil]; // ... and crbug.com/29054. 451 [window_ orderOut:nil]; // ... and crbug.com/29054.
426 452
427 [[window_ contentView] setThemeProvider:nil]; 453 [[window_ contentView] setThemeProvider:nil];
428 } 454 }
429 455
430 void StatusBubbleMac::AnimationDidStop(CAAnimation* animation, bool finished) { 456 void StatusBubbleMac::AnimationDidStop() {
431 DCHECK([NSThread isMainThread]); 457 DCHECK([NSThread isMainThread]);
432 DCHECK(state_ == kBubbleShowingFadeIn || state_ == kBubbleHidingFadeOut); 458 DCHECK(state_ == kBubbleShowingFadeIn || state_ == kBubbleHidingFadeOut);
433 DCHECK(is_attached()); 459 DCHECK(is_attached());
434 460
435 if (finished) { 461 if (state_ == kBubbleShowingFadeIn) {
436 // Because of the mechanism used to interrupt animations, this is never 462 DCHECK_EQ([[window_ animator] alphaValue], kBubbleOpacity);
437 // actually called with finished set to false. If animations ever become 463 SetState(kBubbleShown);
438 // directly interruptible, the check will ensure that state_ remains 464 } else {
439 // properly synchronized. 465 DCHECK_EQ([[window_ animator] alphaValue], 0.0);
440 if (state_ == kBubbleShowingFadeIn) { 466 SetState(kBubbleHidden);
441 DCHECK_EQ([[window_ animator] alphaValue], kBubbleOpacity);
442 SetState(kBubbleShown);
443 } else {
444 DCHECK_EQ([[window_ animator] alphaValue], 0.0);
445 SetState(kBubbleHidden);
446 }
447 } 467 }
448 } 468 }
449 469
450 void StatusBubbleMac::SetState(StatusBubbleState state) { 470 void StatusBubbleMac::SetState(StatusBubbleState state) {
451 if (state == state_) 471 if (state == state_)
452 return; 472 return;
453 473
454 if (state == kBubbleHidden) { 474 if (state == kBubbleHidden) {
455 // When hidden (with alpha of 0), make the window have the minimum size, 475 // When hidden (with alpha of 0), make the window have the minimum size,
456 // while still keeping the same origin. It's important to not set the 476 // while still keeping the same origin. It's important to not set the
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
499 // If an incomplete transition has left the opacity somewhere between 0 and 519 // If an incomplete transition has left the opacity somewhere between 0 and
500 // kBubbleOpacity, the fade rate is kept constant by shortening the duration. 520 // kBubbleOpacity, the fade rate is kept constant by shortening the duration.
501 NSTimeInterval duration = 521 NSTimeInterval duration =
502 full_duration * 522 full_duration *
503 fabs(opacity - [[window_ animator] alphaValue]) / kBubbleOpacity; 523 fabs(opacity - [[window_ animator] alphaValue]) / kBubbleOpacity;
504 524
505 // 0.0 will not cancel an in-progress animation. 525 // 0.0 will not cancel an in-progress animation.
506 if (duration == 0.0) 526 if (duration == 0.0)
507 duration = kMinimumTimeInterval; 527 duration = kMinimumTimeInterval;
508 528
509 // This will cancel an in-progress transition and replace it with this fade. 529 // Cancel an in-progress transition and replace it with this fade.
510 [NSAnimationContext beginGrouping]; 530 AnimateWindowAlpha(opacity, duration);
511 // Don't use the GTM additon for the "Steve" slowdown because this can happen 531 }
512 // async from user actions and the effects could be a surprise. 532
513 [[NSAnimationContext currentContext] setDuration:duration]; 533 void StatusBubbleMac::AnimateWindowAlpha(CGFloat alpha,
514 [[window_ animator] setAlphaValue:opacity]; 534 NSTimeInterval duration) {
515 [NSAnimationContext endGrouping]; 535 completion_handler_factory_.InvalidateWeakPtrs();
536 base::WeakPtr<StatusBubbleMac> weak_ptr(
537 completion_handler_factory_.GetWeakPtr());
538 [window_
539 runAnimationGroup:^(NSAnimationContext* context) {
540 [context setDuration:duration];
541 [[window_ animator] setAlphaValue:alpha];
542 }
543 completionHandler:^{
544 if (weak_ptr)
545 weak_ptr->AnimationDidStop();
546 }];
516 } 547 }
517 548
518 void StatusBubbleMac::StartTimer(int64 delay_ms) { 549 void StatusBubbleMac::StartTimer(int64 delay_ms) {
519 DCHECK([NSThread isMainThread]); 550 DCHECK([NSThread isMainThread]);
520 DCHECK(state_ == kBubbleShowingTimer || state_ == kBubbleHidingTimer); 551 DCHECK(state_ == kBubbleShowingTimer || state_ == kBubbleHidingTimer);
521 552
522 if (immediate_) { 553 if (immediate_) {
523 TimerFired(); 554 TimerFired();
524 return; 555 return;
525 } 556 }
(...skipping 218 matching lines...) Expand 10 before | Expand all | Expand 10 after
744 } 775 }
745 776
746 // Round the top corners when the bubble is below the parent window. 777 // Round the top corners when the bubble is below the parent window.
747 if (NSMinY(window_frame) < NSMinY(parent_frame)) { 778 if (NSMinY(window_frame) < NSMinY(parent_frame)) {
748 corner_flags |= kRoundedTopLeftCorner | kRoundedTopRightCorner; 779 corner_flags |= kRoundedTopLeftCorner | kRoundedTopRightCorner;
749 } 780 }
750 } 781 }
751 782
752 return corner_flags; 783 return corner_flags;
753 } 784 }
OLDNEW
« no previous file with comments | « chrome/browser/ui/cocoa/status_bubble_mac.h ('k') | chrome/browser/ui/cocoa/status_bubble_mac_unittest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698