chromium/components/exo/test/shell_surface_builder.cc

// Copyright 2021 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/test/shell_surface_builder.h"

#include <tuple>

#include "ash/wm/desks/desks_util.h"
#include "base/memory/raw_ptr.h"
#include "chromeos/ui/base/app_types.h"
#include "chromeos/ui/base/window_properties.h"
#include "components/exo/buffer.h"
#include "components/exo/display.h"
#include "components/exo/security_delegate.h"
#include "components/exo/sub_surface.h"
#include "components/exo/surface.h"
#include "components/exo/test/exo_test_base.h"
#include "components/exo/test/test_security_delegate.h"
#include "components/exo/xdg_shell_surface.h"
#include "gpu/command_buffer/client/gpu_memory_buffer_manager.h"
#include "ui/aura/env.h"
#include "ui/display/types/display_constants.h"

namespace {

// Internal structure that owns buffer, surface and subsurface instances.
// This is owned by the host window as an owned property.
struct Holder {
  raw_ptr<exo::Surface, DanglingUntriaged> root_surface = nullptr;
  std::vector<std::tuple<std::unique_ptr<exo::Buffer>,
                         std::unique_ptr<exo::Surface>,
                         std::unique_ptr<exo::SubSurface>>>
      sub_surfaces;
  std::unique_ptr<exo::SecurityDelegate> security_delegate_;

  void AddRootSurface(const gfx::Size& size,
                      std::optional<gfx::BufferFormat> buffer_format) {
    auto surface = std::make_unique<exo::Surface>();
    std::unique_ptr<exo::Buffer> buffer;
    if (!size.IsEmpty() && buffer_format) {
      buffer = exo::test::ExoTestHelper::CreateBuffer(size, *buffer_format);
      surface->Attach(buffer.get());
    }
    root_surface = surface.get();
    sub_surfaces.push_back(
        std::make_tuple<>(std::move(buffer), std::move(surface), nullptr));
  }

  exo::Surface* AddChildSurface(exo::Surface* parent, const gfx::Rect& bounds) {
    auto buffer = exo::test::ExoTestHelper::CreateBuffer(bounds.size());
    auto surface = std::make_unique<exo::Surface>();
    surface->Attach(buffer.get());
    auto sub_surface = std::make_unique<exo::SubSurface>(surface.get(), parent);
    sub_surface->SetPosition(gfx::PointF(bounds.origin()));

    auto* surface_ptr = surface.get();
    sub_surfaces.push_back(std::make_tuple<>(
        std::move(buffer), std::move(surface), std::move(sub_surface)));
    return surface_ptr;
  }

  void DestroyRootSurface() {
    DCHECK(root_surface);
    for (auto& tuple : sub_surfaces) {
      if (std::get<1>(tuple).get() == root_surface) {
        std::get<0>(tuple).reset();
        std::get<1>(tuple).reset();
        std::get<2>(tuple).reset();
        break;
      }
    }
    root_surface = nullptr;
  }

  exo::SecurityDelegate* CreateTestSecurityDelegate() {
    security_delegate_ = std::make_unique<exo::test::TestSecurityDelegate>();
    return security_delegate_.get();
  }
};

}  // namespace

DEFINE_UI_CLASS_PROPERTY_TYPE(Holder*)

