X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=src%2Fui%2Fviews%2Fcocoa%2Fbridged_native_widget_unittest.mm;h=eb44b9f758b97f2f0c93dcc55279c8952beeb9cc;hb=1afa4dd80ef85af7c90efaea6959db1d92330844;hp=14a7f9c3c0bfc17fe35a420705f2ce6209b2426a;hpb=90762837333c13ccf56f2ad88e4481fc71e8d281;p=platform%2Fframework%2Fweb%2Fcrosswalk.git diff --git a/src/ui/views/cocoa/bridged_native_widget_unittest.mm b/src/ui/views/cocoa/bridged_native_widget_unittest.mm index 14a7f9c..eb44b9f 100644 --- a/src/ui/views/cocoa/bridged_native_widget_unittest.mm +++ b/src/ui/views/cocoa/bridged_native_widget_unittest.mm @@ -6,7 +6,12 @@ #import +#import "base/mac/foundation_util.h" +#import "base/mac/mac_util.h" +#import "base/mac/sdk_forward_declarations.h" #include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" #include "base/strings/sys_string_conversions.h" #include "base/strings/utf_string_conversions.h" #import "testing/gtest_mac.h" @@ -16,6 +21,7 @@ #include "ui/views/ime/input_method.h" #include "ui/views/view.h" #include "ui/views/widget/native_widget_mac.h" +#include "ui/views/widget/root_view.h" #include "ui/views/widget/widget.h" #include "ui/views/widget/widget_observer.h" @@ -37,6 +43,112 @@ NSRange EmptyRange() { } // namespace +@interface NativeWidgetMacNotificationWaiter : NSObject { + @private + scoped_ptr runLoop_; + base::scoped_nsobject window_; + int enterCount_; + int exitCount_; + int targetEnterCount_; + int targetExitCount_; +} + +@property(readonly, nonatomic) int enterCount; +@property(readonly, nonatomic) int exitCount; + +// Initialize for the given window and start tracking notifications. +- (id)initWithWindow:(NSWindow*)window; + +// Keep spinning a run loop until the enter and exit counts match. +- (void)waitForEnterCount:(int)enterCount exitCount:(int)exitCount; + +// private: +// Exit the RunLoop if there is one and the counts being tracked match. +- (void)maybeQuitForChangedArg:(int*)changedArg; + +- (void)onEnter:(NSNotification*)notification; +- (void)onExit:(NSNotification*)notification; + +@end + +@implementation NativeWidgetMacNotificationWaiter + +@synthesize enterCount = enterCount_; +@synthesize exitCount = exitCount_; + +- (id)initWithWindow:(NSWindow*)window { + if ((self = [super init])) { + window_.reset([window retain]); + NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter]; + [defaultCenter addObserver:self + selector:@selector(onEnter:) + name:NSWindowDidEnterFullScreenNotification + object:window]; + [defaultCenter addObserver:self + selector:@selector(onExit:) + name:NSWindowDidExitFullScreenNotification + object:window]; + } + return self; +} + +- (void)dealloc { + DCHECK(!runLoop_); + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [super dealloc]; +} + +- (void)waitForEnterCount:(int)enterCount exitCount:(int)exitCount { + if (enterCount_ >= enterCount && exitCount_ >= exitCount) + return; + + targetEnterCount_ = enterCount; + targetExitCount_ = exitCount; + runLoop_.reset(new base::RunLoop); + runLoop_->Run(); + runLoop_.reset(); +} + +- (void)maybeQuitForChangedArg:(int*)changedArg { + ++*changedArg; + if (!runLoop_) + return; + + if (enterCount_ >= targetEnterCount_ && exitCount_ >= targetExitCount_) + runLoop_->Quit(); +} + +- (void)onEnter:(NSNotification*)notification { + [self maybeQuitForChangedArg:&enterCount_]; +} + +- (void)onExit:(NSNotification*)notification { + [self maybeQuitForChangedArg:&exitCount_]; +} + +@end + +// Class to override -[NSWindow toggleFullScreen:] to a no-op. This simulates +// NSWindow's behavior when attempting to toggle fullscreen state again, when +// the last attempt failed but Cocoa has not yet sent +// windowDidFailToEnterFullScreen:. +@interface BridgedNativeWidgetTestFullScreenWindow : NSWindow { + @private + int ignoredToggleFullScreenCount_; +} +@property(readonly, nonatomic) int ignoredToggleFullScreenCount; +@end + +@implementation BridgedNativeWidgetTestFullScreenWindow + +@synthesize ignoredToggleFullScreenCount = ignoredToggleFullScreenCount_; + +- (void)toggleFullScreen:(id)sender { + ++ignoredToggleFullScreenCount_; +} + +@end + namespace views { namespace test { @@ -51,7 +163,7 @@ class MockNativeWidgetMac : public NativeWidgetMac { } // internal::NativeWidgetPrivate: - virtual void InitNativeWidget(const Widget::InitParams& params) OVERRIDE { + virtual void InitNativeWidget(const Widget::InitParams& params) override { ownership_ = params.ownership; // Usually the bridge gets initialized here. It is skipped to run extra @@ -59,7 +171,7 @@ class MockNativeWidgetMac : public NativeWidgetMac { delegate()->OnNativeWidgetCreated(true); } - virtual void ReorderNativeViews() OVERRIDE { + virtual void ReorderNativeViews() override { // Called via Widget::Init to set the content view. No-op in these tests. } @@ -80,7 +192,7 @@ class BridgedNativeWidgetTestBase : public ui::CocoaTest { } // Overridden from testing::Test: - virtual void SetUp() OVERRIDE { + virtual void SetUp() override { ui::CocoaTest::SetUp(); Widget::InitParams params; @@ -109,11 +221,10 @@ class BridgedNativeWidgetTest : public BridgedNativeWidgetTestBase { std::string GetText(); // testing::Test: - virtual void SetUp() OVERRIDE; - virtual void TearDown() OVERRIDE; + virtual void SetUp() override; + virtual void TearDown() override; protected: - // TODO(tapted): Make this a EventCountView from widget_unittest.cc. scoped_ptr view_; scoped_ptr bridge_; BridgedContentView* ns_view_; // Weak. Owned by bridge_. @@ -145,9 +256,12 @@ std::string BridgedNativeWidgetTest::GetText() { void BridgedNativeWidgetTest::SetUp() { BridgedNativeWidgetTestBase::SetUp(); - view_.reset(new views::View); + view_.reset(new views::internal::RootView(widget_.get())); base::scoped_nsobject window([test_window() retain]); + // BridgedNativeWidget expects to be initialized with a hidden (deferred) + // window. + [window orderOut:nil]; EXPECT_FALSE([window delegate]); bridge()->Init(window, Widget::InitParams()); @@ -156,6 +270,8 @@ void BridgedNativeWidgetTest::SetUp() { bridge()->SetRootView(view_.get()); ns_view_ = bridge()->ns_view(); + // Pretend it has been shown via NativeWidgetMac::Show(). + [window orderFront:nil]; [test_window() makePretendKeyWindowAndSetFirstResponder:bridge()->ns_view()]; } @@ -246,7 +362,7 @@ TEST_F(BridgedNativeWidgetInitTest, ParentWindowNotNativeWidgetMac) { [[NSWindow alloc] initWithContentRect:NSMakeRect(50, 50, 400, 300) styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered - defer:NO]); + defer:YES]); [child_window setReleasedWhenClosed:NO]; // Owned by scoped_nsobject. EXPECT_FALSE([child_window parentWindow]); @@ -415,5 +531,176 @@ TEST_F(BridgedNativeWidgetTest, TextInput_DeleteForward) { EXPECT_EQ_RANGE(NSMakeRange(0, 0), [ns_view_ selectedRange]); } +// Tests for correct fullscreen tracking, regardless of whether it is initiated +// by the Widget code or elsewhere (e.g. by the user). +TEST_F(BridgedNativeWidgetTest, FullscreenSynchronousState) { + EXPECT_FALSE(widget_->IsFullscreen()); + if (base::mac::IsOSSnowLeopard()) + return; + + // Allow user-initiated fullscreen changes on the Window. + [test_window() + setCollectionBehavior:[test_window() collectionBehavior] | + NSWindowCollectionBehaviorFullScreenPrimary]; + + base::scoped_nsobject waiter( + [[NativeWidgetMacNotificationWaiter alloc] initWithWindow:test_window()]); + const gfx::Rect restored_bounds = widget_->GetRestoredBounds(); + + // Simulate a user-initiated fullscreen. Note trying to to this again before + // spinning a runloop will cause Cocoa to emit text to stdio and ignore it. + [test_window() toggleFullScreen:nil]; + EXPECT_TRUE(widget_->IsFullscreen()); + EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds()); + + // Note there's now an animation running. While that's happening, toggling the + // state should work as expected, but do "nothing". + widget_->SetFullscreen(false); + EXPECT_FALSE(widget_->IsFullscreen()); + EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds()); + widget_->SetFullscreen(false); // Same request - should no-op. + EXPECT_FALSE(widget_->IsFullscreen()); + EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds()); + + widget_->SetFullscreen(true); + EXPECT_TRUE(widget_->IsFullscreen()); + EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds()); + + // Always finish out of fullscreen. Otherwise there are 4 NSWindow objects + // that Cocoa creates which don't close themselves and will be seen by the Mac + // test harness on teardown. Note that the test harness will be waiting until + // all animations complete, since these temporary animation windows will not + // be removed from the window list until they do. + widget_->SetFullscreen(false); + EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds()); + + // Now we must wait for the notifications. Since, if the widget is torn down, + // the NSWindowDelegate is removed, and the pending request to take out of + // fullscreen is lost. Since a message loop has not yet spun up in this test + // we can reliably say there will be one enter and one exit, despite all the + // toggling above. + base::MessageLoopForUI message_loop; + [waiter waitForEnterCount:1 exitCount:1]; + EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds()); +} + +// Test fullscreen without overlapping calls and without changing collection +// behavior on the test window. +TEST_F(BridgedNativeWidgetTest, FullscreenEnterAndExit) { + base::MessageLoopForUI message_loop; + base::scoped_nsobject waiter( + [[NativeWidgetMacNotificationWaiter alloc] initWithWindow:test_window()]); + + EXPECT_FALSE(widget_->IsFullscreen()); + const gfx::Rect restored_bounds = widget_->GetRestoredBounds(); + EXPECT_FALSE(restored_bounds.IsEmpty()); + + // Ensure this works without having to change collection behavior as for the + // test above. + widget_->SetFullscreen(true); + if (base::mac::IsOSSnowLeopard()) { + // On Snow Leopard, SetFullscreen() isn't implemented. But shouldn't crash. + EXPECT_FALSE(widget_->IsFullscreen()); + return; + } + + EXPECT_TRUE(widget_->IsFullscreen()); + EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds()); + + // Should be zero until the runloop spins. + EXPECT_EQ(0, [waiter enterCount]); + [waiter waitForEnterCount:1 exitCount:0]; + + // Verify it hasn't exceeded. + EXPECT_EQ(1, [waiter enterCount]); + EXPECT_EQ(0, [waiter exitCount]); + EXPECT_TRUE(widget_->IsFullscreen()); + EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds()); + + widget_->SetFullscreen(false); + EXPECT_FALSE(widget_->IsFullscreen()); + EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds()); + + [waiter waitForEnterCount:1 exitCount:1]; + EXPECT_EQ(1, [waiter enterCount]); + EXPECT_EQ(1, [waiter exitCount]); + EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds()); +} + +typedef BridgedNativeWidgetTestBase BridgedNativeWidgetSimulateFullscreenTest; + +// Simulate the notifications that AppKit would send out if a fullscreen +// operation begins, and then fails and must abort. This notification sequence +// was determined by posting delayed tasks to toggle fullscreen state and then +// mashing Ctrl+Left/Right to keep OSX in a transition between Spaces to cause +// the fullscreen transition to fail. +TEST_F(BridgedNativeWidgetSimulateFullscreenTest, FailToEnterAndExit) { + if (base::mac::IsOSSnowLeopard()) + return; + + base::scoped_nsobject owned_window( + [[BridgedNativeWidgetTestFullScreenWindow alloc] + initWithContentRect:NSMakeRect(50, 50, 400, 300) + styleMask:NSBorderlessWindowMask + backing:NSBackingStoreBuffered + defer:YES]); + [owned_window setReleasedWhenClosed:NO]; // Owned by scoped_nsobject. + bridge()->Init(owned_window, Widget::InitParams()); // Transfers ownership. + + BridgedNativeWidgetTestFullScreenWindow* window = + base::mac::ObjCCastStrict( + widget_->GetNativeWindow()); + widget_->Show(); + + NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; + + EXPECT_FALSE(bridge()->target_fullscreen_state()); + + // Simulate an initial toggleFullScreen: (user- or Widget-initiated). + [center postNotificationName:NSWindowWillEnterFullScreenNotification + object:window]; + + // On a failure, Cocoa starts by sending an unexpected *exit* fullscreen, and + // BridgedNativeWidget will think it's just a delayed transition and try to go + // back into fullscreen but get ignored by Cocoa. + EXPECT_EQ(0, [window ignoredToggleFullScreenCount]); + EXPECT_TRUE(bridge()->target_fullscreen_state()); + [center postNotificationName:NSWindowDidExitFullScreenNotification + object:window]; + EXPECT_EQ(1, [window ignoredToggleFullScreenCount]); + EXPECT_FALSE(bridge()->target_fullscreen_state()); + + // Cocoa follows up with a failure message sent to the NSWindowDelegate (there + // is no equivalent notification for failure). Called via id so that this + // compiles on 10.6. + id window_delegate = [window delegate]; + [window_delegate windowDidFailToEnterFullScreen:window]; + EXPECT_FALSE(bridge()->target_fullscreen_state()); + + // Now perform a successful fullscreen operation. + [center postNotificationName:NSWindowWillEnterFullScreenNotification + object:window]; + EXPECT_TRUE(bridge()->target_fullscreen_state()); + [center postNotificationName:NSWindowDidEnterFullScreenNotification + object:window]; + EXPECT_TRUE(bridge()->target_fullscreen_state()); + + // And try to get out. + [center postNotificationName:NSWindowWillExitFullScreenNotification + object:window]; + EXPECT_FALSE(bridge()->target_fullscreen_state()); + + // On a failure, Cocoa sends a failure message, but then just dumps the window + // out of fullscreen anyway (in that order). + [window_delegate windowDidFailToExitFullScreen:window]; + EXPECT_FALSE(bridge()->target_fullscreen_state()); + [center postNotificationName:NSWindowDidExitFullScreenNotification + object:window]; + EXPECT_EQ(1, [window ignoredToggleFullScreenCount]); // No change. + EXPECT_FALSE(bridge()->target_fullscreen_state()); + + widget_->CloseNow(); +} + } // namespace test } // namespace views