OLD | NEW |
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/debug/stack_trace.h" | |
12 #include "base/mac/mac_util.h" | 11 #include "base/mac/mac_util.h" |
13 #include "base/mac/scoped_block.h" | 12 #include "base/mac/scoped_block.h" |
14 #include "base/mac/sdk_forward_declarations.h" | 13 #include "base/mac/sdk_forward_declarations.h" |
15 #include "base/message_loop/message_loop.h" | 14 #include "base/message_loop/message_loop.h" |
16 #include "base/strings/string_util.h" | 15 #include "base/strings/string_util.h" |
17 #include "base/strings/sys_string_conversions.h" | 16 #include "base/strings/sys_string_conversions.h" |
18 #include "base/strings/utf_string_conversions.h" | 17 #include "base/strings/utf_string_conversions.h" |
19 #import "chrome/browser/ui/cocoa/bubble_view.h" | 18 #import "chrome/browser/ui/cocoa/bubble_view.h" |
20 #include "chrome/browser/ui/elide_url.h" | 19 #include "chrome/browser/ui/elide_url.h" |
21 #include "net/base/net_util.h" | 20 #include "net/base/net_util.h" |
(...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
136 changes([NSAnimationContext currentContext]); | 135 changes([NSAnimationContext currentContext]); |
137 // At this point, -animationForKey should have been called by CoreAnimation | 136 // At this point, -animationForKey should have been called by CoreAnimation |
138 // to set up the animation to run. Verify this. | 137 // to set up the animation to run. Verify this. |
139 DCHECK(completionHandler_ == nil); | 138 DCHECK(completionHandler_ == nil); |
140 [NSAnimationContext endGrouping]; | 139 [NSAnimationContext endGrouping]; |
141 } | 140 } |
142 } | 141 } |
143 | 142 |
144 @end | 143 @end |
145 | 144 |
146 // Mac implementation of the status bubble. | |
147 // | |
148 // Child windows interact with Spaces in interesting ways, so this code has to | |
149 // follow these rules: | |
150 // | |
151 // 1) NSWindows cannot have zero size. At times when the status bubble window | |
152 // has no specific size (for example, when hidden), its size is set to | |
153 // ui::kWindowSizeDeterminedLater. | |
154 // | |
155 // 2) Child window frames are in the coordinate space of the screen, not of the | |
156 // parent window. If a child window has its origin at (0, 0), Spaces will | |
157 // position it in the corner of the screen but group it with the parent | |
158 // window in Spaces. This causes Chrome windows to have a large (mostly | |
159 // blank) area in Spaces. To avoid this, child windows always have their | |
160 // origin set to the lower-left corner of the window. | |
161 // | |
162 // 3) Detached child windows may show up as top-level windows in Spaces. To | |
163 // avoid this, once the status bubble is Attach()ed to the parent, it is | |
164 // never detached (except in rare cases when reparenting to a fullscreen | |
165 // window). | |
166 // | |
167 // 4) To avoid unnecessary redraws, if a bubble is in the kBubbleHidden state, | |
168 // its size is always set to ui::kWindowSizeDeterminedLater. The proper | |
169 // width for the current URL or status text is not calculated until the | |
170 // bubble leaves the kBubbleHidden state. | |
171 | |
172 StatusBubbleMac::StatusBubbleMac(NSWindow* parent, id delegate) | 145 StatusBubbleMac::StatusBubbleMac(NSWindow* parent, id delegate) |
173 : parent_(parent), | 146 : parent_(parent), |
174 delegate_(delegate), | 147 delegate_(delegate), |
175 window_(nil), | 148 window_(nil), |
176 status_text_(nil), | 149 status_text_(nil), |
177 url_text_(nil), | 150 url_text_(nil), |
178 state_(kBubbleHidden), | 151 state_(kBubbleHidden), |
179 immediate_(false), | 152 immediate_(false), |
180 is_expanded_(false), | 153 is_expanded_(false), |
181 timer_factory_(this), | 154 timer_factory_(this), |
(...skipping 15 matching lines...) Expand all Loading... |
197 } | 170 } |
198 | 171 |
199 void StatusBubbleMac::SetStatus(const base::string16& status) { | 172 void StatusBubbleMac::SetStatus(const base::string16& status) { |
200 SetText(status, false); | 173 SetText(status, false); |
201 } | 174 } |
202 | 175 |
203 void StatusBubbleMac::SetURL(const GURL& url, const std::string& languages) { | 176 void StatusBubbleMac::SetURL(const GURL& url, const std::string& languages) { |
204 url_ = url; | 177 url_ = url; |
205 languages_ = languages; | 178 languages_ = languages; |
206 | 179 |
207 CGFloat bubble_width = NSWidth([window_ frame]); | 180 NSRect frame = [window_ frame]; |
| 181 |
| 182 // Reset frame size when bubble is hidden. |
208 if (state_ == kBubbleHidden) { | 183 if (state_ == kBubbleHidden) { |
209 // TODO(rohitrao): The window size is expected to be (1,1) whenever the | 184 is_expanded_ = false; |
210 // window is hidden, but the GPU bots are hitting cases where this is not | 185 frame.size.width = NSWidth(CalculateWindowFrame(/*expand=*/false)); |
211 // true. Instead of enforcing this invariant with a DCHECK, add temporary | 186 [window_ setFrame:frame display:NO]; |
212 // logging to try and debug it and fix up the window size if needed. | |
213 // This logging is temporary and should be removed: crbug.com/467998 | |
214 NSRect frame = [window_ frame]; | |
215 if (!CGSizeEqualToSize(frame.size, ui::kWindowSizeDeterminedLater.size)) { | |
216 LOG(ERROR) << "Window size should be (1,1), but is instead (" | |
217 << frame.size.width << "," << frame.size.height << ")"; | |
218 LOG(ERROR) << base::debug::StackTrace().ToString(); | |
219 frame.size = ui::kWindowSizeDeterminedLater.size; | |
220 [window_ setFrame:frame display:NO]; | |
221 } | |
222 bubble_width = NSWidth(CalculateWindowFrame(/*expand=*/false)); | |
223 } | 187 } |
224 | 188 |
225 int text_width = static_cast<int>(bubble_width - | 189 int text_width = static_cast<int>(NSWidth(frame) - |
226 kBubbleViewTextPositionX - | 190 kBubbleViewTextPositionX - |
227 kTextPadding); | 191 kTextPadding); |
228 | 192 |
229 // Scale from view to window coordinates before eliding URL string. | 193 // Scale from view to window coordinates before eliding URL string. |
230 NSSize scaled_width = NSMakeSize(text_width, 0); | 194 NSSize scaled_width = NSMakeSize(text_width, 0); |
231 scaled_width = [[parent_ contentView] convertSize:scaled_width fromView:nil]; | 195 scaled_width = [[parent_ contentView] convertSize:scaled_width fromView:nil]; |
232 text_width = static_cast<int>(scaled_width.width); | 196 text_width = static_cast<int>(scaled_width.width); |
233 NSFont* font = [[window_ contentView] font]; | 197 NSFont* font = [[window_ contentView] font]; |
234 gfx::FontList font_list_chr( | 198 gfx::FontList font_list_chr( |
235 gfx::Font(base::SysNSStringToUTF8([font fontName]), [font pointSize])); | 199 gfx::Font(base::SysNSStringToUTF8([font fontName]), [font pointSize])); |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
289 | 253 |
290 bool show = true; | 254 bool show = true; |
291 if ([*main length] > 0) | 255 if ([*main length] > 0) |
292 [[window_ contentView] setContent:*main]; | 256 [[window_ contentView] setContent:*main]; |
293 else if ([*backup length] > 0) | 257 else if ([*backup length] > 0) |
294 [[window_ contentView] setContent:*backup]; | 258 [[window_ contentView] setContent:*backup]; |
295 else | 259 else |
296 show = false; | 260 show = false; |
297 | 261 |
298 if (show) { | 262 if (show) { |
299 // Call StartShowing() first to update the current bubble state before | 263 UpdateSizeAndPosition(); |
300 // calculating a new size. | |
301 StartShowing(); | 264 StartShowing(); |
302 UpdateSizeAndPosition(); | |
303 } else { | 265 } else { |
304 StartHiding(); | 266 StartHiding(); |
305 } | 267 } |
306 } | 268 } |
307 | 269 |
308 void StatusBubbleMac::Hide() { | 270 void StatusBubbleMac::Hide() { |
309 CancelTimer(); | 271 CancelTimer(); |
310 CancelExpandTimer(); | 272 CancelExpandTimer(); |
311 is_expanded_ = false; | 273 is_expanded_ = false; |
312 | 274 |
313 bool fade_out = false; | 275 bool fade_out = false; |
314 if (state_ == kBubbleHidingFadeOut || state_ == kBubbleShowingFadeIn) { | 276 if (state_ == kBubbleHidingFadeOut || state_ == kBubbleShowingFadeIn) { |
315 SetState(kBubbleHidingFadeOut); | 277 SetState(kBubbleHidingFadeOut); |
316 | 278 |
317 if (!immediate_) { | 279 if (!immediate_) { |
318 // 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. |
319 // Use kMinimumTimeInterval to set the opacity as rapidly as possible. | 281 // Use kMinimumTimeInterval to set the opacity as rapidly as possible. |
320 fade_out = true; | 282 fade_out = true; |
321 AnimateWindowAlpha(0.0, kMinimumTimeInterval); | 283 AnimateWindowAlpha(0.0, kMinimumTimeInterval); |
322 } | 284 } |
323 } | 285 } |
324 | 286 |
325 NSRect frame = CalculateWindowFrame(/*expand=*/false); | |
326 if (!fade_out) { | 287 if (!fade_out) { |
327 // 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. |
328 [window_ setAlphaValue:0.0]; | 289 [window_ setAlphaValue:0.0]; |
329 SetState(kBubbleHidden); | 290 SetState(kBubbleHidden); |
330 frame.size = ui::kWindowSizeDeterminedLater.size; | |
331 } | 291 } |
332 | 292 |
333 // Stop any width animation and reset the bubble size. | 293 // Stop any width animation and reset the bubble size. |
334 if (!immediate_) { | 294 if (!immediate_) { |
335 [NSAnimationContext beginGrouping]; | 295 [NSAnimationContext beginGrouping]; |
336 [[NSAnimationContext currentContext] setDuration:kMinimumTimeInterval]; | 296 [[NSAnimationContext currentContext] setDuration:kMinimumTimeInterval]; |
337 [[window_ animator] setFrame:frame display:NO]; | 297 [[window_ animator] setFrame:CalculateWindowFrame(/*expand=*/false) |
| 298 display:NO]; |
338 [NSAnimationContext endGrouping]; | 299 [NSAnimationContext endGrouping]; |
339 } else { | 300 } else { |
340 [window_ setFrame:frame display:NO]; | 301 [window_ setFrame:CalculateWindowFrame(/*expand=*/false) display:NO]; |
341 } | 302 } |
342 | 303 |
343 [status_text_ release]; | 304 [status_text_ release]; |
344 status_text_ = nil; | 305 status_text_ = nil; |
345 [url_text_ release]; | 306 [url_text_ release]; |
346 url_text_ = nil; | 307 url_text_ = nil; |
347 } | 308 } |
348 | 309 |
349 void StatusBubbleMac::SetFrameAvoidingMouse( | 310 void StatusBubbleMac::SetFrameAvoidingMouse( |
350 NSRect window_frame, const gfx::Point& mouse_pos) { | 311 NSRect window_frame, const gfx::Point& mouse_pos) { |
(...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
478 [window_ orderFront:nil]; | 439 [window_ orderFront:nil]; |
479 [parent_ addChildWindow:window_ ordered:NSWindowAbove]; | 440 [parent_ addChildWindow:window_ ordered:NSWindowAbove]; |
480 | 441 |
481 [[window_ contentView] setThemeProvider:parent_]; | 442 [[window_ contentView] setThemeProvider:parent_]; |
482 } | 443 } |
483 | 444 |
484 void StatusBubbleMac::Detach() { | 445 void StatusBubbleMac::Detach() { |
485 DCHECK(is_attached()); | 446 DCHECK(is_attached()); |
486 | 447 |
487 // 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 . |
488 // TODO(rohitrao): Does the frame size actually matter here? Can we always | 449 [window_ setFrame:CalculateWindowFrame(/*expand=*/false) display:NO]; |
489 // set it to kWindowSizeDeterminedLater? | |
490 NSRect frame = [window_ frame]; | |
491 frame.size = ui::kWindowSizeDeterminedLater.size; | |
492 if (state_ != kBubbleHidden) { | |
493 frame = CalculateWindowFrame(/*expand=*/false); | |
494 } | |
495 [window_ setFrame:frame display:NO]; | |
496 [parent_ removeChildWindow:window_]; // See crbug.com/28107 ... | 450 [parent_ removeChildWindow:window_]; // See crbug.com/28107 ... |
497 [window_ orderOut:nil]; // ... and crbug.com/29054. | 451 [window_ orderOut:nil]; // ... and crbug.com/29054. |
498 | 452 |
499 [[window_ contentView] setThemeProvider:nil]; | 453 [[window_ contentView] setThemeProvider:nil]; |
500 } | 454 } |
501 | 455 |
502 void StatusBubbleMac::AnimationDidStop() { | 456 void StatusBubbleMac::AnimationDidStop() { |
503 DCHECK([NSThread isMainThread]); | 457 DCHECK([NSThread isMainThread]); |
504 DCHECK(state_ == kBubbleShowingFadeIn || state_ == kBubbleHidingFadeOut); | 458 DCHECK(state_ == kBubbleShowingFadeIn || state_ == kBubbleHidingFadeOut); |
505 DCHECK(is_attached()); | 459 DCHECK(is_attached()); |
506 | 460 |
507 if (state_ == kBubbleShowingFadeIn) { | 461 if (state_ == kBubbleShowingFadeIn) { |
508 DCHECK_EQ([[window_ animator] alphaValue], kBubbleOpacity); | 462 DCHECK_EQ([[window_ animator] alphaValue], kBubbleOpacity); |
509 SetState(kBubbleShown); | 463 SetState(kBubbleShown); |
510 } else { | 464 } else { |
511 DCHECK_EQ([[window_ animator] alphaValue], 0.0); | 465 DCHECK_EQ([[window_ animator] alphaValue], 0.0); |
512 SetState(kBubbleHidden); | 466 SetState(kBubbleHidden); |
513 } | 467 } |
514 } | 468 } |
515 | 469 |
516 void StatusBubbleMac::SetState(StatusBubbleState state) { | 470 void StatusBubbleMac::SetState(StatusBubbleState state) { |
517 if (state == state_) | 471 if (state == state_) |
518 return; | 472 return; |
519 | 473 |
520 if (state == kBubbleHidden) { | 474 if (state == kBubbleHidden) { |
521 is_expanded_ = false; | |
522 | |
523 // 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, |
524 // 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 |
525 // origin to 0,0 as that will cause the window to use more space in | 477 // origin to 0,0 as that will cause the window to use more space in |
526 // Expose/Mission Control. See http://crbug.com/81969. | 478 // Expose/Mission Control. See http://crbug.com/81969. |
527 // | 479 // |
528 // Also, doing it this way instead of detaching the window avoids bugs with | 480 // Also, doing it this way instead of detaching the window avoids bugs with |
529 // Spaces and Cmd-`. See http://crbug.com/31821 and http://crbug.com/61629. | 481 // Spaces and Cmd-`. See http://crbug.com/31821 and http://crbug.com/61629. |
530 NSRect frame = [window_ frame]; | 482 NSRect frame = [window_ frame]; |
531 frame.size = ui::kWindowSizeDeterminedLater.size; | 483 frame.size = ui::kWindowSizeDeterminedLater.size; |
532 [window_ setFrame:frame display:YES]; | 484 [window_ setFrame:frame display:YES]; |
(...skipping 225 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
758 [NSAnimationContext beginGrouping]; | 710 [NSAnimationContext beginGrouping]; |
759 [[NSAnimationContext currentContext] setDuration:kExpansionDurationSeconds]; | 711 [[NSAnimationContext currentContext] setDuration:kExpansionDurationSeconds]; |
760 [[window_ animator] setFrame:actual_window_frame display:YES]; | 712 [[window_ animator] setFrame:actual_window_frame display:YES]; |
761 [NSAnimationContext endGrouping]; | 713 [NSAnimationContext endGrouping]; |
762 } | 714 } |
763 | 715 |
764 void StatusBubbleMac::UpdateSizeAndPosition() { | 716 void StatusBubbleMac::UpdateSizeAndPosition() { |
765 if (!window_) | 717 if (!window_) |
766 return; | 718 return; |
767 | 719 |
768 // There is no need to update the size if the bubble is hidden. | |
769 if (state_ == kBubbleHidden) { | |
770 // Verify that hidden bubbles always have size equal to | |
771 // ui::kWindowSizeDeterminedLater. | |
772 | |
773 // TODO(rohitrao): The GPU bots are hitting cases where this is not true. | |
774 // Instead of enforcing this invariant with a DCHECK, add temporary logging | |
775 // to try and debug it and fix up the window size if needed. | |
776 // This logging is temporary and should be removed: crbug.com/467998 | |
777 NSRect frame = [window_ frame]; | |
778 if (!CGSizeEqualToSize(frame.size, ui::kWindowSizeDeterminedLater.size)) { | |
779 LOG(ERROR) << "Window size should be (1,1), but is instead (" | |
780 << frame.size.width << "," << frame.size.height << ")"; | |
781 LOG(ERROR) << base::debug::StackTrace().ToString(); | |
782 frame.size = ui::kWindowSizeDeterminedLater.size; | |
783 [window_ setFrame:frame display:YES]; | |
784 } | |
785 return; | |
786 } | |
787 | |
788 SetFrameAvoidingMouse(CalculateWindowFrame(/*expand=*/false), | 720 SetFrameAvoidingMouse(CalculateWindowFrame(/*expand=*/false), |
789 GetMouseLocation()); | 721 GetMouseLocation()); |
790 } | 722 } |
791 | 723 |
792 void StatusBubbleMac::SwitchParentWindow(NSWindow* parent) { | 724 void StatusBubbleMac::SwitchParentWindow(NSWindow* parent) { |
793 DCHECK(parent); | 725 DCHECK(parent); |
794 DCHECK(is_attached()); | 726 DCHECK(is_attached()); |
795 | 727 |
796 Detach(); | 728 Detach(); |
797 parent_ = parent; | 729 parent_ = parent; |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
843 } | 775 } |
844 | 776 |
845 // 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. |
846 if (NSMinY(window_frame) < NSMinY(parent_frame)) { | 778 if (NSMinY(window_frame) < NSMinY(parent_frame)) { |
847 corner_flags |= kRoundedTopLeftCorner | kRoundedTopRightCorner; | 779 corner_flags |= kRoundedTopLeftCorner | kRoundedTopRightCorner; |
848 } | 780 } |
849 } | 781 } |
850 | 782 |
851 return corner_flags; | 783 return corner_flags; |
852 } | 784 } |
OLD | NEW |