namespace exo {
namespace test {
namespace {

DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(Holder, kBuilderResourceHolderKey, nullptr)

Holder* FindHolder(Surface* surface) {
  aura::Window* window = surface->window();
  Holder* holder = window->GetProperty(kBuilderResourceHolderKey);
  while (!holder && window->parent()) {
    window = window->parent();
    holder = window->GetProperty(kBuilderResourceHolderKey);
  }
  return holder;
}

}  // namespace

ShellSurfaceBuilder::ShellSurfaceBuilder(const gfx::Size& buffer_size)
    : root_buffer_size_(buffer_size) {}

ShellSurfaceBuilder::~ShellSurfaceBuilder() = default;

ShellSurfaceBuilder& ShellSurfaceBuilder::SetNoRootBuffer() {
  DCHECK(!built_);
  root_buffer_format_.reset();
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::SetRootBufferFormat(
    gfx::BufferFormat buffer_format) {
  DCHECK(!built_);
  root_buffer_format_ = buffer_format;
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::SetOrigin(const gfx::Point& origin) {
  DCHECK(!built_);
  origin_ = origin;
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::SetUseSystemModalContainer() {
  DCHECK(!built_);
  use_system_modal_container_ = true;
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::EnableSystemModal() {
  DCHECK(!built_);
  system_modal_ = true;
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::SetNoCommit() {
  DCHECK(!built_);
  commit_on_build_ = false;
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::SetCanMinimize(bool can_minimize) {
  DCHECK(!built_);
  can_minimize_ = can_minimize;
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::SetCanMaximize(bool can_maximize) {
  DCHECK(!built_);
  can_maximize_ = can_maximize;
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::SetMaximumSize(
    const gfx::Size& size) {
  DCHECK(!built_);
  max_size_ = size;
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::SetMinimumSize(
    const gfx::Size& size) {
  DCHECK(!built_);
  min_size_ = size;
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::SetGeometry(
    const gfx::Rect& geometry) {
  DCHECK(!built_);
  geometry_ = geometry;
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::SetInputRegion(
    const cc::Region& region) {
  DCHECK(!built_);
  input_region_ = region;
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::SetFrame(SurfaceFrameType type) {
  DCHECK(!built_);
  type_ = type;
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::SetFrameColors(SkColor active,
                                                         SkColor inactive) {
  DCHECK(!built_);
  active_frame_color_ = active;
  inactive_frame_color_ = inactive;
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::SetApplicationId(
    const std::string& application_id) {
  DCHECK(!built_);
  application_id_ = application_id;
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::SetDisableMovement() {
  DCHECK(!built_);
  disable_movement_ = true;
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::SetCentered() {
  DCHECK(!built_);
  centered_ = true;
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::SetSecurityDelegate(
    SecurityDelegate* security_delegate) {
  DCHECK(!built_);
  security_delegate_ = security_delegate;
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::SetAppType(
    chromeos::AppType app_type) {
  DCHECK(!built_);
  app_type_ = app_type;
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::SetParent(ShellSurface* parent) {
  DCHECK(!built_);
  parent_shell_surface_ = parent;
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::SetAsPopup() {
  DCHECK(!built_);
  popup_ = true;
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::SetAsMenu() {
  DCHECK(!built_);
  menu_ = true;
  return SetAsPopup();
}

ShellSurfaceBuilder& ShellSurfaceBuilder::SetGrab() {
  DCHECK(!built_);
  grab_ = true;
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::SetClientSubmitsInPixelCoordinates(
    bool enabled) {
  DCHECK(!built_);
  client_submits_surfaces_in_pixel_coordinates_ = enabled;
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::SetWindowState(
    chromeos::WindowStateType window_state) {
  DCHECK(!built_);
  window_state_ = window_state;
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::EnableDefaultScaleCancellation() {
  DCHECK(!built_);
  default_scale_cancellation_ = true;
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::SetDelegate(
    std::unique_ptr<ClientControlledShellSurface::Delegate> delegate) {
  DCHECK(!built_);
  delegate_ = std::move(delegate);
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::DisableSupportsFloatedState() {
  DCHECK(!built_);
  supports_floated_state_ = false;
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::SetDisplayId(int64_t display_id) {
  DCHECK(!built_);
  DCHECK_NE(display_id, display::kInvalidDisplayId);
  display_id_ = display_id;
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::SetBounds(const gfx::Rect& bounds) {
  DCHECK(!built_);
  bounds_.emplace(bounds);
  return *this;
}

ShellSurfaceBuilder& ShellSurfaceBuilder::SetConfigureCallback(
    ShellSurface::ConfigureCallback configure_callback) {
  configure_callback_ = configure_callback;
  return *this;
}

// static
void ShellSurfaceBuilder::DestroyRootSurface(ShellSurfaceBase* shell_surface) {
  Holder* holder =
      shell_surface->host_window()->GetProperty(kBuilderResourceHolderKey);
  DCHECK(holder);
  holder->DestroyRootSurface();
}

// static
Surface* ShellSurfaceBuilder::AddChildSurface(Surface* parent,
                                              const gfx::Rect& bounds) {
  Holder* holder = FindHolder(parent);
  DCHECK(holder);
  return holder->AddChildSurface(parent, bounds);
}

std::unique_ptr<ShellSurface> ShellSurfaceBuilder::BuildShellSurface() {
  // Create a ShellSurface instance.
  DCHECK(!built_);
  DCHECK(IsConfigurationValidForShellSurface());
  built_ = true;

  auto holder = std::make_unique<Holder>();
  holder->AddRootSurface(root_buffer_size_, root_buffer_format_);
  auto shell_surface = std::make_unique<XdgShellSurface>(
      holder->root_surface, origin_, can_minimize_, GetContainer());

  if (!configure_callback_.is_null()) {
    shell_surface->set_configure_callback(configure_callback_);
  }

  shell_surface->host_window()->SetProperty(kBuilderResourceHolderKey,
                                            std::move(holder));

  // Set the properties specific to ShellSurface.
  if (parent_shell_surface_)
    shell_surface->SetParent(parent_shell_surface_);
  if (popup_)
    shell_surface->SetPopup();
  if (menu_)
    shell_surface->SetMenu();
  if (grab_) {
    shell_surface->Grab();
  }
  if (client_submits_surfaces_in_pixel_coordinates_.has_value()) {
    shell_surface->set_client_submits_surfaces_in_pixel_coordinates(
        client_submits_surfaces_in_pixel_coordinates_.value());
  }

  if (window_state_.has_value()) {
    switch (window_state_.value()) {
      case chromeos::WindowStateType::kDefault:
      case chromeos::WindowStateType::kNormal:
        shell_surface->Restore();
        break;
      case chromeos::WindowStateType::kMaximized:
        shell_surface->Maximize();
        break;
      case chromeos::WindowStateType::kMinimized:
        shell_surface->Minimize();
        break;
      case chromeos::WindowStateType::kFullscreen:
        shell_surface->SetFullscreen(/*fullscreen=*/true,
                                     /*display_id=*/display::kInvalidDisplayId);
        break;
      default:
        // Other states are not supported as initial state in ShellSurface.
        NOTREACHED_IN_MIGRATION();
    }
  }

  SetCommonPropertiesAndCommitIfNecessary(shell_surface.get());

  // The widget becomes available after the first commit.
  if (shell_surface->GetWidget() && app_type_ != chromeos::AppType::NON_APP) {
    shell_surface->GetWidget()->GetNativeWindow()->SetProperty(
        chromeos::kAppTypeKey, app_type_);
  }
  return shell_surface;
}

std::unique_ptr<ClientControlledShellSurface>
ShellSurfaceBuilder::BuildClientControlledShellSurface() {
  // Create a ClientControlledShellSurface instance.
  DCHECK(!built_);
  DCHECK(IsConfigurationValidForClientControlledShellSurface());
  built_ = true;
  auto holder = std::make_unique<Holder>();
  holder->AddRootSurface(root_buffer_size_, root_buffer_format_);
  auto shell_surface = Display().CreateOrGetClientControlledShellSurface(
      holder->root_surface, GetContainer(), default_scale_cancellation_,
      supports_floated_state_);
  shell_surface->host_window()->SetProperty(kBuilderResourceHolderKey,
                                            std::move(holder));

  // Set the properties specific to ClientControlledShellSurface.
  shell_surface->SetApplicationId(!application_id_.empty()
                                      ? application_id_.c_str()
                                      : "org.chromium.arc.1");
  // ARC's default min size is non-empty.
  if (!min_size_.has_value())
    shell_surface->SetMinimumSize(gfx::Size(1, 1));
  if (delegate_) {
    shell_surface->set_delegate(std::move(delegate_));
  } else {
    shell_surface->set_delegate(
        std::make_unique<ClientControlledShellSurfaceDelegate>(
            shell_surface.get()));
  }

  if (window_state_.has_value()) {
    switch (window_state_.value()) {
      case chromeos::WindowStateType::kDefault:
      case chromeos::WindowStateType::kNormal:
        shell_surface->SetRestored();
        break;
      case chromeos::WindowStateType::kMaximized:
        shell_surface->SetMaximized();
        break;
      case chromeos::WindowStateType::kMinimized:
        shell_surface->SetMinimized();
        break;
      case chromeos::WindowStateType::kFullscreen:
        shell_surface->SetFullscreen(/*fullscreen=*/true,
                                     /*display_id=*/display::kInvalidDisplayId);
        break;
      case chromeos::WindowStateType::kPrimarySnapped:
        shell_surface->SetSnapPrimary(chromeos::kDefaultSnapRatio);
        break;
      case chromeos::WindowStateType::kSecondarySnapped:
        shell_surface->SetSnapSecondary(chromeos::kDefaultSnapRatio);
        break;
      case chromeos::WindowStateType::kPip:
        shell_surface->SetPip();
        break;
      default:
        NOTREACHED_IN_MIGRATION();
    }
  }

  SetCommonPropertiesAndCommitIfNecessary(shell_surface.get());

  // The widget becomes available after the first commit.
  if (shell_surface->GetWidget()) {
    CHECK(app_type_ == chromeos::AppType::NON_APP ||
          app_type_ == chromeos::AppType::ARC_APP)
        << "Incompatible app type is set for ClientControlledShellSurface.";
    shell_surface->GetWidget()->GetNativeWindow()->SetProperty(
        chromeos::kAppTypeKey, chromeos::AppType::ARC_APP);
  }

  shell_surface->SetCanMaximize(can_maximize_);

  return shell_surface;
}

bool ShellSurfaceBuilder::IsConfigurationValidForShellSurface() {
  return !default_scale_cancellation_ && !delegate_;
}

bool ShellSurfaceBuilder::
    IsConfigurationValidForClientControlledShellSurface() {
  return !parent_shell_surface_ && !popup_;
}

void ShellSurfaceBuilder::SetCommonPropertiesAndCommitIfNecessary(
    ShellSurfaceBase* shell_surface) {
  if (display_id_ != display::kInvalidDisplayId) {
    shell_surface->SetDisplay(display_id_);
  }

  if (bounds_) {
    shell_surface->SetWindowBounds(*bounds_);
  }

  if (disable_movement_)
    shell_surface->DisableMovement();

  if (max_size_.has_value())
    shell_surface->SetMaximumSize(max_size_.value());

  if (min_size_.has_value())
    shell_surface->SetMinimumSize(min_size_.value());

  if (geometry_.has_value())
    shell_surface->SetGeometry(geometry_.value());

  if (input_region_.has_value()) {
    shell_surface->root_surface()->SetInputRegion(input_region_.value());
  }

  if (type_.has_value()) {
    shell_surface->root_surface()->SetFrame(type_.value());
  }

  if (active_frame_color_.has_value()) {
    shell_surface->root_surface()->SetFrameColors(
        active_frame_color_.value(), inactive_frame_color_.value());
  }

  if (system_modal_) {
    shell_surface->SetSystemModal(true);
  }

  if (security_delegate_) {
    shell_surface->SetSecurityDelegate(security_delegate_);
  } else {
    auto* holder =
        shell_surface->host_window()->GetProperty(kBuilderResourceHolderKey);
    shell_surface->SetSecurityDelegate(holder->CreateTestSecurityDelegate());
  }

  if (commit_on_build_) {
    shell_surface->root_surface()->Commit();
    if (centered_) {
      auto* window = shell_surface->GetWidget()->GetNativeWindow();
      const display::Display display =
          display::Screen::GetScreen()->GetDisplayNearestWindow(window);
      gfx::Rect center_bounds = display.work_area();
      center_bounds.ClampToCenteredSize(window->bounds().size());
      window->SetBoundsInScreen(center_bounds, display);
    }
  } else {
    // 'SetCentered' requires its shell surface to be committed when creatted.
    DCHECK(!centered_);
  }
}

int ShellSurfaceBuilder::GetContainer() {
  return use_system_modal_container_
             ? ash::kShellWindowId_SystemModalContainer
             : ash::desks_util::GetActiveDeskContainerId();
}

}  // namespace test
}  // namespace exo