Chromium Code Reviews| 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" | |
| 11 #include "base/mac/mac_util.h" | 12 #include "base/mac/mac_util.h" |
| 12 #include "base/mac/scoped_block.h" | 13 #include "base/mac/scoped_block.h" |
| 13 #include "base/mac/sdk_forward_declarations.h" | 14 #include "base/mac/sdk_forward_declarations.h" |
| 14 #include "base/message_loop/message_loop.h" | 15 #include "base/message_loop/message_loop.h" |
| 15 #include "base/strings/string_util.h" | 16 #include "base/strings/string_util.h" |
| 16 #include "base/strings/sys_string_conversions.h" | 17 #include "base/strings/sys_string_conversions.h" |
| 17 #include "base/strings/utf_string_conversions.h" | 18 #include "base/strings/utf_string_conversions.h" |
| 18 #import "chrome/browser/ui/cocoa/bubble_view.h" | 19 #import "chrome/browser/ui/cocoa/bubble_view.h" |
| 19 #include "chrome/browser/ui/elide_url.h" | 20 #include "chrome/browser/ui/elide_url.h" |
| 20 #include "net/base/net_util.h" | 21 #include "net/base/net_util.h" |
| (...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 135 changes([NSAnimationContext currentContext]); | 136 changes([NSAnimationContext currentContext]); |
| 136 // At this point, -animationForKey should have been called by CoreAnimation | 137 // At this point, -animationForKey should have been called by CoreAnimation |
| 137 // to set up the animation to run. Verify this. | 138 // to set up the animation to run. Verify this. |
| 138 DCHECK(completionHandler_ == nil); | 139 DCHECK(completionHandler_ == nil); |
| 139 [NSAnimationContext endGrouping]; | 140 [NSAnimationContext endGrouping]; |
| 140 } | 141 } |
| 141 } | 142 } |
| 142 | 143 |
| 143 @end | 144 @end |
| 144 | 145 |
| 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 | |
| 145 StatusBubbleMac::StatusBubbleMac(NSWindow* parent, id delegate) | 172 StatusBubbleMac::StatusBubbleMac(NSWindow* parent, id delegate) |
| 146 : parent_(parent), | 173 : parent_(parent), |
| 147 delegate_(delegate), | 174 delegate_(delegate), |
| 148 window_(nil), | 175 window_(nil), |
| 149 status_text_(nil), | 176 status_text_(nil), |
| 150 url_text_(nil), | 177 url_text_(nil), |
| 151 state_(kBubbleHidden), | 178 state_(kBubbleHidden), |
| 152 immediate_(false), | 179 immediate_(false), |
| 153 is_expanded_(false), | 180 is_expanded_(false), |
| 154 timer_factory_(this), | 181 timer_factory_(this), |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 170 } | 197 } |
| 171 | 198 |
| 172 void StatusBubbleMac::SetStatus(const base::string16& status) { | 199 void StatusBubbleMac::SetStatus(const base::string16& status) { |
| 173 SetText(status, false); | 200 SetText(status, false); |
| 174 } | 201 } |
| 175 | 202 |
| 176 void StatusBubbleMac::SetURL(const GURL& url, const std::string& languages) { | 203 void StatusBubbleMac::SetURL(const GURL& url, const std::string& languages) { |
| 177 url_ = url; | 204 url_ = url; |
| 178 languages_ = languages; | 205 languages_ = languages; |
| 179 | 206 |
| 180 NSRect frame = [window_ frame]; | 207 CGFloat bubble_width = NSWidth([window_ frame]); |
| 181 | |
| 182 // Reset frame size when bubble is hidden. | |
| 183 if (state_ == kBubbleHidden) { | 208 if (state_ == kBubbleHidden) { |
| 184 is_expanded_ = false; | 209 // TODO(rohitrao): The window size is expected to be (1,1) whenever the |
| 185 frame.size.width = NSWidth(CalculateWindowFrame(/*expand=*/false)); | 210 // window is hidden, but the GPU bots are hitting cases where this is not |
| 186 [window_ setFrame:frame display:NO]; | 211 // true. Instead of enforcing this invariant with a DCHECK, add temporary |
| 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)); | |
| 187 } | 223 } |
| 188 | 224 |
| 189 int text_width = static_cast<int>(NSWidth(frame) - | 225 int text_width = static_cast<int>(bubble_width - |
| 190 kBubbleViewTextPositionX - | 226 kBubbleViewTextPositionX - |
| 191 kTextPadding); | 227 kTextPadding); |
| 192 | 228 |
| 193 // Scale from view to window coordinates before eliding URL string. | 229 // Scale from view to window coordinates before eliding URL string. |
| 194 NSSize scaled_width = NSMakeSize(text_width, 0); | 230 NSSize scaled_width = NSMakeSize(text_width, 0); |
| 195 scaled_width = [[parent_ contentView] convertSize:scaled_width fromView:nil]; | 231 scaled_width = [[parent_ contentView] convertSize:scaled_width fromView:nil]; |
| 196 text_width = static_cast<int>(scaled_width.width); | 232 text_width = static_cast<int>(scaled_width.width); |
| 197 NSFont* font = [[window_ contentView] font]; | 233 NSFont* font = [[window_ contentView] font]; |
| 198 gfx::FontList font_list_chr( | 234 gfx::FontList font_list_chr( |
| 199 gfx::Font(base::SysNSStringToUTF8([font fontName]), [font pointSize])); | 235 gfx::Font(base::SysNSStringToUTF8([font fontName]), [font pointSize])); |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 253 | 289 |
| 254 bool show = true; | 290 bool show = true; |
| 255 if ([*main length] > 0) | 291 if ([*main length] > 0) |
| 256 [[window_ contentView] setContent:*main]; | 292 [[window_ contentView] setContent:*main]; |
| 257 else if ([*backup length] > 0) | 293 else if ([*backup length] > 0) |
| 258 [[window_ contentView] setContent:*backup]; | 294 [[window_ contentView] setContent:*backup]; |
| 259 else | 295 else |
| 260 show = false; | 296 show = false; |
| 261 | 297 |
| 262 if (show) { | 298 if (show) { |
| 299 // Call StartShowing() first to update the current bubble state before | |
| 300 // calculating a new size. | |
| 301 StartShowing(); | |
| 263 UpdateSizeAndPosition(); | 302 UpdateSizeAndPosition(); |
| 264 StartShowing(); | |
| 265 } else { | 303 } else { |
| 266 StartHiding(); | 304 StartHiding(); |
| 267 } | 305 } |
| 268 } | 306 } |
| 269 | 307 |
| 270 void StatusBubbleMac::Hide() { | 308 void StatusBubbleMac::Hide() { |
| 271 CancelTimer(); | 309 CancelTimer(); |
| 272 CancelExpandTimer(); | 310 CancelExpandTimer(); |
| 273 is_expanded_ = false; | 311 is_expanded_ = false; |
| 274 | 312 |
| 275 bool fade_out = false; | 313 bool fade_out = false; |
| 276 if (state_ == kBubbleHidingFadeOut || state_ == kBubbleShowingFadeIn) { | 314 if (state_ == kBubbleHidingFadeOut || state_ == kBubbleShowingFadeIn) { |
| 277 SetState(kBubbleHidingFadeOut); | 315 SetState(kBubbleHidingFadeOut); |
| 278 | 316 |
| 279 if (!immediate_) { | 317 if (!immediate_) { |
| 280 // An animation is in progress. Cancel it by starting a new animation. | 318 // An animation is in progress. Cancel it by starting a new animation. |
| 281 // Use kMinimumTimeInterval to set the opacity as rapidly as possible. | 319 // Use kMinimumTimeInterval to set the opacity as rapidly as possible. |
| 282 fade_out = true; | 320 fade_out = true; |
| 283 AnimateWindowAlpha(0.0, kMinimumTimeInterval); | 321 AnimateWindowAlpha(0.0, kMinimumTimeInterval); |
| 284 } | 322 } |
| 285 } | 323 } |
| 286 | 324 |
| 325 NSRect frame = CalculateWindowFrame(/*expand=*/false); | |
| 287 if (!fade_out) { | 326 if (!fade_out) { |
| 288 // No animation is in progress, so the opacity can be set directly. | 327 // No animation is in progress, so the opacity can be set directly. |
| 289 [window_ setAlphaValue:0.0]; | 328 [window_ setAlphaValue:0.0]; |
| 290 SetState(kBubbleHidden); | 329 SetState(kBubbleHidden); |
| 330 frame.size = ui::kWindowSizeDeterminedLater.size; | |
| 291 } | 331 } |
| 292 | 332 |
| 293 // Stop any width animation and reset the bubble size. | 333 // Stop any width animation and reset the bubble size. |
| 294 if (!immediate_) { | 334 if (!immediate_) { |
| 295 [NSAnimationContext beginGrouping]; | 335 [NSAnimationContext beginGrouping]; |
| 296 [[NSAnimationContext currentContext] setDuration:kMinimumTimeInterval]; | 336 [[NSAnimationContext currentContext] setDuration:kMinimumTimeInterval]; |
| 297 [[window_ animator] setFrame:CalculateWindowFrame(/*expand=*/false) | 337 [[window_ animator] setFrame:frame display:NO]; |
| 298 display:NO]; | |
| 299 [NSAnimationContext endGrouping]; | 338 [NSAnimationContext endGrouping]; |
| 300 } else { | 339 } else { |
| 301 [window_ setFrame:CalculateWindowFrame(/*expand=*/false) display:NO]; | 340 [window_ setFrame:frame display:NO]; |
| 302 } | 341 } |
| 303 | 342 |
| 304 [status_text_ release]; | 343 [status_text_ release]; |
| 305 status_text_ = nil; | 344 status_text_ = nil; |
| 306 [url_text_ release]; | 345 [url_text_ release]; |
| 307 url_text_ = nil; | 346 url_text_ = nil; |
| 308 } | 347 } |
| 309 | 348 |
| 310 void StatusBubbleMac::SetFrameAvoidingMouse( | 349 void StatusBubbleMac::SetFrameAvoidingMouse( |
| 311 NSRect window_frame, const gfx::Point& mouse_pos) { | 350 NSRect window_frame, const gfx::Point& mouse_pos) { |
| (...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 439 [window_ orderFront:nil]; | 478 [window_ orderFront:nil]; |
| 440 [parent_ addChildWindow:window_ ordered:NSWindowAbove]; | 479 [parent_ addChildWindow:window_ ordered:NSWindowAbove]; |
| 441 | 480 |
| 442 [[window_ contentView] setThemeProvider:parent_]; | 481 [[window_ contentView] setThemeProvider:parent_]; |
| 443 } | 482 } |
| 444 | 483 |
| 445 void StatusBubbleMac::Detach() { | 484 void StatusBubbleMac::Detach() { |
| 446 DCHECK(is_attached()); | 485 DCHECK(is_attached()); |
| 447 | 486 |
| 448 // Magic setFrame: See http://crbug.com/58506 and http://crrev.com/3564021 . | 487 // Magic setFrame: See http://crbug.com/58506 and http://crrev.com/3564021 . |
| 449 [window_ setFrame:CalculateWindowFrame(/*expand=*/false) display:NO]; | 488 // TODO(rohitrao): Does the frame size actually matter here? Can we always |
| 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]; | |
| 450 [parent_ removeChildWindow:window_]; // See crbug.com/28107 ... | 496 [parent_ removeChildWindow:window_]; // See crbug.com/28107 ... |
| 451 [window_ orderOut:nil]; // ... and crbug.com/29054. | 497 [window_ orderOut:nil]; // ... and crbug.com/29054. |
| 452 | 498 |
| 453 [[window_ contentView] setThemeProvider:nil]; | 499 [[window_ contentView] setThemeProvider:nil]; |
| 454 } | 500 } |
| 455 | 501 |
| 456 void StatusBubbleMac::AnimationDidStop() { | 502 void StatusBubbleMac::AnimationDidStop() { |
| 457 DCHECK([NSThread isMainThread]); | 503 DCHECK([NSThread isMainThread]); |
| 458 DCHECK(state_ == kBubbleShowingFadeIn || state_ == kBubbleHidingFadeOut); | 504 DCHECK(state_ == kBubbleShowingFadeIn || state_ == kBubbleHidingFadeOut); |
| 459 DCHECK(is_attached()); | 505 DCHECK(is_attached()); |
| 460 | 506 |
| 461 if (state_ == kBubbleShowingFadeIn) { | 507 if (state_ == kBubbleShowingFadeIn) { |
| 462 DCHECK_EQ([[window_ animator] alphaValue], kBubbleOpacity); | 508 DCHECK_EQ([[window_ animator] alphaValue], kBubbleOpacity); |
| 463 SetState(kBubbleShown); | 509 SetState(kBubbleShown); |
| 464 } else { | 510 } else { |
| 465 DCHECK_EQ([[window_ animator] alphaValue], 0.0); | 511 DCHECK_EQ([[window_ animator] alphaValue], 0.0); |
| 466 SetState(kBubbleHidden); | 512 SetState(kBubbleHidden); |
| 467 } | 513 } |
| 468 } | 514 } |
| 469 | 515 |
| 470 void StatusBubbleMac::SetState(StatusBubbleState state) { | 516 void StatusBubbleMac::SetState(StatusBubbleState state) { |
| 471 if (state == state_) | 517 if (state == state_) |
| 472 return; | 518 return; |
| 473 | 519 |
| 474 if (state == kBubbleHidden) { | 520 if (state == kBubbleHidden) { |
| 521 is_expanded_ = false; | |
| 522 | |
| 475 // When hidden (with alpha of 0), make the window have the minimum size, | 523 // When hidden (with alpha of 0), make the window have the minimum size, |
| 476 // while still keeping the same origin. It's important to not set the | 524 // while still keeping the same origin. It's important to not set the |
| 477 // origin to 0,0 as that will cause the window to use more space in | 525 // origin to 0,0 as that will cause the window to use more space in |
| 478 // Expose/Mission Control. See http://crbug.com/81969. | 526 // Expose/Mission Control. See http://crbug.com/81969. |
| 479 // | 527 // |
| 480 // Also, doing it this way instead of detaching the window avoids bugs with | 528 // Also, doing it this way instead of detaching the window avoids bugs with |
| 481 // Spaces and Cmd-`. See http://crbug.com/31821 and http://crbug.com/61629. | 529 // Spaces and Cmd-`. See http://crbug.com/31821 and http://crbug.com/61629. |
| 482 NSRect frame = [window_ frame]; | 530 NSRect frame = [window_ frame]; |
| 483 frame.size = ui::kWindowSizeDeterminedLater.size; | 531 frame.size = ui::kWindowSizeDeterminedLater.size; |
| 484 [window_ setFrame:frame display:YES]; | 532 [window_ setFrame:frame display:YES]; |
| (...skipping 225 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 710 [NSAnimationContext beginGrouping]; | 758 [NSAnimationContext beginGrouping]; |
| 711 [[NSAnimationContext currentContext] setDuration:kExpansionDurationSeconds]; | 759 [[NSAnimationContext currentContext] setDuration:kExpansionDurationSeconds]; |
| 712 [[window_ animator] setFrame:actual_window_frame display:YES]; | 760 [[window_ animator] setFrame:actual_window_frame display:YES]; |
| 713 [NSAnimationContext endGrouping]; | 761 [NSAnimationContext endGrouping]; |
| 714 } | 762 } |
| 715 | 763 |
| 716 void StatusBubbleMac::UpdateSizeAndPosition() { | 764 void StatusBubbleMac::UpdateSizeAndPosition() { |
| 717 if (!window_) | 765 if (!window_) |
| 718 return; | 766 return; |
| 719 | 767 |
| 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 } | |
| 784 | |
| 785 // During the fullscreen animation, the parent window's origin may change | |
| 786 // without updating the status bubble. To avoid animation glitches, always | |
| 787 // update the bubble's origin to match the parent's, even when hidden. | |
| 788 frame.origin = [parent_ frame].origin; | |
| 789 [window_ setFrame:frame display:NO]; | |
|
rohitrao (ping after 24h)
2015/04/27 23:29:48
Updated to not force a display. Setting this to N
| |
| 790 return; | |
| 791 } | |
| 792 | |
| 720 SetFrameAvoidingMouse(CalculateWindowFrame(/*expand=*/false), | 793 SetFrameAvoidingMouse(CalculateWindowFrame(/*expand=*/false), |
| 721 GetMouseLocation()); | 794 GetMouseLocation()); |
| 722 } | 795 } |
| 723 | 796 |
| 724 void StatusBubbleMac::SwitchParentWindow(NSWindow* parent) { | 797 void StatusBubbleMac::SwitchParentWindow(NSWindow* parent) { |
| 725 DCHECK(parent); | 798 DCHECK(parent); |
| 726 DCHECK(is_attached()); | 799 DCHECK(is_attached()); |
| 727 | 800 |
| 728 Detach(); | 801 Detach(); |
| 729 parent_ = parent; | 802 parent_ = parent; |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 775 } | 848 } |
| 776 | 849 |
| 777 // Round the top corners when the bubble is below the parent window. | 850 // Round the top corners when the bubble is below the parent window. |
| 778 if (NSMinY(window_frame) < NSMinY(parent_frame)) { | 851 if (NSMinY(window_frame) < NSMinY(parent_frame)) { |
| 779 corner_flags |= kRoundedTopLeftCorner | kRoundedTopRightCorner; | 852 corner_flags |= kRoundedTopLeftCorner | kRoundedTopRightCorner; |
| 780 } | 853 } |
| 781 } | 854 } |
| 782 | 855 |
| 783 return corner_flags; | 856 return corner_flags; |
| 784 } | 857 } |
| OLD | NEW |