blob: 21c047bd9c08f1191d40e4803031e6733440ffbb [file] [log] [blame]
// 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