| // Copyright 2015 The Chromium Authors |
| // 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 <sstream> |
| #include <vector> |
| |
| #include "ash/capture_mode/capture_mode_test_util.h" |
| #include "ash/constants/app_types.h" |
| #include "ash/frame/non_client_frame_view_ash.h" |
| #include "ash/frame_throttler/frame_throttling_controller.h" |
| #include "ash/frame_throttler/mock_frame_throttling_observer.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/overview/overview_controller.h" |
| #include "ash/wm/overview/overview_test_util.h" |
| #include "ash/wm/raster_scale/raster_scale_controller.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/functional/bind.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/test/bind.h" |
| #include "base/test/mock_callback.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "chromeos/constants/chromeos_features.h" |
| #include "chromeos/ui/base/window_properties.h" |
| #include "components/app_restore/window_properties.h" |
| #include "components/exo/buffer.h" |
| #include "components/exo/client_controlled_shell_surface.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/mock_security_delegate.h" |
| #include "components/exo/test/shell_surface_builder.h" |
| #include "components/exo/test/test_security_delegate.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/aura/window_event_dispatcher.h" |
| #include "ui/aura/window_targeter.h" |
| #include "ui/aura/window_tree_host.h" |
| #include "ui/base/hit_test.h" |
| #include "ui/compositor/compositor.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/compositor/scoped_animation_duration_scale_mode.h" |
| #include "ui/compositor_extra/shadow.h" |
| #include "ui/display/display.h" |
| #include "ui/display/display_layout_builder.h" |
| #include "ui/display/manager/display_manager.h" |
| #include "ui/display/screen.h" |
| #include "ui/display/test/display_manager_test_api.h" |
| #include "ui/display/types/display_constants.h" |
| #include "ui/events/base_event_utils.h" |
| #include "ui/events/event.h" |
| #include "ui/gfx/geometry/rounded_corners_f.h" |
| #include "ui/views/controls/textfield/textfield.h" |
| #include "ui/views/widget/any_widget_observer.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/views/window/caption_button_layout_constants.h" |
| #include "ui/wm/core/shadow_controller.h" |
| #include "ui/wm/core/shadow_types.h" |
| #include "ui/wm/core/window_properties.h" |
| #include "ui/wm/core/window_util.h" |
| |
| namespace exo { |
| |
| const gfx::BufferFormat kOpaqueFormat = gfx::BufferFormat::RGBX_8888; |
| |
| using ShellSurfaceTest = test::ExoTestBase; |
| |
| namespace { |
| |
| 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, |
| float raster_scale, |
| aura::Window::OcclusionState occlusion_state, |
| std::optional<chromeos::WindowStateType> restore_state_type) { |
| 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(); |
| } |
| |
| struct ConfigureData { |
| gfx::Rect suggested_bounds; |
| chromeos::WindowStateType state_type = chromeos::WindowStateType::kDefault; |
| bool is_resizing = false; |
| bool is_active = false; |
| std::optional<chromeos::WindowStateType> restore_state_type = std::nullopt; |
| float raster_scale = 1.0f; |
| aura::Window::OcclusionState occlusion_state; |
| size_t count = 0; |
| }; |
| |
| uint32_t Configure( |
| ConfigureData* config_data, |
| const gfx::Rect& bounds, |
| chromeos::WindowStateType state_type, |
| bool resizing, |
| bool activated, |
| const gfx::Vector2d& origin_offset, |
| float raster_scale, |
| aura::Window::OcclusionState occlusion_state, |
| std::optional<chromeos::WindowStateType> restore_state_type) { |
| config_data->suggested_bounds = bounds; |
| config_data->state_type = state_type; |
| config_data->is_resizing = resizing; |
| config_data->is_active = activated; |
| config_data->raster_scale = raster_scale; |
| config_data->occlusion_state = occlusion_state; |
| config_data->restore_state_type = restore_state_type; |
| config_data->count++; |
| return 0; |
| } |
| |
| uint32_t ConfigureSerial( |
| ConfigureData* config_data, |
| const gfx::Rect& bounds, |
| chromeos::WindowStateType state_type, |
| bool resizing, |
| bool activated, |
| const gfx::Vector2d& origin_offset, |
| float raster_scale, |
| aura::Window::OcclusionState occlusion_state, |
| std::optional<chromeos::WindowStateType> restore_state_type) { |
| config_data->suggested_bounds = bounds; |
| config_data->state_type = state_type; |
| config_data->is_resizing = resizing; |
| config_data->is_active = activated; |
| config_data->raster_scale = raster_scale; |
| config_data->occlusion_state = occlusion_state; |
| config_data->restore_state_type = restore_state_type; |
| config_data->count++; |
| return config_data->count; |
| } |
| |
| uint32_t ConfigureSerialVec( |
| std::vector<ConfigureData>* config_vec, |
| const gfx::Rect& bounds, |
| chromeos::WindowStateType state_type, |
| bool resizing, |
| bool activated, |
| const gfx::Vector2d& origin_offset, |
| float raster_scale, |
| aura::Window::OcclusionState occlusion_state, |
| std::optional<chromeos::WindowStateType> restore_state_type) { |
| config_vec->emplace_back(config_vec->empty() ? ConfigureData{} |
| : config_vec->back()); |
| return ConfigureSerial(&config_vec->back(), bounds, state_type, resizing, |
| activated, origin_offset, raster_scale, |
| occlusion_state, restore_state_type); |
| } |
| |
| bool IsCaptureWindow(ShellSurface* shell_surface) { |
| aura::Window* window = shell_surface->GetWidget()->GetNativeWindow(); |
| return WMHelper::GetInstance()->GetCaptureClient()->GetCaptureWindow() == |
| window; |
| } |
| |
| cc::Region CreateRegion(ui::Layer::ShapeRects shape_rects) { |
| cc::Region shape_region; |
| for (const gfx::Rect& rect : shape_rects) { |
| shape_region.Union(rect); |
| } |
| return shape_region; |
| } |
| |
| } // namespace |
| |
| TEST_F(ShellSurfaceTest, AcknowledgeConfigure) { |
| constexpr gfx::Size kBufferSize(32, 32); |
| auto shell_surface = |
| test::ShellSurfaceBuilder(kBufferSize).BuildShellSurface(); |
| auto* surface = shell_surface->root_surface(); |
| |
| gfx::Point origin(100, 100); |
| shell_surface->GetWidget()->SetBounds(gfx::Rect(origin, kBufferSize)); |
| 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, display::kInvalidDisplayId); |
| |
| // 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); |
| auto fullscreen_buffer = |
| test::ExoTestHelper::CreateBuffer(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) { |
| constexpr gfx::Size kBufferSize(256, 256); |
| auto parent_shell_surface = |
| test::ShellSurfaceBuilder(kBufferSize).BuildShellSurface(); |
| auto* parent_surface = parent_shell_surface->root_surface(); |
| |
| auto shell_surface = test::ShellSurfaceBuilder(kBufferSize) |
| .SetParent(parent_shell_surface.get()) |
| .BuildShellSurface(); |
| 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, |
| 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) { |
| constexpr gfx::Size kBufferSize(256, 256); |
| auto parent_shell_surface = |
| test::ShellSurfaceBuilder(kBufferSize).BuildShellSurface(); |
| |
| auto child1_shell_surface = test::ShellSurfaceBuilder(kBufferSize) |
| .SetParent(parent_shell_surface.get()) |
| .BuildShellSurface(); |
| auto child2_shell_surface = test::ShellSurfaceBuilder(kBufferSize) |
| .SetParent(parent_shell_surface.get()) |
| .BuildShellSurface(); |
| auto child3_shell_surface = test::ShellSurfaceBuilder(kBufferSize) |
| .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, CommitAndConfigure) { |
| // Test that a commit produces an expected configure. A commit to a surface |
| // without a buffer attached should produce a configure with zero size. Then |
| // once a buffer is attached, a commit should produce a configure |
| // corresponding to the size of the buffer. |
| auto shell_surface = |
| test::ShellSurfaceBuilder().SetNoCommit().BuildShellSurface(); |
| |
| uint32_t serial = 0; |
| gfx::Rect bounds; |
| aura::Window::OcclusionState occlusion_state; |
| auto configure_callback = base::BindRepeating( |
| [](uint32_t* const serial_ptr, gfx::Rect* bounds_ptr, |
| aura::Window::OcclusionState* occlusion_state_ptr, |
| const gfx::Rect& bounds, chromeos::WindowStateType state_type, |
| bool resizing, bool activated, const gfx::Vector2d& origin_offset, |
| float raster_scale, aura::Window::OcclusionState occlusion_state, |
| std::optional<chromeos::WindowStateType>) { |
| *bounds_ptr = bounds; |
| *occlusion_state_ptr = occlusion_state; |
| return ++(*serial_ptr); |
| }, |
| &serial, &bounds, &occlusion_state); |
| shell_surface->set_configure_callback(configure_callback); |
| |
| // Receiving a commit without a buffer should result in an initial configure |
| // with bounds gfx::Rect(0, 0, 0, 0). Note that although `ShellSurface` does |
| // not implement xdg-shell, in xdg-shell spec sending 0x0 bounds is used to |
| // hint to a client that the bounds should be determined by the client (i.e. |
| // ShellSurface implicitly follows xdg-shell spec here). |
| shell_surface->root_surface()->Commit(); |
| ASSERT_EQ(1u, serial); |
| EXPECT_EQ(bounds, gfx::Rect(0, 0, 0, 0)); |
| |
| // Attaching a buffer and committing should produce a single configure with |
| // the size equal to the buffer size. |
| constexpr gfx::Size kBufferSize(64, 64); |
| auto buffer = test::ExoTestHelper::CreateBuffer(kBufferSize); |
| shell_surface->root_surface()->Attach(buffer.get()); |
| shell_surface->root_surface()->Commit(); |
| ASSERT_EQ(2u, serial); |
| EXPECT_EQ(bounds.size(), kBufferSize); |
| // The occlusion state should be visible since there is no other window |
| // occluding the surface. |
| EXPECT_EQ(occlusion_state, aura::Window::OcclusionState::VISIBLE); |
| } |
| |
| TEST_F(ShellSurfaceTest, Maximize) { |
| auto shell_surface = |
| test::ShellSurfaceBuilder({256, 256}).BuildShellSurface(); |
| auto* surface = shell_surface->root_surface(); |
| EXPECT_TRUE(shell_surface->IsReady()); |
| |
| 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) { |
| auto shell_surface = |
| test::ShellSurfaceBuilder({400, 300}).BuildShellSurface(); |
| |
| // 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) { |
| constexpr gfx::Size kBufferSize(400, 300); |
| auto shell_surface = test::ShellSurfaceBuilder(kBufferSize) |
| .SetMinimumSize(kBufferSize) |
| .SetMaximumSize(kBufferSize) |
| .BuildShellSurface(); |
| |
| // 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, display::kInvalidDisplayId); |
| 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, display::kInvalidDisplayId); |
| 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) { |
| constexpr gfx::Size kBufferSize(256, 256); |
| auto shell_surface = |
| test::ShellSurfaceBuilder(kBufferSize).SetNoCommit().BuildShellSurface(); |
| |
| EXPECT_FALSE(shell_surface->IsReady()); |
| 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()); |
| EXPECT_FALSE(shell_surface->IsReady()); |
| |
| // Committing the buffer will create a widget with minimized state. |
| views::NamedWidgetShownWaiter widget_waiter( |
| views::test::AnyWidgetTestPasskey{}, "ExoShellSurface-0"); |
| uint32_t serial = 0; |
| chromeos::WindowStateType state[1]{chromeos::WindowStateType::kNormal}; |
| auto configure_callback = base::BindRepeating( |
| [](uint32_t* const serial_ptr, chromeos::WindowStateType* state_ptr, |
| const gfx::Rect& bounds, chromeos::WindowStateType state_type, |
| bool resizing, bool activated, const gfx::Vector2d& origin_offset, |
| float raster_scale, aura::Window::OcclusionState occlusion_state, |
| std::optional<chromeos::WindowStateType>) { |
| state_ptr[*serial_ptr] = state_type; |
| CHECK(*serial_ptr < 2); |
| return ++(*serial_ptr); |
| }, |
| &serial, state); |
| shell_surface->set_configure_callback(configure_callback); |
| |
| shell_surface->root_surface()->Commit(); |
| EXPECT_TRUE(shell_surface->GetWidget()->IsMinimized()); |
| |
| // Two configures (initial configure and the state change configure) should be |
| // sent with the minimzied state. |
| ASSERT_EQ(1u, serial); |
| EXPECT_EQ(chromeos::WindowStateType::kMinimized, state[0]); |
| shell_surface->set_configure_callback(ShellSurface::ConfigureCallback()); |
| |
| // Minimized widget should be Shown. |
| widget_waiter.WaitIfNeededAndGet(); |
| |
| EXPECT_TRUE(shell_surface->IsReady()); |
| |
| shell_surface->Restore(); |
| EXPECT_FALSE(shell_surface->GetWidget()->IsMinimized()); |
| |
| auto child_shell_surface = |
| test::ShellSurfaceBuilder(kBufferSize).SetNoCommit().BuildShellSurface(); |
| auto* child_surface = child_shell_surface->root_surface(); |
| |
| // Transient shell surfaces cannot be minimized. |
| child_surface->SetParent(shell_surface->root_surface(), gfx::Point()); |
| child_surface->Commit(); |
| EXPECT_FALSE(child_shell_surface->CanMinimize()); |
| } |
| |
| TEST_F(ShellSurfaceTest, Restore) { |
| constexpr gfx::Size kBufferSize(256, 256); |
| auto shell_surface = |
| test::ShellSurfaceBuilder(kBufferSize).BuildShellSurface(); |
| |
| 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( |
| kBufferSize.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, display::kInvalidDisplayId); |
| 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, display::kInvalidDisplayId); |
| 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) { |
| constexpr gfx::Point kOrigin(0, 0); |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetOrigin(kOrigin) |
| .BuildShellSurface(); |
| |
| shell_surface->root_surface()->SetSurfaceHierarchyContentBoundsForTest( |
| gfx::Rect(0, 0, 50, 50)); |
| |
| // Host Window Bounds size before committing. |
| EXPECT_EQ(gfx::Rect(0, 0, 256, 256), 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, HostWindowBoundsUpdatedWithNegativeCoordinate) { |
| constexpr gfx::Point kOrigin(20, 20); |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetOrigin(kOrigin) |
| .BuildShellSurface(); |
| |
| // Set content bounds to negative and larger than surface. This happens when |
| // subsurfaces are outside of root surface boundary. |
| shell_surface->root_surface()->SetSurfaceHierarchyContentBoundsForTest( |
| gfx::Rect(-20, -20, 300, 300)); |
| |
| // Host Window Bounds size before committing. |
| EXPECT_EQ(gfx::Rect(0, 0, 256, 256), shell_surface->host_window()->bounds()); |
| EXPECT_TRUE(shell_surface->OnPreWidgetCommit()); |
| shell_surface->CommitWidget(); |
| // CommitWidget should update the Host Window Bounds. |
| EXPECT_EQ(gfx::Rect(-20, -20, 300, 300), |
| shell_surface->host_window()->bounds()); |
| // Root surface origin must be adjusted relative to host window. |
| EXPECT_EQ(gfx::Point(20, 20), shell_surface->root_surface_origin_pixel()); |
| } |
| |
| TEST_F(ShellSurfaceTest, HostWindowIncludesAllSubSurfaces) { |
| constexpr gfx::Point kOrigin(20, 20); |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetOrigin(kOrigin) |
| .BuildShellSurface(); |
| |
| constexpr gfx::Size kChildBufferSize(32, 32); |
| |
| // Add child buffer at the upper-left corner of the root surface. |
| auto child_buffer1 = test::ExoTestHelper::CreateBuffer(kChildBufferSize); |
| auto child_surface1 = std::make_unique<Surface>(); |
| child_surface1->Attach(child_buffer1.get()); |
| auto subsurface1 = std::make_unique<SubSurface>( |
| child_surface1.get(), shell_surface->root_surface()); |
| subsurface1->SetPosition(gfx::PointF(-10, -10)); |
| child_surface1->Commit(); |
| |
| // Add child buffer at the bottom-right corner of the root surface. |
| auto child_buffer2 = test::ExoTestHelper::CreateBuffer(kChildBufferSize); |
| auto child_surface2 = std::make_unique<Surface>(); |
| child_surface2->Attach(child_buffer2.get()); |
| auto subsurface2 = std::make_unique<SubSurface>( |
| child_surface2.get(), shell_surface->root_surface()); |
| subsurface2->SetPosition(gfx::PointF(250, 250)); |
| child_surface2->Commit(); |
| |
| shell_surface->root_surface()->Commit(); |
| |
| ASSERT_TRUE(shell_surface->GetWidget()); |
| shell_surface->SetGeometry(gfx::Rect(0, 0, 256, 256)); |
| shell_surface->root_surface()->Commit(); |
| |
| // Host window must be set to include all children subsurfaces. |
| EXPECT_EQ(gfx::Rect(-10, -10, 292, 292), |
| shell_surface->host_window()->bounds()); |
| // Root surface origin must be adjusted relative to host window. |
| EXPECT_EQ(gfx::Point(10, 10), shell_surface->root_surface_origin_pixel()); |
| } |
| |
| TEST_F(ShellSurfaceTest, HostWindowIncludesAllSubSurfacesWithScaleFactor) { |
| constexpr gfx::Point kOrigin(20, 20); |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetOrigin(kOrigin) |
| .BuildShellSurface(); |
| |
| // Set scale. |
| constexpr float kScaleFactor = 2.0; |
| shell_surface->set_client_submits_surfaces_in_pixel_coordinates(true); |
| shell_surface->SetScaleFactor(kScaleFactor); |
| |
| constexpr gfx::Size kChildBufferSize(32, 32); |
| |
| // Add child buffer at the upper-left corner of the root surface. |
| auto child_buffer1 = test::ExoTestHelper::CreateBuffer(kChildBufferSize); |
| auto child_surface1 = std::make_unique<Surface>(); |
| child_surface1->Attach(child_buffer1.get()); |
| auto subsurface1 = std::make_unique<SubSurface>( |
| child_surface1.get(), shell_surface->root_surface()); |
| subsurface1->SetPosition(gfx::PointF(-10, -10)); |
| child_surface1->Commit(); |
| |
| // Add child buffer at the bottom-right corner of the root surface. |
| auto child_buffer2 = test::ExoTestHelper::CreateBuffer(kChildBufferSize); |
| auto child_surface2 = std::make_unique<Surface>(); |
| child_surface2->Attach(child_buffer2.get()); |
| auto subsurface2 = std::make_unique<SubSurface>( |
| child_surface2.get(), shell_surface->root_surface()); |
| subsurface2->SetPosition(gfx::PointF(250, 250)); |
| child_surface2->Commit(); |
| |
| shell_surface->root_surface()->Commit(); |
| |
| ASSERT_TRUE(shell_surface->GetWidget()); |
| shell_surface->SetGeometry(gfx::Rect(0, 0, 256, 256)); |
| shell_surface->root_surface()->Commit(); |
| |
| // Host window must be set to include all children subsurfaces. |
| EXPECT_EQ(gfx::Rect(-5, -5, 146, 146), |
| shell_surface->host_window()->bounds()); |
| // Root surface origin must be adjusted relative to host window. |
| EXPECT_EQ(gfx::Point(10, 10), shell_surface->root_surface_origin_pixel()); |
| } |
| |
| TEST_F(ShellSurfaceTest, HostWindowNotIncludeAugmentedChild) { |
| constexpr gfx::Point kOrigin(20, 20); |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetOrigin(kOrigin) |
| .BuildShellSurface(); |
| |
| constexpr gfx::Size kChildBufferSize(32, 32); |
| |
| // Add child buffer at the upper-right corner of the root surface. |
| auto child_buffer1 = test::ExoTestHelper::CreateBuffer(kChildBufferSize); |
| auto child_surface1 = std::make_unique<Surface>(); |
| child_surface1->Attach(child_buffer1.get()); |
| child_surface1->set_is_augmented(true); |
| child_surface1->SetClipRect(std::make_optional(gfx::RectF(5, 5, 32, 32))); |
| auto subsurface1 = std::make_unique<SubSurface>( |
| child_surface1.get(), shell_surface->root_surface()); |
| subsurface1->SetPosition(gfx::PointF(-10, -10)); |
| child_surface1->Commit(); |
| |
| // Add child buffer at the bottom-left corner of the root surface. |
| auto child_buffer2 = test::ExoTestHelper::CreateBuffer(kChildBufferSize); |
| auto child_surface2 = std::make_unique<Surface>(); |
| child_surface2->Attach(child_buffer2.get()); |
| auto subsurface2 = std::make_unique<SubSurface>( |
| child_surface2.get(), shell_surface->root_surface()); |
| subsurface2->SetPosition(gfx::PointF(-10, 250)); |
| child_surface2->Commit(); |
| |
| shell_surface->root_surface()->Commit(); |
| |
| ASSERT_TRUE(shell_surface->GetWidget()); |
| shell_surface->SetGeometry(gfx::Rect(256, 256)); |
| shell_surface->root_surface()->Commit(); |
| |
| // Host window must be set to include all children subsurfaces, but not the |
| // clipped area. |
| EXPECT_EQ(gfx::Rect(-10, 0, 266, 282), |
| shell_surface->host_window()->bounds()); |
| // Root surface origin must be adjusted relative to host window. |
| EXPECT_EQ(gfx::Point(10, 0), shell_surface->root_surface_origin_pixel()); |
| } |
| |
| TEST_F(ShellSurfaceTest, HostWindowNotIncludeAugmentedChildWithScaleFactor) { |
| constexpr gfx::Point kOrigin(20, 20); |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetOrigin(kOrigin) |
| .BuildShellSurface(); |
| |
| // Set scale. |
| constexpr float kScaleFactor = 2.0; |
| shell_surface->set_client_submits_surfaces_in_pixel_coordinates(true); |
| shell_surface->SetScaleFactor(kScaleFactor); |
| |
| constexpr gfx::Size kChildBufferSize(32, 32); |
| |
| // Add child buffer at the upper-right corner of the root surface. |
| auto child_buffer1 = test::ExoTestHelper::CreateBuffer(kChildBufferSize); |
| auto child_surface1 = std::make_unique<Surface>(); |
| child_surface1->Attach(child_buffer1.get()); |
| child_surface1->set_is_augmented(true); |
| child_surface1->SetClipRect(std::make_optional(gfx::RectF(5, 5, 32, 32))); |
| auto subsurface1 = std::make_unique<SubSurface>( |
| child_surface1.get(), shell_surface->root_surface()); |
| subsurface1->SetPosition(gfx::PointF(-10, -10)); |
| child_surface1->Commit(); |
| |
| // Add child buffer at the bottom-left corner of the root surface. |
| auto child_buffer2 = test::ExoTestHelper::CreateBuffer(kChildBufferSize); |
| auto child_surface2 = std::make_unique<Surface>(); |
| child_surface2->Attach(child_buffer2.get()); |
| auto subsurface2 = std::make_unique<SubSurface>( |
| child_surface2.get(), shell_surface->root_surface()); |
| subsurface2->SetPosition(gfx::PointF(-10, 250)); |
| child_surface2->Commit(); |
| |
| shell_surface->root_surface()->Commit(); |
| |
| ASSERT_TRUE(shell_surface->GetWidget()); |
| shell_surface->SetGeometry(gfx::Rect(0, 0, 256, 256)); |
| shell_surface->root_surface()->Commit(); |
| |
| // Host window must be set to include all children subsurfaces, but not the |
| // clipped area. |
| EXPECT_EQ(gfx::Rect(-5, 0, 133, 141), shell_surface->host_window()->bounds()); |
| // Root surface origin must be adjusted relative to host window. |
| EXPECT_EQ(gfx::Point(10, 0), shell_surface->root_surface_origin_pixel()); |
| } |
| |
| TEST_F(ShellSurfaceTest, LocalSurfaceIdUpdatedOnHostWindowOriginChanged) { |
| constexpr gfx::Point kOrigin(100, 100); |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({100, 100}) |
| .SetOrigin(kOrigin) |
| .SetGeometry(gfx::Rect(100, 100)) |
| .BuildShellSurface(); |
| |
| auto* root_surface = shell_surface->root_surface(); |
| |
| auto child_buffer = test::ExoTestHelper::CreateBuffer(gfx::Size(200, 200)); |
| auto child_surface = std::make_unique<Surface>(); |
| child_surface->Attach(child_buffer.get()); |
| auto subsurface = |
| std::make_unique<SubSurface>(child_surface.get(), root_surface); |
| subsurface->SetPosition(gfx::PointF(-50, -50)); |
| |
| child_surface->Commit(); |
| root_surface->Commit(); |
| EXPECT_EQ(gfx::Rect(-50, -50, 200, 200), |
| shell_surface->host_window()->bounds()); |
| |
| // Store the current local surface id. |
| const viz::LocalSurfaceId old_id = |
| shell_surface->GetSurfaceId().local_surface_id(); |
| |
| // If nothing is changed, no need to update local surface id. |
| child_surface->Commit(); |
| root_surface->Commit(); |
| EXPECT_EQ(shell_surface->GetSurfaceId().local_surface_id(), old_id); |
| |
| // If the host window origin is updated, need to update local surface id. |
| subsurface->SetPosition(gfx::PointF(-25, -25)); |
| child_surface->Commit(); |
| root_surface->Commit(); |
| EXPECT_EQ(gfx::Rect(-25, -25, 200, 200), |
| shell_surface->host_window()->bounds()); |
| EXPECT_TRUE( |
| shell_surface->GetSurfaceId().local_surface_id().IsNewerThan(old_id)); |
| |
| EXPECT_EQ(gfx::Vector2dF(), |
| shell_surface->host_window()->layer()->GetSubpixelOffset()); |
| } |
| |
| TEST_F(ShellSurfaceTest, |
| LocalSurfaceIdUpdatedOnHostWindowOriginChangedWithScaleFactor) { |
| constexpr gfx::Point kOrigin(100, 100); |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({100, 100}) |
| .SetOrigin(kOrigin) |
| .SetGeometry(gfx::Rect(100, 100)) |
| .BuildShellSurface(); |
| |
| // Set scale. |
| constexpr float kScaleFactor = 2.0; |
| shell_surface->set_client_submits_surfaces_in_pixel_coordinates(true); |
| shell_surface->SetScaleFactor(kScaleFactor); |
| |
| auto* root_surface = shell_surface->root_surface(); |
| |
| auto child_buffer = test::ExoTestHelper::CreateBuffer(gfx::Size(200, 200)); |
| auto child_surface = std::make_unique<Surface>(); |
| child_surface->Attach(child_buffer.get()); |
| auto subsurface = |
| std::make_unique<SubSurface>(child_surface.get(), root_surface); |
| subsurface->SetPosition(gfx::PointF(-50, -50)); |
| |
| child_surface->Commit(); |
| root_surface->Commit(); |
| EXPECT_EQ(gfx::Rect(-25, -25, 100, 100), |
| shell_surface->host_window()->bounds()); |
| |
| // Store the current local surface id. |
| const viz::LocalSurfaceId old_id = |
| shell_surface->GetSurfaceId().local_surface_id(); |
| |
| // If nothing is changed, no need to update local surface id. |
| child_surface->Commit(); |
| root_surface->Commit(); |
| EXPECT_EQ(shell_surface->GetSurfaceId().local_surface_id(), old_id); |
| |
| // If the host window origin is updated, need to update local surface id. |
| subsurface->SetPosition(gfx::PointF(-25, -25)); |
| child_surface->Commit(); |
| root_surface->Commit(); |
| EXPECT_EQ(gfx::Rect(-12, -12, 100, 100), |
| shell_surface->host_window()->bounds()); |
| EXPECT_TRUE( |
| shell_surface->GetSurfaceId().local_surface_id().IsNewerThan(old_id)); |
| |
| EXPECT_EQ(gfx::Vector2dF(-0.5, -0.5), |
| shell_surface->host_window()->layer()->GetSubpixelOffset()); |
| } |
| |
| TEST_F(ShellSurfaceTest, |
| LocalSurfaceIdNotUpdatedOnSurfaceOutOfWindowButClipped) { |
| constexpr gfx::Point kOrigin(100, 100); |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({100, 100}) |
| .SetOrigin(kOrigin) |
| .SetGeometry(gfx::Rect(100, 100)) |
| .BuildShellSurface(); |
| |
| auto* root_surface = shell_surface->root_surface(); |
| |
| auto child_buffer = test::ExoTestHelper::CreateBuffer(gfx::Size(200, 200)); |
| auto child_surface = std::make_unique<Surface>(); |
| child_surface->Attach(child_buffer.get()); |
| child_surface->set_is_augmented(true); |
| child_surface->SetClipRect(std::make_optional(gfx::RectF(50, 50, 100, 100))); |
| auto subsurface = |
| std::make_unique<SubSurface>(child_surface.get(), root_surface); |
| subsurface->SetPosition(gfx::PointF(-50, -50)); |
| |
| child_surface->Commit(); |
| root_surface->Commit(); |
| EXPECT_EQ(gfx::Rect(100, 100), shell_surface->host_window()->bounds()); |
| |
| // Store the current local surface id. |
| const viz::LocalSurfaceId old_id = |
| shell_surface->GetSurfaceId().local_surface_id(); |
| |
| // If nothing is changed, no need to update local surface id. |
| child_surface->Commit(); |
| root_surface->Commit(); |
| EXPECT_EQ(shell_surface->GetSurfaceId().local_surface_id(), old_id); |
| |
| // If the surface is moving around the out of window while it's clipped, we do |
| // not allocate local surface id. |
| child_surface->SetClipRect(std::make_optional(gfx::RectF(25, 25, 100, 100))); |
| subsurface->SetPosition(gfx::PointF(-25, -25)); |
| child_surface->Commit(); |
| root_surface->Commit(); |
| EXPECT_EQ(gfx::Rect(100, 100), shell_surface->host_window()->bounds()); |
| EXPECT_EQ(old_id, shell_surface->GetSurfaceId().local_surface_id()); |
| |
| EXPECT_EQ(gfx::Vector2dF(), |
| shell_surface->host_window()->layer()->GetSubpixelOffset()); |
| } |
| |
| TEST_F(ShellSurfaceTest, EventTargetWithNegativeHostWindowOrigin) { |
| constexpr gfx::Point kOrigin(20, 20); |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetOrigin(kOrigin) |
| .SetGeometry(gfx::Rect(256, 256)) |
| .SetInputRegion(gfx::Rect(256, 256)) |
| .SetFrame(SurfaceFrameType::SHADOW) |
| .BuildShellSurface(); |
| |
| auto* root_surface = shell_surface->root_surface(); |
| |
| // Add child buffer at the upper-left corner of the root surface with empty |
| // input region. |
| auto* child_surface1 = test::ShellSurfaceBuilder::AddChildSurface( |
| root_surface, gfx::Rect(-10, -10, 32, 32)); |
| child_surface1->SetInputRegion(cc::Region()); |
| // Add child buffer at the bottom-right corner of the root surface with empty |
| // input region. |
| auto* child_surface2 = test::ShellSurfaceBuilder::AddChildSurface( |
| root_surface, gfx::Rect(250, 250, 32, 32)); |
| child_surface2->SetInputRegion(cc::Region()); |
| |
| child_surface1->Commit(); |
| child_surface2->Commit(); |
| root_surface->Commit(); |
| |
| ASSERT_TRUE(shell_surface->GetWidget()); |
| aura::Window* window = shell_surface->GetWidget()->GetNativeWindow(); |
| aura::Window* root_window = window->GetRootWindow(); |
| ui::EventTargeter* targeter = |
| root_window->GetHost()->dispatcher()->GetDefaultEventTargeter(); |
| |
| { |
| // Mouse is in the middle of the root surface. |
| ui::MouseEvent event(ui::ET_MOUSE_MOVED, gfx::Point(120, 120), |
| gfx::Point(120, 120), ui::EventTimeForNow(), |
| ui::EF_NONE, ui::EF_NONE); |
| EXPECT_EQ(root_surface->window(), |
| targeter->FindTargetForEvent(root_window, &event)); |
| } |
| |
| { |
| // Mouse is on upper-left of the root surface. |
| ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(21, 21), |
| gfx::Point(21, 21), ui::EventTimeForNow(), ui::EF_NONE, |
| ui::EF_NONE); |
| EXPECT_EQ(root_surface->window(), |
| targeter->FindTargetForEvent(root_window, &event)); |
| } |
| |
| { |
| // Mouse is on bottom-right of the root surface. |
| ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(275, 275), |
| gfx::Point(275, 275), ui::EventTimeForNow(), |
| ui::EF_NONE, ui::EF_NONE); |
| EXPECT_EQ(root_surface->window(), |
| targeter->FindTargetForEvent(root_window, &event)); |
| } |
| |
| { |
| // Mouse is outside of the root surface and host window. |
| ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(300, 300), |
| gfx::Point(300, 300), ui::EventTimeForNow(), |
| ui::EF_NONE, ui::EF_NONE); |
| EXPECT_FALSE(window->Contains(static_cast<aura::Window*>( |
| targeter->FindTargetForEvent(root_window, &event)))); |
| } |
| |
| { |
| // Mouse is on the left side of the root surface but inside host window. |
| ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(19, 100), |
| gfx::Point(19, 100), ui::EventTimeForNow(), |
| ui::EF_NONE, ui::EF_NONE); |
| EXPECT_FALSE(window->Contains(static_cast<aura::Window*>( |
| targeter->FindTargetForEvent(root_window, &event)))); |
| } |
| |
| { |
| // Mouse is on the right side of the root surface but inside host window. |
| ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(277, 100), |
| gfx::Point(277, 100), ui::EventTimeForNow(), |
| ui::EF_NONE, ui::EF_NONE); |
| EXPECT_FALSE(window->Contains(static_cast<aura::Window*>( |
| targeter->FindTargetForEvent(root_window, &event)))); |
| } |
| |
| { |
| // Mouse is above the root surface but inside host window. |
| ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(100, 19), |
| gfx::Point(100, 19), ui::EventTimeForNow(), |
| ui::EF_NONE, ui::EF_NONE); |
| EXPECT_FALSE(window->Contains(static_cast<aura::Window*>( |
| targeter->FindTargetForEvent(root_window, &event)))); |
| } |
| |
| { |
| // Mouse is below the root surface but inside host window. |
| ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(100, 277), |
| gfx::Point(100, 277), ui::EventTimeForNow(), |
| ui::EF_NONE, ui::EF_NONE); |
| EXPECT_FALSE(window->Contains(static_cast<aura::Window*>( |
| targeter->FindTargetForEvent(root_window, &event)))); |
| } |
| |
| { |
| // Mouse is on `child_surface1` but not on the root surface. |
| ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(19, 19), |
| gfx::Point(19, 19), ui::EventTimeForNow(), ui::EF_NONE, |
| ui::EF_NONE); |
| EXPECT_FALSE(window->Contains(static_cast<aura::Window*>( |
| targeter->FindTargetForEvent(root_window, &event)))); |
| } |
| |
| { |
| // Mouse is on `child_surface2` but not on the root surface. |
| ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(277, 277), |
| gfx::Point(277, 277), ui::EventTimeForNow(), |
| ui::EF_NONE, ui::EF_NONE); |
| EXPECT_FALSE(window->Contains(static_cast<aura::Window*>( |
| targeter->FindTargetForEvent(root_window, &event)))); |
| } |
| } |
| |
| TEST_F(ShellSurfaceTest, |
| EventTargetWithNegativeHostWindowOriginWithScaleFactor) { |
| constexpr gfx::Point kOrigin(20, 20); |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetOrigin(kOrigin) |
| .SetGeometry(gfx::Rect(256, 256)) |
| .SetInputRegion(gfx::Rect(256, 256)) |
| .SetFrame(SurfaceFrameType::SHADOW) |
| .BuildShellSurface(); |
| |
| // Set scale. |
| constexpr float kScaleFactor = 2.0; |
| shell_surface->set_client_submits_surfaces_in_pixel_coordinates(true); |
| shell_surface->SetScaleFactor(kScaleFactor); |
| |
| auto* root_surface = shell_surface->root_surface(); |
| |
| // Add child buffer at the upper-left corner of the root surface with empty |
| // input region. |
| auto* child_surface1 = test::ShellSurfaceBuilder::AddChildSurface( |
| root_surface, gfx::Rect(-10, -10, 32, 32)); |
| child_surface1->SetInputRegion(cc::Region()); |
| // Add child buffer at the bottom-right corner of the root surface with empty |
| // input region. |
| auto* child_surface2 = test::ShellSurfaceBuilder::AddChildSurface( |
| root_surface, gfx::Rect(250, 250, 32, 32)); |
| child_surface2->SetInputRegion(cc::Region()); |
| |
| child_surface1->Commit(); |
| child_surface2->Commit(); |
| root_surface->Commit(); |
| |
| ASSERT_TRUE(shell_surface->GetWidget()); |
| aura::Window* window = shell_surface->GetWidget()->GetNativeWindow(); |
| aura::Window* root_window = window->GetRootWindow(); |
| ui::EventTargeter* targeter = |
| root_window->GetHost()->dispatcher()->GetDefaultEventTargeter(); |
| |
| { |
| // Mouse is in the middle of the root surface. |
| ui::MouseEvent event(ui::ET_MOUSE_MOVED, gfx::Point(80, 80), |
| gfx::Point(80, 80), ui::EventTimeForNow(), ui::EF_NONE, |
| ui::EF_NONE); |
| EXPECT_EQ(root_surface->window(), |
| targeter->FindTargetForEvent(root_window, &event)); |
| } |
| |
| { |
| // Mouse is on upper-left of the root surface. |
| ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(21, 21), |
| gfx::Point(21, 21), ui::EventTimeForNow(), ui::EF_NONE, |
| ui::EF_NONE); |
| EXPECT_EQ(root_surface->window(), |
| targeter->FindTargetForEvent(root_window, &event)); |
| } |
| |
| { |
| // Mouse is on bottom-right of the root surface. |
| ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(147, 147), |
| gfx::Point(147, 147), ui::EventTimeForNow(), |
| ui::EF_NONE, ui::EF_NONE); |
| EXPECT_EQ(root_surface->window(), |
| targeter->FindTargetForEvent(root_window, &event)); |
| } |
| |
| { |
| // Mouse is outside of the root surface and host window. |
| ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(200, 200), |
| gfx::Point(200, 200), ui::EventTimeForNow(), |
| ui::EF_NONE, ui::EF_NONE); |
| EXPECT_FALSE(window->Contains(static_cast<aura::Window*>( |
| targeter->FindTargetForEvent(root_window, &event)))); |
| } |
| |
| { |
| // Mouse is on the left side of the root surface but inside host window. |
| ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(19, 100), |
| gfx::Point(19, 100), ui::EventTimeForNow(), |
| ui::EF_NONE, ui::EF_NONE); |
| EXPECT_FALSE(window->Contains(static_cast<aura::Window*>( |
| targeter->FindTargetForEvent(root_window, &event)))); |
| } |
| |
| { |
| // Mouse is on the right side of the root surface but inside host window. |
| ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(149, 100), |
| gfx::Point(149, 100), ui::EventTimeForNow(), |
| ui::EF_NONE, ui::EF_NONE); |
| EXPECT_FALSE(window->Contains(static_cast<aura::Window*>( |
| targeter->FindTargetForEvent(root_window, &event)))); |
| } |
| |
| { |
| // Mouse is above the root surface but inside host window. |
| ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(100, 19), |
| gfx::Point(100, 19), ui::EventTimeForNow(), |
| ui::EF_NONE, ui::EF_NONE); |
| EXPECT_FALSE(window->Contains(static_cast<aura::Window*>( |
| targeter->FindTargetForEvent(root_window, &event)))); |
| } |
| |
| { |
| // Mouse is below the root surface but inside host window. |
| ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(100, 149), |
| gfx::Point(100, 149), ui::EventTimeForNow(), |
| ui::EF_NONE, ui::EF_NONE); |
| EXPECT_FALSE(window->Contains(static_cast<aura::Window*>( |
| targeter->FindTargetForEvent(root_window, &event)))); |
| } |
| |
| { |
| // Mouse is on `child_surface1` but not on the root surface. |
| ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(19, 19), |
| gfx::Point(19, 19), ui::EventTimeForNow(), ui::EF_NONE, |
| ui::EF_NONE); |
| EXPECT_FALSE(window->Contains(static_cast<aura::Window*>( |
| targeter->FindTargetForEvent(root_window, &event)))); |
| } |
| |
| { |
| // Mouse is on `child_surface2` but not on the root surface. |
| ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(149, 149), |
| gfx::Point(149, 149), ui::EventTimeForNow(), |
| ui::EF_NONE, ui::EF_NONE); |
| EXPECT_FALSE(window->Contains(static_cast<aura::Window*>( |
| targeter->FindTargetForEvent(root_window, &event)))); |
| } |
| } |
| |
| TEST_F(ShellSurfaceTest, SetFullscreen) { |
| auto shell_surface = |
| test::ShellSurfaceBuilder({256, 256}).SetNoCommit().BuildShellSurface(); |
| auto* surface = shell_surface->root_surface(); |
| |
| shell_surface->SetFullscreen(true, display::kInvalidDisplayId); |
| surface->Commit(); |
| EXPECT_FALSE(HasBackdrop()); |
| EXPECT_EQ(GetContext()->bounds().ToString(), |
| shell_surface->GetWidget()->GetWindowBoundsInScreen().ToString()); |
| shell_surface->SetFullscreen(false, display::kInvalidDisplayId); |
| 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, display::kInvalidDisplayId); |
| 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, display::kInvalidDisplayId); |
| shell_surface->Maximize(); |
| EXPECT_EQ(shell_surface->GetWidget(), nullptr); |
| shell_surface->root_surface()->Commit(); |
| EXPECT_TRUE(shell_surface->GetWidget()->IsFullscreen()); |
| } |
| |
| TEST_F(ShellSurfaceTest, SetTitle) { |
| auto shell_surface = |
| test::ShellSurfaceBuilder({256, 256}).BuildShellSurface(); |
| |
| shell_surface->SetTitle(std::u16string(u"test")); |
| |
| // 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) { |
| auto shell_surface = |
| test::ShellSurfaceBuilder({64, 64}).SetNoCommit().BuildShellSurface(); |
| auto* surface = shell_surface->root_surface(); |
| |
| EXPECT_FALSE(shell_surface->GetWidget()); |
| shell_surface->SetApplicationId("pre-widget-id"); |
| |
| 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, ActivationPermissionLegacy) { |
| auto shell_surface = test::ShellSurfaceBuilder({64, 64}).BuildShellSurface(); |
| |
| 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, WidgetActivationLegacy) { |
| constexpr gfx::Size kBufferSize(64, 64); |
| auto security_delegate = std::make_unique<test::TestSecurityDelegate>(); |
| |
| auto shell_surface1 = test::ShellSurfaceBuilder(kBufferSize) |
| .SetSecurityDelegate(security_delegate.get()) |
| .BuildShellSurface(); |
| auto* surface1 = shell_surface1->root_surface(); |
| |
| // The window is active. |
| views::Widget* widget1 = shell_surface1->GetWidget(); |
| EXPECT_TRUE(widget1->IsActive()); |
| |
| // Create a second window. |
| auto shell_surface2 = test::ShellSurfaceBuilder(kBufferSize) |
| .SetSecurityDelegate(security_delegate.get()) |
| .BuildShellSurface(); |
| auto* surface2 = shell_surface2->root_surface(); |
| |
| // 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, WidgetActivation) { |
| test::MockSecurityDelegate security_delegate; |
| constexpr gfx::Size kBufferSize(64, 64); |
| std::unique_ptr<ShellSurface> shell_surface1 = |
| test::ShellSurfaceBuilder(kBufferSize) |
| .SetSecurityDelegate(&security_delegate) |
| .BuildShellSurface(); |
| |
| // The window is active. |
| views::Widget* widget1 = shell_surface1->GetWidget(); |
| EXPECT_TRUE(widget1->IsActive()); |
| |
| // Create a second window. |
| std::unique_ptr<ShellSurface> shell_surface2 = |
| test::ShellSurfaceBuilder(kBufferSize) |
| .SetSecurityDelegate(&security_delegate) |
| .BuildShellSurface(); |
| |
| // Now the second window is active. |
| views::Widget* widget2 = shell_surface2->GetWidget(); |
| EXPECT_FALSE(widget1->IsActive()); |
| EXPECT_TRUE(widget2->IsActive()); |
| |
| // The first window can activate itself. |
| EXPECT_CALL(security_delegate, CanSelfActivate(widget1->GetNativeWindow())) |
| .WillOnce(testing::Return(true)); |
| shell_surface1->surface_for_testing()->RequestActivation(); |
| EXPECT_TRUE(widget1->IsActive()); |
| EXPECT_FALSE(widget2->IsActive()); |
| |
| // The second window cannot activate itself. |
| EXPECT_CALL(security_delegate, CanSelfActivate(widget2->GetNativeWindow())) |
| .WillOnce(testing::Return(false)); |
| shell_surface2->surface_for_testing()->RequestActivation(); |
| EXPECT_TRUE(widget1->IsActive()); |
| EXPECT_FALSE(widget2->IsActive()); |
| } |
| |
| TEST_F(ShellSurfaceTest, EmulateOverrideRedirect) { |
| constexpr gfx::Size kBufferSize(64, 64); |
| auto buffer = test::ExoTestHelper::CreateBuffer(kBufferSize); |
| 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) { |
| auto shell_surface = |
| test::ShellSurfaceBuilder({64, 64}).SetNoCommit().BuildShellSurface(); |
| auto* surface = shell_surface->root_surface(); |
| |
| EXPECT_FALSE(shell_surface->GetWidget()); |
| shell_surface->SetStartupId("pre-widget-id"); |
| |
| 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, AckRotateFocus) { |
| std::unique_ptr<ShellSurface> surface1 = |
| test::ShellSurfaceBuilder({256, 256}).BuildShellSurface(); |
| |
| uint32_t serial = 0; |
| |
| auto dummy_cb = base::BindLambdaForTesting( |
| [&serial](ash::FocusCycler::Direction, bool) { return serial; }); |
| |
| views::View* v1 = new views::View(); |
| v1->SetFocusBehavior(views::View::FocusBehavior::ALWAYS); |
| surface1->AddChildView(v1); |
| surface1->set_rotate_focus_callback(dummy_cb); |
| |
| std::unique_ptr<ShellSurface> surface2 = |
| test::ShellSurfaceBuilder({256, 256}).BuildShellSurface(); |
| views::View* v2 = new views::View(); |
| v2->SetFocusBehavior(views::View::FocusBehavior::ALWAYS); |
| surface2->AddChildView(v2); |
| surface2->set_rotate_focus_callback(dummy_cb); |
| |
| std::unique_ptr<ShellSurface> surface3 = |
| test::ShellSurfaceBuilder({256, 256}).BuildShellSurface(); |
| views::View* v3 = new views::View(); |
| v3->SetFocusBehavior(views::View::FocusBehavior::ALWAYS); |
| surface3->AddChildView(v3); |
| surface3->set_rotate_focus_callback(dummy_cb); |
| |
| ash::Shell::Get()->focus_cycler()->AddWidget(surface1->GetWidget()); |
| ash::Shell::Get()->focus_cycler()->AddWidget(surface2->GetWidget()); |
| ash::Shell::Get()->focus_cycler()->AddWidget(surface3->GetWidget()); |
| |
| // We will do most of our testing with surface2 because it is in the middle. |
| // This will allow us to easily test directional logic. |
| ash::Shell::Get()->focus_cycler()->FocusWidget(surface2->GetWidget()); |
| ASSERT_TRUE(surface2->GetWidget()->IsActive()); |
| |
| // Test handled. This should result in no rotation. |
| surface2->RotatePaneFocusFromView(v2, true, false); |
| surface2->AckRotateFocus(serial++, true); |
| ASSERT_TRUE(surface2->GetWidget()->IsActive()); |
| |
| surface2->RotatePaneFocusFromView(v2, true, false); |
| surface2->AckRotateFocus(serial++, true); |
| ASSERT_TRUE(surface2->GetWidget()->IsActive()); |
| |
| // Now test unhandled in the forward direction. The next widget should be |
| // focused. |
| surface2->RotatePaneFocusFromView(v2, true, false); |
| surface2->AckRotateFocus(serial++, false); |
| ASSERT_TRUE(surface3->GetWidget()->IsActive()); |
| |
| // Reset |
| ash::Shell::Get()->focus_cycler()->FocusWidget(surface2->GetWidget()); |
| ASSERT_TRUE(surface2->GetWidget()->IsActive()); |
| |
| // Now test unhandled in the forward direction. The next widget should be |
| // focused. |
| surface2->RotatePaneFocusFromView(v2, false, false); |
| surface2->AckRotateFocus(serial++, false); |
| ASSERT_TRUE(surface1->GetWidget()->IsActive()); |
| } |
| |
| TEST_F(ShellSurfaceTest, RotatePaneFocusFromView) { |
| using ::testing::Return; |
| |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({256, 256}).BuildShellSurface(); |
| base::MockRepeatingCallback<uint32_t(ash::FocusCycler::Direction, bool)> cb; |
| shell_surface->set_rotate_focus_callback(cb.Get()); |
| |
| auto serial = 0; |
| |
| EXPECT_CALL(cb, Run(ash::FocusCycler::FORWARD, true)) |
| .WillOnce(Return(serial++)); |
| auto rotated = shell_surface->RotatePaneFocusFromView(nullptr, true, true); |
| // Async operations always return successful rotation immediately. |
| EXPECT_TRUE(rotated); |
| |
| EXPECT_CALL(cb, Run(ash::FocusCycler::BACKWARD, false)) |
| .WillOnce(Return(serial++)); |
| rotated = shell_surface->RotatePaneFocusFromView(nullptr, false, false); |
| EXPECT_TRUE(rotated); |
| } |
| |
| TEST_F(ShellSurfaceTest, RotatePaneFocusFromView_NoCallback) { |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({256, 256}).BuildShellSurface(); |
| |
| auto rotated = shell_surface->RotatePaneFocusFromView(nullptr, true, true); |
| // No focusable view for the shell surface. This should result in a |
| // non-rotation using the base rotation logic. |
| EXPECT_FALSE(rotated); |
| } |
| |
| TEST_F(ShellSurfaceTest, StartMove) { |
| auto shell_surface = test::ShellSurfaceBuilder({64, 64}).BuildShellSurface(); |
| |
| ASSERT_TRUE(shell_surface->GetWidget()); |
| |
| aura::Env::GetInstance()->set_mouse_button_flags(ui::EF_LEFT_MOUSE_BUTTON); |
| // The interactive move should end when surface is destroyed. |
| ASSERT_TRUE(shell_surface->StartMove()); |
| |
| // Test that destroying the shell surface before move ends is OK. |
| shell_surface.reset(); |
| } |
| |
| TEST_F(ShellSurfaceTest, StartResize) { |
| constexpr gfx::Size kBufferSize(64, 64); |
| auto buffer = test::ExoTestHelper::CreateBuffer(kBufferSize); |
| 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()); |
| |
| aura::Env::GetInstance()->set_mouse_button_flags(ui::EF_LEFT_MOUSE_BUTTON); |
| // The interactive resize should end when surface is destroyed. |
| ASSERT_TRUE(shell_surface->StartResize(HTBOTTOMRIGHT)); |
| |
| // Test that destroying the surface before resize ends is OK. |
| surface.reset(); |
| } |
| |
| TEST_F(ShellSurfaceTest, StartResizeAndDestroyShell) { |
| auto shell_surface = |
| test::ShellSurfaceBuilder({64, 64}).SetNoCommit().BuildShellSurface(); |
| auto* surface = shell_surface->root_surface(); |
| |
| 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, float raster_scale, |
| aura::Window::OcclusionState occlusion_state, |
| std::optional<chromeos::WindowStateType>) { return ++(*serial_ptr); }, |
| &serial); |
| |
| // Map shell surface. |
| shell_surface->set_configure_callback(configure_callback); |
| |
| surface->Commit(); |
| ASSERT_TRUE(shell_surface->GetWidget()); |
| |
| aura::Env::GetInstance()->set_mouse_button_flags(ui::EF_LEFT_MOUSE_BUTTON); |
| // The interactive resize should end when surface is destroyed. |
| ASSERT_TRUE(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, |
| float raster_scale, aura::Window::OcclusionState occlusion_state, |
| std::optional<chromeos::WindowStateType>) { |
| 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) { |
| constexpr gfx::Size kBufferSize(64, 64); |
| gfx::Rect geometry(16, 16, 32, 32); |
| auto shell_surface = test::ShellSurfaceBuilder(kBufferSize) |
| .SetGeometry(geometry) |
| .BuildShellSurface(); |
| |
| EXPECT_EQ( |
| geometry.size().ToString(), |
| shell_surface->GetWidget()->GetWindowBoundsInScreen().size().ToString()); |
| EXPECT_EQ(gfx::Rect(gfx::Point() - geometry.OffsetFromOrigin(), kBufferSize) |
| .ToString(), |
| shell_surface->host_window()->bounds().ToString()); |
| } |
| |
| TEST_F(ShellSurfaceTest, SetMinimumSize) { |
| constexpr gfx::Size kBufferSize(64, 64); |
| auto shell_surface = test::ShellSurfaceBuilder(kBufferSize) |
| .SetFrameColors(SK_ColorWHITE, SK_ColorWHITE) |
| .BuildShellSurface(); |
| auto* surface = shell_surface->root_surface(); |
| |
| constexpr gfx::Size kSizes[] = {{50, 50}, {100, 50}}; |
| for (const gfx::Size size : kSizes) { |
| SCOPED_TRACE( |
| base::StringPrintf("MinSize=%dx%d", size.width(), size.height())); |
| ConfigureData config_data; |
| shell_surface->set_configure_callback( |
| base::BindRepeating(&Configure, base::Unretained(&config_data))); |
| |
| shell_surface->SetMinimumSize(size); |
| 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 expected_size(kBufferSize); |
| expected_size.set_width(std::max(kBufferSize.width(), size.width())); |
| EXPECT_EQ(expected_size, |
| shell_surface->GetWidget()->GetWindowBoundsInScreen().size()); |
| if (kBufferSize.width() > size.width()) { |
| EXPECT_TRUE(config_data.suggested_bounds.IsEmpty()); |
| } else { |
| EXPECT_EQ(expected_size, config_data.suggested_bounds.size()); |
| } |
| } |
| // Reset configure callback because config_data is out of scope. |
| shell_surface->set_configure_callback(base::NullCallback()); |
| // With frame. |
| surface->SetFrame(SurfaceFrameType::NORMAL); |
| for (const gfx::Size size : kSizes) { |
| SCOPED_TRACE(base::StringPrintf("MinSize=%dx%d with frame", size.width(), |
| size.height())); |
| ConfigureData config_data; |
| shell_surface->set_configure_callback( |
| base::BindRepeating(&Configure, base::Unretained(&config_data))); |
| |
| const gfx::Size size_with_frame(size.width(), size.height() + 32); |
| shell_surface->SetMinimumSize(size); |
| surface->Commit(); |
| 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()); |
| gfx::Size expected_size(kBufferSize); |
| expected_size.set_width(std::max(kBufferSize.width(), size.width())); |
| if (kBufferSize.width() > size.width()) { |
| EXPECT_TRUE(config_data.suggested_bounds.IsEmpty()); |
| } else { |
| EXPECT_EQ(expected_size, config_data.suggested_bounds.size()); |
| } |
| shell_surface->set_configure_callback(base::NullCallback()); |
| } |
| } |
| |
| TEST_F(ShellSurfaceTest, SetMinimumSizeTooLargeAndTranform) { |
| auto* screen = display::Screen::GetScreen(); |
| auto fullscreen_bounds = screen->GetPrimaryDisplay().bounds(); |
| auto work_area_bounds = screen->GetPrimaryDisplay().work_area(); |
| |
| auto shell_surface = test::ShellSurfaceBuilder({64, 64}) |
| .SetMinimumSize(fullscreen_bounds.size()) |
| .SetMaximumSize(fullscreen_bounds.size()) |
| .SetBounds(work_area_bounds) |
| .BuildShellSurface(); |
| |
| auto* surface = shell_surface->root_surface(); |
| auto* widget = shell_surface->GetWidget(); |
| |
| EXPECT_EQ(work_area_bounds, widget->GetWindowBoundsInScreen()); |
| |
| widget->GetNativeWindow()->SetTransform( |
| gfx::Transform::Affine(1, 1, 1, 1, 10, 10)); |
| |
| // Updating the buffer with expected (work area) size should not |
| // update the widget's bounds even when the transform is applied. |
| auto buffer = test::ExoTestHelper::CreateBuffer(work_area_bounds.size()); |
| surface->Attach(buffer.get()); |
| surface->Commit(); |
| widget->GetNativeWindow()->SetTransform(gfx::Transform()); |
| |
| EXPECT_EQ(work_area_bounds, widget->GetWindowBoundsInScreen()); |
| } |
| |
| TEST_F(ShellSurfaceTest, SetMaximumSize) { |
| constexpr gfx::Size kBufferSize(256, 256); |
| auto shell_surface = |
| test::ShellSurfaceBuilder(kBufferSize).BuildShellSurface(); |
| auto* surface = shell_surface->root_surface(); |
| |
| constexpr gfx::Size kSizes[] = {{300, 300}, {200, 300}}; |
| for (const gfx::Size size : kSizes) { |
| SCOPED_TRACE( |
| base::StringPrintf("MaxSize=%dx%d", size.width(), size.height())); |
| ConfigureData config_data; |
| shell_surface->set_configure_callback( |
| base::BindRepeating(&Configure, base::Unretained(&config_data))); |
| |
| shell_surface->SetMaximumSize(size); |
| surface->Commit(); |
| EXPECT_EQ(size, shell_surface->GetMaximumSize()); |
| gfx::Size expected_size(kBufferSize); |
| expected_size.set_width(std::min(size.width(), kBufferSize.width())); |
| EXPECT_EQ(expected_size, |
| shell_surface->GetWidget()->GetWindowBoundsInScreen().size()); |
| |
| if (kBufferSize.width() < size.width()) { |
| EXPECT_TRUE(config_data.suggested_bounds.IsEmpty()); |
| } else { |
| EXPECT_EQ(expected_size, config_data.suggested_bounds.size()); |
| } |
| } |
| } |
| |
| 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) { |
| auto shell_surface = |
| test::ShellSurfaceBuilder({64, 64}).SetNoCommit().BuildShellSurface(); |
| auto* surface = shell_surface->root_surface(); |
| |
| 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->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()); |
| } |
| |
| TEST_F(ShellSurfaceTest, ConfigureCallbackSendsRestoreState) { |
| ConfigureData config_data; |
| auto shell_surface = test::ShellSurfaceBuilder({256, 256}) |
| .SetMaximumSize(gfx::Size(10, 10)) |
| .BuildShellSurface(); |
| shell_surface->set_configure_callback( |
| base::BindRepeating(&Configure, base::Unretained(&config_data))); |
| |
| shell_surface->root_surface()->Commit(); |
| shell_surface->Maximize(); |
| shell_surface->root_surface()->Commit(); |
| shell_surface->SetFullscreen(true, display::kInvalidDisplayId); |
| shell_surface->root_surface()->Commit(); |
| EXPECT_EQ(chromeos::WindowStateType::kFullscreen, config_data.state_type); |
| EXPECT_EQ(chromeos::WindowStateType::kMaximized, |
| config_data.restore_state_type.value()); |
| } |
| |
| TEST_F(ShellSurfaceTest, ConfigureCallback) { |
| // Must be before shell_surface so it outlives it, for shell_surface's |
| // destructor calls Configure() referencing these 4 variables. |
| ConfigureData config_data; |
| |
| auto shell_surface = |
| test::ShellSurfaceBuilder().SetNoCommit().BuildShellSurface(); |
| auto* surface = shell_surface->root_surface(); |
| |
| shell_surface->set_configure_callback( |
| base::BindRepeating(&Configure, base::Unretained(&config_data))); |
| |
| 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(); |
| ASSERT_TRUE(config_data.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(config_data.suggested_bounds.IsEmpty()); |
| EXPECT_EQ(maximized_bounds.size(), config_data.suggested_bounds.size()); |
| EXPECT_EQ(chromeos::WindowStateType::kMaximized, config_data.state_type); |
| |
| constexpr gfx::Size kBufferSize(64, 64); |
| auto buffer = test::ExoTestHelper::CreateBuffer(kBufferSize); |
| surface->Attach(buffer.get()); |
| surface->Commit(); |
| |
| EXPECT_TRUE(shell_surface->GetWidget()); |
| EXPECT_EQ(maximized_bounds.size(), config_data.suggested_bounds.size()); |
| EXPECT_EQ(chromeos::WindowStateType::kMaximized, config_data.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, display::kInvalidDisplayId); |
| shell_surface->AcknowledgeConfigure(0); |
| EXPECT_EQ(GetContext()->bounds().size(), config_data.suggested_bounds.size()); |
| EXPECT_EQ(chromeos::WindowStateType::kFullscreen, config_data.state_type); |
| shell_surface->SetFullscreen(false, display::kInvalidDisplayId); |
| shell_surface->AcknowledgeConfigure(0); |
| EXPECT_EQ(geometry.size(), shell_surface->CalculatePreferredSize()); |
| |
| shell_surface->GetWidget()->Activate(); |
| shell_surface->AcknowledgeConfigure(0); |
| EXPECT_TRUE(config_data.is_active); |
| shell_surface->GetWidget()->Deactivate(); |
| shell_surface->AcknowledgeConfigure(0); |
| EXPECT_FALSE(config_data.is_active); |
| |
| EXPECT_FALSE(config_data.is_resizing); |
| |
| aura::Env::GetInstance()->set_mouse_button_flags(ui::EF_LEFT_MOUSE_BUTTON); |
| ASSERT_TRUE(shell_surface->StartResize(HTBOTTOMRIGHT)); |
| shell_surface->AcknowledgeConfigure(0); |
| EXPECT_TRUE(config_data.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. |
| ConfigureData config_data; |
| |
| auto shell_surface = |
| test::ShellSurfaceBuilder().SetNoCommit().BuildShellSurface(); |
| auto* surface = shell_surface->root_surface(); |
| |
| shell_surface->set_configure_callback( |
| base::BindRepeating(&Configure, base::Unretained(&config_data))); |
| |
| 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(config_data.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. |
| ConfigureData config_data; |
| |
| auto shell_surface = |
| test::ShellSurfaceBuilder().SetNoCommit().BuildShellSurface(); |
| auto* surface = shell_surface->root_surface(); |
| |
| shell_surface->set_configure_callback( |
| base::BindRepeating(&Configure, base::Unretained(&config_data))); |
| |
| 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(config_data.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(), config_data.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. |
| ConfigureData config_data; |
| constexpr gfx::Size kBufferSize(256, 256); |
| |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder(kBufferSize) |
| .SetNoRootBuffer() |
| .BuildShellSurface(); |
| |
| shell_surface->set_configure_callback( |
| base::BindRepeating(&Configure, base::Unretained(&config_data))); |
| |
| 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 = test::ExoTestHelper::CreateBuffer(kBufferSize); |
| root_surface->Attach(buffer.get()); |
| |
| gfx::Rect geometry_full(kBufferSize); |
| 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. |
| ConfigureData config_data; |
| constexpr gfx::Size kBufferSize(256, 256); |
| auto buffer = test::ExoTestHelper::CreateBuffer(kBufferSize); |
| |
| auto shell_surface = |
| test::ShellSurfaceBuilder().SetNoCommit().BuildShellSurface(); |
| auto* surface = shell_surface->root_surface(); |
| |
| shell_surface->set_configure_callback( |
| base::BindRepeating(&Configure, base::Unretained(&config_data))); |
| |
| 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) { |
| constexpr gfx::Size kBufferSize(256, 256); |
| auto shell_surface = |
| test::ShellSurfaceBuilder(kBufferSize).BuildShellSurface(); |
| |
| EXPECT_FALSE(HasBackdrop()); |
| EXPECT_EQ(kBufferSize, |
| 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) { |
| auto shell_surface = |
| test::ShellSurfaceBuilder({64, 64}).SetNoCommit().BuildShellSurface(); |
| auto* surface = shell_surface->root_surface(); |
| |
| 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) { |
| constexpr gfx::Size kBufferSize(256, 256); |
| auto shell_surface = |
| test::ShellSurfaceBuilder(kBufferSize).BuildShellSurface(); |
| auto* surface = shell_surface->root_surface(); |
| |
| EXPECT_EQ(kBufferSize, |
| shell_surface->GetWidget()->GetWindowBoundsInScreen().size()); |
| |
| ash::WindowSnapWMEvent 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->Commit(); |
| |
| // Commit shouldn't change widget bounds when snapped. |
| EXPECT_EQ(GetContext()->bounds().width() / 2, |
| shell_surface->GetWidget()->GetWindowBoundsInScreen().width()); |
| } |
| |
| TEST_F(ShellSurfaceTest, ShellSurfaceMaximize) { |
| 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: Can't resize when max_size is set. |
| EXPECT_FALSE(window_state->CanMaximize()); |
| EXPECT_FALSE(window_state->CanSnap()); |
| |
| shell_surface->SetMaximumSize(gfx::Size(0, 0)); |
| shell_surface->root_surface()->Commit(); |
| |
| // Expect: Can resize without a max_size. |
| EXPECT_TRUE(window_state->CanMaximize()); |
| EXPECT_TRUE(window_state->CanSnap()); |
| } |
| |
| TEST_F(ShellSurfaceTest, ShellSurfaceMaxSizeResizabilityOnlyMaximise) { |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetMaximumSize(gfx::Size(10, 10)) |
| .SetMinimumSize(gfx::Size(0, 0)) |
| .BuildShellSurface(); |
| shell_surface->GetWidget()->GetNativeWindow()->SetProperty( |
| kMaximumSizeForResizabilityOnly, true); |
| shell_surface->root_surface()->Commit(); |
| |
| auto* window_state = |
| ash::WindowState::Get(shell_surface->GetWidget()->GetNativeWindow()); |
| |
| // Expect: Can resize with max_size > min_size. |
| EXPECT_TRUE(window_state->CanMaximize()); |
| EXPECT_TRUE(window_state->CanResize()); |
| EXPECT_TRUE(window_state->CanSnap()); |
| |
| shell_surface->SetMaximumSize(gfx::Size(0, 0)); |
| shell_surface->root_surface()->Commit(); |
| |
| // Expect: Can resize with max_size unset. |
| EXPECT_TRUE(window_state->CanMaximize()); |
| EXPECT_TRUE(window_state->CanResize()); |
| EXPECT_TRUE(window_state->CanSnap()); |
| |
| shell_surface->SetMaximumSize(gfx::Size(10, 10)); |
| shell_surface->SetMinimumSize(gfx::Size(10, 10)); |
| shell_surface->root_surface()->Commit(); |
| |
| // Expect: Can't resize where max_size is set and max_size == min_size. |
| EXPECT_FALSE(window_state->CanMaximize()); |
| EXPECT_FALSE(window_state->CanResize()); |
| EXPECT_FALSE(window_state->CanSnap()); |
| } |
| |
| TEST_F(ShellSurfaceTest, Transient) { |
| constexpr gfx::Size kBufferSize(256, 256); |
| auto parent_shell_surface = |
| test::ShellSurfaceBuilder(kBufferSize).BuildShellSurface(); |
| auto* parent_surface = parent_shell_surface->root_surface(); |
| |
| auto child_shell_surface = |
| test::ShellSurfaceBuilder(kBufferSize).SetNoCommit().BuildShellSurface(); |
| auto* child_surface = child_shell_surface->root_surface(); |
| // Importantly, a transient window has an associated application. |
| child_surface->SetApplicationId("fake_app_id"); |
| child_surface->SetParent(parent_surface, 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) { |
| constexpr gfx::Size kBufferSize(256, 256); |
| auto shell_surface = |
| test::ShellSurfaceBuilder(kBufferSize).BuildShellSurface(); |
| auto* surface = shell_surface->root_surface(); |
| |
| shell_surface->GetWidget()->SetBounds(gfx::Rect(0, 0, 256, 256)); |
| |
| auto popup_buffer = test::ExoTestHelper::CreateBuffer(kBufferSize); |
| 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_TRUE(IsCaptureWindow(popup_shell_surface.get())); |
| |
| // 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 created. |
| auto sub_popup_buffer = test::ExoTestHelper::CreateBuffer(kBufferSize); |
| std::unique_ptr<Surface> sub_popup_surface(new Surface); |
| sub_popup_surface->Attach(sub_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(); |
| EXPECT_EQ(aura::client::WINDOW_TYPE_POPUP, target->GetType()); |
| // The capture should be on `sub_popup_shell_surface`. |
| EXPECT_TRUE(IsCaptureWindow(sub_popup_shell_surface.get())); |
| |
| { |
| // 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, GetTargetSurfaceForLocatedEvent(&event)); |
| } |
| |
| // Removing top most popup moves the grab to parent popup. |
| sub_popup_shell_surface.reset(); |
| EXPECT_TRUE(IsCaptureWindow(popup_shell_surface.get())); |
| |
| { |
| // 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( |
| popup_shell_surface->GetWidget()->GetNativeWindow()); |
| EXPECT_EQ(popup_surface.get(), GetTargetSurfaceForLocatedEvent(&event)); |
| } |
| } |
| |
| TEST_F(ShellSurfaceTest, GainCaptureFromSiblingSubPopup) { |
| // Test that in the following setup: |
| // |
| // popup_shell_surface1 popup_shell_surface2 |
| // (has grab, loses capture) (has grab, gains capture) |
| // \ / |
| // popup_shell_surface |
| // |
| // when popup_shell_surface2 is added, capture is correctly transferred to it |
| // from popup_shell_surface1. |
| |
| constexpr gfx::Size kBufferSize(256, 256); |
| auto shell_surface = |
| test::ShellSurfaceBuilder(kBufferSize).BuildShellSurface(); |
| shell_surface->GetWidget()->SetBounds(gfx::Rect(0, 0, 256, 256)); |
| |
| auto popup_buffer = test::ExoTestHelper::CreateBuffer(kBufferSize); |
| 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(); |
| EXPECT_TRUE(IsCaptureWindow(popup_shell_surface.get())); |
| |
| auto popup_buffer1 = test::ExoTestHelper::CreateBuffer(kBufferSize); |
| auto popup_surface1 = std::make_unique<Surface>(); |
| popup_surface1->Attach(popup_buffer1.get()); |
| std::unique_ptr<ShellSurface> popup_shell_surface1(CreatePopupShellSurface( |
| popup_surface1.get(), popup_shell_surface.get(), gfx::Point(100, 50))); |
| popup_shell_surface1->Grab(); |
| popup_surface1->Commit(); |
| EXPECT_TRUE(IsCaptureWindow(popup_shell_surface1.get())); |
| |
| auto popup_buffer2 = test::ExoTestHelper::CreateBuffer(kBufferSize); |
| auto popup_surface2 = std::make_unique<Surface>(); |
| popup_surface2->Attach(popup_buffer2.get()); |
| std::unique_ptr<ShellSurface> popup_shell_surface2(CreatePopupShellSurface( |
| popup_surface2.get(), popup_shell_surface.get(), gfx::Point(50, 100))); |
| popup_shell_surface2->Grab(); |
| popup_surface2->Commit(); |
| EXPECT_TRUE(IsCaptureWindow(popup_shell_surface2.get())); |
| } |
| |
| TEST_F(ShellSurfaceTest, GainCaptureFromNieceSubPopup) { |
| // Test that in the following setup: |
| // |
| // popup_shell_surface3 |
| // (no grab) |
| // | |
| // popup_shell_surface2 |
| // (has grab; loses capture) |
| // | |
| // popup_shell_surface1 popup_shell_surface4 |
| // (no grab) (has grab; gains capture) |
| // \ / |
| // popup_shell_surface |
| // |
| // when popup_shell_surface4 is added, capture is correctly transferred to |
| // it from popup_shell_surface2. |
| |
| constexpr gfx::Size kBufferSize(256, 256); |
| auto shell_surface = |
| test::ShellSurfaceBuilder(kBufferSize).BuildShellSurface(); |
| shell_surface->GetWidget()->SetBounds(gfx::Rect(0, 0, 256, 256)); |
| |
| auto popup_buffer = test::ExoTestHelper::CreateBuffer(kBufferSize); |
| 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(); |
| EXPECT_TRUE(IsCaptureWindow(popup_shell_surface.get())); |
| |
| auto popup_buffer1 = test::ExoTestHelper::CreateBuffer(kBufferSize); |
| auto popup_surface1 = std::make_unique<Surface>(); |
| popup_surface1->Attach(popup_buffer1.get()); |
| std::unique_ptr<ShellSurface> popup_shell_surface1(CreatePopupShellSurface( |
| popup_surface1.get(), popup_shell_surface.get(), gfx::Point(100, 50))); |
| popup_surface1->Commit(); |
| // Doesn't change capture. |
| EXPECT_TRUE(IsCaptureWindow(popup_shell_surface.get())); |
| |
| auto popup_buffer2 = test::ExoTestHelper::CreateBuffer(kBufferSize); |
| auto popup_surface2 = std::make_unique<Surface>(); |
| popup_surface2->Attach(popup_buffer2.get()); |
| std::unique_ptr<ShellSurface> popup_shell_surface2(CreatePopupShellSurface( |
| popup_surface2.get(), popup_shell_surface1.get(), gfx::Point(150, 50))); |
| popup_shell_surface2->Grab(); |
| popup_surface2->Commit(); |
| EXPECT_TRUE(IsCaptureWindow(popup_shell_surface2.get())); |
| |
| auto popup_buffer3 = test::ExoTestHelper::CreateBuffer(kBufferSize); |
| auto popup_surface3 = std::make_unique<Surface>(); |
| popup_surface3->Attach(popup_buffer3.get()); |
| std::unique_ptr<ShellSurface> popup_shell_surface3(CreatePopupShellSurface( |
| popup_surface3.get(), popup_shell_surface2.get(), gfx::Point(200, 50))); |
| popup_surface3->Commit(); |
| // Doesn't change capture. |
| EXPECT_TRUE(IsCaptureWindow(popup_shell_surface2.get())); |
| |
| auto popup_buffer4 = test::ExoTestHelper::CreateBuffer(kBufferSize); |
| auto popup_surface4 = std::make_unique<Surface>(); |
| popup_surface4->Attach(popup_buffer4.get()); |
| std::unique_ptr<ShellSurface> popup_shell_surface4(CreatePopupShellSurface( |
| popup_surface4.get(), popup_shell_surface.get(), gfx::Point(50, 100))); |
| popup_shell_surface4->Grab(); |
| popup_surface4->Commit(); |
| EXPECT_TRUE(IsCaptureWindow(popup_shell_surface4.get())); |
| } |
| |
| TEST_F(ShellSurfaceTest, GainCaptureFromDescendantSubPopup) { |
| // Test that in the following setup: |
| // |
| // popup_shell_surface3 |
| // (has grab; loses capture) |
| // | |
| // popup_shell_surface2 |
| // (no grab) |
| // | |
| // popup_shell_surface1 |
| // (has grab; gains capture) |
| // | |
| // popup_shell_surface |
| // |
| // when popup_shell_surface3 is closed, capture is correctly transferred to |
| // popup_shell_surface1. |
| |
| constexpr gfx::Size kBufferSize(256, 256); |
| auto shell_surface = |
| test::ShellSurfaceBuilder(kBufferSize).BuildShellSurface(); |
| shell_surface->GetWidget()->SetBounds(gfx::Rect(0, 0, 256, 256)); |
| |
| auto popup_buffer = test::ExoTestHelper::CreateBuffer(kBufferSize); |
| 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(); |
| EXPECT_TRUE(IsCaptureWindow(popup_shell_surface.get())); |
| |
| auto popup_buffer1 = test::ExoTestHelper::CreateBuffer(kBufferSize); |
| auto popup_surface1 = std::make_unique<Surface>(); |
| popup_surface1->Attach(popup_buffer1.get()); |
| std::unique_ptr<ShellSurface> popup_shell_surface1(CreatePopupShellSurface( |
| popup_surface1.get(), popup_shell_surface.get(), gfx::Point(100, 50))); |
| popup_shell_surface1->Grab(); |
| popup_surface1->Commit(); |
| EXPECT_TRUE(IsCaptureWindow(popup_shell_surface1.get())); |
| |
| auto popup_buffer2 = test::ExoTestHelper::CreateBuffer(kBufferSize); |
| auto popup_surface2 = std::make_unique<Surface>(); |
| popup_surface2->Attach(popup_buffer2.get()); |
| std::unique_ptr<ShellSurface> popup_shell_surface2(CreatePopupShellSurface( |
| popup_surface2.get(), popup_shell_surface1.get(), gfx::Point(150, 50))); |
| popup_surface2->Commit(); |
| // Doesn't change capture. |
| EXPECT_TRUE(IsCaptureWindow(popup_shell_surface1.get())); |
| |
| auto popup_buffer3 = test::ExoTestHelper::CreateBuffer(kBufferSize); |
| auto popup_surface3 = std::make_unique<Surface>(); |
| popup_surface3->Attach(popup_buffer3.get()); |
| std::unique_ptr<ShellSurface> popup_shell_surface3(CreatePopupShellSurface( |
| popup_surface3.get(), popup_shell_surface2.get(), gfx::Point(200, 50))); |
| popup_shell_surface3->Grab(); |
| popup_surface3->Commit(); |
| EXPECT_TRUE(IsCaptureWindow(popup_shell_surface3.get())); |
| |
| popup_shell_surface3.reset(); |
| EXPECT_TRUE(IsCaptureWindow(popup_shell_surface1.get())); |
| } |
| |
| TEST_F(ShellSurfaceTest, Menu) { |
| std::unique_ptr<ShellSurface> root_shell_surface = |
| test::ShellSurfaceBuilder({256, 256}).BuildShellSurface(); |
| EXPECT_TRUE(root_shell_surface->GetWidget()->IsVisible()); |
| |
| std::unique_ptr<ShellSurface> menu_shell_surface = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetAsMenu() |
| .SetParent(root_shell_surface.get()) |
| .BuildShellSurface(); |
| EXPECT_TRUE(menu_shell_surface->GetWidget()->IsVisible()); |
| EXPECT_EQ(aura::client::WINDOW_TYPE_MENU, |
| menu_shell_surface->GetWidget()->GetNativeWindow()->GetType()); |
| } |
| |
| TEST_F(ShellSurfaceTest, MenuOnPopup) { |
| std::unique_ptr<ShellSurface> root_shell_surface = |
| test::ShellSurfaceBuilder({256, 256}).BuildShellSurface(); |
| EXPECT_TRUE(root_shell_surface->GetWidget()->IsVisible()); |
| |
| std::unique_ptr<ShellSurface> popup_shell_surface = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetAsPopup() |
| .SetParent(root_shell_surface.get()) |
| .BuildShellSurface(); |
| EXPECT_TRUE(popup_shell_surface->GetWidget()->IsVisible()); |
| EXPECT_EQ(aura::client::WINDOW_TYPE_POPUP, |
| popup_shell_surface->GetWidget()->GetNativeWindow()->GetType()); |
| |
| std::unique_ptr<ShellSurface> menu_shell_surface = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetAsMenu() |
| .SetParent(popup_shell_surface.get()) |
| .BuildShellSurface(); |
| EXPECT_TRUE(menu_shell_surface->GetWidget()->IsVisible()); |
| EXPECT_EQ(aura::client::WINDOW_TYPE_MENU, |
| menu_shell_surface->GetWidget()->GetNativeWindow()->GetType()); |
| } |
| |
| TEST_F(ShellSurfaceTest, PopupOnMenu) { |
| std::unique_ptr<ShellSurface> root_shell_surface = |
| test::ShellSurfaceBuilder({256, 256}).BuildShellSurface(); |
| EXPECT_TRUE(root_shell_surface->GetWidget()->IsVisible()); |
| |
| std::unique_ptr<ShellSurface> menu_shell_surface = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetAsMenu() |
| .SetParent(root_shell_surface.get()) |
| .BuildShellSurface(); |
| EXPECT_TRUE(menu_shell_surface->GetWidget()->IsVisible()); |
| EXPECT_EQ(aura::client::WINDOW_TYPE_MENU, |
| menu_shell_surface->GetWidget()->GetNativeWindow()->GetType()); |
| |
| std::unique_ptr<ShellSurface> popup_shell_surface = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetAsPopup() |
| .SetParent(menu_shell_surface.get()) |
| .BuildShellSurface(); |
| EXPECT_TRUE(popup_shell_surface->GetWidget()->IsVisible()); |
| EXPECT_EQ(aura::client::WINDOW_TYPE_POPUP, |
| popup_shell_surface->GetWidget()->GetNativeWindow()->GetType()); |
| } |
| |
| TEST_F(ShellSurfaceTest, PopupOnPopup) { |
| std::unique_ptr<ShellSurface> root_shell_surface = |
| test::ShellSurfaceBuilder({256, 256}).BuildShellSurface(); |
| EXPECT_TRUE(root_shell_surface->GetWidget()->IsVisible()); |
| |
| std::unique_ptr<ShellSurface> popup_shell_surface_1 = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetAsPopup() |
| .SetParent(root_shell_surface.get()) |
| .BuildShellSurface(); |
| EXPECT_TRUE(popup_shell_surface_1->GetWidget()->IsVisible()); |
| EXPECT_EQ(aura::client::WINDOW_TYPE_POPUP, |
| popup_shell_surface_1->GetWidget()->GetNativeWindow()->GetType()); |
| |
| std::unique_ptr<ShellSurface> popup_shell_surface_2 = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetAsPopup() |
| .SetParent(popup_shell_surface_1.get()) |
| .BuildShellSurface(); |
| EXPECT_TRUE(popup_shell_surface_2->GetWidget()->IsVisible()); |
| EXPECT_EQ(aura::client::WINDOW_TYPE_POPUP, |
| popup_shell_surface_2->GetWidget()->GetNativeWindow()->GetType()); |
| } |
| |
| TEST_F(ShellSurfaceTest, MenuOnMenu) { |
| std::unique_ptr<ShellSurface> root_shell_surface = |
| test::ShellSurfaceBuilder({256, 256}).BuildShellSurface(); |
| EXPECT_TRUE(root_shell_surface->GetWidget()->IsVisible()); |
| |
| std::unique_ptr<ShellSurface> menu_shell_surface_1 = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetAsMenu() |
| .SetParent(root_shell_surface.get()) |
| .BuildShellSurface(); |
| EXPECT_TRUE(menu_shell_surface_1->GetWidget()->IsVisible()); |
| EXPECT_EQ(aura::client::WINDOW_TYPE_MENU, |
| menu_shell_surface_1->GetWidget()->GetNativeWindow()->GetType()); |
| |
| std::unique_ptr<ShellSurface> menu_shell_surface_2 = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetAsMenu() |
| .SetParent(menu_shell_surface_1.get()) |
| .BuildShellSurface(); |
| EXPECT_TRUE(menu_shell_surface_2->GetWidget()->IsVisible()); |
| EXPECT_EQ(aura::client::WINDOW_TYPE_MENU, |
| menu_shell_surface_2->GetWidget()->GetNativeWindow()->GetType()); |
| } |
| |
| TEST_F(ShellSurfaceTest, PopupWithInputRegion) { |
| constexpr gfx::Size kBufferSize(256, 256); |
| auto shell_surface = test::ShellSurfaceBuilder(kBufferSize) |
| .SetInputRegion(cc::Region()) |
| .BuildShellSurface(); |
| auto* surface = shell_surface->root_surface(); |
| |
| auto child_buffer = test::ExoTestHelper::CreateBuffer(kBufferSize); |
| std::unique_ptr<Surface> child_surface(new Surface); |
| child_surface->Attach(child_buffer.get()); |
| |
| auto subsurface = std::make_unique<SubSurface>(child_surface.get(), surface); |
| 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)); |
| |
| auto popup_buffer = test::ExoTestHelper::CreateBuffer(kBufferSize); |
| 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 that popup does not close when trying to take a screenshot. |
| TEST_F(ShellSurfaceTest, PopupWithCaptureMode) { |
| // Setup popup_shell_surface. |
| constexpr gfx::Size kBufferSize(256, 256); |
| auto shell_surface = |
| test::ShellSurfaceBuilder(kBufferSize).BuildShellSurface(); |
| auto popup_buffer = test::ExoTestHelper::CreateBuffer(kBufferSize); |
| 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(); |
| |
| bool closed = false; |
| auto callback = |
| base::BindRepeating([](bool* closed) { *closed = true; }, &closed); |
| popup_shell_surface->set_close_callback(callback); |
| |
| // This simulates enabling (screenshot) capture mode. |
| ash::GetTestDelegate()->OnSessionStateChanged(true); |
| popup_shell_surface->OnCaptureChanged( |
| popup_shell_surface->GetWidget()->GetNativeWindow(), nullptr); |
| // With (screenshot) capture mode on, losing capture should not close the |
| // shell surface. |
| EXPECT_FALSE(closed); |
| |
| // This simulates ending (screenshot) capture mode. |
| ash::GetTestDelegate()->OnSessionStateChanged(false); |
| popup_shell_surface->OnCaptureChanged( |
| popup_shell_surface->GetWidget()->GetNativeWindow(), nullptr); |
| // With (screenshot) capture mode off, losing capture should close the shell |
| // surface. |
| EXPECT_TRUE(closed); |
| } |
| |
| 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) { |
| auto shell_surface = |
| test::ShellSurfaceBuilder({256, 256}).SetNoCommit().BuildShellSurface(); |
| auto* surface = shell_surface->root_surface(); |
| |
| 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, GetTargetSurfaceForLocatedEvent(&event)); |
| } |
| } |
| |
| TEST_F(ShellSurfaceTest, DragMaximizedWindow) { |
| auto shell_surface = |
| test::ShellSurfaceBuilder({256, 256}).SetNoCommit().BuildShellSurface(); |
| auto* surface = shell_surface->root_surface(); |
| |
| 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, |
| float raster_scale, aura::Window::OcclusionState occlusion_state, |
| std::optional<chromeos::WindowStateType>) { |
| 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) { |
| constexpr gfx::Size kBufferSize(256, 256); |
| auto shell_surface = test::ShellSurfaceBuilder(kBufferSize) |
| .SetRootBufferFormat(kOpaqueFormat) |
| .SetFrame(SurfaceFrameType::NORMAL) |
| .BuildShellSurface(); |
| auto* surface = shell_surface->root_surface(); |
| shell_surface->GetWidget()->SetBounds(gfx::Rect(0, 0, 256, 288)); |
| |
| auto popup_buffer = |
| test::ExoTestHelper::CreateBuffer(kBufferSize, kOpaqueFormat); |
| 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, GetTargetSurfaceForLocatedEvent(&event)); |
| } |
| } |
| |
| TEST_F(ShellSurfaceTest, SkipImeProcessingPropagateToSurface) { |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetFrame(SurfaceFrameType::NORMAL) |
| .BuildShellSurface(); |
| shell_surface->GetWidget()->SetBounds(gfx::Rect(0, 0, 256, 256)); |
| |
| 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); |
| } |
| |
| TEST_F(ShellSurfaceTest, LacrosToggleAxisMaximize) { |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({64, 64}) |
| .SetOrigin({10, 10}) |
| .BuildShellSurface(); |
| shell_surface->OnSetServerStartResize(); |
| auto* widget = shell_surface->GetWidget(); |
| gfx::Size size = widget->GetWindowBoundsInScreen().size(); |
| |
| gfx::Rect restored_bounds = shell_surface->GetBoundsInScreen(); |
| |
| ui::test::EventGenerator* event_generator = GetEventGenerator(); |
| |
| // Move mouse to top middle and double click to vertically maximize. |
| event_generator->MoveMouseTo(10 + size.width() / 2, 10); |
| event_generator->DoubleClickLeftButton(); |
| |
| gfx::Rect work_area = |
| display::Screen::GetScreen()->GetPrimaryDisplay().work_area(); |
| gfx::Rect bounds_in_screen = shell_surface->GetBoundsInScreen(); |
| |
| EXPECT_EQ(restored_bounds.x(), bounds_in_screen.x()); |
| EXPECT_EQ(restored_bounds.width(), bounds_in_screen.width()); |
| EXPECT_EQ(work_area.y(), bounds_in_screen.y()); |
| EXPECT_EQ(work_area.height(), bounds_in_screen.height()); |
| |
| // Move mouse to top middle and double click to vertically Restore. |
| event_generator->MoveMouseTo(10 + size.width() / 2, 0); |
| event_generator->DoubleClickLeftButton(); |
| bounds_in_screen = shell_surface->GetBoundsInScreen(); |
| EXPECT_EQ(restored_bounds, bounds_in_screen); |
| } |
| |
| TEST_F(ShellSurfaceTest, ServerStartResizeComponent) { |
| 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)); |
| |
| 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, float raster_scale, |
| aura::Window::OcclusionState occlusion_state, |
| std::optional<chromeos::WindowStateType>) { return ++(*serial_ptr); }, |
| &serial); |
| |
| ui::test::EventGenerator* event_generator = GetEventGenerator(); |
| event_generator->MoveMouseTo(size.width() + 2, size.height() / 2); |
| |
| EXPECT_EQ(shell_surface->resize_component_for_test(), HTCAPTION); |
| |
| constexpr int kDragAmount = 10; |
| event_generator->PressLeftButton(); |
| event_generator->MoveMouseBy(kDragAmount, 0); |
| EXPECT_EQ(shell_surface->resize_component_for_test(), HTCAPTION); |
| shell_surface->AcknowledgeConfigure(serial); |
| shell_surface->root_surface()->Commit(); |
| EXPECT_EQ(shell_surface->resize_component_for_test(), HTRIGHT); |
| event_generator->ReleaseLeftButton(); |
| shell_surface->AcknowledgeConfigure(serial); |
| shell_surface->root_surface()->Commit(); |
| EXPECT_EQ(shell_surface->resize_component_for_test(), HTCAPTION); |
| } |
| |
| // Make sure that dragging to another display will update the origin to |
| // correct value. |
| TEST_F(ShellSurfaceTest, UpdateBoundsWhenDraggedToAnotherDisplay) { |
| exo::test::TestSecurityDelegate securityDelegate; |
| securityDelegate.SetCanSetBounds( |
| SecurityDelegate::SetBoundsPolicy::DCHECK_IF_DECORATED); |
| UpdateDisplay("800x600, 800x600"); |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({64, 64}) |
| .SetSecurityDelegate(&securityDelegate) |
| .BuildShellSurface(); |
| ui::test::EventGenerator* event_generator = GetEventGenerator(); |
| shell_surface->SetWindowBounds({0, 0, 64, 64}); |
| |
| gfx::Point last_origin; |
| auto origin_change = [&](const gfx::Point& p) { last_origin = p; }; |
| |
| shell_surface->set_origin_change_callback( |
| base::BindLambdaForTesting(origin_change)); |
| event_generator->MoveMouseTo(1, 1); |
| event_generator->PressLeftButton(); |
| ASSERT_TRUE(shell_surface->StartMove()); |
| event_generator->MoveMouseTo(801, 1); |
| event_generator->ReleaseLeftButton(); |
| EXPECT_EQ(last_origin, gfx::Point(800, 0)); |
| } |
| |
| // Make sure that commit during window drag should not move the |
| // window to another display. |
| TEST_F(ShellSurfaceTest, CommitShouldNotMoveDisplay) { |
| UpdateDisplay("800x600, 800x600"); |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({64, 64}) |
| .SetOrigin({750, 0}) |
| .BuildShellSurface(); |
| auto* screen = display::Screen::GetScreen(); |
| auto* root_surface = shell_surface->root_surface(); |
| |
| EXPECT_EQ(screen->GetPrimaryDisplay().id(), |
| screen |
| ->GetDisplayNearestWindow( |
| shell_surface->GetWidget()->GetNativeWindow()) |
| .id()); |
| |
| aura::Env::GetInstance()->set_mouse_button_flags(ui::EF_LEFT_MOUSE_BUTTON); |
| ASSERT_TRUE(shell_surface->StartMove()); |
| |
| constexpr gfx::Size kBufferSize(256, 256); |
| auto new_buffer = test::ExoTestHelper::CreateBuffer(kBufferSize); |
| root_surface->Attach(new_buffer.get()); |
| root_surface->Commit(); |
| |
| EXPECT_EQ(screen->GetPrimaryDisplay().id(), |
| screen |
| ->GetDisplayNearestWindow( |
| shell_surface->GetWidget()->GetNativeWindow()) |
| .id()); |
| |
| GetEventGenerator()->ReleaseLeftButton(); |
| |
| // shell_surface->EndDrag(); |
| |
| // Ending drag will not move the window unless the mouse cursor enters |
| // another display. |
| EXPECT_EQ(screen->GetPrimaryDisplay().id(), |
| screen |
| ->GetDisplayNearestWindow( |
| shell_surface->GetWidget()->GetNativeWindow()) |
| .id()); |
| } |
| |
| TEST_F(ShellSurfaceTest, ShadowBoundsWithNegativeCoordinate) { |
| constexpr gfx::Point kOrigin(20, 20); |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetOrigin(kOrigin) |
| .BuildShellSurface(); |
| shell_surface->root_surface()->Commit(); |
| ASSERT_TRUE(shell_surface->GetWidget()); |
| |
| // Create subsurface outside of root surface. |
| constexpr gfx::Size kChildBufferSize(32, 32); |
| auto child_buffer = test::ExoTestHelper::CreateBuffer(kChildBufferSize); |
| auto child_surface = std::make_unique<Surface>(); |
| child_surface->Attach(child_buffer.get()); |
| auto subsurface = std::make_unique<SubSurface>(child_surface.get(), |
| shell_surface->root_surface()); |
| // Set subsurface to left and upper of the window. |
| subsurface->SetPosition(gfx::PointF(-10, -10)); |
| child_surface->Commit(); |
| shell_surface->root_surface()->Commit(); |
| |
| ASSERT_TRUE(shell_surface->GetWidget()); |
| auto* widget = shell_surface->GetWidget(); |
| // Geometry is relative to the root surface. |
| shell_surface->SetGeometry(gfx::Rect(0, 0, 256, 256)); |
| shell_surface->root_surface()->SetFrame(SurfaceFrameType::SHADOW); |
| shell_surface->root_surface()->Commit(); |
| EXPECT_FALSE(widget->GetNativeWindow()->GetProperty( |
| aura::client::kUseWindowBoundsForShadow)); |
| // Host window should be a rectangle including root surface and subsurface. |
| EXPECT_EQ(gfx::Rect(-10, -10, 266, 266), |
| shell_surface->host_window()->bounds()); |
| |
| // Shadow content bounds is relative to ExoShellSurface and should use window |
| // bounds. |
| ui::Shadow* shadow = |
| wm::ShadowController::GetShadowForWindow(widget->GetNativeWindow()); |
| ASSERT_TRUE(shadow); |
| EXPECT_EQ(gfx::Rect(0, 0, 256, 256), shadow->content_bounds()); |
| } |
| |
| TEST_F(ShellSurfaceTest, ShadowBoundsWithScaleFactor) { |
| constexpr gfx::Point kOrigin(20, 20); |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetOrigin(kOrigin) |
| .BuildShellSurface(); |
| shell_surface->root_surface()->Commit(); |
| ASSERT_TRUE(shell_surface->GetWidget()); |
| |
| // Set scale. |
| constexpr float kScaleFactor = 2.0; |
| shell_surface->set_client_submits_surfaces_in_pixel_coordinates(true); |
| shell_surface->SetScaleFactor(kScaleFactor); |
| |
| // Create subsurface outside of root surface. |
| constexpr gfx::Size kChildBufferSize(32, 32); |
| auto child_buffer = test::ExoTestHelper::CreateBuffer(kChildBufferSize); |
| auto child_surface = std::make_unique<Surface>(); |
| child_surface->Attach(child_buffer.get()); |
| auto subsurface = std::make_unique<SubSurface>(child_surface.get(), |
| shell_surface->root_surface()); |
| // Set subsurface to left and upper of the window. |
| subsurface->SetPosition(gfx::PointF(-10, -10)); |
| child_surface->Commit(); |
| shell_surface->root_surface()->Commit(); |
| |
| ASSERT_TRUE(shell_surface->GetWidget()); |
| auto* widget = shell_surface->GetWidget(); |
| // Geometry is relative to the root surface. |
| shell_surface->SetGeometry(gfx::Rect(0, 0, 256, 256)); |
| shell_surface->root_surface()->SetFrame(SurfaceFrameType::SHADOW); |
| shell_surface->root_surface()->Commit(); |
| EXPECT_FALSE(widget->GetNativeWindow()->GetProperty( |
| aura::client::kUseWindowBoundsForShadow)); |
| // Host window should be a rectangle including root surface and subsurface. |
| EXPECT_EQ(gfx::Rect(-5, -5, 133, 133), |
| shell_surface->host_window()->bounds()); |
| |
| // Shadow content bounds is relative to ExoShellSurface and should use window |
| // bounds. |
| ui::Shadow* shadow = |
| wm::ShadowController::GetShadowForWindow(widget->GetNativeWindow()); |
| ASSERT_TRUE(shadow); |
| EXPECT_EQ(gfx::Rect(0, 0, 256, 256), shadow->content_bounds()); |
| } |
| |
| TEST_F(ShellSurfaceTest, ShadowRoundedCorners) { |
| constexpr gfx::Point kOrigin(20, 20); |
| constexpr int kWindowCornerRadius = 12; |
| |
| base::test::ScopedFeatureList scoped_feature_list( |
| chromeos::features::kRoundedWindows); |
| |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetOrigin(kOrigin) |
| .SetWindowState(chromeos::WindowStateType::kNormal) |
| .SetFrame(SurfaceFrameType::NORMAL) |
| .BuildShellSurface(); |
| |
| Surface* root_surface = shell_surface->root_surface(); |
| |
| root_surface->Commit(); |
| views::Widget* widget = shell_surface->GetWidget(); |
| ASSERT_TRUE(widget); |
| |
| aura::Window* window = widget->GetNativeWindow(); |
| ui::Shadow* shadow = wm::ShadowController::GetShadowForWindow(window); |
| ASSERT_TRUE(shadow); |
| |
| // Window shadow radius needs to match the window radius. |
| EXPECT_EQ(shadow->rounded_corner_radius_for_testing(), 0); |
| |
| // Have a window with radius of 12dp. |
| shell_surface->SetWindowCornersRadii( |
| gfx::RoundedCornersF(kWindowCornerRadius)); |
| root_surface->Commit(); |
| |
| shadow = wm::ShadowController::GetShadowForWindow(window); |
| ASSERT_TRUE(shadow); |
| EXPECT_EQ(shadow->rounded_corner_radius_for_testing(), kWindowCornerRadius); |
| |
| // Have a window with radius of 0dp. |
| shell_surface->SetWindowCornersRadii(gfx::RoundedCornersF()); |
| root_surface->Commit(); |
| |
| shadow = wm::ShadowController::GetShadowForWindow(window); |
| ASSERT_TRUE(shadow); |
| EXPECT_EQ(shadow->rounded_corner_radius_for_testing(), 0); |
| } |
| |
| // Make sure that resize shadow does not update until commit when the window |
| // property |aura::client::kUseWindowBoundsForShadow| is false. |
| TEST_F(ShellSurfaceTest, ResizeShadowIndependentBounds) { |
| constexpr gfx::Point kOrigin(10, 10); |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({64, 64}) |
| .SetOrigin(kOrigin) |
| .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(); |
| EXPECT_FALSE(widget->GetNativeWindow()->GetProperty( |
| aura::client::kUseWindowBoundsForShadow)); |
| |
| ui::Shadow* normal_shadow = |
| wm::ShadowController::GetShadowForWindow(widget->GetNativeWindow()); |
| ASSERT_TRUE(normal_shadow); |
| |
| // ash::ResizeShadow::InitParams set the default |thickness| to 8. |
| constexpr 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()); |
| |
| constexpr gfx::Rect kNewBounds(kOrigin, {100, 100}); |
| 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, float raster_scale, |
| aura::Window::OcclusionState occlusion_state, |
| std::optional<chromeos::WindowStateType>) { return ++(*serial_ptr); }, |
| &serial); |
| |
| shell_surface->set_configure_callback(configure_callback); |
| |
| // Resize the widget and set geometry. |
| aura::Env::GetInstance()->set_mouse_button_flags(ui::EF_LEFT_MOUSE_BUTTON); |
| ASSERT_TRUE(shell_surface->StartResize(HTBOTTOMRIGHT)); |
| shell_surface->SetWidgetBounds(kNewBounds, /*adjusted by server=*/false); |
| shell_surface->SetGeometry(gfx::Rect(kNewBounds.size())); |
| |
| // 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(kNewBounds.width() + kResizeShadowThickness, |
| kNewBounds.height()), |
| resize_shadow->GetLayerForTest()->bounds().size()); |
| EXPECT_EQ(kNewBounds.size(), normal_shadow->content_bounds().size()); |
| |
| // Explicitly ends the drag here. |
| ash::Shell::Get()->toplevel_window_event_handler()->CompleteDragForTesting( |
| ash::ToplevelWindowEventHandler::DragResult::SUCCESS); |
| // Hide Shadow |
| event_generator->MoveMouseTo({0, 0}); |
| |
| EXPECT_FALSE(resize_shadow->visible()); |
| |
| // Move |
| UpdateDisplay("800x600,1200x1000"); |
| shell_surface->GetWidget()->SetBounds({{1000, 100}, kNewBounds.size()}); |
| |
| shell_surface->AcknowledgeConfigure(serial); |
| shell_surface->root_surface()->Commit(); |
| |
| auto* screen = display::Screen::GetScreen(); |
| int64_t secondary_id = |
| display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager()) |
| .GetSecondaryDisplay() |
| .id(); |
| ASSERT_EQ(secondary_id, screen |
| ->GetDisplayNearestWindow( |
| shell_surface->GetWidget()->GetNativeWindow()) |
| .id()); |
| |
| // Use outside to start drag because exo consumes events inside. |
| event_generator->MoveMouseTo({999, 99}); |
| |
| gfx::Rect bounds = shell_surface->GetWidget()->GetNativeWindow()->bounds(); |
| |
| EXPECT_TRUE(resize_shadow->visible()); |
| gfx::Rect expected_shadow_on_secondary( |
| bounds.x() - kResizeShadowThickness, bounds.y() - kResizeShadowThickness, |
| bounds.width() + kResizeShadowThickness, |
| bounds.height() + kResizeShadowThickness); |
| EXPECT_EQ(expected_shadow_on_secondary, |
| resize_shadow->GetLayerForTest()->bounds()); |
| |
| aura::Env::GetInstance()->set_mouse_button_flags(ui::EF_LEFT_MOUSE_BUTTON); |
| constexpr gfx::Rect kResizedBoundsOn2nd{950, 50, 150, 150}; |
| ASSERT_TRUE(shell_surface->StartResize(HTTOPLEFT)); |
| shell_surface->GetWidget()->SetBounds(kResizedBoundsOn2nd); |
| shell_surface->SetGeometry(gfx::Rect(kResizedBoundsOn2nd.size())); |
| shell_surface->AcknowledgeConfigure(serial); |
| |
| EXPECT_EQ(expected_shadow_on_secondary, |
| resize_shadow->GetLayerForTest()->bounds()); |
| |
| shell_surface->root_surface()->Commit(); |
| constexpr gfx::Rect kExpectedShadowBoundsOn2nd( |
| 150 - kResizeShadowThickness, 50 - kResizeShadowThickness, |
| kResizedBoundsOn2nd.width() + kResizeShadowThickness, |
| kResizedBoundsOn2nd.height() + kResizeShadowThickness); |
| |
| EXPECT_EQ(kExpectedShadowBoundsOn2nd, |
| resize_shadow->GetLayerForTest()->bounds()); |
| } |
| |
| // 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(); |
| EXPECT_FALSE(widget->GetNativeWindow()->GetProperty( |
| aura::client::kUseWindowBoundsForShadow)); |
| // Override the property to update the shadow bounds immediately. |
| widget->GetNativeWindow()->SetProperty( |
| aura::client::kUseWindowBoundsForShadow, true); |
| |
| 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); |
| aura::Env::GetInstance()->set_mouse_button_flags(ui::EF_LEFT_MOUSE_BUTTON); |
| // Resize the widget and set geometry. |
| ASSERT_TRUE(shell_surface->StartResize(HTBOTTOMRIGHT)); |
| shell_surface->SetWidgetBounds(new_bounds, /*adjusted_by_server=*/false); |
| 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". |
| { |
| auto shell_surface = |
| test::ShellSurfaceBuilder({256, 256}).SetNoCommit().BuildShellSurface(); |
| auto* surface = shell_surface->root_surface(); |
| |
| surface->SetApplicationId("test"); |
| 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); |
| { |
| auto shell_surface = |
| test::ShellSurfaceBuilder({256, 256}).SetNoCommit().BuildShellSurface(); |
| auto* surface = shell_surface->root_surface(); |
| |
| surface->SetApplicationId("testx"); |
| surface->Commit(); |
| EXPECT_NE(1, shell_surface->GetWidget()->GetNativeWindow()->GetProperty( |
| ash::kShelfItemTypeKey)); |
| |
| surface->SetApplicationId("testy"); |
| 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}) |
| .SetFrame(SurfaceFrameType::NORMAL) |
| .BuildShellSurface(); |
| shell_surface->GetWidget()->GetNativeWindow()->SetProperty( |
| aura::client::kSkipImeProcessing, true); |
| |
| 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::GetCaptionButtonLayoutSize( |
| views::CaptionButtonLayoutSize::kNonBrowserCaption) |
| .height()); |
| 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}) |
| .SetFrame(SurfaceFrameType::NORMAL) |
| .BuildShellSurface(); |
| shell_surface->GetWidget()->GetNativeWindow()->SetProperty( |
| aura::client::kSkipImeProcessing, true); |
| |
| 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<raw_ptr<aura::Window, VectorExperimental>>& |
| observed_windows() { |
| return windows_; |
| } |
| |
| private: |
| std::vector<raw_ptr<aura::Window, VectorExperimental>> 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()->window()->GetOcclusionState()); |
| 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); |
| } |
| |
| // Tests raster scale changes happen before occlusion changes on entering |
| // overview mode, and raster scale changes happen after occlusion changes on |
| // exiting overview mode. This to ensure windows that become visible do not get |
| // rastered twice. |
| TEST_F(ShellSurfaceTest, RasterScaleChangeVsOcclusionChangeOrder) { |
| ash::Shell::Get() |
| ->raster_scale_controller() |
| ->set_raster_scale_slop_proportion_for_testing(0.0f); |
| ash::Shell::Get()->overview_controller()->set_windows_have_snapshot_for_test( |
| false); |
| auto* overview_controller = ash::Shell::Get()->overview_controller(); |
| overview_controller->set_occlusion_pause_duration_for_end_for_test( |
| base::Milliseconds(1)); |
| std::vector<ConfigureData> config_vec; |
| auto shell_surface = |
| test::ShellSurfaceBuilder({100, 100}) |
| .SetConfigureCallback(base::BindRepeating( |
| &ConfigureSerialVec, base::Unretained(&config_vec))) |
| .BuildShellSurface(); |
| auto* surface = shell_surface->root_surface(); |
| aura::Window* root_window = surface->window(); |
| aura::Window* widget_window = shell_surface->GetWidget()->GetNativeWindow(); |
| shell_surface->Minimize(); |
| |
| // Initial configure and configure for minimize. |
| EXPECT_EQ(2u, config_vec.size()); |
| |
| ui::ScopedAnimationDurationScaleMode test_duration_mode( |
| ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); |
| |
| EXPECT_EQ(aura::Window::OcclusionState::HIDDEN, |
| root_window->GetOcclusionState()); |
| EXPECT_EQ(1.0, widget_window->GetProperty(aura::client::kRasterScale)); |
| overview_controller->StartOverview(ash::OverviewStartAction::kTests); |
| ash::WaitForOverviewEnterAnimation(); |
| |
| EXPECT_EQ(aura::Window::OcclusionState::VISIBLE, |
| root_window->GetOcclusionState()); |
| EXPECT_NE(1.0, widget_window->GetProperty(aura::client::kRasterScale)); |
| |
| // Make sure raster scale was changed first. |
| ASSERT_EQ(4u, config_vec.size()); |
| EXPECT_NE(1.0, config_vec[2].raster_scale); |
| EXPECT_EQ(aura::Window::OcclusionState::HIDDEN, |
| config_vec[2].occlusion_state); |
| EXPECT_NE(1.0, config_vec[3].raster_scale); |
| EXPECT_EQ(aura::Window::OcclusionState::VISIBLE, |
| config_vec[3].occlusion_state); |
| |
| ash::WaitForOverviewEnterAnimation(); |
| |
| // No extra configure should occur. |
| EXPECT_EQ(4u, config_vec.size()); |
| |
| overview_controller->EndOverview(ash::OverviewEndAction::kTests); |
| |
| EXPECT_EQ(aura::Window::OcclusionState::VISIBLE, |
| root_window->GetOcclusionState()); |
| EXPECT_NE(1.0, widget_window->GetProperty(aura::client::kRasterScale)); |
| |
| // No extra configure should occur. |
| EXPECT_EQ(4u, config_vec.size()); |
| |
| ash::WaitForOverviewExitAnimation(); |
| |
| // Overview mode on exiting pauses the occlusion tracker for a while. |
| ash::WaitForOcclusionStateChange(root_window, |
| aura::Window::OcclusionState::HIDDEN); |
| EXPECT_EQ(1.0, widget_window->GetProperty(aura::client::kRasterScale)); |
| |
| // Make sure occlusion is updated before raster scale. |
| ASSERT_EQ(6u, config_vec.size()); |
| EXPECT_NE(1.0, config_vec[4].raster_scale); |
| EXPECT_EQ(aura::Window::OcclusionState::HIDDEN, |
| config_vec[4].occlusion_state); |
| EXPECT_EQ(1.0, config_vec[5].raster_scale); |
| EXPECT_EQ(aura::Window::OcclusionState::HIDDEN, |
| config_vec[5].occlusion_state); |
| } |
| |
| TEST_F(ShellSurfaceTest, ThrottleFrameRateViaController) { |
| ash::FrameThrottlingController* frame_throttling_controller = |
| ash::Shell::Get()->frame_throttling_controller(); |
| for (auto app_type : {ash::AppType::LACROS, ash::AppType::BROWSER, |
| ash::AppType::CROSTINI_APP}) { |
| auto shell_surface = test::ShellSurfaceBuilder({20, 20}) |
| .SetAppType(app_type) |
| .BuildShellSurface(); |
| |
| aura::Window* window = shell_surface->GetWidget()->GetNativeWindow(); |
| frame_throttling_controller->StartThrottling({window}); |
| |
| // Crostini should not be throttled currently. |
| const auto should_throttle_set = |
| app_type != ash::AppType::CROSTINI_APP |
| ? testing::UnorderedElementsAreArray( |
| {shell_surface->GetSurfaceId().frame_sink_id()}) |
| : testing::UnorderedElementsAreArray<viz::FrameSinkId>({}); |
| EXPECT_THAT(frame_throttling_controller->GetFrameSinkIdsToThrottle(), |
| should_throttle_set); |
| |
| // ash::kFrameRateThrottleKey is only set for lacros. |
| const bool should_set_property = app_type == ash::AppType::LACROS; |
| EXPECT_EQ(should_set_property, |
| window->GetProperty(ash::kFrameRateThrottleKey)); |
| } |
| } |
| |
| TEST_F(ShellSurfaceTest, ThrottleFrameRateViaControllerArc) { |
| ash::MockFrameThrottlingObserver observer; |
| ash::FrameThrottlingController* frame_throttling_controller = |
| ash::Shell::Get()->frame_throttling_controller(); |
| frame_throttling_controller->AddArcObserver(&observer); |
| |
| auto shell_surface = test::ShellSurfaceBuilder({20, 20}) |
| .SetAppType(ash::AppType::ARC_APP) |
| .BuildShellSurface(); |
| |
| aura::Window* window = shell_surface->GetWidget()->GetNativeWindow(); |
| |
| EXPECT_CALL(observer, |
| OnThrottlingStarted( |
| testing::UnorderedElementsAreArray({window}), |
| frame_throttling_controller->GetCurrentThrottledFrameRate())); |
| frame_throttling_controller->StartThrottling({window}); |
| |
| frame_throttling_controller->RemoveArcObserver(&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, |
| float raster_scale, |
| aura::Window::OcclusionState occlusion_state, |
| std::optional<chromeos::WindowStateType> restore_state_type) { |
| 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(); |
| } |
| std::optional<ConfigureState> configure_state; |
| std::optional<gfx::Point> origin; |
| int32_t serial = 0; |
| }; |
| |
| } // namespace |
| |
| TEST_F(ShellSurfaceTest, DragWithHTCLIENT) { |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({64, 64}).BuildShellSurface(); |
| ShellSurfaceCallbacks callbacks; |
| shell_surface->set_configure_callback(base::BindRepeating( |
| &ShellSurfaceCallbacks::OnConfigure, base::Unretained(&callbacks))); |
| ash::WindowState::Get(shell_surface->GetWidget()->GetNativeWindow()) |
| ->CreateDragDetails(gfx::PointF(0, 0), HTCLIENT, |
| ::wm::WINDOW_MOVE_SOURCE_TOUCH); |
| |
| shell_surface->GetWidget()->SetBounds(gfx::Rect(0, 0, 80, 80)); |
| shell_surface->AcknowledgeConfigure(callbacks.serial); |
| shell_surface->root_surface()->Commit(); |
| } |
| |
| TEST_F(ShellSurfaceTest, ScreenCoordinates) { |
| exo::test::TestSecurityDelegate securityDelegate; |
| securityDelegate.SetCanSetBounds( |
| SecurityDelegate::SetBoundsPolicy::DCHECK_IF_DECORATED); |
| auto shell_surface = test::ShellSurfaceBuilder({20, 20}) |
| .SetSecurityDelegate(&securityDelegate) |
| .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(); |
| EXPECT_FALSE(shell_surface->IsReady()); |
| 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()); |
| EXPECT_FALSE(shell_surface->IsReady()); |
| |
| gfx::Size size(256, 256); |
| auto new_buffer = test::ExoTestHelper::CreateBuffer(size); |
| shell_surface->root_surface()->Attach(new_buffer.get()); |
| shell_surface->root_surface()->Commit(); |
| EXPECT_TRUE(shell_surface->GetWidget()->IsVisible()); |
| EXPECT_TRUE(shell_surface->IsReady()); |
| |
| 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)); |
| } |
| |
| TEST_F(ShellSurfaceTest, SetNotPersistable) { |
| auto shell_surface = test::ShellSurfaceBuilder(gfx::Size(20, 30)) |
| .SetNoCommit() |
| .BuildShellSurface(); |
| shell_surface->SetPersistable(/*persistable=*/false); |
| shell_surface->root_surface()->Commit(); |
| |
| EXPECT_TRUE(shell_surface->GetWidget()->IsVisible()); |
| EXPECT_FALSE(shell_surface->GetWidget()->GetNativeWindow()->GetProperty( |
| wm::kPersistableKey)); |
| } |
| |
| // Test that restore id is set correctly. |
| TEST_F(ShellSurfaceTest, SetRestoreInfoWithWindowIdSource) { |
| int32_t restore_session_id = 200; |
| const std::string app_id = "app_id"; |
| |
| gfx::Size size(20, 30); |
| auto shell_surface = |
| test::ShellSurfaceBuilder(size).SetNoCommit().BuildShellSurface(); |
| |
| shell_surface->SetRestoreInfoWithWindowIdSource(restore_session_id, app_id); |
| shell_surface->Restore(); |
| shell_surface->root_surface()->Commit(); |
| |
| EXPECT_TRUE(shell_surface->GetWidget()->IsVisible()); |
| |
| // FetchRestoreWindowId will return 0, because no app with id "app_id" is |
| // installed. |
| EXPECT_EQ(0, shell_surface->GetWidget()->GetNativeWindow()->GetProperty( |
| app_restore::kRestoreWindowIdKey)); |
| EXPECT_EQ(app_id, *shell_surface->GetWidget()->GetNativeWindow()->GetProperty( |
| app_restore::kAppIdKey)); |
| } |
| |
| // Surfaces without non-client view should not crash. |
| TEST_F(ShellSurfaceTest, NoNonClientViewWithConfigure) { |
| exo::test::TestSecurityDelegate securityDelegate; |
| securityDelegate.SetCanSetBounds( |
| SecurityDelegate::SetBoundsPolicy::DCHECK_IF_DECORATED); |
| // Popup windows don't have a non-client view. |
| auto shell_surface = test::ShellSurfaceBuilder({20, 20}) |
| .SetAsPopup() |
| .SetSecurityDelegate(&securityDelegate) |
| .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}) |
| .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()); |
| } |
| |
| TEST_F(ShellSurfaceTest, PipInitialPosition) { |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetUseSystemModalContainer() |
| .SetNoCommit() |
| .BuildShellSurface(); |
| shell_surface->SetWindowBounds(gfx::Rect(20, 20, 256, 256)); |
| shell_surface->SetPip(); |
| shell_surface->root_surface()->Commit(); |
| // PIP positioner place the PIP window to the edge that is closer to the given |
| // position |
| EXPECT_EQ(gfx::Rect(8, 20, 256, 256), |
| shell_surface->GetWidget()->GetWindowBoundsInScreen()); |
| } |
| |
| TEST_F(ShellSurfaceTest, PostWindowChangeCallback) { |
| chromeos::WindowStateType state_type = chromeos::WindowStateType::kDefault; |
| auto test_callback = base::BindRepeating( |
| [](chromeos::WindowStateType* state_type, const gfx::Rect&, |
| chromeos::WindowStateType new_type, bool, bool, const gfx::Vector2d&, |
| float, aura::Window::OcclusionState, |
| std::optional<chromeos::WindowStateType>) -> uint32_t { |
| *state_type = new_type; |
| return 0; |
| }, |
| &state_type); |
| |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({256, 256}).BuildShellSurface(); |
| |
| shell_surface->set_configure_callback(test_callback); |
| |
| auto* state = ash::WindowState::Get( |
| shell_surface->GetWidget()->GetNativeWindow()->GetToplevelWindow()); |
| |
| // Make sure we are in a non-snapped state before testing state change. |
| ASSERT_FALSE(state->IsSnapped()); |
| |
| auto snap_event = |
| std::make_unique<ash::WindowSnapWMEvent>(ash::WM_EVENT_SNAP_PRIMARY); |
| |
| // Trigger a snap event, this should cause a configure event. |
| state->OnWMEvent(snap_event.get()); |
| |
| EXPECT_EQ(state_type, chromeos::WindowStateType::kPrimarySnapped); |
| } |
| |
| // A single configuration event should be sent when both the bounds and the |
| // window state change. |
| TEST_F(ShellSurfaceTest, ConfigureOnlySentOnceForBoundsAndWindowStateChange) { |
| int times_configured = 0; |
| auto test_callback = base::BindRepeating( |
| [](int* times_configured, const gfx::Rect&, |
| chromeos::WindowStateType new_type, bool, bool, const gfx::Vector2d&, |
| float, aura::Window::OcclusionState, |
| std::optional<chromeos::WindowStateType>) -> uint32_t { |
| ++(*times_configured); |
| return 0; |
| }, |
| ×_configured); |
| |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({1, 1}).BuildShellSurface(); |
| |
| shell_surface->set_configure_callback(test_callback); |
| |
| auto* state = ash::WindowState::Get( |
| shell_surface->GetWidget()->GetNativeWindow()->GetToplevelWindow()); |
| |
| // Make sure we are in normal mode. Maximizing from this state should result |
| // in BOTH the bounds and state changing. |
| ASSERT_TRUE(state->IsNormalStateType()); |
| |
| auto maximize_event = std::make_unique<ash::WMEvent>(ash::WM_EVENT_MAXIMIZE); |
| |
| // Trigger a snap event, this should cause a configure event. |
| state->OnWMEvent(maximize_event.get()); |
| |
| // The bounds change event should have been suppressed because the window |
| // state is changing. |
| EXPECT_EQ(times_configured, 1); |
| } |
| |
| TEST_F(ShellSurfaceTest, SetImmersiveModeTriggersConfigure) { |
| int times_configured = 0; |
| auto test_callback = base::BindRepeating( |
| [](int* times_configured, const gfx::Rect&, |
| chromeos::WindowStateType new_type, bool, bool, const gfx::Vector2d&, |
| float, aura::Window::OcclusionState, |
| std::optional<chromeos::WindowStateType>) -> uint32_t { |
| ++(*times_configured); |
| return 0; |
| }, |
| ×_configured); |
| |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({1, 1}).BuildShellSurface(); |
| |
| shell_surface->set_configure_callback(test_callback); |
| |
| shell_surface->SetUseImmersiveForFullscreen(true); |
| |
| EXPECT_EQ(times_configured, 1); |
| } |
| |
| TEST_F(ShellSurfaceTest, |
| SetRasterScaleWindowPropertyConfiguresRasterScaleAndWaitsForAck) { |
| ConfigureData config_data; |
| constexpr gfx::Size kBufferSize(256, 256); |
| |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder(kBufferSize).BuildShellSurface(); |
| |
| shell_surface->set_configure_callback( |
| base::BindRepeating(&Configure, base::Unretained(&config_data))); |
| |
| auto* window = shell_surface->GetWidget()->GetNativeWindow(); |
| window->SetProperty(aura::client::kRasterScale, 0.1f); |
| shell_surface->AcknowledgeConfigure(0); |
| EXPECT_EQ(0.1f, config_data.raster_scale); |
| |
| window->SetProperty(aura::client::kRasterScale, 1.0f); |
| shell_surface->AcknowledgeConfigure(0); |
| EXPECT_EQ(1.0f, config_data.raster_scale); |
| } |
| |
| TEST_F(ShellSurfaceTest, MoveParentWithoutWidget) { |
| UpdateDisplay("800x600, 800x600"); |
| constexpr gfx::Size kSize{256, 256}; |
| std::unique_ptr<ShellSurface> parent_surface = |
| test::ShellSurfaceBuilder(kSize).BuildShellSurface(); |
| |
| std::unique_ptr<ShellSurface> child_surface = |
| test::ShellSurfaceBuilder(kSize).SetNoCommit().BuildShellSurface(); |
| child_surface->SetParent(parent_surface.get()); |
| auto* parent_widget = parent_surface->GetWidget(); |
| auto* root_before = parent_widget->GetNativeWindow()->GetRootWindow(); |
| parent_widget->SetBounds({{1000, 0}, kSize}); |
| // Crash (crbug.com/1395433) happens when a transient parent moved |
| // to another root window before widget is created. Make sure that |
| // happened. |
| EXPECT_NE(root_before, parent_widget->GetNativeWindow()->GetRootWindow()); |
| } |
| |
| // Assert SetShape() applies the shape to the host window's layer on commit. |
| TEST_F(ShellSurfaceTest, SetShapeAppliedAfterSurfaceCommit) { |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({64, 64}).BuildShellSurface(); |
| shell_surface->OnSetServerStartResize(); |
| shell_surface->OnSetFrame(SurfaceFrameType::SHADOW); |
| shell_surface->root_surface()->Commit(); |
| const views::Widget* widget = shell_surface->GetWidget(); |
| ASSERT_TRUE(widget); |
| |
| // Windows shadows should be applied with resizing enabled. |
| EXPECT_NE(wm::kShadowElevationNone, |
| wm::GetShadowElevationConvertDefault(widget->GetNativeWindow())); |
| EXPECT_TRUE(shell_surface->server_side_resize()); |
| |
| // Create a window shape from two unique rects. |
| const cc::Region shape_region = |
| CreateRegion({{10, 10, 32, 32}, {20, 20, 32, 32}}); |
| |
| // Apply the shape to the surface. This should not yet be reflected on the |
| // host window's layer. |
| shell_surface->SetShape(shape_region); |
| const ui::Layer::ShapeRects* layer_shape_rects = |
| widget->GetNativeWindow()->layer()->alpha_shape(); |
| EXPECT_FALSE(layer_shape_rects); |
| |
| // After surface commit the shape should have been applied to the layer. |
| shell_surface->root_surface()->Commit(); |
| layer_shape_rects = widget->GetNativeWindow()->layer()->alpha_shape(); |
| EXPECT_TRUE(layer_shape_rects); |
| EXPECT_EQ(shape_region, CreateRegion(*layer_shape_rects)); |
| |
| // Window shadows and resizing should be disabled when window shapes are set. |
| EXPECT_EQ(wm::kShadowElevationNone, |
| wm::GetShadowElevationConvertDefault(widget->GetNativeWindow())); |
| EXPECT_FALSE(shell_surface->server_side_resize()); |
| |
| // Ensure the window targeter correctly passes through events to areas of the |
| // window not covered by the shape. |
| gfx::Rect target_bounds = widget->GetWindowBoundsInScreen(); |
| ui::test::EventGenerator* event_generator = GetEventGenerator(); |
| { |
| // Send an event to the point just outside of the region, it should not |
| // target the root surface. |
| gfx::Point location = target_bounds.origin() + gfx::Vector2d(9, 9); |
| ui::MouseEvent event(ui::ET_MOUSE_MOVED, location, location, |
| ui::EventTimeForNow(), 0, 0); |
| event_generator->Dispatch(&event); |
| EXPECT_NE(shell_surface->root_surface(), |
| GetTargetSurfaceForLocatedEvent(&event)); |
| } |
| { |
| // Send an event to the point just within of the region, it should target |
| // the root surface. |
| gfx::Point location = target_bounds.origin() + gfx::Vector2d(11, 11); |
| ui::MouseEvent event(ui::ET_MOUSE_MOVED, location, location, |
| ui::EventTimeForNow(), 0, 0); |
| event_generator->Dispatch(&event); |
| EXPECT_EQ(shell_surface->root_surface(), |
| GetTargetSurfaceForLocatedEvent(&event)); |
| } |
| } |
| |
| // Assert SetShape() updates the host window's layer with the most recent shape |
| // when the surface commits. |
| TEST_F(ShellSurfaceTest, SetShapeUpdatesAndUnsetsCorrectlyAfterCommit) { |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({64, 64}) |
| .SetFrame(SurfaceFrameType::SHADOW) |
| .BuildShellSurface(); |
| shell_surface->OnSetServerStartResize(); |
| shell_surface->root_surface()->Commit(); |
| const views::Widget* widget = shell_surface->GetWidget(); |
| ASSERT_TRUE(widget); |
| |
| // Create several unique window shapes. |
| const cc::Region shape_region_1 = |
| CreateRegion({{5, 5, 32, 32}, {10, 10, 32, 32}}); |
| const cc::Region shape_region_2 = |
| CreateRegion({{15, 15, 32, 32}, {20, 20, 32, 32}}); |
| const cc::Region shape_region_3 = |
| CreateRegion({{25, 25, 32, 32}, {30, 40, 32, 32}}); |
| |
| // Apply two shapes to the surface without committing. Neither should be |
| // applied to the host window's layer. |
| shell_surface->SetShape(shape_region_1); |
| shell_surface->SetShape(shape_region_2); |
| const ui::Layer::ShapeRects* layer_shape_rects = |
| widget->GetNativeWindow()->layer()->alpha_shape(); |
| EXPECT_FALSE(layer_shape_rects); |
| |
| // After surface commit only the most recent shape should have been applied. |
| shell_surface->root_surface()->Commit(); |
| layer_shape_rects = widget->GetNativeWindow()->layer()->alpha_shape(); |
| EXPECT_TRUE(layer_shape_rects); |
| EXPECT_EQ(shape_region_2, CreateRegion(*layer_shape_rects)); |
| |
| // Apply another shape to the surface. The layer shape should not change. |
| shell_surface->SetShape(shape_region_3); |
| layer_shape_rects = widget->GetNativeWindow()->layer()->alpha_shape(); |
| EXPECT_TRUE(layer_shape_rects); |
| EXPECT_EQ(shape_region_2, CreateRegion(*layer_shape_rects)); |
| |
| // The new shape should have been applied after the surface is committed. |
| shell_surface->root_surface()->Commit(); |
| layer_shape_rects = widget->GetNativeWindow()->layer()->alpha_shape(); |
| EXPECT_TRUE(layer_shape_rects); |
| EXPECT_EQ(shape_region_3, CreateRegion(*layer_shape_rects)); |
| |
| // Setting a null shape should unset the host window's layer shape. |
| shell_surface->SetShape(std::nullopt); |
| shell_surface->root_surface()->Commit(); |
| layer_shape_rects = widget->GetNativeWindow()->layer()->alpha_shape(); |
| EXPECT_FALSE(layer_shape_rects); |
| } |
| |
| // SetShape() is not supported for windows with the frame enabled. |
| TEST_F(ShellSurfaceTest, SetShapeWithFrameNotSupported) { |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({64, 64}) |
| .SetFrame(SurfaceFrameType::NORMAL) |
| .BuildShellSurface(); |
| |
| shell_surface->OnSetServerStartResize(); |
| shell_surface->root_surface()->Commit(); |
| const views::Widget* widget = shell_surface->GetWidget(); |
| ASSERT_TRUE(widget); |
| |
| // Create a window shape from two unique rects. |
| const cc::Region shape_region = |
| CreateRegion({{10, 10, 32, 32}, {20, 20, 32, 32}}); |
| |
| // Try to apply the shape to the surface and commit, this should have no |
| // effect. |
| shell_surface->SetShape(shape_region); |
| const ui::Layer::ShapeRects* layer_shape_rects = |
| widget->GetNativeWindow()->layer()->alpha_shape(); |
| shell_surface->root_surface()->Commit(); |
| layer_shape_rects = widget->GetNativeWindow()->layer()->alpha_shape(); |
| EXPECT_FALSE(layer_shape_rects); |
| } |
| |
| TEST_F(ShellSurfaceTest, MaximizedOrFullscreenInitialState) { |
| UpdateDisplay("800x600, 800x600"); |
| constexpr gfx::Size kEmptySize{0, 0}; |
| // on secondary display. |
| constexpr gfx::Rect kInitialBounds{800, 0, 100, 100}; |
| const auto primary_display = GetPrimaryDisplay(); |
| const auto secondary_display = GetSecondaryDisplay(); |
| for (auto initial_state : {chromeos::WindowStateType::kMaximized, |
| chromeos::WindowStateType::kFullscreen}) { |
| std::stringstream ss; |
| ss << initial_state; |
| SCOPED_TRACE(ss.str()); |
| gfx::Rect primary_bounds = |
| initial_state == chromeos::WindowStateType::kMaximized |
| ? primary_display.work_area() |
| : primary_display.bounds(); |
| gfx::Rect secondary_bounds = |
| initial_state == chromeos::WindowStateType::kMaximized |
| ? secondary_display.work_area() |
| : secondary_display.bounds(); |
| // While it is possible to start in fullscreen, SessionRestore restores the |
| // originally fullscreen window to maximized, so the fullscreen window won't |
| // have restore bounds. |
| bool verify_restore_bounds = |
| initial_state == chromeos::WindowStateType::kMaximized; |
| { |
| ConfigureData config_data; |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder(kEmptySize) |
| .SetConfigureCallback(base::BindRepeating( |
| &Configure, base::Unretained(&config_data))) |
| .SetWindowState(initial_state) |
| .BuildShellSurface(); |
| EXPECT_EQ(1u, config_data.count); |
| EXPECT_EQ(initial_state, config_data.state_type); |
| EXPECT_EQ(primary_bounds, config_data.suggested_bounds); |
| } |
| { |
| ConfigureData config_data; |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder(kEmptySize) |
| .SetConfigureCallback(base::BindRepeating( |
| &Configure, base::Unretained(&config_data))) |
| .SetWindowState(initial_state) |
| .SetDisplayId(secondary_display.id()) |
| .BuildShellSurface(); |
| EXPECT_EQ(1u, config_data.count); |
| EXPECT_EQ(initial_state, config_data.state_type); |
| EXPECT_EQ(secondary_bounds, config_data.suggested_bounds); |
| } |
| { |
| ConfigureData config_data; |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder(kEmptySize) |
| .SetConfigureCallback(base::BindRepeating( |
| &Configure, base::Unretained(&config_data))) |
| .SetWindowState(initial_state) |
| .SetBounds(kInitialBounds) |
| .BuildShellSurface(); |
| EXPECT_EQ(1u, config_data.count); |
| EXPECT_EQ(initial_state, config_data.state_type); |
| EXPECT_EQ(secondary_bounds, config_data.suggested_bounds); |
| EXPECT_EQ(secondary_bounds, |
| shell_surface->GetWidget()->GetWindowBoundsInScreen()); |
| if (verify_restore_bounds) { |
| EXPECT_EQ(kInitialBounds, |
| shell_surface->GetWidget()->GetRestoredBounds()); |
| } |
| } |
| { |
| ConfigureData config_data; |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder(kEmptySize) |
| .SetConfigureCallback(base::BindRepeating( |
| &Configure, base::Unretained(&config_data))) |
| .SetWindowState(initial_state) |
| .SetBounds(kInitialBounds) |
| .BuildShellSurface(); |
| EXPECT_EQ(1u, config_data.count); |
| EXPECT_EQ(initial_state, config_data.state_type); |
| EXPECT_EQ(secondary_bounds, config_data.suggested_bounds); |
| if (verify_restore_bounds) { |
| EXPECT_EQ(kInitialBounds, |
| shell_surface->GetWidget()->GetRestoredBounds()); |
| } |
| } |
| { |
| // The display id has higher priority. |
| ConfigureData config_data; |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder(kEmptySize) |
| .SetConfigureCallback(base::BindRepeating( |
| &Configure, base::Unretained(&config_data))) |
| .SetWindowState(initial_state) |
| .SetBounds(kInitialBounds) |
| .SetDisplayId(primary_display.id()) |
| .BuildShellSurface(); |
| EXPECT_EQ(1u, config_data.count); |
| EXPECT_EQ(initial_state, config_data.state_type); |
| EXPECT_EQ(primary_bounds, config_data.suggested_bounds); |
| if (verify_restore_bounds) { |
| EXPECT_EQ(kInitialBounds, |
| shell_surface->GetWidget()->GetRestoredBounds()); |
| } |
| } |
| } |
| } |
| |
| TEST_F(ShellSurfaceTest, MinimizedInitialState) { |
| constexpr gfx::Rect kInitialBounds(100, 50, 400, 300); |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder() |
| .SetBounds(kInitialBounds) |
| .SetGeometry(gfx::Rect(kInitialBounds.size())) |
| .SetWindowState(chromeos::WindowStateType::kMinimized) |
| .BuildShellSurface(); |
| EXPECT_TRUE(shell_surface->GetWidget()->IsMinimized()); |
| EXPECT_EQ(kInitialBounds, shell_surface->GetWidget()->GetRestoredBounds()); |
| // The buffer hasn't been attached yet. |
| ASSERT_FALSE(shell_surface->root_surface()->GetBuffer()); |
| |
| // Initial buffer arrives and the window should stay in minimized. |
| auto new_buffer = test::ExoTestHelper::CreateBuffer(kInitialBounds.size()); |
| shell_surface->root_surface()->Attach(new_buffer.get()); |
| shell_surface->root_surface()->Commit(); |
| EXPECT_TRUE(shell_surface->GetWidget()->IsMinimized()); |
| |
| shell_surface->GetWidget()->Activate(); |
| EXPECT_FALSE(shell_surface->GetWidget()->IsMinimized()); |
| EXPECT_EQ(kInitialBounds, |
| shell_surface->GetWidget()->GetWindowBoundsInScreen()); |
| } |
| |
| TEST_F(ShellSurfaceTest, NoGeometryWidgetBoundsUpdate) { |
| constexpr gfx::Size kInitialSize(100, 100); |
| constexpr gfx::Size kLargerSize(256, 256); |
| constexpr gfx::Size kSmallerSize(50, 50); |
| |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder(kInitialSize).BuildShellSurface(); |
| |
| EXPECT_EQ(kInitialSize, |
| shell_surface->GetWidget()->GetWindowBoundsInScreen().size()); |
| |
| auto larger_buffer = test::ExoTestHelper::CreateBuffer(kLargerSize); |
| |
| shell_surface->root_surface()->Attach(larger_buffer.get()); |
| shell_surface->root_surface()->Commit(); |
| |
| EXPECT_EQ(kLargerSize, |
| shell_surface->GetWidget()->GetWindowBoundsInScreen().size()); |
| |
| auto smaller_buffer = test::ExoTestHelper::CreateBuffer(kSmallerSize); |
| |
| shell_surface->root_surface()->Attach(smaller_buffer.get()); |
| shell_surface->root_surface()->Commit(); |
| |
| EXPECT_EQ(kSmallerSize, |
| shell_surface->GetWidget()->GetWindowBoundsInScreen().size()); |
| } |
| |
| TEST_F(ShellSurfaceTest, SubpixelPositionOffset) { |
| UpdateDisplay("1200x800*1.6"); |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetOrigin({20, 20}) |
| .SetFrame(SurfaceFrameType::NORMAL) |
| .BuildShellSurface(); |
| // Enabling a normal frame makes `GetClientViewBounds().origin()` return |
| // (0, 32), which makes the host window not align with any pixel boundary. |
| shell_surface->root_surface()->SetSurfaceHierarchyContentBoundsForTest( |
| gfx::Rect(-20, -20, 256, 256)); |
| EXPECT_TRUE(shell_surface->OnPreWidgetCommit()); |
| shell_surface->CommitWidget(); |
| EXPECT_EQ(gfx::Point(20, 20), shell_surface->root_surface_origin_pixel()); |
| EXPECT_EQ(gfx::Rect(-12, 20, 256, 256), |
| shell_surface->host_window()->bounds()); |
| // Verify that 'root_surface_origin()' is exactly preservered in pixels with |
| // subpixel offset. |
| // (0, -0.125) is caused by the frame like above, and (-0.5, -0.5) is for |
| // 'root_surface_origin()'. |
| EXPECT_EQ(gfx::Vector2dF(-0.5, -0.625), |
| shell_surface->host_window()->layer()->GetSubpixelOffset()); |
| } |
| |
| // Make sure the shell surface with capture can be safely deleted |
| // even if the widget is not visible. |
| TEST_F(ShellSurfaceTest, DeleteWithGrab) { |
| auto shell_surface = |
| test::ShellSurfaceBuilder({200, 200}).BuildShellSurface(); |
| auto popup_shell_surface = test::ShellSurfaceBuilder({100, 100}) |
| .SetAsPopup() |
| .SetParent(shell_surface.get()) |
| .SetGrab() |
| .BuildShellSurface(); |
| popup_shell_surface->GetWidget()->GetNativeWindow()->layer()->SetVisible( |
| false); |
| popup_shell_surface.reset(); |
| |
| // Close with grab. |
| popup_shell_surface = test::ShellSurfaceBuilder({100, 100}) |
| .SetAsPopup() |
| .SetParent(shell_surface.get()) |
| .SetGrab() |
| .BuildShellSurface(); |
| popup_shell_surface->GetWidget()->Close(); |
| // Close is async. |
| base::RunLoop().RunUntilIdle(); |
| popup_shell_surface.reset(); |
| |
| // CloseNow with grab. |
| popup_shell_surface = test::ShellSurfaceBuilder({100, 100}) |
| .SetAsPopup() |
| .SetParent(shell_surface.get()) |
| .SetGrab() |
| .BuildShellSurface(); |
| popup_shell_surface->GetWidget()->CloseNow(); |
| popup_shell_surface.reset(); |
| } |
| |
| TEST_F(ShellSurfaceTest, WindowPropertyChangedNotificationWithoutRootSurface) { |
| // Test OnWindowPropertyChanged() notification on a ShellSurface, whose root |
| // surface has gone. |
| |
| auto* overview_controller = ash::Shell::Get()->overview_controller(); |
| |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetAppType(ash::AppType::LACROS) |
| .BuildShellSurface(); |
| |
| std::unique_ptr<ShellSurface> shell_surface1 = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetAppType(ash::AppType::LACROS) |
| .BuildShellSurface(); |
| |
| overview_controller->StartOverview(ash::OverviewStartAction::kTests); |
| ash::WaitForOverviewEnterAnimation(); |
| |
| test::ShellSurfaceBuilder::DestroyRootSurface(shell_surface1.get()); |
| |
| // Destroying `shell_surface` will close its aura window, causing update of |
| // frame throttling in the overview mode for the remaining Lacros window(s). |
| // In this case, the remaining window is associated with `shell_surface1`. It |
| // receives OnWindowPropertyChanged() notification with |
| // ash::kFrameRateThrottleKey key. The root surface of `shell_surface1` has |
| // gone at this point. Verify that it doesn't cause crash. |
| shell_surface.reset(); |
| |
| overview_controller->EndOverview(ash::OverviewEndAction::kTests); |
| } |
| |
| TEST_F(ShellSurfaceTest, SurfaceSyncWithShellSurfaceCreatedOnDisplayWithScale) { |
| ash::Shell::GetRootWindowForNewWindows()->layer()->OnDeviceScaleFactorChanged( |
| 2.f); |
| |
| // Empty means no initial size. |
| constexpr gfx::Size kEmptySize(0, 0); |
| ConfigureData config_data; |
| auto shell_surface = |
| test::ShellSurfaceBuilder(kEmptySize) |
| .SetNoCommit() |
| .SetConfigureCallback(base::BindRepeating( |
| &ConfigureSerial, base::Unretained(&config_data))) |
| .BuildShellSurface(); |
| auto* surface = shell_surface->root_surface(); |
| |
| // Creates the shell widget, and add the surface_tree_host's window as child. |
| surface->Commit(); |
| |
| EXPECT_EQ(config_data.count, 1u); |
| EXPECT_EQ(config_data.suggested_bounds, gfx::Rect()); |
| EXPECT_EQ(shell_surface->GetCommitTargetLayer(), |
| shell_surface->host_window()->layer()); |
| EXPECT_EQ(shell_surface->GetSurfaceId(), |
| shell_surface->host_window()->GetSurfaceId()); |
| } |
| |
| TEST_F(ShellSurfaceTest, |
| DisplayScaleChangeTakesCompositorLockForMaximizedWindow) { |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetWindowState(chromeos::WindowStateType::kMaximized) |
| .BuildShellSurface(); |
| auto* surface = shell_surface->root_surface(); |
| |
| 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, float raster_scale, |
| aura::Window::OcclusionState occlusion_state, |
| std::optional<chromeos::WindowStateType>) { return ++(*serial_ptr); }, |
| &serial); |
| shell_surface->set_configure_callback(configure_callback); |
| |
| ui::Compositor* compositor = |
| shell_surface->GetWidget()->GetNativeWindow()->layer()->GetCompositor(); |
| EXPECT_FALSE(compositor->IsLocked()); |
| |
| auto* display_manager = ash::Shell::Get()->display_manager(); |
| const auto display_id = display_manager->GetDisplayAt(0).id(); |
| display_manager->ZoomDisplay(display_id, /*up=*/true); |
| |
| // Compositor locked until maximized window updates. |
| EXPECT_TRUE(compositor->IsLocked()); |
| |
| shell_surface->AcknowledgeConfigure(serial); |
| EXPECT_TRUE(compositor->IsLocked()); |
| |
| surface->Commit(); |
| EXPECT_FALSE(compositor->IsLocked()); |
| |
| display_manager->ZoomDisplay(display_id, /*up=*/false); |
| |
| // Compositor locked until maximized window updates. |
| EXPECT_TRUE(compositor->IsLocked()); |
| |
| shell_surface->AcknowledgeConfigure(serial); |
| EXPECT_TRUE(compositor->IsLocked()); |
| |
| surface->Commit(); |
| EXPECT_FALSE(compositor->IsLocked()); |
| } |
| |
| TEST_F(ShellSurfaceTest, |
| DisplayScaleChangeDoesNotTakeCompositorLockForFreeformWindow) { |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({256, 256}).BuildShellSurface(); |
| |
| ui::Compositor* compositor = |
| shell_surface->GetWidget()->GetNativeWindow()->layer()->GetCompositor(); |
| EXPECT_FALSE(compositor->IsLocked()); |
| |
| auto* display_manager = ash::Shell::Get()->display_manager(); |
| const auto display_id = display_manager->GetDisplayAt(0).id(); |
| display_manager->ZoomDisplay(display_id, /*up=*/true); |
| |
| // Should not take compositor lock. |
| EXPECT_FALSE(compositor->IsLocked()); |
| |
| display_manager->ZoomDisplay(display_id, /*up=*/false); |
| |
| // Should not take compositor lock. |
| EXPECT_FALSE(compositor->IsLocked()); |
| } |
| |
| // Tests that updates to the display layout configuration update the origin on |
| // relevant hosted surfaces. |
| TEST_F(ShellSurfaceTest, DisplayLayoutConfigurationUpdatesSurfaceOrigin) { |
| // Start with a single display configuration. |
| UpdateDisplay("800x600"); |
| |
| // Create the surface. |
| constexpr gfx::Size kBufferSize(256, 256); |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder(kBufferSize).SetNoCommit().BuildShellSurface(); |
| |
| // Set origin and leave/enter callbacks. |
| gfx::Point client_origin; |
| int64_t old_display_id = display::kInvalidDisplayId; |
| int64_t new_display_id = display::kInvalidDisplayId; |
| shell_surface->set_origin_change_callback(base::BindLambdaForTesting( |
| [&](const gfx::Point& origin) { client_origin = origin; })); |
| shell_surface->root_surface()->set_leave_enter_callback( |
| base::BindLambdaForTesting([&](int64_t old_id, int64_t new_id) { |
| old_display_id = old_id; |
| new_display_id = new_id; |
| return true; |
| })); |
| |
| // Creating a new shell surface should notify on which display it is created. |
| constexpr gfx::Point kInitialOrigin = {200, 200}; |
| shell_surface->root_surface()->Commit(); |
| shell_surface->GetWidget()->SetBounds({kInitialOrigin, kBufferSize}); |
| EXPECT_EQ(display::kInvalidDisplayId, old_display_id); |
| EXPECT_EQ(GetPrimaryDisplay().id(), new_display_id); |
| EXPECT_EQ(kInitialOrigin, client_origin); |
| |
| // Attaching a second display should not change where the surface is located. |
| UpdateDisplay("800x600,800x600"); |
| EXPECT_EQ(kInitialOrigin, client_origin); |
| |
| // Move the window to second display. |
| constexpr gfx::Point kNewOrigin = {1000, 200}; |
| shell_surface->GetWidget()->SetBounds({kNewOrigin, kBufferSize}); |
| EXPECT_EQ(GetPrimaryDisplay().id(), old_display_id); |
| EXPECT_EQ(GetSecondaryDisplay().id(), new_display_id); |
| EXPECT_EQ(kNewOrigin, client_origin); |
| |
| // Reposition the second display, the surface should receive an origin change |
| // event representing the updated bounds in screen coordinates. |
| constexpr int kVerticalOffset = 200; |
| display::DisplayLayoutBuilder builder(GetPrimaryDisplay().id()); |
| builder.SetSecondaryPlacement(GetSecondaryDisplay().id(), |
| display::DisplayPlacement::RIGHT, |
| kVerticalOffset); |
| ash::Shell::Get()->display_manager()->SetLayoutForCurrentDisplays( |
| builder.Build()); |
| |
| EXPECT_EQ(kNewOrigin + gfx::Vector2d(0, kVerticalOffset), client_origin); |
| } |
| |
| // Tests the unnecessary occlusion events are fired when opaque buffer and no |
| // frame are used. |
| TEST_F(ShellSurfaceTest, DisplayScaleChangeDoesNotSendOcclusionUpdates) { |
| std::unique_ptr<ShellSurface> shell_surface1 = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetRootBufferFormat(kOpaqueFormat) |
| .BuildShellSurface(); |
| std::unique_ptr<ShellSurface> shell_surface2 = |
| test::ShellSurfaceBuilder({256, 256}) |
| .SetRootBufferFormat(kOpaqueFormat) |
| .BuildShellSurface(); |
| auto* surface1 = shell_surface1->root_surface(); |
| auto* surface2 = shell_surface2->root_surface(); |
| auto* window1 = shell_surface1->GetWidget()->GetNativeWindow(); |
| auto* window2 = shell_surface2->GetWidget()->GetNativeWindow(); |
| |
| // xdg-shell without a frame type will use NOT_DRAWN layer type and |
| // should control the opacity by themselves. |
| EXPECT_EQ(ui::LAYER_NOT_DRAWN, window1->layer()->type()); |
| EXPECT_FALSE(window1->GetProperty(chromeos::kWindowManagerManagesOpacityKey)); |
| EXPECT_EQ(ui::LAYER_NOT_DRAWN, window2->layer()->type()); |
| EXPECT_FALSE(window2->GetProperty(chromeos::kWindowManagerManagesOpacityKey)); |
| |
| const std::vector<gfx::Rect> kNormalOpaqueRegion{gfx::Rect(256, 256)}; |
| |
| // Normal window should have custom opacity regions. |
| EXPECT_TRUE(window1->GetTransparent()); |
| EXPECT_TRUE(window2->GetTransparent()); |
| EXPECT_EQ(kNormalOpaqueRegion, window1->opaque_regions_for_occlusion()); |
| EXPECT_EQ(kNormalOpaqueRegion, window2->opaque_regions_for_occlusion()); |
| |
| // Test Maximzied State |
| shell_surface1->Maximize(); |
| shell_surface2->Maximize(); |
| |
| EXPECT_FALSE(window1->GetTransparent()); |
| EXPECT_FALSE(window2->GetTransparent()); |
| const std::vector<gfx::Rect> kMaximizedOpaqueRegion{gfx::Rect(800, 552)}; |
| EXPECT_EQ(kMaximizedOpaqueRegion, window1->opaque_regions_for_occlusion()); |
| EXPECT_EQ(kMaximizedOpaqueRegion, window2->opaque_regions_for_occlusion()); |
| |
| // Update root surfaces (this happens asynchronously normally) and set |
| // occlusion tracking. |
| surface1->SetOcclusionTracking(true); |
| auto surface1_buffer = |
| test::ExoTestHelper::CreateBuffer(shell_surface1.get(), kOpaqueFormat); |
| surface1->Attach(surface1_buffer.get()); |
| surface1->Commit(); |
| surface2->SetOcclusionTracking(true); |
| auto surface2_buffer = |
| test::ExoTestHelper::CreateBuffer(shell_surface2.get(), kOpaqueFormat); |
| surface2->Attach(surface2_buffer.get()); |
| surface2->Commit(); |
| |
| SurfaceObserverForTest observer1(surface1->window()->GetOcclusionState()); |
| surface1->AddSurfaceObserver(&observer1); |
| SurfaceObserverForTest observer2(surface2->window()->GetOcclusionState()); |
| surface2->AddSurfaceObserver(&observer2); |
| |
| EXPECT_EQ(aura::Window::OcclusionState::OCCLUDED, |
| surface1->window()->GetOcclusionState()); |
| EXPECT_EQ(aura::Window::OcclusionState::VISIBLE, |
| surface2->window()->GetOcclusionState()); |
| |
| auto* display_manager = ash::Shell::Get()->display_manager(); |
| const auto display_id = display_manager->GetDisplayAt(0).id(); |
| |
| EXPECT_EQ(0, observer1.num_occlusion_state_changes()); |
| EXPECT_EQ(0, observer2.num_occlusion_state_changes()); |
| |
| display_manager->ZoomDisplay(display_id, /*up=*/true); |
| // Update root surfaces (this happens asynchronously normally). |
| { |
| auto surface1_buffer_zoom = |
| test::ExoTestHelper::CreateBuffer(shell_surface1.get(), kOpaqueFormat); |
| surface1->Attach(surface1_buffer_zoom.get()); |
| surface1->Commit(); |
| auto surface2_buffer_zoom = |
| test::ExoTestHelper::CreateBuffer(shell_surface2.get(), kOpaqueFormat); |
| surface2->Attach(surface2_buffer_zoom.get()); |
| surface2->Commit(); |
| } |
| EXPECT_EQ(0, observer1.num_occlusion_state_changes()); |
| EXPECT_EQ(0, observer2.num_occlusion_state_changes()); |
| |
| display_manager->ZoomDisplay(display_id, /*up=*/false); |
| { |
| auto surface1_buffer_zoom = |
| test::ExoTestHelper::CreateBuffer(shell_surface1.get(), kOpaqueFormat); |
| surface1->Attach(surface1_buffer_zoom.get()); |
| surface1->Commit(); |
| auto surface2_buffer_zoom = |
| test::ExoTestHelper::CreateBuffer(shell_surface2.get(), kOpaqueFormat); |
| surface2->Attach(surface2_buffer_zoom.get()); |
| surface2->Commit(); |
| } |
| // Should not get any occlusion changes - requires occlusion tracking clip |
| // to the root window and that the shelf occlude what is below it, too. |
| EXPECT_EQ(0, observer1.num_occlusion_state_changes()); |
| EXPECT_EQ(0, observer2.num_occlusion_state_changes()); |
| |
| // Test Snapped State |
| ash::WindowSnapWMEvent snap_event(ash::WM_EVENT_SNAP_PRIMARY); |
| ash::WindowState* window_state1 = ash::WindowState::Get(window1); |
| ash::WindowState* window_state2 = ash::WindowState::Get(window2); |
| |
| window_state1->OnWMEvent(&snap_event); |
| window_state2->OnWMEvent(&snap_event); |
| EXPECT_EQ(0, observer1.num_occlusion_state_changes()); |
| EXPECT_EQ(0, observer2.num_occlusion_state_changes()); |
| |
| EXPECT_TRUE(window1->GetTransparent()); |
| EXPECT_TRUE(window2->GetTransparent()); |
| |
| const std::vector<gfx::Rect> kSnappedOpaqueRegion{gfx::Rect(400, 552)}; |
| EXPECT_EQ(kSnappedOpaqueRegion, window1->opaque_regions_for_occlusion()); |
| EXPECT_EQ(kSnappedOpaqueRegion, window2->opaque_regions_for_occlusion()); |
| { |
| auto snapped_buffer1 = |
| test::ExoTestHelper::CreateBuffer(shell_surface1.get(), kOpaqueFormat); |
| surface1->Attach(snapped_buffer1.get()); |
| surface1->Commit(); |
| |
| auto snapped_buffer2 = |
| test::ExoTestHelper::CreateBuffer(shell_surface2.get(), kOpaqueFormat); |
| surface2->Attach(snapped_buffer2.get()); |
| surface2->Commit(); |
| } |
| EXPECT_EQ(0, observer1.num_occlusion_state_changes()); |
| EXPECT_EQ(0, observer2.num_occlusion_state_changes()); |
| |
| display_manager->ZoomDisplay(display_id, /*up=*/true); |
| { |
| auto snapped_buffer1 = |
| test::ExoTestHelper::CreateBuffer(shell_surface1.get(), kOpaqueFormat); |
| surface1->Attach(snapped_buffer1.get()); |
| surface1->Commit(); |
| |
| auto snapped_buffer2 = |
| test::ExoTestHelper::CreateBuffer(shell_surface2.get(), kOpaqueFormat); |
| surface2->Attach(snapped_buffer2.get()); |
| surface2->Commit(); |
| } |
| EXPECT_EQ(0, observer1.num_occlusion_state_changes()); |
| EXPECT_EQ(0, observer2.num_occlusion_state_changes()); |
| |
| display_manager->ZoomDisplay(display_id, /*up=*/false); |
| { |
| auto snapped_buffer1 = |
| test::ExoTestHelper::CreateBuffer(shell_surface1.get(), kOpaqueFormat); |
| surface1->Attach(snapped_buffer1.get()); |
| surface1->Commit(); |
| |
| auto snapped_buffer2 = |
| test::ExoTestHelper::CreateBuffer(shell_surface2.get(), kOpaqueFormat); |
| surface2->Attach(snapped_buffer2.get()); |
| surface2->Commit(); |
| } |
| EXPECT_EQ(0, observer1.num_occlusion_state_changes()); |
| EXPECT_EQ(0, observer2.num_occlusion_state_changes()); |
| |
| // Make sure the occlusion tracking is working. |
| surface2->RemoveSurfaceObserver(&observer2); |
| shell_surface2.reset(); |
| |
| EXPECT_EQ(1, observer1.num_occlusion_state_changes()); |
| |
| surface1->RemoveSurfaceObserver(&observer1); |
| } |
| |
| TEST_F(ShellSurfaceTest, GetWidgetHitTestMask) { |
| auto shell_surface = test::ShellSurfaceBuilder({256, 256}) |
| .SetOrigin({100, 100}) |
| .BuildShellSurface(); |
| |
| EXPECT_TRUE(shell_surface->WidgetHasHitTestMask()); |
| SkPath mask; |
| shell_surface->GetWidgetHitTestMask(&mask); |
| // Returned HitMask should be in the widget local coordinates. |
| EXPECT_EQ(SkRect::MakeLTRB(0, 0, 256, 256), mask.getBounds()); |
| } |
| |
| TEST_F(ShellSurfaceTest, InitiallyMaximizedWindowIsOpaque) { |
| auto shell_surface = test::ShellSurfaceBuilder({256, 256}) |
| .SetFrame(SurfaceFrameType::SHADOW) |
| .SetOrigin({100, 100}) |
| .SetNoCommit() |
| .BuildShellSurface(); |
| shell_surface->Maximize(); |
| |
| shell_surface->root_surface()->Commit(); |
| |
| EXPECT_FALSE(shell_surface->GetWidget()->GetNativeWindow()->GetTransparent()); |
| } |
| |
| TEST_F(ShellSurfaceTest, ConfigureOcclusionSentAfterShellSurfaceIsReady) { |
| std::vector<ConfigureData> config_vec; |
| auto shell_surface = |
| test::ShellSurfaceBuilder({100, 100}) |
| .SetConfigureCallback(base::BindRepeating( |
| &ConfigureSerialVec, base::Unretained(&config_vec))) |
| .SetWindowState(chromeos::WindowStateType::kMinimized) |
| .SetNoRootBuffer() |
| .BuildShellSurface(); |
| |
| Surface* root_surface = shell_surface->root_surface(); |
| root_surface->Commit(); |
| |
| // Expect initial configure with no content (buffer for root surface). |
| EXPECT_EQ(1u, config_vec.size()); |
| |
| // TODO(crbug.com/328172097): We use VISIBLE window state for minimized |
| // windows on initial configure for now. |
| EXPECT_EQ(aura::Window::OcclusionState::VISIBLE, |
| config_vec[0].occlusion_state); |
| |
| constexpr gfx::Size kBufferSize(256, 256); |
| auto buffer = test::ExoTestHelper::CreateBuffer(kBufferSize); |
| root_surface->Attach(buffer.get()); |
| root_surface->Commit(); |
| |
| // Once we provide a buffer for the root surface, expect that the occlusion |
| // state is updated to HIDDEN, since it is a minimized window. |
| EXPECT_EQ(2u, config_vec.size()); |
| EXPECT_EQ(aura::Window::OcclusionState::HIDDEN, |
| config_vec[1].occlusion_state); |
| } |
| |
| // Regression test for crbug.com/322388171. Ensure shell surfaces with no |
| // backing widget handle display changes without crashing. |
| TEST_F(ShellSurfaceTest, HandlesDisplayChangeNoWidget) { |
| // Start with a single display configuration. |
| UpdateDisplay("800x600"); |
| |
| // Create the surface. |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({256, 256}).BuildShellSurface(); |
| EXPECT_TRUE(shell_surface->GetWidget()); |
| |
| // Keep the shell surface alive but close the widget. |
| shell_surface->GetWidget()->CloseNow(); |
| EXPECT_FALSE(shell_surface->GetWidget()); |
| |
| // Trigger an update to the display configuration, the shell surface should |
| // handle this without crashing. |
| UpdateDisplay("1200x800"); |
| } |
| |
| } // namespace exo |