Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / ui / views / cocoa / bridged_native_widget_unittest.mm
index 14a7f9c..eb44b9f 100644 (file)
@@ -6,7 +6,12 @@
 
 #import <Cocoa/Cocoa.h>
 
+#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<base::RunLoop> runLoop_;
+  base::scoped_nsobject<NSWindow> 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<views::View> view_;
   scoped_ptr<BridgedNativeWidget> 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<NSWindow> 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<NativeWidgetMacNotificationWaiter> 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<NativeWidgetMacNotificationWaiter> 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<NSWindow> 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<BridgedNativeWidgetTestFullScreenWindow>(
+          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