summaryrefslogtreecommitdiffstats
path: root/chrome/browser
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser')
-rw-r--r--chrome/browser/cocoa/status_bubble_mac.h76
-rw-r--r--chrome/browser/cocoa/status_bubble_mac.mm339
-rw-r--r--chrome/browser/cocoa/status_bubble_mac_unittest.mm306
3 files changed, 665 insertions, 56 deletions
diff --git a/chrome/browser/cocoa/status_bubble_mac.h b/chrome/browser/cocoa/status_bubble_mac.h
index 9dc177d..f110b1d 100644
--- a/chrome/browser/cocoa/status_bubble_mac.h
+++ b/chrome/browser/cocoa/status_bubble_mac.h
@@ -8,7 +8,9 @@
#include <string>
#import <Cocoa/Cocoa.h>
+#import <QuartzCore/QuartzCore.h>
+#include "base/task.h"
#include "chrome/browser/status_bubble.h"
class GURL;
@@ -16,6 +18,17 @@ class StatusBubbleMacTest;
class StatusBubbleMac : public StatusBubble {
public:
+ // The various states that a status bubble may be in. Public for delegate
+ // access (for testing).
+ enum StatusBubbleState {
+ kBubbleHidden, // Fully hidden
+ kBubbleShowingTimer, // Waiting to fade in
+ kBubbleShowingFadeIn, // In a fade-in transition
+ kBubbleShown, // Fully visible
+ kBubbleHidingTimer, // Waiting to fade out
+ kBubbleHidingFadeOut // In a fade-out transition
+ };
+
StatusBubbleMac(NSWindow* parent, id delegate);
virtual ~StatusBubbleMac();
@@ -31,17 +44,53 @@ class StatusBubbleMac : public StatusBubble {
// exist.
void UpdateSizeAndPosition();
+ // Delegate method called when a fade-in or fade-out transition has
+ // completed. This is public so that it may be visible to the CAAnimation
+ // delegate, which is an Objective-C object.
+ void AnimationDidStop(CAAnimation* animation, bool finished);
+
private:
friend class StatusBubbleMacTest;
- void SetStatus(NSString* status, bool is_url);
+ // Setter for state_. Use this instead of writing to state_ directly so
+ // that state changes can be observed by unit tests.
+ void SetState(StatusBubbleState state);
+
+ // Sets the bubble text for SetStatus and SetURL.
+ void SetText(const std::wstring& text, bool is_url);
// Construct the window/widget if it does not already exist. (Safe to call if
// it does.)
void Create();
- void FadeIn();
- void FadeOut();
+ // Attaches the status bubble window to its parent window.
+ void Attach();
+
+ // Begins fading the status bubble window in or out depending on the value
+ // of |show|. This must be called from the appropriate fade state,
+ // kBubbleShowingFadeIn or kBubbleHidingFadeOut, or from the appropriate
+ // fully-shown/hidden state, kBubbleShown or kBubbleHidden. This may be
+ // called at any point during a fade-in or fade-out; it is even possible to
+ // reverse a transition before it has completed.
+ void Fade(bool show);
+
+ // One-shot timer operations to manage the delays associated with the
+ // kBubbleShowingTimer and kBubbleHidingTimer states. StartTimer and
+ // TimerFired must be called from one of these states. StartTimer may be
+ // called while the timer is still running; in that case, the timer will be
+ // reset. CancelTimer may be called from any state.
+ void StartTimer(int64 time_ms);
+ void CancelTimer();
+ void TimerFired();
+
+ // Begin the process of showing or hiding the status bubble. These may be
+ // called from any state, and will take the appropriate action to initiate
+ // any state changes that may be needed.
+ void StartShowing();
+ void StartHiding();
+
+ // The timer factory used for show and hide delay timers.
+ ScopedRunnableMethodFactory<StatusBubbleMac> timer_factory_;
// Calculate the appropriate frame for the status bubble window.
NSRect CalculateWindowFrame();
@@ -61,15 +110,26 @@ class StatusBubbleMac : public StatusBubble {
// The url we want to display when there is no status text to display.
NSString* url_text_;
- // How vertically offset the bubble is from its root position.
- int offset_;
+ // The status bubble's current state. Do not write to this field directly;
+ // use SetState().
+ StatusBubbleState state_;
+
+ // True if operations are to be performed immediately rather than waiting
+ // for delays and transitions. Normally false, this should only be set to
+ // true for testing.
+ bool immediate_;
+
+ DISALLOW_COPY_AND_ASSIGN(StatusBubbleMac);
};
-// Delegate interface that allows the StatusBubble to query its delegate about
-// the vertical offset (if any) that should be applied to the StatusBubble's
-// position.
+// Delegate interface
@interface NSObject(StatusBubbleDelegate)
+// Called to query the delegate about the vertical offset (if any) that should
+// be applied to the StatusBubble's position.
- (float)verticalOffsetForStatusBubble;
+
+// Called from SetState to notify the delegate of state changes.
+- (void)statusBubbleWillEnterState:(StatusBubbleMac::StatusBubbleState)state;
@end
#endif // #ifndef CHROME_BROWSER_COCOA_STATUS_BUBBLE_MAC_H_
diff --git a/chrome/browser/cocoa/status_bubble_mac.mm b/chrome/browser/cocoa/status_bubble_mac.mm
index 3eef461..fb954cc 100644
--- a/chrome/browser/cocoa/status_bubble_mac.mm
+++ b/chrome/browser/cocoa/status_bubble_mac.mm
@@ -4,7 +4,11 @@
#include "chrome/browser/cocoa/status_bubble_mac.h"
+#include <limits>
+
#include "app/gfx/text_elider.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop.h"
#include "base/string_util.h"
#include "base/sys_string_conversions.h"
#import "chrome/browser/cocoa/bubble_view.h"
@@ -16,8 +20,9 @@
namespace {
const int kWindowHeight = 18;
+
// The width of the bubble in relation to the width of the parent window.
-const float kWindowWidthPercent = 1.0f/3.0f;
+const double kWindowWidthPercent = 1.0 / 3.0;
// How close the mouse can get to the infobubble before it starts sliding
// off-screen.
@@ -25,33 +30,91 @@ const int kMousePadding = 20;
const int kTextPadding = 3;
-// How long each fade should last for.
-const int kShowFadeDuration = 0.120f;
-const int kHideFadeDuration = 0.200f;
+// The animation key used for fade-in and fade-out transitions.
+const NSString* kFadeAnimationKey = @"alphaValue";
+
+// The status bubble's maximum opacity, when fully faded in.
+const CGFloat kBubbleOpacity = 1.0;
+
+// Delay before showing or hiding the bubble after a SetStatus or SetURL call.
+const int64 kShowDelayMilliseconds = 80;
+const int64 kHideDelayMilliseconds = 250;
+
+// How long each fade should last.
+const NSTimeInterval kShowFadeInDurationSeconds = 0.120;
+const NSTimeInterval kHideFadeOutDurationSeconds = 0.200;
+
+// The minimum representable time interval. This can be used as the value
+// passed to +[NSAnimationContext setDuration:] to stop an in-progress
+// animation as quickly as possible.
+const NSTimeInterval kMinimumTimeInterval =
+ std::numeric_limits<NSTimeInterval>::min();
+} // namespace
+
+@interface StatusBubbleAnimationDelegate : NSObject {
+ @private
+ StatusBubbleMac* statusBubble_; // weak; owns us indirectly
}
-// TODO(avi):
-// - do display delay
+- (id)initWithStatusBubble:(StatusBubbleMac*)statusBubble;
+
+// Invalidates this object so that no further calls will be made to
+// statusBubble_. This should be called when statusBubble_ is released, to
+// prevent attempts to call into the released object.
+- (void)invalidate;
+
+// CAAnimation delegate method
+- (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)finished;
+@end
+
+@implementation StatusBubbleAnimationDelegate
+
+- (id)initWithStatusBubble:(StatusBubbleMac*)statusBubble {
+ if ((self = [super init])) {
+ statusBubble_ = statusBubble;
+ }
+
+ return self;
+}
+
+- (void)invalidate {
+ statusBubble_ = NULL;
+}
+
+- (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)finished {
+ if (statusBubble_)
+ statusBubble_->AnimationDidStop(animation, finished ? true : false);
+}
+
+@end
StatusBubbleMac::StatusBubbleMac(NSWindow* parent, id delegate)
- : parent_(parent),
+ : ALLOW_THIS_IN_INITIALIZER_LIST(timer_factory_(this)),
+ parent_(parent),
delegate_(delegate),
window_(nil),
status_text_(nil),
- url_text_(nil) {
+ url_text_(nil),
+ state_(kBubbleHidden),
+ immediate_(false) {
}
StatusBubbleMac::~StatusBubbleMac() {
Hide();
+
+ if (window_) {
+ [[[window_ animationForKey:kFadeAnimationKey] delegate] invalidate];
+ [parent_ removeChildWindow:window_];
+ [window_ release];
+ window_ = nil;
+ }
}
void StatusBubbleMac::SetStatus(const std::wstring& status) {
Create();
- NSString* status_ns = base::SysWideToNSString(status);
-
- SetStatus(status_ns, false);
+ SetText(status, false);
}
void StatusBubbleMac::SetURL(const GURL& url, const std::wstring& languages) {
@@ -67,12 +130,17 @@ void StatusBubbleMac::SetURL(const GURL& url, const std::wstring& languages) {
[font pointSize]);
std::wstring status = gfx::ElideUrl(url, font_chr, text_width, languages);
- NSString* status_ns = base::SysWideToNSString(status);
- SetStatus(status_ns, true);
+ SetText(status, true);
}
-void StatusBubbleMac::SetStatus(NSString* status, bool is_url) {
+void StatusBubbleMac::SetText(const std::wstring& text, bool is_url) {
+ // The status bubble allows the status and URL strings to be set
+ // independently. Whichever was set non-empty most recently will be the
+ // value displayed. When both are empty, the status bubble hides.
+
+ NSString* text_ns = base::SysWideToNSString(text);
+
NSString** main;
NSString** backup;
@@ -84,29 +152,51 @@ void StatusBubbleMac::SetStatus(NSString* status, bool is_url) {
backup = &url_text_;
}
- if ([status isEqualToString:*main])
- return;
+ // Don't return from this function early. It's important to make sure that
+ // all calls to StartShowing and StartHiding are made, so that all delays
+ // are observed properly. Specifically, if the state is currently
+ // kBubbleShowingTimer, the timer will need to be restarted even if
+ // [text_ns isEqualToString:*main] is true.
+
+ [*main autorelease];
+ *main = [text_ns retain];
- [*main release];
- *main = [status retain];
- if ([*main length] > 0) {
+ bool show = true;
+ if ([*main length] > 0)
[[window_ contentView] setContent:*main];
- } else if ([*backup length] > 0) {
+ else if ([*backup length] > 0)
[[window_ contentView] setContent:*backup];
- } else {
- Hide();
- }
+ else
+ show = false;
- FadeIn();
+ if (show)
+ StartShowing();
+ else
+ StartHiding();
}
void StatusBubbleMac::Hide() {
- FadeOut();
+ CancelTimer();
+
+ bool fade_out = false;
+ if (state_ == kBubbleHidingFadeOut || state_ == kBubbleShowingFadeIn) {
+ SetState(kBubbleHidingFadeOut);
+
+ if (!immediate_) {
+ // An animation is in progress. Cancel it by starting a new animation.
+ // Use kMinimumTimeInterval to set the opacity as rapidly as possible.
+ fade_out = true;
+ [NSAnimationContext beginGrouping];
+ [[NSAnimationContext currentContext] setDuration:kMinimumTimeInterval];
+ [[window_ animator] setAlphaValue:0.0];
+ [NSAnimationContext endGrouping];
+ }
+ }
- if (window_) {
- [parent_ removeChildWindow:window_];
- [window_ release];
- window_ = nil;
+ if (!fade_out) {
+ // No animation is in progress, so the opacity can be set directly.
+ [window_ setAlphaValue:0.0];
+ SetState(kBubbleHidden);
}
[status_text_ release];
@@ -165,10 +255,8 @@ void StatusBubbleMac::MouseMoved() {
[[window_ contentView] setCornerFlags:kRoundedTopRightCorner];
}
- offset_ = offset;
window_frame.origin.y -= offset;
} else {
- offset_ = 0;
[[window_ contentView] setCornerFlags:kRoundedTopRightCorner];
}
@@ -201,29 +289,194 @@ void StatusBubbleMac::Create() {
[[BubbleView alloc] initWithFrame:NSZeroRect themeProvider:parent_]);
[window_ setContentView:view];
- [parent_ addChildWindow:window_ ordered:NSWindowAbove];
-
- [window_ setAlphaValue:0.0f];
+ [window_ setAlphaValue:0.0];
+
+ // Set a delegate for the fade-in and fade-out transitions to be notified
+ // when fades are complete. The ownership model is for window_ to own
+ // animation_dictionary, which owns animation, which owns
+ // animation_delegate.
+ CAAnimation* animation = [[window_ animationForKey:kFadeAnimationKey] copy];
+ [animation autorelease];
+ StatusBubbleAnimationDelegate* animation_delegate =
+ [[StatusBubbleAnimationDelegate alloc] initWithStatusBubble:this];
+ [animation_delegate autorelease];
+ [animation setDelegate:animation_delegate];
+ NSMutableDictionary* animation_dictionary =
+ [NSMutableDictionary dictionaryWithDictionary:[window_ animations]];
+ [animation_dictionary setObject:animation forKey:kFadeAnimationKey];
+ [window_ setAnimations:animation_dictionary];
+
+ Attach();
- offset_ = 0;
[view setCornerFlags:kRoundedTopRightCorner];
MouseMoved();
}
-void StatusBubbleMac::FadeIn() {
- [NSAnimationContext beginGrouping];
- [[NSAnimationContext currentContext] setDuration:kShowFadeDuration];
- [[window_ animator] setAlphaValue:1.0f];
- [NSAnimationContext endGrouping];
+void StatusBubbleMac::Attach() {
+ // If the parent window is offscreen when the child is added, the child will
+ // never be displayed, even when the parent moves on-screen. This method
+ // may be called several times during the process of creating or showing a
+ // status bubble to attach the bubble to its parent window.
+ if (![window_ parentWindow] && [parent_ isVisible])
+ [parent_ addChildWindow:window_ ordered:NSWindowAbove];
+}
+
+void StatusBubbleMac::AnimationDidStop(CAAnimation* animation, bool finished) {
+ DCHECK(state_ == kBubbleShowingFadeIn || state_ == kBubbleHidingFadeOut);
+
+ if (finished) {
+ // Because of the mechanism used to interrupt animations, this is never
+ // actually called with finished set to false. If animations ever become
+ // directly interruptible, the check will ensure that state_ remains
+ // properly synchronized.
+ if (state_ == kBubbleShowingFadeIn) {
+ DCHECK_EQ([[window_ animator] alphaValue], kBubbleOpacity);
+ state_ = kBubbleShown;
+ } else {
+ DCHECK_EQ([[window_ animator] alphaValue], 0.0);
+ state_ = kBubbleHidden;
+ }
+ }
}
-void StatusBubbleMac::FadeOut() {
+void StatusBubbleMac::SetState(StatusBubbleState state) {
+ if (state == state_)
+ return;
+
+ if ([delegate_ respondsToSelector:@selector(statusBubbleWillEnterState:)])
+ [delegate_ statusBubbleWillEnterState:state];
+
+ state_ = state;
+}
+
+void StatusBubbleMac::Fade(bool show) {
+ StatusBubbleState fade_state = kBubbleShowingFadeIn;
+ StatusBubbleState target_state = kBubbleShown;
+ NSTimeInterval full_duration = kShowFadeInDurationSeconds;
+ CGFloat opacity = kBubbleOpacity;
+
+ if (!show) {
+ fade_state = kBubbleHidingFadeOut;
+ target_state = kBubbleHidden;
+ full_duration = kHideFadeOutDurationSeconds;
+ opacity = 0.0;
+ }
+
+ DCHECK(state_ == fade_state || state_ == target_state);
+
+ if (state_ == target_state)
+ return;
+
+ Attach();
+
+ if (immediate_) {
+ [window_ setAlphaValue:opacity];
+ SetState(target_state);
+ return;
+ }
+
+ // If an incomplete transition has left the opacity somewhere between 0 and
+ // kBubbleOpacity, the fade rate is kept constant by shortening the duration.
+ NSTimeInterval duration =
+ full_duration *
+ fabs(opacity - [[window_ animator] alphaValue]) / kBubbleOpacity;
+
+ // 0.0 will not cancel an in-progress animation.
+ if (duration == 0.0)
+ duration = kMinimumTimeInterval;
+
+ // This will cancel an in-progress transition and replace it with this fade.
[NSAnimationContext beginGrouping];
- [[NSAnimationContext currentContext] setDuration:kHideFadeDuration];
- [[window_ animator] setAlphaValue:0.0f];
+ [[NSAnimationContext currentContext] setDuration:duration];
+ [[window_ animator] setAlphaValue:opacity];
[NSAnimationContext endGrouping];
}
+void StatusBubbleMac::StartTimer(int64 delay_ms) {
+ DCHECK(state_ == kBubbleShowingTimer || state_ == kBubbleHidingTimer);
+
+ if (immediate_) {
+ TimerFired();
+ return;
+ }
+
+ // There can only be one running timer.
+ CancelTimer();
+
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ timer_factory_.NewRunnableMethod(&StatusBubbleMac::TimerFired),
+ delay_ms);
+}
+
+void StatusBubbleMac::CancelTimer() {
+ if (!timer_factory_.empty())
+ timer_factory_.RevokeAll();
+}
+
+void StatusBubbleMac::TimerFired() {
+ DCHECK(state_ == kBubbleShowingTimer || state_ == kBubbleHidingTimer);
+
+ if (state_ == kBubbleShowingTimer) {
+ SetState(kBubbleShowingFadeIn);
+ Fade(true);
+ } else {
+ SetState(kBubbleHidingFadeOut);
+ Fade(false);
+ }
+}
+
+void StatusBubbleMac::StartShowing() {
+ Attach();
+
+ if (state_ == kBubbleHidden) {
+ // Arrange to begin fading in after a delay.
+ SetState(kBubbleShowingTimer);
+ StartTimer(kShowDelayMilliseconds);
+ } else if (state_ == kBubbleHidingFadeOut) {
+ // Cancel the fade-out in progress and replace it with a fade in.
+ SetState(kBubbleShowingFadeIn);
+ Fade(true);
+ } else if (state_ == kBubbleHidingTimer) {
+ // The bubble was already shown but was waiting to begin fading out. It's
+ // given a stay of execution.
+ SetState(kBubbleShown);
+ CancelTimer();
+ } else if (state_ == kBubbleShowingTimer) {
+ // The timer was already running but nothing was showing yet. Reaching
+ // this point means that there is a new request to show something. Start
+ // over again by resetting the timer, effectively invalidating the earlier
+ // request.
+ StartTimer(kShowDelayMilliseconds);
+ }
+
+ // If the state is kBubbleShown or kBubbleShowingFadeIn, leave everything
+ // alone.
+}
+
+void StatusBubbleMac::StartHiding() {
+ if (state_ == kBubbleShown) {
+ // Arrange to begin fading out after a delay.
+ SetState(kBubbleHidingTimer);
+ StartTimer(kHideDelayMilliseconds);
+ } else if (state_ == kBubbleShowingFadeIn) {
+ // Cancel the fade-in in progress and replace it with a fade out.
+ SetState(kBubbleHidingFadeOut);
+ Fade(false);
+ } else if (state_ == kBubbleShowingTimer) {
+ // The bubble was already hidden but was waiting to begin fading in. Too
+ // bad, it won't get the opportunity now.
+ SetState(kBubbleHidden);
+ CancelTimer();
+ }
+
+ // If the state is kBubbleHidden, kBubbleHidingFadeOut, or
+ // kBubbleHidingTimer, leave everything alone. The timer is not reset as
+ // with kBubbleShowingTimer in StartShowing() because a subsequent request
+ // to hide something while one is already in flight does not invalidate the
+ // earlier request.
+}
+
void StatusBubbleMac::UpdateSizeAndPosition() {
if (!window_)
return;
diff --git a/chrome/browser/cocoa/status_bubble_mac_unittest.mm b/chrome/browser/cocoa/status_bubble_mac_unittest.mm
index 8d5681b..4e30d2f 100644
--- a/chrome/browser/cocoa/status_bubble_mac_unittest.mm
+++ b/chrome/browser/cocoa/status_bubble_mac_unittest.mm
@@ -6,14 +6,16 @@
#include "base/scoped_nsobject.h"
#include "base/scoped_ptr.h"
+#import "chrome/browser/cocoa/bubble_view.h"
+#import "chrome/browser/cocoa/browser_test_helper.h"
#import "chrome/browser/cocoa/cocoa_test_helper.h"
-#include "chrome/browser/cocoa/status_bubble_mac.h"
+#import "chrome/browser/cocoa/status_bubble_mac.h"
#include "googleurl/src/gurl.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
#import "third_party/GTM/AppKit/GTMTheme.h"
-@interface StatusBubbleMacTestWindowDelegate : NSObject <GTMThemeDelegate>;
+@interface StatusBubbleMacTestWindowDelegate : NSObject <GTMThemeDelegate>
@end
@implementation StatusBubbleMacTestWindowDelegate
- (GTMTheme*)gtm_themeForWindow:(NSWindow*)window {
@@ -25,17 +27,47 @@
}
@end
+// The test delegate records all of the status bubble object's state
+// transitions.
+@interface StatusBubbleMacTestDelegate : NSObject {
+ @private
+ std::vector<StatusBubbleMac::StatusBubbleState> states_;
+}
+- (void)statusBubbleWillEnterState:(StatusBubbleMac::StatusBubbleState)state;
+@end
+@implementation StatusBubbleMacTestDelegate
+- (void)statusBubbleWillEnterState:(StatusBubbleMac::StatusBubbleState)state {
+ states_.push_back(state);
+}
+- (std::vector<StatusBubbleMac::StatusBubbleState>*)states {
+ return &states_;
+}
+@end
+
class StatusBubbleMacTest : public PlatformTest {
public:
StatusBubbleMacTest() {
NSWindow* window = cocoa_helper_.window();
- bubble_.reset(new StatusBubbleMac(window, nil));
+ EXPECT_TRUE(window);
+ delegate_.reset([[StatusBubbleMacTestDelegate alloc] init]);
+ EXPECT_TRUE(delegate_.get());
+ bubble_.reset(new StatusBubbleMac(window, delegate_));
EXPECT_TRUE(bubble_.get());
+
+ // Turn off delays and transitions for test mode. This doesn't just speed
+ // things along, it's actually required to get StatusBubbleMac to behave
+ // synchronously, because the tests here don't know how to wait for
+ // results. This allows these tests to be much more complete with a
+ // minimal loss of coverage and without any heinous rearchitecting.
+ bubble_->immediate_ = true;
+
EXPECT_FALSE(bubble_->window_); // lazily creates window
}
bool IsVisible() {
- return [bubble_->window_ isVisible] ? true: false;
+ if (![bubble_->window_ isVisible])
+ return false;
+ return [bubble_->window_ alphaValue] > 0.0;
}
NSString* GetText() {
return bubble_->status_text_;
@@ -43,13 +75,31 @@ class StatusBubbleMacTest : public PlatformTest {
NSString* GetURLText() {
return bubble_->url_text_;
}
+ NSString* GetBubbleViewText() {
+ BubbleView* bubbleView = [bubble_->window_ contentView];
+ return [bubbleView content];
+ }
NSWindow* GetWindow() {
return bubble_->window_;
}
NSWindow* GetParent() {
return bubble_->parent_;
}
+ StatusBubbleMac::StatusBubbleState GetState() {
+ return bubble_->state_;
+ }
+ void SetState(StatusBubbleMac::StatusBubbleState state) {
+ bubble_->SetState(state);
+ }
+ std::vector<StatusBubbleMac::StatusBubbleState>* States() {
+ return [delegate_ states];
+ }
+ StatusBubbleMac::StatusBubbleState StateAt(int index) {
+ return (*States())[index];
+ }
CocoaTestHelper cocoa_helper_; // Inits Cocoa, creates window, etc...
+ BrowserTestHelper browser_helper_;
+ scoped_nsobject<StatusBubbleMacTestDelegate> delegate_;
scoped_ptr<StatusBubbleMac> bubble_;
};
@@ -74,7 +124,6 @@ TEST_F(StatusBubbleMacTest, SetStatus) {
// Hide it
bubble_->SetStatus(L"");
EXPECT_FALSE(IsVisible());
- EXPECT_FALSE(GetText());
}
TEST_F(StatusBubbleMacTest, SetURL) {
@@ -106,6 +155,253 @@ TEST_F(StatusBubbleMacTest, Hides) {
EXPECT_FALSE(IsVisible());
}
+// Test the "main"/"backup" behavior in StatusBubbleMac::SetText().
+TEST_F(StatusBubbleMacTest, SetStatusAndURL) {
+ EXPECT_FALSE(IsVisible());
+ bubble_->SetStatus(L"Status");
+ EXPECT_TRUE(IsVisible());
+ EXPECT_TRUE([GetBubbleViewText() isEqualToString:@"Status"]);
+ bubble_->SetURL(GURL("http://www.nytimes.com/"), L"");
+ EXPECT_TRUE(IsVisible());
+ EXPECT_TRUE([GetBubbleViewText() isEqualToString:@"http://www.nytimes.com/"]);
+ bubble_->SetURL(GURL(), L"");
+ EXPECT_TRUE(IsVisible());
+ EXPECT_TRUE([GetBubbleViewText() isEqualToString:@"Status"]);
+ bubble_->SetStatus(L"");
+ EXPECT_FALSE(IsVisible());
+ bubble_->SetURL(GURL("http://www.nytimes.com/"), L"");
+ EXPECT_TRUE(IsVisible());
+ EXPECT_TRUE([GetBubbleViewText() isEqualToString:@"http://www.nytimes.com/"]);
+ bubble_->SetStatus(L"Status");
+ EXPECT_TRUE(IsVisible());
+ EXPECT_TRUE([GetBubbleViewText() isEqualToString:@"Status"]);
+ bubble_->SetStatus(L"");
+ EXPECT_TRUE(IsVisible());
+ EXPECT_TRUE([GetBubbleViewText() isEqualToString:@"http://www.nytimes.com/"]);
+ bubble_->SetURL(GURL(), L"");
+ EXPECT_FALSE(IsVisible());
+}
+
+// Test that the status bubble goes through the correct delay and fade states.
+// The delay and fade duration are simulated and not actually experienced
+// during the test because StatusBubbleMacTest sets immediate_ mode.
+TEST_F(StatusBubbleMacTest, StateTransitions) {
+ // First, some sanity
+
+ EXPECT_FALSE(IsVisible());
+ EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
+
+ States()->clear();
+ EXPECT_TRUE(States()->empty());
+
+ bubble_->SetStatus(L"");
+ EXPECT_FALSE(IsVisible());
+ EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
+ EXPECT_TRUE(States()->empty()); // no change from initial kBubbleHidden state
+
+ // Next, a few ordinary cases
+
+ // Test StartShowing from kBubbleHidden
+ bubble_->SetStatus(L"Status");
+ EXPECT_TRUE(IsVisible());
+ // Check GetState before checking States to make sure that all state
+ // transitions have been flushed to States.
+ EXPECT_EQ(StatusBubbleMac::kBubbleShown, GetState());
+ EXPECT_EQ(3u, States()->size());
+ EXPECT_EQ(StatusBubbleMac::kBubbleShowingTimer, StateAt(0));
+ EXPECT_EQ(StatusBubbleMac::kBubbleShowingFadeIn, StateAt(1));
+ EXPECT_EQ(StatusBubbleMac::kBubbleShown, StateAt(2));
+
+ // Test StartShowing from kBubbleShown with the same message
+ States()->clear();
+ bubble_->SetStatus(L"Status");
+ EXPECT_TRUE(IsVisible());
+ EXPECT_EQ(StatusBubbleMac::kBubbleShown, GetState());
+ EXPECT_TRUE(States()->empty());
+
+ // Test StartShowing from kBubbleShown with a different message
+ bubble_->SetStatus(L"New Status");
+ EXPECT_TRUE(IsVisible());
+ EXPECT_EQ(StatusBubbleMac::kBubbleShown, GetState());
+ EXPECT_TRUE(States()->empty());
+
+ // Test StartHiding from kBubbleShown
+ bubble_->SetStatus(L"");
+ EXPECT_FALSE(IsVisible());
+ // Check GetState before checking States to make sure that all state
+ // transitions have been flushed to States.
+ EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
+ EXPECT_EQ(3u, States()->size());
+ EXPECT_EQ(StatusBubbleMac::kBubbleHidingTimer, StateAt(0));
+ EXPECT_EQ(StatusBubbleMac::kBubbleHidingFadeOut, StateAt(1));
+ EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(2));
+
+ // Test StartHiding from kBubbleHidden
+ States()->clear();
+ bubble_->SetStatus(L"");
+ EXPECT_FALSE(IsVisible());
+ EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
+ EXPECT_TRUE(States()->empty());
+
+ // Now, the edge cases
+
+ // Test StartShowing from kBubbleShowingTimer
+ bubble_->SetStatus(L"Status");
+ SetState(StatusBubbleMac::kBubbleShowingTimer);
+ [GetWindow() setAlphaValue:0.0];
+ States()->clear();
+ EXPECT_TRUE(States()->empty());
+ bubble_->SetStatus(L"Status");
+ EXPECT_EQ(StatusBubbleMac::kBubbleShown, GetState());
+ EXPECT_EQ(2u, States()->size());
+ EXPECT_EQ(StatusBubbleMac::kBubbleShowingFadeIn, StateAt(0));
+ EXPECT_EQ(StatusBubbleMac::kBubbleShown, StateAt(1));
+
+ // Test StartShowing from kBubbleShowingFadeIn
+ bubble_->SetStatus(L"Status");
+ SetState(StatusBubbleMac::kBubbleShowingFadeIn);
+ [GetWindow() setAlphaValue:0.5];
+ States()->clear();
+ EXPECT_TRUE(States()->empty());
+ bubble_->SetStatus(L"Status");
+ // The actual state values can't be tested in immediate_ mode because
+ // the window wasn't actually fading in. Without immediate_ mode,
+ // expect kBubbleShown.
+ bubble_->SetStatus(L""); // Go back to a deterministic state.
+
+ // Test StartShowing from kBubbleHidingTimer
+ bubble_->SetStatus(L"");
+ SetState(StatusBubbleMac::kBubbleHidingTimer);
+ [GetWindow() setAlphaValue:1.0];
+ States()->clear();
+ EXPECT_TRUE(States()->empty());
+ bubble_->SetStatus(L"Status");
+ EXPECT_EQ(StatusBubbleMac::kBubbleShown, GetState());
+ EXPECT_EQ(1u, States()->size());
+ EXPECT_EQ(StatusBubbleMac::kBubbleShown, StateAt(0));
+
+ // Test StartShowing from kBubbleHidingFadeOut
+ bubble_->SetStatus(L"");
+ SetState(StatusBubbleMac::kBubbleHidingFadeOut);
+ [GetWindow() setAlphaValue:0.5];
+ States()->clear();
+ EXPECT_TRUE(States()->empty());
+ bubble_->SetStatus(L"Status");
+ EXPECT_EQ(StatusBubbleMac::kBubbleShown, GetState());
+ EXPECT_EQ(2u, States()->size());
+ EXPECT_EQ(StatusBubbleMac::kBubbleShowingFadeIn, StateAt(0));
+ EXPECT_EQ(StatusBubbleMac::kBubbleShown, StateAt(1));
+
+ // Test StartHiding from kBubbleShowingTimer
+ bubble_->SetStatus(L"Status");
+ SetState(StatusBubbleMac::kBubbleShowingTimer);
+ [GetWindow() setAlphaValue:0.0];
+ States()->clear();
+ EXPECT_TRUE(States()->empty());
+ bubble_->SetStatus(L"");
+ EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
+ EXPECT_EQ(1u, States()->size());
+ EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(0));
+
+ // Test StartHiding from kBubbleShowingFadeIn
+ bubble_->SetStatus(L"Status");
+ SetState(StatusBubbleMac::kBubbleShowingFadeIn);
+ [GetWindow() setAlphaValue:0.5];
+ States()->clear();
+ EXPECT_TRUE(States()->empty());
+ bubble_->SetStatus(L"");
+ EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
+ EXPECT_EQ(2u, States()->size());
+ EXPECT_EQ(StatusBubbleMac::kBubbleHidingFadeOut, StateAt(0));
+ EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(1));
+
+ // Test StartHiding from kBubbleHidingTimer
+ bubble_->SetStatus(L"");
+ SetState(StatusBubbleMac::kBubbleHidingTimer);
+ [GetWindow() setAlphaValue:1.0];
+ States()->clear();
+ EXPECT_TRUE(States()->empty());
+ bubble_->SetStatus(L"");
+ // The actual state values can't be tested in immediate_ mode because
+ // the timer wasn't actually running. Without immediate_ mode, expect
+ // kBubbleHidingFadeOut and kBubbleHidden.
+ bubble_->SetStatus(L"Status"); // Go back to a deterministic state.
+
+ // Test StartHiding from kBubbleHidingFadeOut
+ bubble_->SetStatus(L"");
+ SetState(StatusBubbleMac::kBubbleHidingFadeOut);
+ [GetWindow() setAlphaValue:0.5];
+ States()->clear();
+ EXPECT_TRUE(States()->empty());
+ bubble_->SetStatus(L"");
+ // The actual state values can't be tested in immediate_ mode because
+ // the window wasn't actually fading out. Without immediate_ mode, expect
+ // kBubbleHidden.
+ bubble_->SetStatus(L"Status"); // Go back to a deterministic state.
+
+ // Test Hide from kBubbleHidden
+ bubble_->SetStatus(L"");
+ EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
+ States()->clear();
+ EXPECT_TRUE(States()->empty());
+ bubble_->Hide();
+ EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
+ EXPECT_TRUE(States()->empty());
+
+ // Test Hide from kBubbleShowingTimer
+ bubble_->SetStatus(L"Status");
+ SetState(StatusBubbleMac::kBubbleShowingTimer);
+ [GetWindow() setAlphaValue:0.0];
+ States()->clear();
+ EXPECT_TRUE(States()->empty());
+ bubble_->Hide();
+ EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
+ EXPECT_EQ(1u, States()->size());
+ EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(0));
+
+ // Test Hide from kBubbleShowingFadeIn
+ bubble_->SetStatus(L"Status");
+ SetState(StatusBubbleMac::kBubbleShowingFadeIn);
+ [GetWindow() setAlphaValue:0.5];
+ States()->clear();
+ EXPECT_TRUE(States()->empty());
+ bubble_->Hide();
+ EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
+ EXPECT_EQ(2u, States()->size());
+ EXPECT_EQ(StatusBubbleMac::kBubbleHidingFadeOut, StateAt(0));
+ EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(1));
+
+ // Test Hide from kBubbleShown
+ bubble_->SetStatus(L"Status");
+ States()->clear();
+ EXPECT_TRUE(States()->empty());
+ bubble_->Hide();
+ EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
+ EXPECT_EQ(1u, States()->size());
+ EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(0));
+
+ // Test Hide from kBubbleHidingTimer
+ bubble_->SetStatus(L"Status");
+ SetState(StatusBubbleMac::kBubbleHidingTimer);
+ States()->clear();
+ EXPECT_TRUE(States()->empty());
+ bubble_->Hide();
+ EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
+ EXPECT_EQ(1u, States()->size());
+ EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(0));
+
+ // Test Hide from kBubbleHidingFadeOut
+ bubble_->SetStatus(L"Status");
+ SetState(StatusBubbleMac::kBubbleHidingFadeOut);
+ [GetWindow() setAlphaValue:0.5];
+ States()->clear();
+ EXPECT_TRUE(States()->empty());
+ bubble_->Hide();
+ EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
+ EXPECT_EQ(1u, States()->size());
+ EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(0));
+}
+
TEST_F(StatusBubbleMacTest, MouseMove) {
// TODO(pinkerton): Not sure what to do here since it relies on
// [NSEvent currentEvent] and the current mouse location.