| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h" |
| |
| #import <Cocoa/Cocoa.h> |
| |
| #import "base/mac/mac_util.h" |
| #include "base/macros.h" |
| #include "base/run_loop.h" |
| #include "ui/base/hit_test.h" |
| #import "ui/base/test/nswindow_fullscreen_notification_waiter.h" |
| #include "ui/base/test/ui_controls.h" |
| #import "ui/base/test/windowed_nsnotification_observer.h" |
| #import "ui/events/test/cocoa_test_event_utils.h" |
| #include "ui/views/cocoa/native_widget_mac_ns_window_host.h" |
| #include "ui/views/test/widget_test.h" |
| #include "ui/views/widget/native_widget_mac.h" |
| #include "ui/views/window/native_frame_view.h" |
| |
| namespace views { |
| namespace test { |
| namespace { |
| |
| // Provide a resizable Widget by default. Starting in 10.11, OSX doesn't |
| // correctly restore the window size when coming out of fullscreen if the window |
| // is not user-sizable. |
| class ResizableDelegateView : public WidgetDelegateView { |
| public: |
| ResizableDelegateView() = default; |
| |
| // WidgetDelgate: |
| bool CanResize() const override { return true; } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ResizableDelegateView); |
| }; |
| |
| } // namespace |
| |
| class BridgedNativeWidgetUITest : public WidgetTest { |
| public: |
| BridgedNativeWidgetUITest() = default; |
| |
| // testing::Test: |
| void SetUp() override { |
| SetUpForInteractiveTests(); |
| WidgetTest::SetUp(); |
| |
| Widget::InitParams init_params = |
| CreateParams(Widget::InitParams::TYPE_WINDOW); |
| init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; |
| init_params.bounds = gfx::Rect(100, 100, 300, 200); |
| init_params.delegate = new ResizableDelegateView; |
| widget_ = std::make_unique<Widget>(); |
| widget_->Init(std::move(init_params)); |
| } |
| |
| void TearDown() override { |
| // Ensures any compositor is removed before ViewsTestBase tears down the |
| // ContextFactory. |
| widget_.reset(); |
| WidgetTest::TearDown(); |
| } |
| |
| NSWindow* test_window() { |
| return widget_->GetNativeWindow().GetNativeNSWindow(); |
| } |
| |
| protected: |
| std::unique_ptr<Widget> widget_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(BridgedNativeWidgetUITest); |
| }; |
| |
| // Tests for correct fullscreen tracking, regardless of whether it is initiated |
| // by the Widget code or elsewhere (e.g. by the user). |
| TEST_F(BridgedNativeWidgetUITest, FullscreenSynchronousState) { |
| EXPECT_FALSE(widget_->IsFullscreen()); |
| |
| // Allow user-initiated fullscreen changes on the Window. |
| [test_window() |
| setCollectionBehavior:[test_window() collectionBehavior] | |
| NSWindowCollectionBehaviorFullScreenPrimary]; |
| |
| base::scoped_nsobject<NSWindowFullscreenNotificationWaiter> waiter( |
| [[NSWindowFullscreenNotificationWaiter alloc] |
| initWithWindow:test_window()]); |
| const gfx::Rect restored_bounds = widget_->GetRestoredBounds(); |
| |
| // First show the widget. A user shouldn't be able to initiate fullscreen |
| // unless the window is visible in the first place. |
| widget_->Show(); |
| |
| // 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. |
| [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(BridgedNativeWidgetUITest, FullscreenEnterAndExit) { |
| base::scoped_nsobject<NSWindowFullscreenNotificationWaiter> waiter( |
| [[NSWindowFullscreenNotificationWaiter 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. Also check that making a hidden widget fullscreen shows it. |
| EXPECT_FALSE(widget_->IsVisible()); |
| widget_->SetFullscreen(true); |
| EXPECT_TRUE(widget_->IsVisible()); |
| |
| 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()); |
| } |
| |
| // Test that Widget::Restore exits fullscreen. |
| TEST_F(BridgedNativeWidgetUITest, FullscreenRestore) { |
| base::scoped_nsobject<NSWindowFullscreenNotificationWaiter> waiter( |
| [[NSWindowFullscreenNotificationWaiter alloc] |
| initWithWindow:test_window()]); |
| |
| EXPECT_FALSE(widget_->IsFullscreen()); |
| const gfx::Rect restored_bounds = widget_->GetRestoredBounds(); |
| EXPECT_FALSE(restored_bounds.IsEmpty()); |
| |
| widget_->SetFullscreen(true); |
| EXPECT_TRUE(widget_->IsFullscreen()); |
| [waiter waitForEnterCount:1 exitCount:0]; |
| |
| widget_->Restore(); |
| EXPECT_FALSE(widget_->IsFullscreen()); |
| [waiter waitForEnterCount:1 exitCount:1]; |
| EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds()); |
| } |
| |
| } // namespace test |
| } // namespace views |