blob: 79c61e6c6799053e002c623c1523be5232919275 [file] [log] [blame]
// Copyright 2015 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.
#include "components/exo/shell_surface.h"
#include <vector>
#include "ash/accessibility/accessibility_delegate.h"
#include "ash/constants/ash_constants.h"
#include "ash/frame/non_client_frame_view_ash.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/test/shell_test_api.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/shell.h"
#include "ash/test/test_widget_builder.h"
#include "ash/wm/resize_shadow.h"
#include "ash/wm/resize_shadow_controller.h"
#include "ash/wm/window_state.h"
#include "ash/wm/wm_event.h"
#include "ash/wm/workspace_controller_test_api.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "components/app_restore/window_properties.h"
#include "components/exo/buffer.h"
#include "components/exo/permission.h"
#include "components/exo/shell_surface_util.h"
#include "components/exo/sub_surface.h"
#include "components/exo/surface.h"
#include "components/exo/surface_test_util.h"
#include "components/exo/test/exo_test_base.h"
#include "components/exo/test/exo_test_helper.h"
#include "components/exo/test/shell_surface_builder.h"
#include "components/exo/window_properties.h"
#include "components/exo/wm_helper.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/capture_client.h"
#include "ui/aura/window.h"
#include "ui/aura/window_delegate.h"
#include "ui/base/hit_test.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/layer.h"
#include "ui/compositor_extra/shadow.h"
#include "ui/display/display.h"
#include "ui/display/manager/display_manager.h"
#include "ui/display/screen.h"
#include "ui/display/test/display_manager_test_api.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/shadow_controller.h"
#include "ui/wm/core/shadow_types.h"
#include "ui/wm/core/window_util.h"
namespace exo {
using ShellSurfaceTest = test::ExoTestBase;
bool HasBackdrop() {
ash::WorkspaceController* wc = ash::ShellTestApi().workspace_controller();
return !!ash::WorkspaceControllerTestApi(wc).GetBackdropWindow();
}
uint32_t ConfigureFullscreen(uint32_t serial,
const gfx::Rect& bounds,
chromeos::WindowStateType state_type,
bool resizing,
bool activated,
const gfx::Vector2d& origin_offset) {
EXPECT_EQ(chromeos::WindowStateType::kFullscreen, state_type);
return serial;
}
std::unique_ptr<ShellSurface> CreatePopupShellSurface(
Surface* popup_surface,
ShellSurface* parent,
const gfx::Point& origin) {
auto popup_shell_surface = std::make_unique<ShellSurface>(popup_surface);
popup_shell_surface->DisableMovement();
popup_shell_surface->SetPopup();
popup_shell_surface->SetParent(parent);
popup_shell_surface->SetOrigin(origin);
return popup_shell_surface;
}
std::unique_ptr<ShellSurface> CreateX11TransientShellSurface(
ShellSurface* parent,
const gfx::Size& size,
const gfx::Point& origin) {
return test::ShellSurfaceBuilder(size)
.SetParent(parent)
.SetOrigin(origin)
.BuildShellSurface();
}
TEST_F(ShellSurfaceTest, AcknowledgeConfigure) {
gfx::Size buffer_size(32, 32);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
surface->Attach(buffer.get());
surface->Commit();
gfx::Point origin(100, 100);
shell_surface->GetWidget()->SetBounds(gfx::Rect(origin, buffer_size));
EXPECT_EQ(origin.ToString(),
surface->window()->GetBoundsInRootWindow().origin().ToString());
const uint32_t kSerial = 1;
shell_surface->set_configure_callback(
base::BindRepeating(&ConfigureFullscreen, kSerial));
shell_surface->SetFullscreen(true);
// Surface origin should not change until configure request is acknowledged.
EXPECT_EQ(origin.ToString(),
surface->window()->GetBoundsInRootWindow().origin().ToString());
// Compositor should be locked until configure request is acknowledged.
ui::Compositor* compositor =
shell_surface->GetWidget()->GetNativeWindow()->layer()->GetCompositor();
EXPECT_TRUE(compositor->IsLocked());
shell_surface->AcknowledgeConfigure(kSerial);
std::unique_ptr<Buffer> fullscreen_buffer(new Buffer(
exo_test_helper()->CreateGpuMemoryBuffer(GetContext()->bounds().size())));
surface->Attach(fullscreen_buffer.get());
surface->Commit();
EXPECT_EQ(gfx::Point().ToString(),
surface->window()->GetBoundsInRootWindow().origin().ToString());
EXPECT_FALSE(compositor->IsLocked());
}
TEST_F(ShellSurfaceTest, SetParent) {
gfx::Size buffer_size(256, 256);
std::unique_ptr<Buffer> parent_buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> parent_surface(new Surface);
std::unique_ptr<ShellSurface> parent_shell_surface(
new ShellSurface(parent_surface.get()));
parent_surface->Attach(parent_buffer.get());
parent_surface->Commit();
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
shell_surface->SetParent(parent_shell_surface.get());
surface->Attach(buffer.get());
surface->Commit();
EXPECT_EQ(
parent_shell_surface->GetWidget()->GetNativeWindow(),
wm::GetTransientParent(shell_surface->GetWidget()->GetNativeWindow()));
// Use OnSetParent to move shell surface to 10, 10.
gfx::Point parent_origin =
parent_shell_surface->GetWidget()->GetWindowBoundsInScreen().origin();
shell_surface->OnSetParent(
parent_surface.get(),
gfx::PointAtOffsetFromOrigin(gfx::Point(10, 10) - parent_origin));
EXPECT_EQ(gfx::Rect(10, 10, 256, 256),
shell_surface->GetWidget()->GetWindowBoundsInScreen());
EXPECT_FALSE(shell_surface->CanActivate());
}
TEST_F(ShellSurfaceTest, DeleteShellSurfaceWithTransientChildren) {
gfx::Size buffer_size(256, 256);
auto parent_shell_surface =
test::ShellSurfaceBuilder(buffer_size).BuildShellSurface();
auto child1_shell_surface = test::ShellSurfaceBuilder(buffer_size)
.SetParent(parent_shell_surface.get())
.BuildShellSurface();
auto child2_shell_surface = test::ShellSurfaceBuilder(buffer_size)
.SetParent(parent_shell_surface.get())
.BuildShellSurface();
auto child3_shell_surface = test::ShellSurfaceBuilder(buffer_size)
.SetParent(parent_shell_surface.get())
.BuildShellSurface();
EXPECT_EQ(parent_shell_surface->GetWidget()->GetNativeWindow(),
wm::GetTransientParent(
child1_shell_surface->GetWidget()->GetNativeWindow()));
EXPECT_EQ(parent_shell_surface->GetWidget()->GetNativeWindow(),
wm::GetTransientParent(
child2_shell_surface->GetWidget()->GetNativeWindow()));
EXPECT_EQ(parent_shell_surface->GetWidget()->GetNativeWindow(),
wm::GetTransientParent(
child3_shell_surface->GetWidget()->GetNativeWindow()));
parent_shell_surface.reset();
}
TEST_F(ShellSurfaceTest, Maximize) {
gfx::Size buffer_size(256, 256);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
surface->Attach(buffer.get());
surface->Commit();
EXPECT_FALSE(HasBackdrop());
shell_surface->Maximize();
EXPECT_FALSE(HasBackdrop());
surface->Commit();
EXPECT_FALSE(HasBackdrop());
EXPECT_EQ(GetContext()->bounds().width(),
shell_surface->GetWidget()->GetWindowBoundsInScreen().width());
EXPECT_TRUE(shell_surface->GetWidget()->IsMaximized());
// Toggle maximize.
ash::WMEvent maximize_event(ash::WM_EVENT_TOGGLE_MAXIMIZE);
aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
ash::WindowState::Get(window)->OnWMEvent(&maximize_event);
EXPECT_FALSE(shell_surface->GetWidget()->IsMaximized());
EXPECT_FALSE(HasBackdrop());
ash::WindowState::Get(window)->OnWMEvent(&maximize_event);
EXPECT_TRUE(shell_surface->GetWidget()->IsMaximized());
EXPECT_FALSE(HasBackdrop());
}
TEST_F(ShellSurfaceTest, CanMaximizeResizableWindow) {
gfx::Size buffer_size(400, 300);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
surface->Attach(buffer.get());
surface->Commit();
// Make sure we've created a resizable window.
EXPECT_TRUE(shell_surface->CanResize());
// Assert: Resizable windows can be maximized.
EXPECT_TRUE(shell_surface->CanMaximize());
}
TEST_F(ShellSurfaceTest, CannotMaximizeNonResizableWindow) {
gfx::Size buffer_size(400, 300);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
surface->Attach(buffer.get());
shell_surface->SetMinimumSize(buffer_size);
shell_surface->SetMaximumSize(buffer_size);
surface->Commit();
// Make sure we've created a non-resizable window.
EXPECT_FALSE(shell_surface->CanResize());
// Assert: Non-resizable windows cannot be maximized.
EXPECT_FALSE(shell_surface->CanMaximize());
}
TEST_F(ShellSurfaceTest, MaximizeFromFullscreen) {
std::unique_ptr<ShellSurface> shell_surface =
test::ShellSurfaceBuilder({256, 256})
.SetMaximumSize(gfx::Size(10, 10))
.BuildShellSurface();
// Act: Maximize after fullscreen
shell_surface->root_surface()->Commit();
shell_surface->SetFullscreen(true);
shell_surface->root_surface()->Commit();
shell_surface->Maximize();
shell_surface->root_surface()->Commit();
// Assert: Window should stay fullscreen.
EXPECT_TRUE(shell_surface->GetWidget()->IsFullscreen());
}
TEST_F(ShellSurfaceTest, MaximizeExitsFullscreen) {
std::unique_ptr<ShellSurface> shell_surface =
test::ShellSurfaceBuilder({256, 256})
.SetMaximumSize(gfx::Size(10, 10))
.BuildShellSurface();
// Act: Set window property kRestoreOrMaximizeExitsFullscreen
// then maximize after fullscreen
shell_surface->root_surface()->Commit();
shell_surface->GetWidget()->GetNativeWindow()->SetProperty(
kRestoreOrMaximizeExitsFullscreen, true);
shell_surface->SetFullscreen(true);
shell_surface->root_surface()->Commit();
shell_surface->Maximize();
shell_surface->root_surface()->Commit();
// Assert: Window should exit fullscreen and be maximized.
EXPECT_TRUE(shell_surface->GetWidget()->GetNativeWindow()->GetProperty(
kRestoreOrMaximizeExitsFullscreen));
EXPECT_TRUE(shell_surface->GetWidget()->IsMaximized());
}
TEST_F(ShellSurfaceTest, Minimize) {
gfx::Size buffer_size(256, 256);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
EXPECT_TRUE(shell_surface->CanMinimize());
// Minimizing can be performed before the surface is committed, but
// widget creation will be deferred.
shell_surface->Minimize();
EXPECT_FALSE(shell_surface->GetWidget());
// Attaching the buffer will create a widget with minimized state.
surface->Attach(buffer.get());
surface->Commit();
EXPECT_TRUE(shell_surface->GetWidget()->IsMinimized());
shell_surface->Restore();
EXPECT_FALSE(shell_surface->GetWidget()->IsMinimized());
std::unique_ptr<Surface> child_surface(new Surface);
std::unique_ptr<ShellSurface> child_shell_surface(
new ShellSurface(child_surface.get()));
// Transient shell surfaces cannot be minimized.
child_surface->SetParent(surface.get(), gfx::Point());
child_surface->Attach(buffer.get());
child_surface->Commit();
EXPECT_FALSE(child_shell_surface->CanMinimize());
}
TEST_F(ShellSurfaceTest, Restore) {
gfx::Size buffer_size(256, 256);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
surface->Attach(buffer.get());
surface->Commit();
EXPECT_FALSE(HasBackdrop());
// Note: Remove contents to avoid issues with maximize animations in tests.
shell_surface->Maximize();
EXPECT_FALSE(HasBackdrop());
shell_surface->Restore();
EXPECT_FALSE(HasBackdrop());
EXPECT_EQ(
buffer_size.ToString(),
shell_surface->GetWidget()->GetWindowBoundsInScreen().size().ToString());
}
TEST_F(ShellSurfaceTest, RestoreFromFullscreen) {
std::unique_ptr<ShellSurface> shell_surface =
test::ShellSurfaceBuilder({256, 256})
.SetMaximumSize(gfx::Size(10, 10))
.BuildShellSurface();
// Act: Restore after fullscreen
shell_surface->SetFullscreen(true);
shell_surface->root_surface()->Commit();
shell_surface->Restore();
shell_surface->root_surface()->Commit();
// Assert: Window should stay fullscreen.
EXPECT_TRUE(shell_surface->GetWidget()->IsFullscreen());
}
TEST_F(ShellSurfaceTest, RestoreExitsFullscreen) {
std::unique_ptr<ShellSurface> shell_surface =
test::ShellSurfaceBuilder({256, 256}).BuildShellSurface();
// Act: Set window property kRestoreOrMaximizeExitsFullscreen
// then restore after fullscreen
shell_surface->root_surface()->Commit();
shell_surface->GetWidget()->GetNativeWindow()->SetProperty(
kRestoreOrMaximizeExitsFullscreen, true);
shell_surface->SetFullscreen(true);
shell_surface->Restore();
shell_surface->root_surface()->Commit();
// Assert: Window should exit fullscreen and be restored.
EXPECT_TRUE(shell_surface->GetWidget()->GetNativeWindow()->GetProperty(
kRestoreOrMaximizeExitsFullscreen));
EXPECT_EQ(gfx::Size(256, 256),
shell_surface->GetWidget()->GetWindowBoundsInScreen().size());
}
TEST_F(ShellSurfaceTest, HostWindowBoundsUpdatedAfterCommitWidget) {
gfx::Size buffer_size(256, 256);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
surface->Attach(buffer.get());
shell_surface->SurfaceTreeHost::OnSurfaceCommit();
shell_surface->root_surface()->SetSurfaceHierarchyContentBoundsForTest(
gfx::Rect(0, 0, 50, 50));
// Host Window Bounds size before committing.
EXPECT_EQ(gfx::Rect(0, 0, 0, 0), shell_surface->host_window()->bounds());
EXPECT_TRUE(shell_surface->OnPreWidgetCommit());
shell_surface->CommitWidget();
// CommitWidget should update the Host Window Bounds.
EXPECT_EQ(gfx::Rect(0, 0, 50, 50), shell_surface->host_window()->bounds());
}
TEST_F(ShellSurfaceTest, SetFullscreen) {
gfx::Size buffer_size(256, 256);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
shell_surface->SetFullscreen(true);
surface->Attach(buffer.get());
surface->Commit();
EXPECT_FALSE(HasBackdrop());
EXPECT_EQ(GetContext()->bounds().ToString(),
shell_surface->GetWidget()->GetWindowBoundsInScreen().ToString());
shell_surface->SetFullscreen(false);
surface->Commit();
EXPECT_FALSE(HasBackdrop());
EXPECT_NE(GetContext()->bounds().ToString(),
shell_surface->GetWidget()->GetWindowBoundsInScreen().ToString());
}
TEST_F(ShellSurfaceTest, PreWidgetUnfullscreen) {
std::unique_ptr<ShellSurface> shell_surface =
test::ShellSurfaceBuilder({256, 256}).SetNoCommit().BuildShellSurface();
shell_surface->Maximize();
shell_surface->SetFullscreen(false);
EXPECT_EQ(shell_surface->GetWidget(), nullptr);
shell_surface->root_surface()->Commit();
EXPECT_TRUE(shell_surface->GetWidget()->IsMaximized());
}
TEST_F(ShellSurfaceTest, PreWidgetMaximizeFromFullscreen) {
std::unique_ptr<ShellSurface> shell_surface =
test::ShellSurfaceBuilder({256, 256})
.SetNoCommit()
.SetMaximumSize(gfx::Size(10, 10))
.BuildShellSurface();
// Fullscreen -> Maximize for non Lacros surfaces should stay fullscreen
shell_surface->SetFullscreen(true);
shell_surface->Maximize();
EXPECT_EQ(shell_surface->GetWidget(), nullptr);
shell_surface->root_surface()->Commit();
EXPECT_TRUE(shell_surface->GetWidget()->IsFullscreen());
}
TEST_F(ShellSurfaceTest, SetTitle) {
gfx::Size buffer_size(256, 256);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
shell_surface->SetTitle(std::u16string(u"test"));
surface->Attach(buffer.get());
surface->Commit();
// NativeWindow's title is used within the overview mode, so it should
// have the specified title.
EXPECT_EQ(u"test", shell_surface->GetWidget()->GetNativeWindow()->GetTitle());
// The titlebar shouldn't show the title.
EXPECT_FALSE(
shell_surface->GetWidget()->widget_delegate()->ShouldShowWindowTitle());
}
TEST_F(ShellSurfaceTest, SetApplicationId) {
gfx::Size buffer_size(64, 64);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
EXPECT_FALSE(shell_surface->GetWidget());
shell_surface->SetApplicationId("pre-widget-id");
surface->Attach(buffer.get());
surface->Commit();
aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
EXPECT_EQ("pre-widget-id", *GetShellApplicationId(window));
shell_surface->SetApplicationId("test");
EXPECT_EQ("test", *GetShellApplicationId(window));
EXPECT_FALSE(ash::WindowState::Get(window)->allow_set_bounds_direct());
shell_surface->SetApplicationId(nullptr);
EXPECT_EQ(nullptr, GetShellApplicationId(window));
}
TEST_F(ShellSurfaceTest, ActivationPermission) {
gfx::Size buffer_size(64, 64);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
surface->Attach(buffer.get());
surface->Commit();
aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
ASSERT_TRUE(window);
// No permission granted so can't activate.
EXPECT_FALSE(HasPermissionToActivate(window));
// Can grant permission.
GrantPermissionToActivate(window, base::Days(1));
exo::Permission* permission = window->GetProperty(kPermissionKey);
EXPECT_TRUE(permission->Check(Permission::Capability::kActivate));
EXPECT_TRUE(HasPermissionToActivate(window));
// Can revoke permission.
RevokePermissionToActivate(window);
EXPECT_FALSE(HasPermissionToActivate(window));
// Can grant permission again.
GrantPermissionToActivate(window, base::Days(2));
exo::Permission* permission2 = window->GetProperty(kPermissionKey);
EXPECT_TRUE(permission2->Check(Permission::Capability::kActivate));
EXPECT_TRUE(HasPermissionToActivate(window));
}
TEST_F(ShellSurfaceTest, WidgetActivation) {
gfx::Size buffer_size(64, 64);
auto buffer1 = std::make_unique<Buffer>(
exo_test_helper()->CreateGpuMemoryBuffer(buffer_size));
auto surface1 = std::make_unique<Surface>();
auto shell_surface1 = std::make_unique<ShellSurface>(surface1.get());
surface1->Attach(buffer1.get());
surface1->Commit();
// The window is active.
views::Widget* widget1 = shell_surface1->GetWidget();
EXPECT_TRUE(widget1->IsActive());
// Create a second window.
auto buffer2 = std::make_unique<Buffer>(
exo_test_helper()->CreateGpuMemoryBuffer(buffer_size));
auto surface2 = std::make_unique<Surface>();
auto shell_surface2 = std::make_unique<ShellSurface>(surface2.get());
surface2->Attach(buffer2.get());
surface2->Commit();
// Now the second window is active.
views::Widget* widget2 = shell_surface2->GetWidget();
EXPECT_FALSE(widget1->IsActive());
EXPECT_TRUE(widget2->IsActive());
// Grant permission to activate the first window.
GrantPermissionToActivate(widget1->GetNativeWindow(), base::Days(1));
// The first window can activate itself.
surface1->RequestActivation();
EXPECT_TRUE(widget1->IsActive());
EXPECT_FALSE(widget2->IsActive());
// The second window cannot activate itself.
surface2->RequestActivation();
EXPECT_TRUE(widget1->IsActive());
EXPECT_FALSE(widget2->IsActive());
}
TEST_F(ShellSurfaceTest, EmulateOverrideRedirect) {
gfx::Size buffer_size(64, 64);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
EXPECT_FALSE(shell_surface->GetWidget());
surface->Attach(buffer.get());
surface->Commit();
aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
EXPECT_FALSE(ash::WindowState::Get(window)->allow_set_bounds_direct());
// Only surface with no app id with parent surface is considered
// override redirect.
std::unique_ptr<Surface> child_surface(new Surface);
std::unique_ptr<ShellSurface> child_shell_surface(
new ShellSurface(child_surface.get()));
child_surface->SetParent(surface.get(), gfx::Point());
child_surface->Attach(buffer.get());
child_surface->Commit();
aura::Window* child_window =
child_shell_surface->GetWidget()->GetNativeWindow();
// The window will not have a window state, thus will no be managed by window
// manager.
EXPECT_TRUE(ash::WindowState::Get(child_window)->allow_set_bounds_direct());
EXPECT_EQ(ash::kShellWindowId_ShelfBubbleContainer,
child_window->parent()->GetId());
// NONE/SHADOW frame type should work on override redirect.
child_surface->SetFrame(SurfaceFrameType::SHADOW);
child_surface->Commit();
EXPECT_EQ(wm::kShadowElevationMenuOrTooltip,
wm::GetShadowElevationConvertDefault(child_window));
child_surface->SetFrame(SurfaceFrameType::NONE);
child_surface->Commit();
EXPECT_EQ(wm::kShadowElevationNone,
wm::GetShadowElevationConvertDefault(child_window));
}
TEST_F(ShellSurfaceTest, SetStartupId) {
gfx::Size buffer_size(64, 64);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
EXPECT_FALSE(shell_surface->GetWidget());
shell_surface->SetStartupId("pre-widget-id");
surface->Attach(buffer.get());
surface->Commit();
aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
EXPECT_EQ("pre-widget-id", *GetShellStartupId(window));
shell_surface->SetStartupId("test");
EXPECT_EQ("test", *GetShellStartupId(window));
shell_surface->SetStartupId(nullptr);
EXPECT_EQ(nullptr, GetShellStartupId(window));
}
TEST_F(ShellSurfaceTest, StartMove) {
// TODO: Ractor out the shell surface creation.
gfx::Size buffer_size(64, 64);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
// Map shell surface.
surface->Attach(buffer.get());
surface->Commit();
ASSERT_TRUE(shell_surface->GetWidget());
// The interactive move should end when surface is destroyed.
shell_surface->StartMove();
// Test that destroying the shell surface before move ends is OK.
shell_surface.reset();
}
TEST_F(ShellSurfaceTest, StartResize) {
gfx::Size buffer_size(64, 64);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
// Map shell surface.
surface->Attach(buffer.get());
surface->Commit();
ASSERT_TRUE(shell_surface->GetWidget());
// The interactive resize should end when surface is destroyed.
shell_surface->StartResize(HTBOTTOMRIGHT);
// Test that destroying the surface before resize ends is OK.
surface.reset();
}
TEST_F(ShellSurfaceTest, StartResizeAndDestroyShell) {
gfx::Size buffer_size(64, 64);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
uint32_t serial = 0;
auto configure_callback = base::BindRepeating(
[](uint32_t* const serial_ptr, const gfx::Rect& bounds,
chromeos::WindowStateType state_type, bool resizing, bool activated,
const gfx::Vector2d& origin_offset) { return ++(*serial_ptr); },
&serial);
// Map shell surface.
surface->Attach(buffer.get());
shell_surface->set_configure_callback(configure_callback);
surface->Commit();
ASSERT_TRUE(shell_surface->GetWidget());
// The interactive resize should end when surface is destroyed.
shell_surface->StartResize(HTBOTTOMRIGHT);
// Go through configure/commit stage to update the resize component.
shell_surface->AcknowledgeConfigure(serial);
surface->Commit();
shell_surface->set_configure_callback(base::BindRepeating(
[](const gfx::Rect& bounds, chromeos::WindowStateType state_type,
bool resizing, bool activated, const gfx::Vector2d& origin_offset) {
ADD_FAILURE() << "Configure Should not be called";
return uint32_t{0};
}));
// Test that destroying the surface before resize ends is OK.
shell_surface.reset();
}
TEST_F(ShellSurfaceTest, SetGeometry) {
gfx::Size buffer_size(64, 64);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
gfx::Rect geometry(16, 16, 32, 32);
shell_surface->SetGeometry(geometry);
surface->Attach(buffer.get());
surface->Commit();
EXPECT_EQ(
geometry.size().ToString(),
shell_surface->GetWidget()->GetWindowBoundsInScreen().size().ToString());
EXPECT_EQ(gfx::Rect(gfx::Point() - geometry.OffsetFromOrigin(), buffer_size)
.ToString(),
shell_surface->host_window()->bounds().ToString());
}
TEST_F(ShellSurfaceTest, SetMinimumSize) {
gfx::Size buffer_size(64, 64);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
gfx::Size size(50, 50);
shell_surface->SetMinimumSize(size);
surface->Attach(buffer.get());
surface->Commit();
EXPECT_EQ(size, shell_surface->GetMinimumSize());
EXPECT_EQ(size, shell_surface->GetWidget()->GetMinimumSize());
EXPECT_EQ(size, shell_surface->GetWidget()
->GetNativeWindow()
->delegate()
->GetMinimumSize());
gfx::Size size_with_frame(50, 82);
surface->SetFrame(SurfaceFrameType::NORMAL);
EXPECT_EQ(size, shell_surface->GetMinimumSize());
EXPECT_EQ(size_with_frame, shell_surface->GetWidget()->GetMinimumSize());
EXPECT_EQ(size_with_frame, shell_surface->GetWidget()
->GetNativeWindow()
->delegate()
->GetMinimumSize());
}
TEST_F(ShellSurfaceTest, SetMaximumSize) {
gfx::Size buffer_size(64, 64);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
gfx::Size size(100, 100);
shell_surface->SetMaximumSize(size);
surface->Attach(buffer.get());
surface->Commit();
EXPECT_EQ(size, shell_surface->GetMaximumSize());
}
void PreClose(int* pre_close_count, int* close_count) {
EXPECT_EQ(*pre_close_count, *close_count);
(*pre_close_count)++;
}
void Close(int* pre_close_count, int* close_count) {
(*close_count)++;
EXPECT_EQ(*pre_close_count, *close_count);
}
TEST_F(ShellSurfaceTest, CloseCallback) {
gfx::Size buffer_size(64, 64);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
int pre_close_call_count = 0;
int close_call_count = 0;
shell_surface->set_pre_close_callback(
base::BindRepeating(&PreClose, base::Unretained(&pre_close_call_count),
base::Unretained(&close_call_count)));
shell_surface->set_close_callback(
base::BindRepeating(&Close, base::Unretained(&pre_close_call_count),
base::Unretained(&close_call_count)));
surface->Attach(buffer.get());
surface->Commit();
EXPECT_EQ(0, pre_close_call_count);
EXPECT_EQ(0, close_call_count);
shell_surface->GetWidget()->Close();
EXPECT_EQ(1, pre_close_call_count);
EXPECT_EQ(1, close_call_count);
}
void DestroyShellSurface(std::unique_ptr<ShellSurface>* shell_surface) {
shell_surface->reset();
}
TEST_F(ShellSurfaceTest, SurfaceDestroyedCallback) {
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
shell_surface->set_surface_destroyed_callback(
base::BindOnce(&DestroyShellSurface, base::Unretained(&shell_surface)));
surface->Commit();
EXPECT_TRUE(shell_surface.get());
surface.reset();
EXPECT_FALSE(shell_surface.get());
}
void DestroyedCallbackCounter(int* count) {
*count += 1;
}
TEST_F(ShellSurfaceTest, ForceClose) {
gfx::Size buffer_size(64, 64);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
surface->Attach(buffer.get());
surface->Commit();
ASSERT_TRUE(shell_surface->GetWidget());
int surface_destroyed_ctr = 0;
shell_surface->set_surface_destroyed_callback(base::BindOnce(
&DestroyedCallbackCounter, base::Unretained(&surface_destroyed_ctr)));
// Since we did not set the close callback, closing this widget will have no
// effect.
shell_surface->GetWidget()->Close();
EXPECT_TRUE(shell_surface->GetWidget());
EXPECT_EQ(surface_destroyed_ctr, 0);
// CloseNow() will always destroy the widget.
shell_surface->GetWidget()->CloseNow();
EXPECT_FALSE(shell_surface->GetWidget());
EXPECT_EQ(surface_destroyed_ctr, 1);
}
uint32_t Configure(gfx::Rect* suggested_bounds,
chromeos::WindowStateType* has_state_type,
bool* is_resizing,
bool* is_active,
const gfx::Rect& bounds,
chromeos::WindowStateType state_type,
bool resizing,
bool activated,
const gfx::Vector2d& origin_offset) {
*suggested_bounds = bounds;
*has_state_type = state_type;
*is_resizing = resizing;
*is_active = activated;
return 0;
}
TEST_F(ShellSurfaceTest, ConfigureCallback) {
// Must be before shell_surface so it outlives it, for shell_surface's
// destructor calls Configure() referencing these 4 variables.
gfx::Rect suggested_bounds;
chromeos::WindowStateType has_state_type = chromeos::WindowStateType::kNormal;
bool is_resizing = false;
bool is_active = false;
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
shell_surface->set_configure_callback(base::BindRepeating(
&Configure, base::Unretained(&suggested_bounds),
base::Unretained(&has_state_type), base::Unretained(&is_resizing),
base::Unretained(&is_active)));
gfx::Rect geometry(16, 16, 32, 32);
shell_surface->SetGeometry(geometry);
// Commit without contents should result in a configure callback with empty
// suggested size as a mechanisms to ask the client size itself.
surface->Commit();
EXPECT_TRUE(suggested_bounds.IsEmpty());
EXPECT_TRUE(shell_surface->GetWidget());
EXPECT_FALSE(shell_surface->GetWidget()->IsVisible());
EXPECT_EQ(geometry.size(), shell_surface->CalculatePreferredSize());
gfx::Rect maximized_bounds =
display::Screen::GetScreen()->GetPrimaryDisplay().work_area();
// State change should be sent even if the content is not attached.
// See crbug.com/1138978.
shell_surface->Maximize();
shell_surface->AcknowledgeConfigure(0);
EXPECT_FALSE(suggested_bounds.IsEmpty());
EXPECT_EQ(maximized_bounds.size(), suggested_bounds.size());
EXPECT_EQ(chromeos::WindowStateType::kMaximized, has_state_type);
gfx::Size buffer_size(64, 64);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
surface->Attach(buffer.get());
surface->Commit();
EXPECT_TRUE(shell_surface->GetWidget());
EXPECT_EQ(maximized_bounds.size(), suggested_bounds.size());
EXPECT_EQ(chromeos::WindowStateType::kMaximized, has_state_type);
shell_surface->Restore();
shell_surface->AcknowledgeConfigure(0);
// It should be restored to the original geometry size.
EXPECT_EQ(geometry.size(), shell_surface->CalculatePreferredSize());
shell_surface->SetFullscreen(true);
shell_surface->AcknowledgeConfigure(0);
EXPECT_EQ(GetContext()->bounds().size(), suggested_bounds.size());
EXPECT_EQ(chromeos::WindowStateType::kFullscreen, has_state_type);
shell_surface->SetFullscreen(false);
shell_surface->AcknowledgeConfigure(0);
EXPECT_EQ(geometry.size(), shell_surface->CalculatePreferredSize());
shell_surface->GetWidget()->Activate();
shell_surface->AcknowledgeConfigure(0);
EXPECT_TRUE(is_active);
shell_surface->GetWidget()->Deactivate();
shell_surface->AcknowledgeConfigure(0);
EXPECT_FALSE(is_active);
EXPECT_FALSE(is_resizing);
shell_surface->StartResize(HTBOTTOMRIGHT);
shell_surface->AcknowledgeConfigure(0);
EXPECT_TRUE(is_resizing);
}
TEST_F(ShellSurfaceTest, CreateMinimizedWindow) {
// Must be before shell_surface so it outlives it, for shell_surface's
// destructor calls Configure() referencing these 4 variables.
gfx::Rect suggested_bounds;
chromeos::WindowStateType has_state_type = chromeos::WindowStateType::kNormal;
bool is_resizing = false;
bool is_active = false;
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
shell_surface->set_configure_callback(base::BindRepeating(
&Configure, base::Unretained(&suggested_bounds),
base::Unretained(&has_state_type), base::Unretained(&is_resizing),
base::Unretained(&is_active)));
gfx::Rect geometry(0, 0, 1, 1);
shell_surface->SetGeometry(geometry);
shell_surface->Minimize();
shell_surface->AcknowledgeConfigure(0);
// Commit without contents should result in a configure callback with empty
// suggested size as a mechanisms to ask the client size itself.
surface->Commit();
EXPECT_TRUE(shell_surface->GetWidget());
EXPECT_TRUE(shell_surface->GetWidget()->IsMinimized());
EXPECT_TRUE(suggested_bounds.IsEmpty());
EXPECT_EQ(geometry.size(), shell_surface->CalculatePreferredSize());
}
TEST_F(ShellSurfaceTest, CreateMinimizedWindow2) {
// Must be before shell_surface so it outlives it, for shell_surface's
// destructor calls Configure() referencing these 4 variables.
gfx::Rect suggested_bounds;
auto has_state_type = chromeos::WindowStateType::kNormal;
bool is_resizing = false;
bool is_active = false;
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
shell_surface->set_configure_callback(base::BindRepeating(
&Configure, base::Unretained(&suggested_bounds),
base::Unretained(&has_state_type), base::Unretained(&is_resizing),
base::Unretained(&is_active)));
gfx::Rect geometry(0, 0, 1, 1);
shell_surface->SetGeometry(geometry);
// Commit without contents should result in a configure callback with empty
// suggested size as a mechanisms to ask the client size itself.
surface->Commit();
EXPECT_TRUE(suggested_bounds.IsEmpty());
EXPECT_TRUE(shell_surface->GetWidget());
EXPECT_FALSE(shell_surface->GetWidget()->IsVisible());
EXPECT_EQ(geometry.size(), shell_surface->CalculatePreferredSize());
shell_surface->Minimize();
shell_surface->AcknowledgeConfigure(0);
// Commit without contents should result in a configure callback with empty
// suggested size as a mechanisms to ask the client size itself.
surface->Commit();
EXPECT_TRUE(shell_surface->GetWidget());
EXPECT_TRUE(shell_surface->GetWidget()->IsMinimized());
EXPECT_EQ(geometry.size(), shell_surface->CalculatePreferredSize());
// Once the initial empty size is sent in configure,
// new configure should send the size requested.
EXPECT_EQ(geometry.size(), suggested_bounds.size());
}
TEST_F(ShellSurfaceTest,
CreateMaximizedWindowWithRestoreBoundsWithoutInitialBuffer) {
// Must be before shell_surface so it outlives it, for shell_surface's
// destructor calls Configure() referencing these 4 variables.
gfx::Rect suggested_bounds;
chromeos::WindowStateType has_state_type = chromeos::WindowStateType::kNormal;
bool is_resizing = false;
bool is_active = false;
gfx::Size buffer_size(256, 256);
std::unique_ptr<ShellSurface> shell_surface =
test::ShellSurfaceBuilder(buffer_size)
.SetNoRootBuffer()
.BuildShellSurface();
shell_surface->set_configure_callback(base::BindRepeating(
&Configure, base::Unretained(&suggested_bounds),
base::Unretained(&has_state_type), base::Unretained(&is_resizing),
base::Unretained(&is_active)));
Surface* root_surface = shell_surface->surface_for_testing();
root_surface->Commit();
shell_surface->AcknowledgeConfigure(0);
// Ash may maximize the window.
EXPECT_TRUE(shell_surface->GetWidget());
EXPECT_FALSE(shell_surface->GetWidget()->IsVisible());
EXPECT_FALSE(shell_surface->GetWidget()->IsMaximized());
shell_surface->Maximize();
shell_surface->AcknowledgeConfigure(0);
EXPECT_FALSE(shell_surface->GetWidget()->IsVisible());
EXPECT_TRUE(shell_surface->GetWidget()->IsMaximized());
auto buffer = std::make_unique<Buffer>(
exo_test_helper()->CreateGpuMemoryBuffer(buffer_size));
root_surface->Attach(buffer.get());
gfx::Rect geometry_full(buffer_size);
shell_surface->SetGeometry(geometry_full);
// Commit without contents should result in a configure callback with empty
// suggested size as a mechanisms to ask the client size itself.
root_surface->Commit();
shell_surface->AcknowledgeConfigure(0);
EXPECT_TRUE(shell_surface->GetWidget()->IsVisible());
EXPECT_TRUE(shell_surface->GetWidget()->IsMaximized());
auto* window_state =
ash::WindowState::Get(shell_surface->GetWidget()->GetNativeWindow());
EXPECT_TRUE(window_state->HasRestoreBounds());
auto bounds = window_state->GetRestoreBoundsInParent();
EXPECT_EQ(geometry_full.size(), bounds.size());
}
TEST_F(ShellSurfaceTest, CreateMaximizedWindowWithRestoreBounds) {
// Must be before shell_surface so it outlives it, for shell_surface's
// destructor calls Configure() referencing these 4 variables.
gfx::Rect suggested_bounds;
chromeos::WindowStateType has_state_type = chromeos::WindowStateType::kNormal;
bool is_resizing = false;
bool is_active = false;
gfx::Size buffer_size(256, 256);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
shell_surface->set_configure_callback(base::BindRepeating(
&Configure, base::Unretained(&suggested_bounds),
base::Unretained(&has_state_type), base::Unretained(&is_resizing),
base::Unretained(&is_active)));
gfx::Rect geometry(0, 0, 1, 1);
shell_surface->SetGeometry(geometry);
shell_surface->Maximize();
// Commit without contents should result in a configure callback with empty
// suggested size as a mechanisms to ask the client size itself.
surface->Attach(buffer.get());
surface->Commit();
shell_surface->AcknowledgeConfigure(0);
gfx::Rect geometry_full(0, 0, 100, 100);
shell_surface->SetGeometry(geometry_full);
surface->Commit();
shell_surface->AcknowledgeConfigure(1);
EXPECT_TRUE(shell_surface->GetWidget());
EXPECT_TRUE(shell_surface->GetWidget()->IsMaximized());
EXPECT_EQ(geometry_full.size(), shell_surface->CalculatePreferredSize());
auto* window_state =
ash::WindowState::Get(shell_surface->GetWidget()->GetNativeWindow());
EXPECT_TRUE(window_state->HasRestoreBounds());
auto bounds = window_state->GetRestoreBoundsInParent();
EXPECT_EQ(geometry.width(), bounds.width());
EXPECT_EQ(geometry.height(), bounds.height());
}
TEST_F(ShellSurfaceTest, ToggleFullscreen) {
gfx::Size buffer_size(256, 256);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
surface->Attach(buffer.get());
surface->Commit();
EXPECT_FALSE(HasBackdrop());
EXPECT_EQ(buffer_size,
shell_surface->GetWidget()->GetWindowBoundsInScreen().size());
shell_surface->Maximize();
EXPECT_FALSE(HasBackdrop());
EXPECT_EQ(GetContext()->bounds().width(),
shell_surface->GetWidget()->GetWindowBoundsInScreen().width());
ash::WMEvent event(ash::WM_EVENT_TOGGLE_FULLSCREEN);
aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
// Enter fullscreen mode.
ash::WindowState::Get(window)->OnWMEvent(&event);
EXPECT_FALSE(HasBackdrop());
EXPECT_EQ(GetContext()->bounds().ToString(),
shell_surface->GetWidget()->GetWindowBoundsInScreen().ToString());
// Leave fullscreen mode.
ash::WindowState::Get(window)->OnWMEvent(&event);
EXPECT_FALSE(HasBackdrop());
// Check that shell surface is maximized.
EXPECT_EQ(GetContext()->bounds().width(),
shell_surface->GetWidget()->GetWindowBoundsInScreen().width());
}
TEST_F(ShellSurfaceTest, FrameColors) {
std::unique_ptr<Surface> surface(new Surface);
gfx::Size buffer_size(64, 64);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
surface->Attach(buffer.get());
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
shell_surface->OnSetFrameColors(SK_ColorRED, SK_ColorTRANSPARENT);
surface->Commit();
const ash::NonClientFrameViewAsh* frame =
static_cast<const ash::NonClientFrameViewAsh*>(
shell_surface->GetWidget()->non_client_view()->frame_view());
// Test if colors set before initial commit are set.
EXPECT_EQ(SK_ColorRED, frame->GetActiveFrameColorForTest());
// Frame should be fully opaque.
EXPECT_EQ(SK_ColorBLACK, frame->GetInactiveFrameColorForTest());
shell_surface->OnSetFrameColors(SK_ColorTRANSPARENT, SK_ColorBLUE);
EXPECT_EQ(SK_ColorBLACK, frame->GetActiveFrameColorForTest());
EXPECT_EQ(SK_ColorBLUE, frame->GetInactiveFrameColorForTest());
}
TEST_F(ShellSurfaceTest, CycleSnap) {
gfx::Size buffer_size(256, 256);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
surface->Attach(buffer.get());
surface->Commit();
EXPECT_EQ(buffer_size,
shell_surface->GetWidget()->GetWindowBoundsInScreen().size());
ash::WMEvent event(ash::WM_EVENT_CYCLE_SNAP_PRIMARY);
aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
// Enter snapped mode.
ash::WindowState::Get(window)->OnWMEvent(&event);
EXPECT_EQ(GetContext()->bounds().width() / 2,
shell_surface->GetWidget()->GetWindowBoundsInScreen().width());
surface->Attach(buffer.get());
surface->Commit();
// Commit shouldn't change widget bounds when snapped.
EXPECT_EQ(GetContext()->bounds().width() / 2,
shell_surface->GetWidget()->GetWindowBoundsInScreen().width());
}
TEST_F(ShellSurfaceTest, ShellSurfaceWithMaximumSize) {
std::unique_ptr<ShellSurface> shell_surface =
test::ShellSurfaceBuilder({256, 256})
.SetMaximumSize(gfx::Size(10, 10))
.BuildShellSurface();
auto* window_state =
ash::WindowState::Get(shell_surface->GetWidget()->GetNativeWindow());
EXPECT_FALSE(window_state->CanMaximize());
EXPECT_FALSE(window_state->CanSnap());
shell_surface->SetMaximumSize(gfx::Size(0, 0));
shell_surface->root_surface()->Commit();
EXPECT_TRUE(window_state->CanMaximize());
EXPECT_TRUE(window_state->CanSnap());
// If the max size is bigger than 16k resolution, allow max/snap state.
shell_surface->SetMaximumSize(
gfx::Size(ash::kAllowMaximizeThreshold, ash::kAllowMaximizeThreshold));
shell_surface->root_surface()->Commit();
EXPECT_FALSE(window_state->CanMaximize());
EXPECT_FALSE(window_state->CanSnap());
// If the max size is bigger than 32k resolution, allow max/snap state.
shell_surface->SetMaximumSize(gfx::Size(ash::kAllowMaximizeThreshold + 1,
ash::kAllowMaximizeThreshold + 1));
shell_surface->root_surface()->Commit();
EXPECT_TRUE(window_state->CanMaximize());
EXPECT_TRUE(window_state->CanSnap());
}
TEST_F(ShellSurfaceTest, Transient) {
gfx::Size buffer_size(256, 256);
std::unique_ptr<Buffer> parent_buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> parent_surface(new Surface);
parent_surface->Attach(parent_buffer.get());
std::unique_ptr<ShellSurface> parent_shell_surface(
new ShellSurface(parent_surface.get()));
parent_surface->Commit();
std::unique_ptr<Buffer> child_buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> child_surface(new Surface);
child_surface->Attach(child_buffer.get());
std::unique_ptr<ShellSurface> child_shell_surface(
new ShellSurface(child_surface.get()));
// Importantly, a transient window has an associated application.
child_surface->SetApplicationId("fake_app_id");
child_surface->SetParent(parent_surface.get(), gfx::Point(50, 50));
child_surface->Commit();
aura::Window* parent_window =
parent_shell_surface->GetWidget()->GetNativeWindow();
aura::Window* child_window =
child_shell_surface->GetWidget()->GetNativeWindow();
ASSERT_TRUE(parent_window && child_window);
// The visibility of transient windows is controlled by the parent.
parent_window->Hide();
EXPECT_FALSE(child_window->IsVisible());
parent_window->Show();
EXPECT_TRUE(child_window->IsVisible());
}
TEST_F(ShellSurfaceTest, X11Transient) {
auto parent = test::ShellSurfaceBuilder({256, 256}).BuildShellSurface();
gfx::Point origin(50, 50);
auto transient =
CreateX11TransientShellSurface(parent.get(), gfx::Size(100, 100), origin);
EXPECT_TRUE(transient->GetWidget()->movement_disabled());
EXPECT_EQ(transient->GetWidget()->GetWindowBoundsInScreen().origin(), origin);
}
TEST_F(ShellSurfaceTest, Popup) {
gfx::Size buffer_size(256, 256);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
surface->Attach(buffer.get());
surface->Commit();
shell_surface->GetWidget()->SetBounds(gfx::Rect(0, 0, 256, 256));
std::unique_ptr<Buffer> popup_buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> popup_surface(new Surface);
popup_surface->Attach(popup_buffer.get());
std::unique_ptr<ShellSurface> popup_shell_surface(CreatePopupShellSurface(
popup_surface.get(), shell_surface.get(), gfx::Point(50, 50)));
popup_shell_surface->Grab();
popup_surface->Commit();
ASSERT_EQ(gfx::Rect(50, 50, 256, 256),
popup_shell_surface->GetWidget()->GetWindowBoundsInScreen());
// Verify that created shell surface is popup and has capture.
EXPECT_EQ(aura::client::WINDOW_TYPE_POPUP,
popup_shell_surface->GetWidget()->GetNativeWindow()->GetType());
EXPECT_EQ(WMHelper::GetInstance()->GetCaptureClient()->GetCaptureWindow(),
popup_shell_surface->GetWidget()->GetNativeWindow());
// Setting frame type on popup should have no effect.
popup_surface->SetFrame(SurfaceFrameType::NORMAL);
EXPECT_FALSE(popup_shell_surface->frame_enabled());
// ShellSurface can capture the event even after it is craeted.
std::unique_ptr<Buffer> sub_popup_buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> sub_popup_surface(new Surface);
sub_popup_surface->Attach(popup_buffer.get());
std::unique_ptr<ShellSurface> sub_popup_shell_surface(CreatePopupShellSurface(
sub_popup_surface.get(), popup_shell_surface.get(), gfx::Point(100, 50)));
sub_popup_shell_surface->Grab();
sub_popup_surface->Commit();
ASSERT_EQ(gfx::Rect(100, 50, 256, 256),
sub_popup_shell_surface->GetWidget()->GetWindowBoundsInScreen());
aura::Window* target =
sub_popup_shell_surface->GetWidget()->GetNativeWindow();
// The capture should be on sub_popup_shell_surface.
EXPECT_EQ(WMHelper::GetInstance()->GetCaptureClient()->GetCaptureWindow(),
target);
EXPECT_EQ(aura::client::WINDOW_TYPE_POPUP, target->GetType());
{
// Mouse is on the top most popup.
ui::MouseEvent event(ui::ET_MOUSE_MOVED, gfx::Point(0, 0),
gfx::Point(100, 50), ui::EventTimeForNow(), 0, 0);
ui::Event::DispatcherApi(&event).set_target(target);
EXPECT_EQ(sub_popup_surface.get(), GetTargetSurfaceForLocatedEvent(&event));
}
{
// Move the mouse to the parent popup.
ui::MouseEvent event(ui::ET_MOUSE_MOVED, gfx::Point(-25, 0),
gfx::Point(75, 50), ui::EventTimeForNow(), 0, 0);
ui::Event::DispatcherApi(&event).set_target(target);
EXPECT_EQ(popup_surface.get(), GetTargetSurfaceForLocatedEvent(&event));
}
{
// Move the mouse to the main window.
ui::MouseEvent event(ui::ET_MOUSE_MOVED, gfx::Point(-25, -25),
gfx::Point(75, 25), ui::EventTimeForNow(), 0, 0);
ui::Event::DispatcherApi(&event).set_target(target);
EXPECT_EQ(surface.get(), GetTargetSurfaceForLocatedEvent(&event));
}
// Removing top most popup moves the grab to parent popup.
sub_popup_shell_surface.reset();
target = popup_shell_surface->GetWidget()->GetNativeWindow();
EXPECT_EQ(WMHelper::GetInstance()->GetCaptureClient()->GetCaptureWindow(),
target);
{
// Targetting should still work.
ui::MouseEvent event(ui::ET_MOUSE_MOVED, gfx::Point(0, 0),
gfx::Point(50, 50), ui::EventTimeForNow(), 0, 0);
ui::Event::DispatcherApi(&event).set_target(target);
EXPECT_EQ(popup_surface.get(), GetTargetSurfaceForLocatedEvent(&event));
}
}
TEST_F(ShellSurfaceTest, PopupWithInputRegion) {
gfx::Size buffer_size(256, 256);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
surface->Attach(buffer.get());
surface->SetInputRegion(cc::Region());
std::unique_ptr<Buffer> child_buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> child_surface(new Surface);
child_surface->Attach(child_buffer.get());
auto subsurface =
std::make_unique<SubSurface>(child_surface.get(), surface.get());
subsurface->SetPosition(gfx::PointF(10, 10));
child_surface->SetInputRegion(cc::Region(gfx::Rect(0, 0, 256, 2560)));
child_surface->Commit();
surface->Commit();
shell_surface->GetWidget()->SetBounds(gfx::Rect(0, 0, 256, 256));
std::unique_ptr<Buffer> popup_buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> popup_surface(new Surface);
popup_surface->Attach(popup_buffer.get());
std::unique_ptr<ShellSurface> popup_shell_surface(CreatePopupShellSurface(
popup_surface.get(), shell_surface.get(), gfx::Point(50, 50)));
popup_shell_surface->Grab();
popup_surface->Commit();
ASSERT_EQ(gfx::Rect(50, 50, 256, 256),
popup_shell_surface->GetWidget()->GetWindowBoundsInScreen());
// Verify that created shell surface is popup and has capture.
EXPECT_EQ(aura::client::WINDOW_TYPE_POPUP,
popup_shell_surface->GetWidget()->GetNativeWindow()->GetType());
EXPECT_EQ(WMHelper::GetInstance()->GetCaptureClient()->GetCaptureWindow(),
popup_shell_surface->GetWidget()->GetNativeWindow());
// Setting frame type on popup should have no effect.
popup_surface->SetFrame(SurfaceFrameType::NORMAL);
EXPECT_FALSE(popup_shell_surface->frame_enabled());
aura::Window* target = popup_shell_surface->GetWidget()->GetNativeWindow();
{
// Mouse is on the popup.
ui::MouseEvent event(ui::ET_MOUSE_MOVED, gfx::Point(0, 0),
gfx::Point(50, 50), ui::EventTimeForNow(), 0, 0);
ui::Event::DispatcherApi(&event).set_target(target);
EXPECT_EQ(popup_surface.get(), GetTargetSurfaceForLocatedEvent(&event));
}
{
// If it matches the parent's sub surface, use it.
ui::MouseEvent event(ui::ET_MOUSE_MOVED, gfx::Point(-25, 0),
gfx::Point(25, 50), ui::EventTimeForNow(), 0, 0);
ui::Event::DispatcherApi(&event).set_target(target);
EXPECT_EQ(child_surface.get(), GetTargetSurfaceForLocatedEvent(&event));
}
{
// If it didnt't match any surface in the parent, fallback to
// the popup's surface.
ui::MouseEvent event(ui::ET_MOUSE_MOVED, gfx::Point(-50, 0),
gfx::Point(0, 50), ui::EventTimeForNow(), 0, 0);
ui::Event::DispatcherApi(&event).set_target(target);
EXPECT_EQ(popup_surface.get(), GetTargetSurfaceForLocatedEvent(&event));
}
popup_surface.reset();
EXPECT_EQ(WMHelper::GetInstance()->GetCaptureClient()->GetCaptureWindow(),
nullptr);
auto window = std::make_unique<aura::Window>(nullptr);
window->Init(ui::LAYER_TEXTURED);
window->SetBounds(gfx::Rect(0, 0, 256, 256));
shell_surface->GetWidget()->GetNativeWindow()->AddChild(window.get());
window->Show();
{
// If non surface window covers the window,
/// GetTargetSurfaceForLocatedEvent should return nullptr.
ui::MouseEvent event(ui::ET_MOUSE_MOVED, gfx::Point(0, 0),
gfx::Point(50, 50), ui::EventTimeForNow(), 0, 0);
ui::Event::DispatcherApi(&event).set_target(window.get());
EXPECT_EQ(nullptr, GetTargetSurfaceForLocatedEvent(&event));
}
}
TEST_F(ShellSurfaceTest, PopupWithInvisibleParent) {
// Invisible main window.
std::unique_ptr<ShellSurface> root_shell_surface =
test::ShellSurfaceBuilder({256, 256})
.SetNoRootBuffer()
.BuildShellSurface();
EXPECT_FALSE(root_shell_surface->GetWidget()->IsVisible());
std::unique_ptr<ShellSurface> popup_shell_surface_1 =
test::ShellSurfaceBuilder({256, 256})
.SetNoRootBuffer()
.SetAsPopup()
.SetDisableMovement()
.SetParent(root_shell_surface.get())
.BuildShellSurface();
EXPECT_EQ(root_shell_surface->GetWidget()->GetNativeWindow(),
wm::GetTransientParent(
popup_shell_surface_1->GetWidget()->GetNativeWindow()));
EXPECT_FALSE(popup_shell_surface_1->GetWidget()->IsVisible());
// Create visible popup.
std::unique_ptr<ShellSurface> popup_shell_surface_2 =
test::ShellSurfaceBuilder({256, 256})
.SetAsPopup()
.SetDisableMovement()
.SetParent(popup_shell_surface_1.get())
.BuildShellSurface();
EXPECT_TRUE(popup_shell_surface_2->GetWidget()->IsVisible());
EXPECT_EQ(popup_shell_surface_1->GetWidget()->GetNativeWindow(),
wm::GetTransientParent(
popup_shell_surface_2->GetWidget()->GetNativeWindow()));
}
TEST_F(ShellSurfaceTest, Caption) {
gfx::Size buffer_size(256, 256);
auto buffer = std::make_unique<Buffer>(
exo_test_helper()->CreateGpuMemoryBuffer(buffer_size));
auto surface = std::make_unique<Surface>();
auto shell_surface = std::make_unique<ShellSurface>(surface.get());
surface->Attach(buffer.get());
shell_surface->OnSetFrame(SurfaceFrameType::NORMAL);
surface->Commit();
shell_surface->GetWidget()->SetBounds(gfx::Rect(0, 0, 256, 256));
aura::Window* target = shell_surface->GetWidget()->GetNativeWindow();
target->SetCapture();
EXPECT_EQ(WMHelper::GetInstance()->GetCaptureClient()->GetCaptureWindow(),
shell_surface->GetWidget()->GetNativeWindow());
{
// Move the mouse at the caption of the captured window.
ui::MouseEvent event(ui::ET_MOUSE_MOVED, gfx::Point(5, 5), gfx::Point(5, 5),
ui::EventTimeForNow(), 0, 0);
ui::Event::DispatcherApi(&event).set_target(target);
EXPECT_EQ(nullptr, GetTargetSurfaceForLocatedEvent(&event));
}
{
// Move the mouse at the center of the captured window.
gfx::Rect bounds = shell_surface->GetWidget()->GetWindowBoundsInScreen();
gfx::Point center = bounds.CenterPoint();
ui::MouseEvent event(ui::ET_MOUSE_MOVED, center - bounds.OffsetFromOrigin(),
center, ui::EventTimeForNow(), 0, 0);
ui::Event::DispatcherApi(&event).set_target(target);
EXPECT_EQ(surface.get(), GetTargetSurfaceForLocatedEvent(&event));
}
}
TEST_F(ShellSurfaceTest, DragMaximizedWindow) {
gfx::Size buffer_size(256, 256);
auto buffer = std::make_unique<Buffer>(
exo_test_helper()->CreateGpuMemoryBuffer(buffer_size));
auto surface = std::make_unique<Surface>();
auto shell_surface = std::make_unique<ShellSurface>(surface.get());
surface->Attach(buffer.get());
shell_surface->OnSetFrame(SurfaceFrameType::NORMAL);
surface->Commit();
shell_surface->GetWidget()->SetBounds(gfx::Rect(0, 0, 256, 256));
// Maximize the window.
shell_surface->Maximize();
chromeos::WindowStateType configured_state =
chromeos::WindowStateType::kDefault;
shell_surface->set_configure_callback(base::BindLambdaForTesting(
[&](const gfx::Rect& bounds, chromeos::WindowStateType state,
bool resizing, bool activated, const gfx::Vector2d& origin_offset) {
configured_state = state;
return uint32_t{0};
}));
// Initiate caption bar dragging for a window.
gfx::Size size = shell_surface->GetWidget()->GetWindowBoundsInScreen().size();
ui::test::EventGenerator* event_generator = GetEventGenerator();
// Move mouse pointer to caption bar.
event_generator->MoveMouseTo(size.width() / 2, 2);
constexpr int kDragAmount = 10;
// Start dragging a window.
event_generator->DragMouseBy(kDragAmount, kDragAmount);
EXPECT_FALSE(shell_surface->GetWidget()->IsMaximized());
// `WindowStateType` after dragging should be `kNormal`.
EXPECT_EQ(chromeos::WindowStateType::kNormal, configured_state);
}
TEST_F(ShellSurfaceTest, CaptionWithPopup) {
gfx::Size buffer_size(256, 256);
auto buffer = std::make_unique<Buffer>(
exo_test_helper()->CreateGpuMemoryBuffer(buffer_size));
auto surface = std::make_unique<Surface>();
auto shell_surface = std::make_unique<ShellSurface>(surface.get());
surface->Attach(buffer.get());
surface->Commit();
shell_surface->GetWidget()->SetBounds(gfx::Rect(0, 0, 256, 256));
shell_surface->OnSetFrame(SurfaceFrameType::NORMAL);
auto popup_buffer = std::make_unique<Buffer>(
exo_test_helper()->CreateGpuMemoryBuffer(buffer_size));
auto popup_surface = std::make_unique<Surface>();
popup_surface->Attach(popup_buffer.get());
std::unique_ptr<ShellSurface> popup_shell_surface(CreatePopupShellSurface(
popup_surface.get(), shell_surface.get(), gfx::Point(50, 50)));
popup_shell_surface->Grab();
popup_surface->Commit();
aura::Window* target = popup_shell_surface->GetWidget()->GetNativeWindow();
EXPECT_EQ(WMHelper::GetInstance()->GetCaptureClient()->GetCaptureWindow(),
target);
{
// Move the mouse at the popup window.
ui::MouseEvent event(ui::ET_MOUSE_MOVED, gfx::Point(5, 5),
gfx::Point(55, 55), ui::EventTimeForNow(), 0, 0);
ui::Event::DispatcherApi(&event).set_target(target);
EXPECT_EQ(popup_surface.get(), GetTargetSurfaceForLocatedEvent(&event));
}
{
// Move the mouse at the caption of the main window.
ui::MouseEvent event(ui::ET_MOUSE_MOVED, gfx::Point(-45, -45),
gfx::Point(5, 5), ui::EventTimeForNow(), 0, 0);
ui::Event::DispatcherApi(&event).set_target(target);
EXPECT_EQ(nullptr, GetTargetSurfaceForLocatedEvent(&event));
}
{
// Move the mouse in the main window.
ui::MouseEvent event(ui::ET_MOUSE_MOVED, gfx::Point(-25, 0),
gfx::Point(25, 50), ui::EventTimeForNow(), 0, 0);
ui::Event::DispatcherApi(&event).set_target(target);
EXPECT_EQ(surface.get(), GetTargetSurfaceForLocatedEvent(&event));
}
}
TEST_F(ShellSurfaceTest, SkipImeProcessingPropagateToSurface) {
std::unique_ptr<ShellSurface> shell_surface =
test::ShellSurfaceBuilder({256, 256}).BuildShellSurface();
shell_surface->GetWidget()->SetBounds(gfx::Rect(0, 0, 256, 256));
shell_surface->OnSetFrame(SurfaceFrameType::NORMAL);
aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
ASSERT_FALSE(window->GetProperty(aura::client::kSkipImeProcessing));
ASSERT_FALSE(shell_surface->root_surface()->window()->GetProperty(
aura::client::kSkipImeProcessing));
window->SetProperty(aura::client::kSkipImeProcessing, true);
EXPECT_TRUE(window->GetProperty(aura::client::kSkipImeProcessing));
EXPECT_TRUE(shell_surface->root_surface()->window()->GetProperty(
aura::client::kSkipImeProcessing));
}
TEST_F(ShellSurfaceTest, NotifyLeaveEnter) {
std::unique_ptr<ShellSurface> shell_surface =
test::ShellSurfaceBuilder({256, 256}).SetNoCommit().BuildShellSurface();
auto func = [](int64_t* old_display_id, int64_t* new_display_id,
int64_t old_id, int64_t new_id) {
DCHECK_EQ(0, *old_display_id);
DCHECK_EQ(0, *new_display_id);
*old_display_id = old_id;
*new_display_id = new_id;
return true;
};
int64_t old_display_id = 0, new_display_id = 0;
shell_surface->root_surface()->set_leave_enter_callback(
base::BindRepeating(func, &old_display_id, &new_display_id));
// Creating a new shell surface should notify on which display
// it is created.
shell_surface->root_surface()->Commit();
EXPECT_EQ(display::kInvalidDisplayId, old_display_id);
EXPECT_EQ(display::Screen::GetScreen()->GetPrimaryDisplay().id(),
new_display_id);
// Attaching a 2nd display should not change where the surface
// is located.
old_display_id = 0;
new_display_id = 0;
UpdateDisplay("800x600, 800x600");
EXPECT_EQ(0, old_display_id);
EXPECT_EQ(0, new_display_id);
// Move the window to 2nd display.
shell_surface->GetWidget()->SetBounds(gfx::Rect(1000, 0, 256, 256));
int64_t secondary_id =
display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager())
.GetSecondaryDisplay()
.id();
EXPECT_EQ(display::Screen::GetScreen()->GetPrimaryDisplay().id(),
old_display_id);
EXPECT_EQ(secondary_id, new_display_id);
// Disconnect the display the surface is currently on.
old_display_id = 0;
new_display_id = 0;
UpdateDisplay("800x600");
EXPECT_EQ(display::Screen::GetScreen()->GetPrimaryDisplay().id(),
new_display_id);
EXPECT_EQ(secondary_id, old_display_id);
}
// Make sure that the server side triggers resize when the
// set_server_start_resize is called, and the resize shadow is created for the
// window.
TEST_F(ShellSurfaceTest, ServerStartResize) {
std::unique_ptr<ShellSurface> shell_surface =
test::ShellSurfaceBuilder({64, 64}).SetNoCommit().BuildShellSurface();
shell_surface->OnSetServerStartResize();
shell_surface->root_surface()->Commit();
ASSERT_TRUE(shell_surface->GetWidget());
auto* widget = shell_surface->GetWidget();
gfx::Size size = widget->GetWindowBoundsInScreen().size();
widget->SetBounds(gfx::Rect(size));
ui::test::EventGenerator* event_generator = GetEventGenerator();
event_generator->MoveMouseTo(size.width() + 2, size.height() / 2);
// Ash creates resize shadow for resizable exo window when the
// server_start_resize is set.
ash::ResizeShadow* resize_shadow =
ash::Shell::Get()->resize_shadow_controller()->GetShadowForWindowForTest(
widget->GetNativeWindow());
ASSERT_TRUE(resize_shadow);
constexpr int kDragAmount = 10;
event_generator->PressLeftButton();
event_generator->MoveMouseBy(kDragAmount, 0);
event_generator->ReleaseLeftButton();
EXPECT_EQ(widget->GetWindowBoundsInScreen().size().width(),
size.width() + kDragAmount);
}
// Make sure that resize shadow does not update until commit when the window
// property |aura::client::kUseWindowBoundsForShadow| is false.
TEST_F(ShellSurfaceTest, ResizeShadowIndependentBounds) {
std::unique_ptr<ShellSurface> shell_surface =
test::ShellSurfaceBuilder({64, 64}).BuildShellSurface();
shell_surface->OnSetServerStartResize();
shell_surface->root_surface()->Commit();
ASSERT_TRUE(shell_surface->GetWidget());
auto* widget = shell_surface->GetWidget();
gfx::Size size = widget->GetWindowBoundsInScreen().size();
widget->SetBounds(gfx::Rect(size));
widget->GetNativeWindow()->SetProperty(
aura::client::kUseWindowBoundsForShadow, false);
// Starts mouse event to make sure resize shadow is created.
ui::test::EventGenerator* event_generator = GetEventGenerator();
event_generator->MoveMouseTo(size.width() + 2, size.height() / 2);
// Creates resize shadow and normal shadow for resizable exo window.
ash::ResizeShadow* resize_shadow =
ash::Shell::Get()->resize_shadow_controller()->GetShadowForWindowForTest(
widget->GetNativeWindow());
ASSERT_TRUE(resize_shadow);
shell_surface->root_surface()->SetFrame(SurfaceFrameType::SHADOW);
shell_surface->root_surface()->Commit();
ui::Shadow* normal_shadow =
wm::ShadowController::GetShadowForWindow(widget->GetNativeWindow());
ASSERT_TRUE(normal_shadow);
// ash::ResizeShadow::InitParams set the default |thickness| to 8.
const int kResizeShadowThickness = 8;
EXPECT_EQ(gfx::Size(size.width() + kResizeShadowThickness, size.height()),
resize_shadow->GetLayerForTest()->bounds().size());
EXPECT_EQ(size, normal_shadow->content_bounds().size());
gfx::Size new_size(100, 100);
gfx::Rect new_bounds(new_size);
uint32_t serial = 0;
auto configure_callback = base::BindRepeating(
[](uint32_t* const serial_ptr, const gfx::Rect& bounds,
chromeos::WindowStateType state_type, bool resizing, bool activated,
const gfx::Vector2d& origin_offset) { return ++(*serial_ptr); },
&serial);
shell_surface->set_configure_callback(configure_callback);
// Resize the widget and set geometry.
shell_surface->StartResize(HTBOTTOMRIGHT);
shell_surface->SetWidgetBounds(new_bounds);
shell_surface->SetGeometry(new_bounds);
// Client acknowledge configure for resizing. Shadow sizes should not be
// updated yet until commit.
shell_surface->AcknowledgeConfigure(serial);
EXPECT_EQ(gfx::Size(size.width() + kResizeShadowThickness, size.height()),
resize_shadow->GetLayerForTest()->bounds().size());
EXPECT_EQ(size, normal_shadow->content_bounds().size());
// Normal and resize shadow sizes are updated after commit.
shell_surface->root_surface()->Commit();
EXPECT_EQ(
gfx::Size(new_size.width() + kResizeShadowThickness, new_size.height()),
resize_shadow->GetLayerForTest()->bounds().size());
EXPECT_EQ(new_size, normal_shadow->content_bounds().size());
}
// Make sure that resize shadow updates as soon as widget bounds change when
// the window property |aura::client::kUseWindowBoundsForShadow| is false.
TEST_F(ShellSurfaceTest, ResizeShadowDependentBounds) {
std::unique_ptr<ShellSurface> shell_surface =
test::ShellSurfaceBuilder({64, 64}).BuildShellSurface();
shell_surface->OnSetServerStartResize();
shell_surface->root_surface()->Commit();
ASSERT_TRUE(shell_surface->GetWidget());
auto* widget = shell_surface->GetWidget();
gfx::Size size = widget->GetWindowBoundsInScreen().size();
widget->SetBounds(gfx::Rect(size));
// Starts mouse event to make sure resize shadow is created.
ui::test::EventGenerator* event_generator = GetEventGenerator();
event_generator->MoveMouseTo(size.width() + 2, size.height() / 2);
// Creates resize shadow and normal shadow for resizable exo window.
ash::ResizeShadow* resize_shadow =
ash::Shell::Get()->resize_shadow_controller()->GetShadowForWindowForTest(
widget->GetNativeWindow());
ASSERT_TRUE(resize_shadow);
shell_surface->root_surface()->SetFrame(SurfaceFrameType::SHADOW);
shell_surface->root_surface()->Commit();
ui::Shadow* normal_shadow =
wm::ShadowController::GetShadowForWindow(widget->GetNativeWindow());
ASSERT_TRUE(normal_shadow);
// ash::ResizeShadow::InitParams set the default |thickness| to 8.
const int kResizeShadowThickness = 8;
EXPECT_EQ(gfx::Size(size.width() + kResizeShadowThickness, size.height()),
resize_shadow->GetLayerForTest()->bounds().size());
EXPECT_EQ(size, normal_shadow->content_bounds().size());
gfx::Size new_size(100, 100);
gfx::Rect new_bounds(new_size);
// Resize the widget and set geometry.
shell_surface->StartResize(HTBOTTOMRIGHT);
shell_surface->SetWidgetBounds(new_bounds);
shell_surface->SetGeometry(new_bounds);
// Shadow bounds are updated as soon as the widget bounds change.
EXPECT_EQ(
gfx::Size(new_size.width() + kResizeShadowThickness, new_size.height()),
resize_shadow->GetLayerForTest()->bounds().size());
EXPECT_EQ(new_size, normal_shadow->content_bounds().size());
}
TEST_F(ShellSurfaceTest, PropertyResolverTest) {
class TestPropertyResolver : public exo::WMHelper::AppPropertyResolver {
public:
TestPropertyResolver() = default;
~TestPropertyResolver() override = default;
void PopulateProperties(
const Params& params,
ui::PropertyHandler& out_properties_container) override {
if (expected_app_id == params.app_id) {
out_properties_container.AcquireAllPropertiesFrom(
std::move(params.for_creation ? properties_for_creation
: properties_after_creation));
}
}
std::string expected_app_id;
ui::PropertyHandler properties_for_creation;
ui::PropertyHandler properties_after_creation;
};
std::unique_ptr<TestPropertyResolver> resolver_holder =
std::make_unique<TestPropertyResolver>();
auto* resolver = resolver_holder.get();
WMHelper::GetInstance()->RegisterAppPropertyResolver(
std::move(resolver_holder));
resolver->properties_for_creation.SetProperty(ash::kShelfItemTypeKey, 1);
resolver->properties_after_creation.SetProperty(ash::kShelfItemTypeKey, 2);
resolver->expected_app_id = "test";
// Make sure that properties are properly populated for both
// "before widget creation", and "after widget creation".
{
// TODO(oshima): create a test API to create a shell surface.
gfx::Size buffer_size(256, 256);
auto buffer = std::make_unique<Buffer>(
exo_test_helper()->CreateGpuMemoryBuffer(buffer_size));
auto surface = std::make_unique<Surface>();
auto shell_surface = std::make_unique<ShellSurface>(surface.get());
surface->SetApplicationId("test");
surface->Attach(buffer.get());
surface->Commit();
EXPECT_EQ(1, shell_surface->GetWidget()->GetNativeWindow()->GetProperty(
ash::kShelfItemTypeKey));
surface->SetApplicationId("test");
EXPECT_EQ(2, shell_surface->GetWidget()->GetNativeWindow()->GetProperty(
ash::kShelfItemTypeKey));
}
// Make sure that properties are will not be popluated when the app ids
// mismatch "before" and "after" widget is created.
resolver->properties_for_creation.SetProperty(ash::kShelfItemTypeKey, 1);
resolver->properties_after_creation.SetProperty(ash::kShelfItemTypeKey, 2);
{
gfx::Size buffer_size(256, 256);
auto buffer = std::make_unique<Buffer>(
exo_test_helper()->CreateGpuMemoryBuffer(buffer_size));
auto surface = std::make_unique<Surface>();
auto shell_surface = std::make_unique<ShellSurface>(surface.get());
surface->SetApplicationId("testx");
surface->Attach(buffer.get());
surface->Commit();
EXPECT_NE(1, shell_surface->GetWidget()->GetNativeWindow()->GetProperty(
ash::kShelfItemTypeKey));
surface->SetApplicationId("testy");
surface->Attach(buffer.get());
surface->Commit();
EXPECT_NE(1, shell_surface->GetWidget()->GetNativeWindow()->GetProperty(
ash::kShelfItemTypeKey));
EXPECT_NE(2, shell_surface->GetWidget()->GetNativeWindow()->GetProperty(
ash::kShelfItemTypeKey));
// Updating to the matching |app_id| should set the window property.
surface->SetApplicationId("test");
EXPECT_EQ(2, shell_surface->GetWidget()->GetNativeWindow()->GetProperty(
ash::kShelfItemTypeKey));
}
}
TEST_F(ShellSurfaceTest, Overlay) {
auto shell_surface =
test::ShellSurfaceBuilder({100, 100}).BuildShellSurface();
shell_surface->GetWidget()->GetNativeWindow()->SetProperty(
aura::client::kSkipImeProcessing, true);
EXPECT_FALSE(shell_surface->HasOverlay());
auto textfield = std::make_unique<views::Textfield>();
auto* textfield_ptr = textfield.get();
ShellSurfaceBase::OverlayParams params(std::move(textfield));
shell_surface->AddOverlay(std::move(params));
EXPECT_TRUE(shell_surface->HasOverlay());
EXPECT_NE(shell_surface->GetWidget()->GetFocusManager()->GetFocusedView(),
textfield_ptr);
EXPECT_TRUE(shell_surface->GetWidget()->widget_delegate()->CanResize());
EXPECT_EQ(gfx::Size(100, 100), shell_surface->overlay_widget_for_testing()
->GetWindowBoundsInScreen()
.size());
PressAndReleaseKey(ui::VKEY_X);
EXPECT_EQ(textfield_ptr->GetText(), u"");
ui::test::EventGenerator* generator = GetEventGenerator();
generator->MoveMouseToCenterOf(shell_surface->GetWidget()->GetNativeWindow());
generator->ClickLeftButton();
// Test normal key input, which should go through IME.
EXPECT_EQ(shell_surface->GetWidget()->GetFocusManager()->GetFocusedView(),
textfield_ptr);
PressAndReleaseKey(ui::VKEY_X);
EXPECT_EQ(textfield_ptr->GetText(), u"x");
EXPECT_TRUE(textfield_ptr->GetSelectedText().empty());
// Controls (Select all) should work.
PressAndReleaseKey(ui::VKEY_A, ui::EF_CONTROL_DOWN);
EXPECT_EQ(textfield_ptr->GetText(), u"x");
EXPECT_EQ(textfield_ptr->GetSelectedText(), u"x");
auto* widget = ash::TestWidgetBuilder()
.SetBounds(gfx::Rect(200, 200))
.BuildOwnedByNativeWidget();
ASSERT_TRUE(widget->IsActive());
PressAndReleaseKey(ui::VKEY_Y);
EXPECT_EQ(textfield_ptr->GetText(), u"x");
EXPECT_EQ(textfield_ptr->GetSelectedText(), u"x");
// Re-activate the surface and make sure that the overlay can still handle
// keys.
shell_surface->GetWidget()->Activate();
// The current text will be replaced with new character because
// the text is selected.
PressAndReleaseKey(ui::VKEY_Y);
EXPECT_EQ(textfield_ptr->GetText(), u"y");
EXPECT_TRUE(textfield_ptr->GetSelectedText().empty());
}
TEST_F(ShellSurfaceTest, OverlayOverlapsFrame) {
auto shell_surface =
test::ShellSurfaceBuilder({100, 100}).BuildShellSurface();
shell_surface->GetWidget()->GetNativeWindow()->SetProperty(
aura::client::kSkipImeProcessing, true);
shell_surface->OnSetFrame(SurfaceFrameType::NORMAL);
EXPECT_FALSE(shell_surface->HasOverlay());
ShellSurfaceBase::OverlayParams params(std::make_unique<views::View>());
params.overlaps_frame = false;
shell_surface->AddOverlay(std::move(params));
EXPECT_TRUE(shell_surface->HasOverlay());
{
gfx::Size overlay_size =
shell_surface->GetWidget()->GetWindowBoundsInScreen().size();
overlay_size.set_height(overlay_size.height() - views::kCaptionButtonWidth);
EXPECT_EQ(overlay_size, shell_surface->overlay_widget_for_testing()
->GetWindowBoundsInScreen()
.size());
}
shell_surface->OnSetFrame(SurfaceFrameType::NONE);
{
gfx::Size overlay_size =
shell_surface->GetWidget()->GetWindowBoundsInScreen().size();
EXPECT_EQ(overlay_size, shell_surface->overlay_widget_for_testing()
->GetWindowBoundsInScreen()
.size());
}
}
TEST_F(ShellSurfaceTest, OverlayCanResize) {
auto shell_surface =
test::ShellSurfaceBuilder({100, 100}).BuildShellSurface();
shell_surface->GetWidget()->GetNativeWindow()->SetProperty(
aura::client::kSkipImeProcessing, true);
shell_surface->OnSetFrame(SurfaceFrameType::NORMAL);
EXPECT_FALSE(shell_surface->HasOverlay());
{
ShellSurfaceBase::OverlayParams params(std::make_unique<views::View>());
params.can_resize = false;
shell_surface->AddOverlay(std::move(params));
}
EXPECT_FALSE(shell_surface->GetWidget()->widget_delegate()->CanResize());
shell_surface->RemoveOverlay();
{
ShellSurfaceBase::OverlayParams params(std::make_unique<views::View>());
params.can_resize = true;
shell_surface->AddOverlay(std::move(params));
}
EXPECT_TRUE(shell_surface->GetWidget()->widget_delegate()->CanResize());
}
class TestWindowObserver : public WMHelper::ExoWindowObserver {
public:
TestWindowObserver() {}
TestWindowObserver(const TestWindowObserver&) = delete;
TestWindowObserver& operator=(const TestWindowObserver&) = delete;
// WMHelper::ExoWindowObserver overrides
void OnExoWindowCreated(aura::Window* window) override {
windows_.push_back(window);
}
const std::vector<aura::Window*>& observed_windows() { return windows_; }
private:
std::vector<aura::Window*> windows_;
};
TEST_F(ShellSurfaceTest, NotifyOnWindowCreation) {
auto shell_surface =
test::ShellSurfaceBuilder({100, 100}).SetNoCommit().BuildShellSurface();
TestWindowObserver observer;
WMHelper::GetInstance()->AddExoWindowObserver(&observer);
// Committing a surface triggers window creation if it isn't already attached
// to the root.
shell_surface->surface_for_testing()->Commit();
EXPECT_EQ(1u, observer.observed_windows().size());
}
TEST_F(ShellSurfaceTest, Reparent) {
auto shell_surface1 = test::ShellSurfaceBuilder({20, 20}).BuildShellSurface();
views::Widget* widget1 = shell_surface1->GetWidget();
// Create a second window.
auto shell_surface2 = test::ShellSurfaceBuilder({20, 20}).BuildShellSurface();
views::Widget* widget2 = shell_surface2->GetWidget();
auto child_shell_surface =
test::ShellSurfaceBuilder({20, 20}).BuildShellSurface();
child_shell_surface->SetParent(shell_surface1.get());
views::Widget* child_widget = child_shell_surface->GetWidget();
// By default, a child widget is not activatable. Explicitly make it
// activatable so that calling child_surface->RequestActivation() is
// possible.
child_widget->widget_delegate()->SetCanActivate(true);
GrantPermissionToActivateIndefinitely(widget1->GetNativeWindow());
GrantPermissionToActivateIndefinitely(widget2->GetNativeWindow());
GrantPermissionToActivateIndefinitely(child_widget->GetNativeWindow());
shell_surface2->Activate();
EXPECT_FALSE(child_widget->ShouldPaintAsActive());
EXPECT_FALSE(widget1->ShouldPaintAsActive());
EXPECT_TRUE(widget2->ShouldPaintAsActive());
child_shell_surface->Activate();
// A widget should have the same paint-as-active state with its parent.
EXPECT_TRUE(child_widget->ShouldPaintAsActive());
EXPECT_TRUE(widget1->ShouldPaintAsActive());
EXPECT_FALSE(widget2->ShouldPaintAsActive());
// Reparent child to widget2.
child_shell_surface->SetParent(shell_surface2.get());
EXPECT_TRUE(child_widget->ShouldPaintAsActive());
EXPECT_TRUE(widget2->ShouldPaintAsActive());
EXPECT_FALSE(widget1->ShouldPaintAsActive());
shell_surface1->Activate();
EXPECT_FALSE(child_widget->ShouldPaintAsActive());
EXPECT_FALSE(widget2->ShouldPaintAsActive());
EXPECT_TRUE(widget1->ShouldPaintAsActive());
// Delete widget1 (i.e. the non-parent widget) shouldn't crash.
widget1->Close();
shell_surface1.reset();
child_shell_surface->Activate();
EXPECT_TRUE(child_widget->ShouldPaintAsActive());
EXPECT_TRUE(widget2->ShouldPaintAsActive());
}
TEST_F(ShellSurfaceTest, ThrottleFrameRate) {
auto shell_surface = test::ShellSurfaceBuilder({20, 20}).BuildShellSurface();
SurfaceObserverForTest observer;
shell_surface->root_surface()->AddSurfaceObserver(&observer);
aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
EXPECT_CALL(observer, ThrottleFrameRate(true));
window->SetProperty(ash::kFrameRateThrottleKey, true);
EXPECT_CALL(observer, ThrottleFrameRate(false));
window->SetProperty(ash::kFrameRateThrottleKey, false);
shell_surface->root_surface()->RemoveSurfaceObserver(&observer);
}
namespace {
struct ShellSurfaceCallbacks {
struct ConfigureState {
gfx::Rect bounds;
chromeos::WindowStateType state_type;
bool resizing;
bool activated;
};
uint32_t OnConfigure(const gfx::Rect& bounds,
chromeos::WindowStateType state_type,
bool resizing,
bool activated,
const gfx::Vector2d& origin_offset) {
configure_state.emplace();
*configure_state = {bounds, state_type, resizing, activated};
return serial++;
}
void OnOriginChange(const gfx::Point& origin_) { origin = origin_; }
void Reset() {
configure_state.reset();
origin.reset();
}
absl::optional<ConfigureState> configure_state;
absl::optional<gfx::Point> origin;
int32_t serial = 1;
};
} // namespace
TEST_F(ShellSurfaceTest, ScreenCoordinates) {
auto shell_surface = test::ShellSurfaceBuilder({20, 20}).BuildShellSurface();
ShellSurfaceCallbacks callbacks;
shell_surface->set_configure_callback(base::BindRepeating(
&ShellSurfaceCallbacks::OnConfigure, base::Unretained(&callbacks)));
shell_surface->set_origin_change_callback(base::BindRepeating(
&ShellSurfaceCallbacks::OnOriginChange, base::Unretained(&callbacks)));
shell_surface->SetWindowBounds(gfx::Rect(10, 10, 300, 300));
ASSERT_TRUE(!!callbacks.configure_state);
ASSERT_TRUE(!callbacks.origin);
EXPECT_EQ(callbacks.configure_state->bounds, gfx::Rect(10, 10, 300, 300));
callbacks.Reset();
shell_surface->SetWindowBounds(gfx::Rect(100, 100, 300, 300));
ASSERT_TRUE(!callbacks.configure_state);
ASSERT_TRUE(!!callbacks.origin);
EXPECT_EQ(*callbacks.origin, gfx::Point(100, 100));
callbacks.Reset();
shell_surface->SetWindowBounds(gfx::Rect(0, 0, 300000, 300000));
ASSERT_TRUE(!!callbacks.configure_state);
EXPECT_EQ(callbacks.configure_state->bounds,
display::Screen::GetScreen()->GetPrimaryDisplay().work_area());
}
TEST_F(ShellSurfaceTest, InitialBounds) {
{
gfx::Size size(20, 30);
auto shell_surface =
test::ShellSurfaceBuilder(size).SetNoCommit().BuildShellSurface();
EXPECT_FALSE(shell_surface->GetWidget());
gfx::Rect bounds(gfx::Point(35, 135), size);
shell_surface->SetWindowBounds(bounds);
shell_surface->root_surface()->Commit();
ASSERT_TRUE(shell_surface->GetWidget());
EXPECT_EQ(bounds, shell_surface->GetWidget()->GetWindowBoundsInScreen());
}
{
// Requesting larger than display work area bounds should not be allowed.
gfx::Size size(2000, 3000);
auto shell_surface =
test::ShellSurfaceBuilder(size).SetNoCommit().BuildShellSurface();
EXPECT_FALSE(shell_surface->GetWidget());
gfx::Rect bounds(gfx::Point(35, 135), size);
shell_surface->SetWindowBounds(bounds);
shell_surface->root_surface()->Commit();
ASSERT_TRUE(shell_surface->GetWidget());
EXPECT_EQ(display::Screen::GetScreen()->GetPrimaryDisplay().work_area(),
shell_surface->GetWidget()->GetWindowBoundsInScreen());
}
}
// Make sure that the centering logic can use the correct size
// even if there is a pending configure.
TEST_F(ShellSurfaceTest, InitialCenteredBoundsWithConfigure) {
auto shell_surface = test::ShellSurfaceBuilder(gfx::Size(0, 0))
.SetNoRootBuffer()
.SetNoCommit()
.BuildShellSurface();
ShellSurfaceCallbacks callbacks;
shell_surface->set_configure_callback(base::BindRepeating(
&ShellSurfaceCallbacks::OnConfigure, base::Unretained(&callbacks)));
shell_surface->root_surface()->Commit();
EXPECT_FALSE(shell_surface->GetWidget()->IsVisible());
gfx::Size size(256, 256);
auto new_buffer =
std::make_unique<Buffer>(exo_test_helper()->CreateGpuMemoryBuffer(size));
shell_surface->root_surface()->Attach(new_buffer.get());
shell_surface->root_surface()->Commit();
EXPECT_TRUE(shell_surface->GetWidget()->IsVisible());
gfx::Rect expected =
display::Screen::GetScreen()->GetPrimaryDisplay().work_area();
expected.ClampToCenteredSize(size);
EXPECT_EQ(expected, shell_surface->GetWidget()->GetWindowBoundsInScreen());
}
// Test that restore info is set correctly.
TEST_F(ShellSurfaceTest, SetRestoreInfo) {
int32_t restore_session_id = 200;
int32_t restore_window_id = 100;
gfx::Size size(20, 30);
auto shell_surface =
test::ShellSurfaceBuilder(size).SetNoCommit().BuildShellSurface();
shell_surface->SetRestoreInfo(restore_session_id, restore_window_id);
shell_surface->Restore();
shell_surface->root_surface()->Commit();
EXPECT_TRUE(shell_surface->GetWidget()->IsVisible());
EXPECT_EQ(restore_session_id,
shell_surface->GetWidget()->GetNativeWindow()->GetProperty(
app_restore::kWindowIdKey));
EXPECT_EQ(restore_window_id,
shell_surface->GetWidget()->GetNativeWindow()->GetProperty(
app_restore::kRestoreWindowIdKey));
}
// Surfaces without non-client view should not crash.
TEST_F(ShellSurfaceTest, NoNonClientViewWithConfigure) {
// Popup windows don't have a non-client view.
auto shell_surface =
test::ShellSurfaceBuilder({20, 20}).SetAsPopup().BuildShellSurface();
ShellSurfaceCallbacks callbacks;
// Having a configure callback leads to a call to GetClientBoundsInScreen().
shell_surface->set_configure_callback(base::BindRepeating(
&ShellSurfaceCallbacks::OnConfigure, base::Unretained(&callbacks)));
shell_surface->SetWindowBounds(gfx::Rect(10, 10, 300, 300));
EXPECT_TRUE(callbacks.configure_state);
EXPECT_EQ(callbacks.configure_state->bounds, gfx::Rect(10, 10, 300, 300));
}
TEST_F(ShellSurfaceTest, WindowIsResizableWithEmptySizeConstraints) {
auto shell_surface = test::ShellSurfaceBuilder({20, 20})
.SetMinimumSize(gfx::Size(0, 0))
.SetMaximumSize(gfx::Size(0, 0))
.BuildShellSurface();
EXPECT_TRUE(shell_surface->GetWidget()->widget_delegate()->CanResize());
}
TEST_F(ShellSurfaceTest, SetSystemModal) {
std::unique_ptr<ShellSurface> shell_surface =
test::ShellSurfaceBuilder({256, 256})
.SetMaximumSize(gfx::Size(10, 10))
.SetUseSystemModalContainer()
.SetNoCommit()
.BuildShellSurface();
shell_surface->SetSystemModal(true);
shell_surface->root_surface()->Commit();
EXPECT_EQ(ui::MODAL_TYPE_SYSTEM, shell_surface->GetModalType());
EXPECT_FALSE(shell_surface->frame_enabled());
}
} // namespace exo