chromium/components/exo/sub_surface_unittest.cc

// 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/sub_surface.h"

#include "components/exo/buffer.h"
#include "components/exo/shell_surface.h"
#include "components/exo/surface.h"
#include "components/exo/test/exo_test_base.h"
#include "components/exo/test/exo_test_helper.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/gfx/geometry/point_conversions.h"

namespace exo {
namespace {

using SubSurfaceTest = test::ExoTestBase;

TEST_F(SubSurfaceTest, SetPosition) {
  auto parent = std::make_unique<Surface>();
  auto shell_surface = std::make_unique<ShellSurface>(parent.get());
  auto surface = std::make_unique<Surface>();
  auto sub_surface = std::make_unique<SubSurface>(surface.get(), parent.get());

  // Initial position is at the origin.
  EXPECT_EQ(gfx::Point().ToString(),
            surface->window()->bounds().origin().ToString());

  // Set position to 10, 10.
  gfx::PointF position(10, 10);
  sub_surface->SetPosition(position);

  // A call to Commit() is required for position to take effect.
  EXPECT_EQ(gfx::Point().ToString(),
            surface->window()->bounds().origin().ToString());

  // Check that position is updated when Commit() is called.
  parent->Commit();
  EXPECT_EQ(gfx::ToRoundedPoint(position).ToString(),
            surface->window()->bounds().origin().ToString());

  // Create and commit a new sub-surface using the same surface.
  sub_surface.reset();
  sub_surface = std::make_unique<SubSurface>(surface.get(), parent.get());
  parent->Commit();

  // Initial position should be reset to origin.
  EXPECT_EQ(gfx::Point().ToString(),
            surface->window()->bounds().origin().ToString());
}

TEST_F(SubSurfaceTest, PlaceAbove) {
  auto parent = std::make_unique<Surface>();
  auto shell_surface = std::make_unique<ShellSurface>(parent.get());
  auto surface1 = std::make_unique<Surface>();
  auto surface2 = std::make_unique<Surface>();
  auto non_sibling_surface = std::make_unique<Surface>();
  auto sub_surface1 =
      std::make_unique<SubSurface>(surface1.get(), parent.get());
  auto sub_surface2 =
      std::make_unique<SubSurface>(surface2.get(), parent.get());

  ASSERT_EQ(2u, parent->window()->children().size());
  EXPECT_EQ(surface1->window(), parent->window()->children()[0]);
  EXPECT_EQ(surface2->window(), parent->window()->children()[1]);
  EXPECT_EQ(surface1.get(), parent->sub_surfaces().front().first);
  EXPECT_EQ(surface2.get(), parent->sub_surfaces().back().first);

  sub_surface2->PlaceAbove(parent.get());
  sub_surface1->PlaceAbove(non_sibling_surface.get());  // Invalid
  sub_surface1->PlaceAbove(surface1.get());             // Invalid
  sub_surface1->PlaceAbove(surface2.get());

  // Nothing should have changed as Commit() is required for new stacking
  // order to take effect.
  EXPECT_EQ(surface1->window(), parent->window()->children()[0]);
  EXPECT_EQ(surface2->window(), parent->window()->children()[1]);
  EXPECT_EQ(surface1.get(), parent->sub_surfaces().front().first);
  EXPECT_EQ(surface2.get(), parent->sub_surfaces().back().first);

  parent->Commit();

  // surface1 should now be stacked above surface2.
  EXPECT_EQ(surface2->window(), parent->window()->children()[0]);
  EXPECT_EQ(surface1->window(), parent->window()->children()[1]);
  EXPECT_EQ(surface2.get(), parent->sub_surfaces().front().first);
  EXPECT_EQ(surface1.get(), parent->sub_surfaces().back().first);

  sub_surface1->PlaceAbove(parent.get());
  parent->Commit();

  // surface2 should now be stacked above surface1.
  EXPECT_EQ(surface1->window(), parent->window()->children()[0]);
  EXPECT_EQ(surface2->window(), parent->window()->children()[1]);
  EXPECT_EQ(surface1.get(), parent->sub_surfaces().front().first);
  EXPECT_EQ(surface2.get(), parent->sub_surfaces().back().first);
}

TEST_F(SubSurfaceTest, PlaceBelow) {
  auto parent = std::make_unique<Surface>();
  auto shell_surface = std::make_unique<ShellSurface>(parent.get());
  auto surface1 = std::make_unique<Surface>();
  auto surface2 = std::make_unique<Surface>();
  auto non_sibling_surface = std::make_unique<Surface>();
  auto sub_surface1 =
      std::make_unique<SubSurface>(surface1.get(), parent.get());
  auto sub_surface2 =
      std::make_unique<SubSurface>(surface2.get(), parent.get());

  ASSERT_EQ(2u, parent->window()->children().size());
  EXPECT_EQ(surface1->window(), parent->window()->children()[0]);
  EXPECT_EQ(surface2->window(), parent->window()->children()[1]);
  EXPECT_EQ(surface1.get(), parent->sub_surfaces().front().first);
  EXPECT_EQ(surface2.get(), parent->sub_surfaces().back().first);

  sub_surface2->PlaceBelow(parent.get());               // Invalid
  sub_surface2->PlaceBelow(non_sibling_surface.get());  // Invalid
  sub_surface1->PlaceBelow(surface2.get());
  sub_surface2->PlaceBelow(surface1.get());

  // Nothing should have changed as Commit() is required for new stacking
  // order to take effect.
  EXPECT_EQ(surface1->window(), parent->window()->children()[0]);
  EXPECT_EQ(surface2->window(), parent->window()->children()[1]);
  EXPECT_EQ(surface1.get(), parent->sub_surfaces().front().first);
  EXPECT_EQ(surface2.get(), parent->sub_surfaces().back().first);

  parent->Commit();

  // surface1 should now be stacked above surface2.
  EXPECT_EQ(surface2->window(), parent->window()->children()[0]);
  EXPECT_EQ(surface1->window(), parent->window()->children()[1]);
  EXPECT_EQ(surface2.get(), parent->sub_surfaces().front().first);
  EXPECT_EQ(surface1.get(), parent->sub_surfaces().back().first);
}

TEST_F(SubSurfaceTest, ParentDamageOnReorder) {
  gfx::Size buffer_size(800, 600);
  auto buffer = test::ExoTestHelper::CreateBuffer(buffer_size);
  auto surface_tree_host = std::make_unique<SurfaceTreeHost>("SubSurfaceTest");
  LayerTreeFrameSinkHolder* frame_sink_holder =
      surface_tree_host->layer_tree_frame_sink_holder();

  auto parent = std::make_unique<Surface>();
  parent->Attach(buffer.get());
  // Set the overlay priority hint to low to prevent a texture draw quad from
  // being used.
  parent->SetOverlayPriorityHint(OverlayPriority::LOW);
  auto surface1 = std::make_unique<Surface>();
  auto surface2 = std::make_unique<Surface>();
  auto non_sibling_surface = std::make_unique<Surface>();
  auto sub_surface1 =
      std::make_unique<SubSurface>(surface1.get(), parent.get());
  auto sub_surface2 =
      std::make_unique<SubSurface>(surface2.get(), parent.get());

  sub_surface2->PlaceBelow(surface1.get());
  parent->Commit();

  viz::CompositorFrame frame1;
  frame1.render_pass_list.push_back(viz::CompositorRenderPass::Create());
  parent->AppendSurfaceHierarchyContentsToFrame(
      gfx::PointF{}, gfx::PointF{},
      /*needs_full_damage=*/false, frame_sink_holder->resource_manager(),
      /*device_scale_factor=*/std::nullopt, &frame1);

  // Parent surface damage is extended when sub_surface stacking order changes.
  EXPECT_FALSE(frame1.render_pass_list.back()->damage_rect.IsEmpty());

  sub_surface1->PlaceAbove(surface2.get());  // no-op
  sub_surface2->PlaceBelow(surface1.get());  // no-op
  parent->Commit();

  viz::CompositorFrame frame2;
  frame2.render_pass_list.push_back(viz::CompositorRenderPass::Create());
  parent->AppendSurfaceHierarchyContentsToFrame(
      gfx::PointF{}, gfx::PointF{},
      /*needs_full_damage=*/false, frame_sink_holder->resource_manager(),
      /*device_scale_factor=*/std::nullopt, &frame2);

  // Parent surface damage is unaffected.
  EXPECT_TRUE(frame2.render_pass_list.back()->damage_rect.IsEmpty());
}

TEST_F(SubSurfaceTest, SetCommitBehavior) {
  auto parent = std::make_unique<Surface>();
  auto shell_surface = std::make_unique<ShellSurface>(parent.get());
  auto child = std::make_unique<Surface>();
  auto grandchild = std::make_unique<Surface>();
  auto child_sub_surface =
      std::make_unique<SubSurface>(child.get(), parent.get());
  auto grandchild_sub_surface =
      std::make_unique<SubSurface>(grandchild.get(), child.get());

  // Initial position is at the origin.
  EXPECT_EQ(gfx::Point().ToString(),
            grandchild->window()->bounds().origin().ToString());

  // Set position to 10, 10.
  gfx::PointF position1(10, 10);
  grandchild_sub_surface->SetPosition(position1);
  child->Commit();

  // Initial commit behavior is synchronous and the effect of the child
  // Commit() call will not take effect until Commit() is called on the
  // parent.
  EXPECT_EQ(gfx::Point().ToString(),
            grandchild->window()->bounds().origin().ToString());

  parent->Commit();

  // Position should have been updated when Commit() has been called on both
  // child and parent.
  EXPECT_EQ(gfx::ToRoundedPoint(position1).ToString(),
            grandchild->window()->bounds().origin().ToString());

  bool synchronized = false;
  child_sub_surface->SetCommitBehavior(synchronized);

  // Set position to 20, 20.
  gfx::PointF position2(20, 20);
  grandchild_sub_surface->SetPosition(position2);
  child->Commit();

  // A Commit() call on child should be sufficient for the position of
  // grandchild to take effect when synchronous is disabled.
  EXPECT_EQ(gfx::ToRoundedPoint(position2).ToString(),
            grandchild->window()->bounds().origin().ToString());
}

TEST_F(SubSurfaceTest, SetOnParent) {
  gfx::Size buffer_size(32, 32);
  auto buffer = test::ExoTestHelper::CreateBuffer(buffer_size);
  auto parent = std::make_unique<Surface>();
  auto shell_surface = std::make_unique<ShellSurface>(parent.get());
  parent->Attach(buffer.get());
  parent->Commit();

  shell_surface->GetWidget()->GetNativeWindow()->SetProperty(
      aura::client::kSkipImeProcessing, true);
  ASSERT_TRUE(parent->window()->GetProperty(aura::client::kSkipImeProcessing));

  // SkipImeProcessing property is propagated to SubSurface.
  auto surface = std::make_unique<Surface>();
  auto sub_surface = std::make_unique<SubSurface>(surface.get(), parent.get());
  surface->SetParent(parent.get(), gfx::Point(10, 10));
  EXPECT_TRUE(surface->window()->GetProperty(aura::client::kSkipImeProcessing));
}

TEST_F(SubSurfaceTest, AugmentedSurfaceDoesNotExpandHierarchy) {
  auto parent = std::make_unique<Surface>();
  auto shell_surface = std::make_unique<ShellSurface>(parent.get());

  auto sub_layer = std::make_unique<Surface>();
  sub_layer->set_is_augmented(true);
  auto sub_surface = std::make_unique<Surface>();
  auto sub_layer_role =
      std::make_unique<SubSurface>(sub_layer.get(), parent.get());
  auto sub_surface_role =
      std::make_unique<SubSurface>(sub_surface.get(), parent.get());

  auto parent_buffer = exo_test_helper()->CreateBuffer(gfx::Size(800, 600));
  auto sub_surface_buffer = exo_test_helper()->CreateBuffer(gfx::Size(10, 10));
  auto sub_layer_buffer = exo_test_helper()->CreateBuffer(gfx::Size(10, 10));

  // Set position to be outside parent surface.
  sub_layer_role->SetPosition(gfx::PointF(-10, 0));
  sub_surface_role->SetPosition(gfx::PointF(0, -10));

  parent->Attach(parent_buffer.get());
  sub_layer->Attach(sub_layer_buffer.get());
  sub_surface->Attach(sub_surface_buffer.get());

  sub_layer->Commit();
  sub_surface->Commit();
  parent->Commit();

  // Only sub_surface affects the surface hierarchy bounds
  EXPECT_EQ(parent->surface_hierarchy_content_bounds(),
            gfx::Rect(0, -10, 800, 610));
  // sub_layer is not in parent aura window's children list.
  EXPECT_EQ(1u, parent->window()->children().size());
  EXPECT_EQ(sub_surface->window(), parent->window()->children()[0]);
}

}  // namespace
}  // namespace exo