| // Copyright 2019 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/wayland/zaura_shell.h" |
| |
| #include <aura-shell-server-protocol.h> |
| |
| #include <sys/socket.h> |
| #include <memory> |
| #include <vector> |
| |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shelf/shelf.h" |
| #include "ash/shell.h" |
| #include "ash/wm/desks/desks_util.h" |
| #include "ash/wm/window_util.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/time/time.h" |
| #include "components/exo/buffer.h" |
| #include "components/exo/shell_surface.h" |
| #include "components/exo/shell_surface_util.h" |
| #include "components/exo/test/exo_test_base.h" |
| #include "components/exo/test/shell_surface_builder.h" |
| #include "components/exo/wayland/scoped_wl.h" |
| #include "components/exo/wayland/wayland_display_observer.h" |
| #include "components/exo/wayland/wayland_display_output.h" |
| #include "components/exo/wayland/wayland_display_util.h" |
| #include "components/exo/wayland/wl_output.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/aura/window_occlusion_tracker.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/compositor/layer_animation_observer.h" |
| #include "ui/compositor/layer_animation_sequence.h" |
| #include "ui/compositor/layer_animator.h" |
| #include "ui/compositor/scoped_animation_duration_scale_mode.h" |
| #include "ui/compositor/scoped_layer_animation_settings.h" |
| #include "ui/compositor/test/layer_animator_test_controller.h" |
| #include "ui/display/screen.h" |
| #include "ui/events/base_event_utils.h" |
| #include "ui/events/event.h" |
| #include "ui/events/test/event_generator.h" |
| #include "ui/gfx/geometry/size_f.h" |
| #include "ui/views/corewm/tooltip_aura.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/wm/core/window_util.h" |
| #include "ui/wm/public/activation_change_observer.h" |
| #include "ui/wm/public/activation_client.h" |
| |
| namespace exo { |
| namespace wayland { |
| |
| namespace { |
| |
| constexpr auto kTransitionDuration = base::Seconds(3); |
| constexpr int kTooltipExpectedHeight = 28; |
| |
| class TestAuraSurface : public AuraSurface { |
| public: |
| explicit TestAuraSurface(Surface* surface) |
| : AuraSurface(surface, /*resource=*/nullptr) {} |
| |
| TestAuraSurface(const TestAuraSurface&) = delete; |
| TestAuraSurface& operator=(const TestAuraSurface&) = delete; |
| |
| float last_sent_occlusion_fraction() const { |
| return last_sent_occlusion_fraction_; |
| } |
| aura::Window::OcclusionState last_sent_occlusion_state() const { |
| return last_sent_occlusion_state_; |
| } |
| int num_occlusion_updates() const { return num_occlusion_updates_; } |
| bool send_occlusion_state_called() const { |
| return send_occlusion_state_called_; |
| } |
| |
| MOCK_METHOD(void, |
| OnTooltipShown, |
| (Surface * surface, |
| std::u16string_view text, |
| const gfx::Rect& bounds), |
| (override)); |
| MOCK_METHOD(void, OnTooltipHidden, (Surface * surface), (override)); |
| |
| protected: |
| void SendOcclusionFraction(float occlusion_fraction) override { |
| last_sent_occlusion_fraction_ = occlusion_fraction; |
| num_occlusion_updates_++; |
| } |
| |
| void SendOcclusionState( |
| const aura::Window::OcclusionState occlusion_state) override { |
| last_sent_occlusion_state_ = occlusion_state; |
| send_occlusion_state_called_ = true; |
| } |
| |
| private: |
| float last_sent_occlusion_fraction_ = -1.0f; |
| aura::Window::OcclusionState last_sent_occlusion_state_ = |
| aura::Window::OcclusionState::UNKNOWN; |
| int num_occlusion_updates_ = 0; |
| bool send_occlusion_state_called_ = false; |
| }; |
| |
| class MockSurfaceDelegate : public SurfaceDelegate { |
| public: |
| MOCK_METHOD(void, OnSurfaceCommit, (), (override)); |
| MOCK_METHOD(bool, IsSurfaceSynchronized, (), (const, override)); |
| MOCK_METHOD(bool, IsInputEnabled, (Surface * surface), (const, override)); |
| MOCK_METHOD(void, OnSetFrame, (SurfaceFrameType type), (override)); |
| MOCK_METHOD(void, |
| OnSetFrameColors, |
| (SkColor active_color, SkColor inactive_color), |
| (override)); |
| MOCK_METHOD(void, |
| OnSetParent, |
| (Surface * parent, const gfx::Point& position), |
| (override)); |
| MOCK_METHOD(void, OnSetStartupId, (const char* startup_id), (override)); |
| MOCK_METHOD(void, |
| OnSetApplicationId, |
| (const char* application_id), |
| (override)); |
| MOCK_METHOD(void, SetUseImmersiveForFullscreen, (bool value), (override)); |
| MOCK_METHOD(void, OnActivationRequested, (), (override)); |
| MOCK_METHOD(void, OnNewOutputAdded, (), (override)); |
| MOCK_METHOD(void, OnSetServerStartResize, (), (override)); |
| MOCK_METHOD(void, ShowSnapPreviewToPrimary, (), (override)); |
| MOCK_METHOD(void, ShowSnapPreviewToSecondary, (), (override)); |
| MOCK_METHOD(void, HideSnapPreview, (), (override)); |
| MOCK_METHOD(void, SetSnapPrimary, (float snap_ratio), (override)); |
| MOCK_METHOD(void, SetSnapSecondary, (float snap_ratio), (override)); |
| MOCK_METHOD(void, UnsetSnap, (), (override)); |
| MOCK_METHOD(void, SetCanGoBack, (), (override)); |
| MOCK_METHOD(void, UnsetCanGoBack, (), (override)); |
| MOCK_METHOD(void, SetPip, (), (override)); |
| MOCK_METHOD(void, UnsetPip, (), (override)); |
| MOCK_METHOD(void, |
| SetFloatToLocation, |
| (chromeos::FloatStartLocation), |
| (override)); |
| MOCK_METHOD(void, |
| SetAspectRatio, |
| (const gfx::SizeF& aspect_ratio), |
| (override)); |
| MOCK_METHOD(void, MoveToDesk, (int desk_index), (override)); |
| MOCK_METHOD(void, SetVisibleOnAllWorkspaces, (), (override)); |
| MOCK_METHOD(void, |
| SetInitialWorkspace, |
| (const char* initial_workspace), |
| (override)); |
| MOCK_METHOD(void, Pin, (bool trusted), (override)); |
| MOCK_METHOD(void, Unpin, (), (override)); |
| MOCK_METHOD(void, SetSystemModal, (bool modal), (override)); |
| MOCK_METHOD(void, SetTopInset, (int height), (override)); |
| MOCK_METHOD(SecurityDelegate*, GetSecurityDelegate, (), (override)); |
| }; |
| |
| } // namespace |
| |
| class ZAuraSurfaceTest : public test::ExoTestBase, |
| public ::wm::ActivationChangeObserver { |
| public: |
| ZAuraSurfaceTest() = default; |
| |
| ZAuraSurfaceTest(const ZAuraSurfaceTest&) = delete; |
| ZAuraSurfaceTest& operator=(const ZAuraSurfaceTest&) = delete; |
| |
| ~ZAuraSurfaceTest() override = default; |
| |
| // test::ExoTestBase overrides: |
| void SetUp() override { |
| test::ExoTestBase::SetUp(); |
| |
| gfx::Size buffer_size(10, 10); |
| auto buffer = test::ExoTestHelper::CreateBuffer(buffer_size); |
| |
| surface_ = std::make_unique<Surface>(); |
| surface_->Attach(buffer.get()); |
| |
| aura_surface_ = std::make_unique<TestAuraSurface>(surface_.get()); |
| |
| gfx::Transform transform; |
| transform.Scale(1.5f, 1.5f); |
| parent_widget_ = |
| CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET); |
| parent_widget_->SetBounds(gfx::Rect(0, 0, 10, 10)); |
| parent_widget_->GetNativeWindow()->SetTransform(transform); |
| parent_widget_->GetNativeWindow()->AddChild(surface_->window()); |
| parent_widget_->Show(); |
| surface_->window()->SetBounds(gfx::Rect(5, 5, 10, 10)); |
| surface_->window()->Show(); |
| |
| ash::Shell::Get()->activation_client()->AddObserver(this); |
| aura_surface_->SetOcclusionTracking(true); |
| } |
| |
| void TearDown() override { |
| ash::Shell::Get()->activation_client()->RemoveObserver(this); |
| |
| parent_widget_.reset(); |
| aura_surface_.reset(); |
| surface_.reset(); |
| |
| test::ExoTestBase::TearDown(); |
| } |
| |
| // ::wm::ActivationChangeObserver overrides: |
| void OnWindowActivated(ActivationReason reason, |
| aura::Window* gained_active, |
| aura::Window* lost_active) override { |
| if (lost_active == parent_widget_->GetNativeWindow()) { |
| occlusion_fraction_on_activation_loss_ = |
| aura_surface().last_sent_occlusion_fraction(); |
| } |
| } |
| |
| protected: |
| TestAuraSurface& aura_surface() { return *aura_surface_; } |
| Surface& surface() { return *surface_; } |
| views::Widget& parent_widget() { return *parent_widget_; } |
| float occlusion_fraction_on_activation_loss() const { |
| return occlusion_fraction_on_activation_loss_; |
| } |
| |
| std::unique_ptr<views::Widget> CreateOpaqueWidget(const gfx::Rect& bounds) { |
| return CreateTestWidget( |
| views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET, |
| /*delegate=*/nullptr, |
| /*container_id=*/ash::desks_util::GetActiveDeskContainerId(), bounds, |
| /*show=*/false); |
| } |
| |
| private: |
| std::unique_ptr<TestAuraSurface> aura_surface_; |
| std::unique_ptr<Surface> surface_; |
| std::unique_ptr<views::Widget> parent_widget_; |
| float occlusion_fraction_on_activation_loss_ = -1.0f; |
| }; |
| |
| TEST_F(ZAuraSurfaceTest, OcclusionTrackingStartsAfterCommit) { |
| surface().OnWindowOcclusionChanged(aura::Window::OcclusionState::UNKNOWN, |
| aura::Window::OcclusionState::UNKNOWN); |
| |
| EXPECT_EQ(-1.0f, aura_surface().last_sent_occlusion_fraction()); |
| EXPECT_EQ(aura::Window::OcclusionState::UNKNOWN, |
| aura_surface().last_sent_occlusion_state()); |
| EXPECT_EQ(0, aura_surface().num_occlusion_updates()); |
| EXPECT_FALSE(surface().IsTrackingOcclusion()); |
| |
| auto widget = CreateOpaqueWidget(gfx::Rect(0, 0, 10, 10)); |
| widget->Show(); |
| surface().Commit(); |
| |
| EXPECT_EQ(0.2f, aura_surface().last_sent_occlusion_fraction()); |
| EXPECT_EQ(aura::Window::OcclusionState::VISIBLE, |
| aura_surface().last_sent_occlusion_state()); |
| EXPECT_EQ(1, aura_surface().num_occlusion_updates()); |
| EXPECT_TRUE(surface().IsTrackingOcclusion()); |
| } |
| |
| TEST_F(ZAuraSurfaceTest, |
| LosingActivationWithNoAnimatingWindowsSendsCorrectOcclusionFraction) { |
| surface().Commit(); |
| EXPECT_EQ(0.0f, aura_surface().last_sent_occlusion_fraction()); |
| EXPECT_EQ(aura::Window::OcclusionState::VISIBLE, |
| aura_surface().last_sent_occlusion_state()); |
| EXPECT_EQ(1, aura_surface().num_occlusion_updates()); |
| ::wm::ActivateWindow(parent_widget().GetNativeWindow()); |
| |
| // Creating an opaque window but don't show it. |
| auto widget = CreateOpaqueWidget(gfx::Rect(0, 0, 10, 10)); |
| |
| // Occlusion sent before de-activation should include that widget. |
| widget->Show(); |
| EXPECT_EQ(0.2f, occlusion_fraction_on_activation_loss()); |
| EXPECT_EQ(0.2f, aura_surface().last_sent_occlusion_fraction()); |
| EXPECT_EQ(aura::Window::OcclusionState::VISIBLE, |
| aura_surface().last_sent_occlusion_state()); |
| EXPECT_EQ(2, aura_surface().num_occlusion_updates()); |
| } |
| |
| TEST_F(ZAuraSurfaceTest, |
| LosingActivationWithAnimatingWindowsSendsTargetOcclusionFraction) { |
| surface().Commit(); |
| EXPECT_EQ(0.0f, aura_surface().last_sent_occlusion_fraction()); |
| EXPECT_EQ(aura::Window::OcclusionState::VISIBLE, |
| aura_surface().last_sent_occlusion_state()); |
| EXPECT_EQ(1, aura_surface().num_occlusion_updates()); |
| ::wm::ActivateWindow(parent_widget().GetNativeWindow()); |
| |
| // Creating an opaque window but don't show it. |
| auto widget = CreateOpaqueWidget(gfx::Rect(0, 0, 10, 10)); |
| widget->GetNativeWindow()->layer()->SetOpacity(0.0f); |
| |
| ui::ScopedAnimationDurationScaleMode scoped_animation_duration_scale_mode( |
| ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION); |
| ui::LayerAnimatorTestController test_controller( |
| ui::LayerAnimator::CreateImplicitAnimator()); |
| ui::ScopedLayerAnimationSettings layer_animation_settings( |
| test_controller.animator()); |
| layer_animation_settings.SetTransitionDuration(kTransitionDuration); |
| widget->GetNativeWindow()->layer()->SetAnimator(test_controller.animator()); |
| widget->GetNativeWindow()->layer()->SetOpacity(1.0f); |
| |
| // Opacity animation uses threaded animation. |
| test_controller.StartThreadedAnimationsIfNeeded(); |
| test_controller.Step(kTransitionDuration / 3); |
| |
| // No occlusion updates should happen until the window is de-activated. |
| EXPECT_EQ(1, aura_surface().num_occlusion_updates()); |
| |
| // Occlusion sent before de-activation should include the window animating |
| // to be completely opaque. |
| widget->Show(); |
| EXPECT_EQ(0.2f, occlusion_fraction_on_activation_loss()); |
| EXPECT_EQ(0.2f, aura_surface().last_sent_occlusion_fraction()); |
| EXPECT_EQ(aura::Window::OcclusionState::VISIBLE, |
| aura_surface().last_sent_occlusion_state()); |
| EXPECT_EQ(2, aura_surface().num_occlusion_updates()); |
| |
| // Explicitly stop animation because threaded animation may have started |
| // a bit later. |kTransitionDuration| may not be quite enough to reach the |
| // end. |
| test_controller.Step(kTransitionDuration / 3); |
| test_controller.Step(kTransitionDuration / 3); |
| widget->GetNativeWindow()->layer()->GetAnimator()->StopAnimating(); |
| widget->GetNativeWindow()->layer()->SetAnimator(nullptr); |
| |
| // Expect the occlusion tracker to send an update after the animation |
| // finishes. |
| EXPECT_EQ(0.2f, aura_surface().last_sent_occlusion_fraction()); |
| EXPECT_EQ(aura::Window::OcclusionState::VISIBLE, |
| aura_surface().last_sent_occlusion_state()); |
| EXPECT_EQ(3, aura_surface().num_occlusion_updates()); |
| } |
| |
| TEST_F(ZAuraSurfaceTest, |
| LosingActivationByTriggeringTheLockScreenDoesNotSendOccludedFraction) { |
| surface().Commit(); |
| EXPECT_EQ(0.0f, aura_surface().last_sent_occlusion_fraction()); |
| EXPECT_EQ(aura::Window::OcclusionState::VISIBLE, |
| aura_surface().last_sent_occlusion_state()); |
| EXPECT_EQ(1, aura_surface().num_occlusion_updates()); |
| ::wm::ActivateWindow(parent_widget().GetNativeWindow()); |
| |
| // Lock the screen. |
| views::Widget::InitParams params( |
| views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET, |
| views::Widget::InitParams::TYPE_WINDOW); |
| auto lock_widget = std::make_unique<views::Widget>(); |
| params.context = GetContext(); |
| params.bounds = gfx::Rect(0, 0, 100, 100); |
| lock_widget->Init(std::move(params)); |
| ash::Shell::GetContainer(ash::Shell::GetPrimaryRootWindow(), |
| ash::kShellWindowId_LockScreenContainer) |
| ->AddChild(lock_widget->GetNativeView()); |
| |
| // Simulate real screen locker to change session state to LOCKED |
| // when it is shown. |
| auto* controller = ash::Shell::Get()->session_controller(); |
| GetSessionControllerClient()->LockScreen(); |
| lock_widget->Show(); |
| EXPECT_TRUE(controller->IsScreenLocked()); |
| EXPECT_TRUE(lock_widget->GetNativeView()->HasFocus()); |
| |
| // We should have lost focus, but not reported that the window has been |
| // fully occluded. |
| EXPECT_NE(parent_widget().GetNativeWindow(), |
| ash::window_util::GetActiveWindow()); |
| EXPECT_EQ(0.0f, occlusion_fraction_on_activation_loss()); |
| EXPECT_EQ(0.0f, aura_surface().last_sent_occlusion_fraction()); |
| EXPECT_EQ(aura::Window::OcclusionState::VISIBLE, |
| aura_surface().last_sent_occlusion_state()); |
| } |
| |
| TEST_F(ZAuraSurfaceTest, OcclusionIncludesOffScreenArea) { |
| UpdateDisplay("200x150"); |
| |
| gfx::Size buffer_size(80, 100); |
| auto buffer = test::ExoTestHelper::CreateBuffer(buffer_size); |
| // This is scaled by 1.5 - set the bounds to (-60, 75, 120, 150) in screen |
| // coordinates so 75% of it is outside of the screen. |
| surface().window()->SetBounds(gfx::Rect(-40, 50, 80, 100)); |
| surface().Attach(buffer.get()); |
| surface().Commit(); |
| |
| ash::Shelf::ForWindow(surface().window()) |
| ->SetAutoHideBehavior(ash::ShelfAutoHideBehavior::kAlwaysHidden); |
| |
| surface().OnWindowOcclusionChanged(aura::Window::OcclusionState::UNKNOWN, |
| aura::Window::OcclusionState::VISIBLE); |
| |
| EXPECT_EQ(0.75f, aura_surface().last_sent_occlusion_fraction()); |
| EXPECT_EQ(aura::Window::OcclusionState::VISIBLE, |
| aura_surface().last_sent_occlusion_state()); |
| } |
| |
| TEST_F(ZAuraSurfaceTest, OcclusionFractionDoesNotDoubleCountOutsideOfScreen) { |
| UpdateDisplay("600x800"); |
| |
| // Create a surface which is halfway offscreen. |
| gfx::Size buffer1_size(80, 100); |
| auto buffer1 = test::ExoTestHelper::CreateBuffer(buffer1_size); |
| surface().window()->SetBounds(gfx::Rect(-40, 50, 80, 100)); |
| surface().Attach(buffer1.get()); |
| surface().Commit(); |
| |
| EXPECT_EQ(0.5f, aura_surface().last_sent_occlusion_fraction()); |
| EXPECT_EQ(aura::Window::OcclusionState::VISIBLE, |
| aura_surface().last_sent_occlusion_state()); |
| |
| // Occlude the previous surface but only offscreen. The occlusion fraction |
| // should still be 0.5. |
| auto window = |
| std::make_unique<aura::Window>(nullptr, aura::client::WINDOW_TYPE_POPUP); |
| window->Init(ui::LAYER_SOLID_COLOR); |
| window->layer()->SetColor(SK_ColorBLACK); |
| window->SetTransparent(false); |
| window->SetBounds(gfx::Rect(-60, 75, 60, 150)); |
| window->Show(); |
| parent_widget().GetNativeWindow()->parent()->AddChild(window.get()); |
| |
| surface().OnWindowOcclusionChanged(aura::Window::OcclusionState::UNKNOWN, |
| aura::Window::OcclusionState::VISIBLE); |
| |
| EXPECT_EQ(0.5f, aura_surface().last_sent_occlusion_fraction()); |
| EXPECT_EQ(aura::Window::OcclusionState::VISIBLE, |
| aura_surface().last_sent_occlusion_state()); |
| |
| // Occlude the previous surface by 25% more additionally inside the screen. |
| window->SetBounds(gfx::Rect(-60, 75, 90, 150)); |
| |
| surface().OnWindowOcclusionChanged(aura::Window::OcclusionState::VISIBLE, |
| aura::Window::OcclusionState::VISIBLE); |
| |
| EXPECT_EQ(0.75f, aura_surface().last_sent_occlusion_fraction()); |
| EXPECT_EQ(aura::Window::OcclusionState::VISIBLE, |
| aura_surface().last_sent_occlusion_state()); |
| } |
| |
| TEST_F(ZAuraSurfaceTest, ZeroSizeWindowSendsZeroOcclusionFraction) { |
| // Zero sized window should not be occluded. |
| surface().window()->SetBounds(gfx::Rect(0, 0, 0, 0)); |
| surface().Commit(); |
| surface().OnWindowOcclusionChanged(aura::Window::OcclusionState::UNKNOWN, |
| aura::Window::OcclusionState::VISIBLE); |
| EXPECT_EQ(0.0f, aura_surface().last_sent_occlusion_fraction()); |
| EXPECT_EQ(aura::Window::OcclusionState::VISIBLE, |
| aura_surface().last_sent_occlusion_state()); |
| } |
| |
| TEST_F(ZAuraSurfaceTest, CanPin) { |
| MockSurfaceDelegate delegate; |
| wl_resource resource; |
| resource.data = &aura_surface(); |
| surface().SetSurfaceDelegate(&delegate); |
| EXPECT_CALL(delegate, Pin(true)); |
| |
| aura_surface().Pin(true); |
| } |
| |
| TEST_F(ZAuraSurfaceTest, CanUnpin) { |
| MockSurfaceDelegate delegate; |
| wl_resource resource; |
| resource.data = &aura_surface(); |
| surface().SetSurfaceDelegate(&delegate); |
| EXPECT_CALL(delegate, Unpin()); |
| |
| aura_surface().Unpin(); |
| } |
| |
| TEST_F(ZAuraSurfaceTest, CanSetFullscreenModeToPlain) { |
| MockSurfaceDelegate delegate; |
| wl_resource resource; |
| resource.data = &aura_surface(); |
| surface().SetSurfaceDelegate(&delegate); |
| EXPECT_CALL(delegate, SetUseImmersiveForFullscreen(false)); |
| |
| aura_surface().SetFullscreenMode(ZAURA_SURFACE_FULLSCREEN_MODE_PLAIN); |
| } |
| |
| TEST_F(ZAuraSurfaceTest, CanSetFullscreenModeToImmersive) { |
| MockSurfaceDelegate delegate; |
| surface().SetSurfaceDelegate(&delegate); |
| EXPECT_CALL(delegate, SetUseImmersiveForFullscreen(true)); |
| |
| aura_surface().SetFullscreenMode(ZAURA_SURFACE_FULLSCREEN_MODE_IMMERSIVE); |
| } |
| |
| TEST_F(ZAuraSurfaceTest, CanSetAccessibilityId) { |
| aura_surface().SetAccessibilityId(123); |
| |
| EXPECT_EQ(123, exo::GetShellClientAccessibilityId(surface().window())); |
| } |
| |
| TEST_F(ZAuraSurfaceTest, CanUnsetAccessibilityId) { |
| aura_surface().SetAccessibilityId(-1); |
| |
| EXPECT_FALSE( |
| exo::GetShellClientAccessibilityId(surface().window()).has_value()); |
| } |
| |
| using ZAuraSurfaceOcclusionTest = test::ExoTestBase; |
| |
| TEST_F(ZAuraSurfaceOcclusionTest, SkipFirstHidden) { |
| Surface surface; |
| TestAuraSurface aura_surface(&surface); |
| |
| surface.SetOcclusionTracking(true); |
| surface.Commit(); |
| EXPECT_TRUE(surface.IsTrackingOcclusion()); |
| |
| // Skip sending occlusion state change if its from UNKNOWN to HIDDEN because |
| // the first state is calculated without a buffer attached to the surface. |
| surface.OnWindowOcclusionChanged(aura::Window::OcclusionState::UNKNOWN, |
| aura::Window::OcclusionState::HIDDEN); |
| EXPECT_FALSE(aura_surface.send_occlusion_state_called()); |
| |
| surface.OnWindowOcclusionChanged(aura::Window::OcclusionState::UNKNOWN, |
| aura::Window::OcclusionState::VISIBLE); |
| EXPECT_TRUE(aura_surface.send_occlusion_state_called()); |
| } |
| |
| // Test without setting surfaces on SetUp(). |
| using ZAuraSurfaceCustomTest = test::ExoTestBase; |
| |
| class MockSurfaceObserver : public SurfaceObserver { |
| public: |
| MockSurfaceObserver() = default; |
| MockSurfaceObserver(const MockSurfaceObserver&) = delete; |
| MockSurfaceObserver& operator=(const MockSurfaceObserver&) = delete; |
| ~MockSurfaceObserver() override = default; |
| |
| MOCK_METHOD(void, OnSurfaceDestroying, (Surface * surface), (override)); |
| MOCK_METHOD(void, |
| OnTooltipShown, |
| (Surface * surface, |
| std::u16string_view text, |
| const gfx::Rect& bounds), |
| (override)); |
| MOCK_METHOD(void, OnTooltipHidden, (Surface * surface), (override)); |
| }; |
| |
| TEST_F(ZAuraSurfaceCustomTest, ShowTooltipFromCursor) { |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({10, 10}).BuildShellSurface(); |
| |
| Surface* surface = shell_surface->root_surface(); |
| auto aura_surface = std::make_unique<TestAuraSurface>(surface); |
| |
| shell_surface->GetWidget()->GetNativeWindow()->SetBounds( |
| gfx::Rect(0, 0, 10, 10)); |
| shell_surface->GetWidget()->GetNativeWindow()->Show(); |
| surface->window()->SetBounds(gfx::Rect(5, 5, 10, 10)); |
| surface->window()->Show(); |
| surface->window()->SetCapture(); |
| |
| MockSurfaceObserver observer; |
| surface->AddSurfaceObserver(&observer); |
| |
| // Move mouse over the window to show tooltip. |
| // This is required since Ash needs to know which window is targeted for a |
| // given tooltip. |
| gfx::Point mouse_position = gfx::Point(6, 6); |
| auto* generator = GetEventGenerator(); |
| generator->MoveMouseTo(mouse_position); |
| |
| static constexpr char16_t kText[] = u"my tooltip"; |
| gfx::Rect expected_tooltip_position = |
| gfx::Rect(mouse_position, gfx::Size(77, kTooltipExpectedHeight)); |
| views::corewm::TooltipAura::AdjustToCursor(&expected_tooltip_position); |
| aura::Window::ConvertRectToTarget(surface->window(), |
| surface->window()->GetToplevelWindow(), |
| &expected_tooltip_position); |
| |
| EXPECT_CALL(observer, OnTooltipShown(surface, std::u16string_view(kText), |
| expected_tooltip_position)); |
| aura_surface->ShowTooltip(kText, gfx::Point(), |
| ZAURA_SURFACE_TOOLTIP_TRIGGER_CURSOR, |
| base::TimeDelta(), base::TimeDelta()); |
| |
| surface->RemoveSurfaceObserver(&observer); |
| } |
| |
| TEST_F(ZAuraSurfaceCustomTest, ShowTooltipFromKeyboard) { |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({10, 10}).BuildShellSurface(); |
| |
| Surface* surface = shell_surface->root_surface(); |
| auto aura_surface = std::make_unique<TestAuraSurface>(surface); |
| |
| shell_surface->GetWidget()->GetNativeWindow()->SetBounds( |
| gfx::Rect(0, 0, 10, 10)); |
| shell_surface->GetWidget()->GetNativeWindow()->Show(); |
| surface->window()->SetBounds(gfx::Rect(0, 0, 10, 10)); |
| surface->window()->Show(); |
| |
| MockSurfaceObserver observer; |
| surface->AddSurfaceObserver(&observer); |
| |
| static constexpr char16_t kText[] = u"my tooltip"; |
| gfx::Point anchor_point = surface->window()->bounds().bottom_center(); |
| gfx::Size expected_tooltip_size = gfx::Size(77, kTooltipExpectedHeight); |
| // Calculate expected tooltip position. For keyboard tooltip, it should be |
| // shown right below and in the center of tooltip target window while it must |
| // fit inside the display bounds. |
| gfx::Rect expected_tooltip_position = |
| gfx::Rect(anchor_point, expected_tooltip_size); |
| expected_tooltip_position.Offset(-expected_tooltip_size.width() / 2, 0); |
| gfx::Rect display_bounds( |
| display::Screen::Get()->GetDisplayNearestPoint(anchor_point).bounds()); |
| expected_tooltip_position.AdjustToFit(display_bounds); |
| aura::Window::ConvertRectToTarget(surface->window(), |
| surface->window()->GetToplevelWindow(), |
| &expected_tooltip_position); |
| |
| EXPECT_CALL(observer, OnTooltipShown(surface, std::u16string_view(kText), |
| expected_tooltip_position)); |
| aura_surface->ShowTooltip(kText, anchor_point, |
| ZAURA_SURFACE_TOOLTIP_TRIGGER_KEYBOARD, |
| base::TimeDelta(), base::TimeDelta()); |
| |
| surface->RemoveSurfaceObserver(&observer); |
| } |
| |
| TEST_F(ZAuraSurfaceCustomTest, ShowTooltipOnMenuFromCursor) { |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({10, 10}).SetAsMenu().BuildShellSurface(); |
| |
| Surface* surface = shell_surface->root_surface(); |
| auto aura_surface = std::make_unique<TestAuraSurface>(surface); |
| |
| shell_surface->GetWidget()->GetNativeWindow()->SetBounds( |
| gfx::Rect(0, 0, 10, 10)); |
| shell_surface->GetWidget()->GetNativeWindow()->Show(); |
| surface->window()->SetBounds(gfx::Rect(5, 5, 10, 10)); |
| surface->window()->Show(); |
| surface->window()->SetCapture(); |
| |
| MockSurfaceObserver observer; |
| surface->AddSurfaceObserver(&observer); |
| |
| // Move mouse over the window to show tooltip. |
| // This is required since Ash needs to know which window is targeted for a |
| // given tooltip. |
| gfx::Point mouse_position = gfx::Point(6, 6); |
| auto* generator = GetEventGenerator(); |
| generator->MoveMouseTo(mouse_position); |
| |
| static constexpr char16_t kText[] = u"my tooltip"; |
| // Size of the tooltip depends on the text to show. |
| gfx::Rect expected_tooltip_position = |
| gfx::Rect(mouse_position, gfx::Size(77, kTooltipExpectedHeight)); |
| views::corewm::TooltipAura::AdjustToCursor(&expected_tooltip_position); |
| aura::Window::ConvertRectToTarget(surface->window(), |
| surface->window()->GetToplevelWindow(), |
| &expected_tooltip_position); |
| |
| EXPECT_CALL(observer, OnTooltipShown(surface, std::u16string_view(kText), |
| expected_tooltip_position)); |
| aura_surface->ShowTooltip(kText, gfx::Point(), |
| ZAURA_SURFACE_TOOLTIP_TRIGGER_CURSOR, |
| base::TimeDelta(), base::TimeDelta()); |
| |
| surface->RemoveSurfaceObserver(&observer); |
| } |
| |
| TEST_F(ZAuraSurfaceCustomTest, ShowTooltipOnMenuFromKeyboard) { |
| std::unique_ptr<ShellSurface> shell_surface = |
| test::ShellSurfaceBuilder({10, 10}).SetAsMenu().BuildShellSurface(); |
| |
| Surface* surface = shell_surface->root_surface(); |
| auto aura_surface = std::make_unique<TestAuraSurface>(surface); |
| |
| shell_surface->GetWidget()->GetNativeWindow()->SetBounds( |
| gfx::Rect(0, 0, 10, 10)); |
| shell_surface->GetWidget()->GetNativeWindow()->Show(); |
| surface->window()->SetBounds(gfx::Rect(0, 0, 10, 10)); |
| surface->window()->Show(); |
| |
| MockSurfaceObserver observer; |
| surface->AddSurfaceObserver(&observer); |
| |
| static constexpr char16_t kText[] = u"my tooltip"; |
| gfx::Point anchor_point = surface->window()->bounds().bottom_center(); |
| gfx::Size expected_tooltip_size = gfx::Size(77, kTooltipExpectedHeight); |
| // Calculate expected tooltip position. For keyboard tooltip, it should be |
| // shown right below and in the center of tooltip target window while it must |
| // fit inside the display bounds. |
| gfx::Rect expected_tooltip_position = |
| gfx::Rect(anchor_point, expected_tooltip_size); |
| expected_tooltip_position.Offset(-expected_tooltip_size.width() / 2, 0); |
| gfx::Rect display_bounds( |
| display::Screen::Get()->GetDisplayNearestPoint(anchor_point).bounds()); |
| expected_tooltip_position.AdjustToFit(display_bounds); |
| aura::Window::ConvertRectToTarget(surface->window(), |
| surface->window()->GetToplevelWindow(), |
| &expected_tooltip_position); |
| |
| EXPECT_CALL(observer, OnTooltipShown(surface, std::u16string_view(kText), |
| expected_tooltip_position)); |
| aura_surface->ShowTooltip(kText, anchor_point, |
| ZAURA_SURFACE_TOOLTIP_TRIGGER_KEYBOARD, |
| base::TimeDelta(), base::TimeDelta()); |
| |
| surface->RemoveSurfaceObserver(&observer); |
| } |
| |
| class MockAuraOutput : public AuraOutput { |
| public: |
| using AuraOutput::AuraOutput; |
| |
| MOCK_METHOD(void, SendInsets, (const gfx::Insets&), (override)); |
| MOCK_METHOD(void, SendLogicalTransform, (int32_t), (override)); |
| MOCK_METHOD(void, SendActiveDisplay, (), (override)); |
| }; |
| |
| class ZAuraOutputTest : public test::ExoTestBase { |
| public: |
| ZAuraOutputTest() = default; |
| ZAuraOutputTest(const ZAuraOutputTest&) = delete; |
| ZAuraOutputTest& operator=(const ZAuraOutputTest&) = delete; |
| ~ZAuraOutputTest() override = default; |
| |
| // test::ExoTestBase: |
| void SetUp() override { |
| test::ExoTestBase::SetUp(); |
| |
| int fds[2]; |
| ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, fds), 0); |
| wayland_display_.reset(wl_display_create()); |
| client_ = wl_client_create(wayland_display_.get(), fds[0]); |
| |
| UpdateDisplayOutput(); |
| } |
| void TearDown() override { |
| output_holder_list_.clear(); |
| test::ExoTestBase::TearDown(); |
| } |
| |
| protected: |
| void ResetDisplayOutput() { |
| for (auto& holder : output_holder_list_) { |
| holder->aura_output.reset(); |
| holder->output.reset(); |
| } |
| } |
| |
| void UpdateDisplayOutput() { |
| auto display_list = display::Screen::Get()->GetAllDisplays(); |
| auto iter = output_holder_list_.begin(); |
| while (iter != output_holder_list_.end()) { |
| auto* out_ptr = (*iter)->output.get(); |
| bool erased = std::erase_if(display_list, |
| [out_ptr](const display::Display& display) { |
| return display.id() == out_ptr->id(); |
| }); |
| if (erased) |
| ++iter; |
| else |
| iter = output_holder_list_.erase(iter); |
| } |
| |
| for (auto& display : display_list) { |
| auto output_holder = std::make_unique<OutputHolder>(); |
| output_holder->client = client_; |
| output_holder->output = std::make_unique<WaylandDisplayOutput>(display); |
| |
| wl_resource* output_resource = wl_resource_create( |
| client_, &wl_output_interface, kWlOutputVersion, 0); |
| output_holder->handler = std::make_unique<WaylandDisplayHandler>( |
| output_holder->output.get(), output_resource); |
| output_holder->handler->Initialize(); |
| output_holder->CreateAuraOutput(); |
| |
| output_holder_list_.push_back(std::move(output_holder)); |
| } |
| } |
| |
| MockAuraOutput* GetPrimaryAuraOutput() { |
| return GetAuraOutput(display::Screen::Get()->GetPrimaryDisplay().id()); |
| } |
| |
| MockAuraOutput* GetAuraOutput(int64_t display_id) { |
| return GetOutputHolder(display_id)->aura_output.get(); |
| } |
| |
| WaylandDisplayHandler* GetPrimaryDisplayHandler() { |
| return GetOutputHolder(display::Screen::Get()->GetPrimaryDisplay().id()) |
| ->handler.get(); |
| } |
| |
| struct OutputHolder { |
| std::unique_ptr<MockAuraOutput> aura_output; |
| std::unique_ptr<WaylandDisplayOutput> output; |
| std::unique_ptr<WaylandDisplayHandler> handler; |
| |
| raw_ptr<wl_client> client; |
| |
| void CreateAuraOutput() { |
| DCHECK(!aura_output); |
| aura_output = std::make_unique<::testing::NiceMock<MockAuraOutput>>( |
| wl_resource_create(client, &zaura_output_interface, |
| kZAuraShellVersion, 0), |
| handler.get()); |
| } |
| }; |
| |
| OutputHolder* GetOutputHolder(int64_t display_id) { |
| auto iter = std::ranges::find_if( |
| output_holder_list_, |
| [display_id](const std::unique_ptr<OutputHolder>& holder) { |
| return holder->output->id() == display_id; |
| }); |
| return iter == output_holder_list_.end() ? nullptr : iter->get(); |
| } |
| |
| private: |
| std::vector<std::unique_ptr<OutputHolder>> output_holder_list_; |
| std::unique_ptr<wl_display, WlDisplayDeleter> wayland_display_; |
| raw_ptr<wl_client> client_ = nullptr; |
| }; |
| |
| TEST_F(ZAuraOutputTest, SendInsets) { |
| auto* mock_aura_output = GetPrimaryAuraOutput(); |
| |
| UpdateDisplay("800x600"); |
| display::Display display = |
| display_manager()->GetDisplayForId(display_manager()->first_display_id()); |
| const gfx::Rect initial_bounds{800, 600}; |
| EXPECT_EQ(display.bounds(), initial_bounds); |
| const gfx::Rect new_work_area{10, 20, 500, 400}; |
| EXPECT_NE(display.work_area(), new_work_area); |
| display.set_work_area(new_work_area); |
| |
| const gfx::Insets expected_insets = initial_bounds.InsetsFrom(new_work_area); |
| EXPECT_CALL(*mock_aura_output, SendInsets(expected_insets)).Times(1); |
| mock_aura_output->SendDisplayMetrics( |
| display, display::DisplayObserver::DISPLAY_METRIC_WORK_AREA); |
| } |
| |
| TEST_F(ZAuraOutputTest, SendLogicalTransform) { |
| auto* mock_aura_output = GetPrimaryAuraOutput(); |
| |
| UpdateDisplay("800x600"); |
| display::Display display = |
| display_manager()->GetDisplayForId(display_manager()->first_display_id()); |
| |
| // Make sure the expected calls happen in order. |
| ::testing::InSequence seq; |
| |
| EXPECT_EQ(display.rotation(), display::Display::ROTATE_0); |
| EXPECT_EQ(display.panel_rotation(), display::Display::ROTATE_0); |
| EXPECT_CALL(*mock_aura_output, |
| SendLogicalTransform(OutputTransform(display.rotation()))) |
| .Times(1); |
| mock_aura_output->SendDisplayMetrics( |
| display, display::DisplayObserver::DISPLAY_METRIC_ROTATION); |
| |
| display.set_rotation(display::Display::ROTATE_270); |
| display.set_panel_rotation(display::Display::ROTATE_180); |
| EXPECT_CALL(*mock_aura_output, |
| SendLogicalTransform(OutputTransform(display.rotation()))) |
| .Times(1); |
| mock_aura_output->SendDisplayMetrics( |
| display, display::DisplayObserver::DISPLAY_METRIC_ROTATION); |
| |
| display.set_rotation(display::Display::ROTATE_90); |
| display.set_panel_rotation(display::Display::ROTATE_180); |
| EXPECT_CALL(*mock_aura_output, |
| SendLogicalTransform(OutputTransform(display.rotation()))) |
| .Times(1); |
| mock_aura_output->SendDisplayMetrics( |
| display, display::DisplayObserver::DISPLAY_METRIC_ROTATION); |
| |
| display.set_rotation(display::Display::ROTATE_270); |
| display.set_panel_rotation(display::Display::ROTATE_270); |
| EXPECT_CALL(*mock_aura_output, |
| SendLogicalTransform(OutputTransform(display.rotation()))) |
| .Times(1); |
| mock_aura_output->SendDisplayMetrics( |
| display, display::DisplayObserver::DISPLAY_METRIC_ROTATION); |
| } |
| |
| // Make sure that data associated with wl/aura outputs are destroyed |
| // properly regardless of which one is destroyed first. |
| TEST_F(ZAuraOutputTest, DestroyAuraOutput) { |
| auto* output_holder = |
| GetOutputHolder(display::Screen::Get()->GetPrimaryDisplay().id()); |
| |
| EXPECT_EQ(1u, GetPrimaryDisplayHandler()->CountObserversForTesting()); |
| output_holder->aura_output.reset(); |
| EXPECT_EQ(0u, GetPrimaryDisplayHandler()->CountObserversForTesting()); |
| output_holder->CreateAuraOutput(); |
| |
| EXPECT_EQ(1u, GetPrimaryDisplayHandler()->CountObserversForTesting()); |
| EXPECT_TRUE(output_holder->aura_output->HasDisplayHandlerForTesting()); |
| output_holder->handler.reset(); |
| EXPECT_FALSE(output_holder->aura_output->HasDisplayHandlerForTesting()); |
| } |
| |
| } // namespace wayland |
| } // namespace exo |