chromium/components/exo/shell_surface_base.h

// 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.

#ifndef COMPONENTS_EXO_SHELL_SURFACE_BASE_H_
#define COMPONENTS_EXO_SHELL_SURFACE_BASE_H_

#include <stdint.h>

#include <cstdint>
#include <memory>
#include <optional>
#include <string>

#include "ash/display/window_tree_host_manager.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "chromeos/ui/base/display_util.h"
#include "chromeos/ui/base/window_pin_type.h"
#include "chromeos/ui/frame/caption_buttons/snap_controller.h"
#include "chromeos/ui/frame/multitask_menu/float_controller_base.h"
#include "components/exo/surface_observer.h"
#include "components/exo/surface_tree_host.h"
#include "ui/accessibility/ax_tree_id.h"
#include "ui/aura/client/capture_client_observer.h"
#include "ui/aura/window_observer.h"
#include "ui/base/hit_test.h"
#include "ui/display/types/display_constants.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/views/widget/widget_observer.h"
#include "ui/views/window/hit_test_utils.h"
#include "ui/wm/public/activation_change_observer.h"
#include "ui/wm/public/tooltip_observer.h"

namespace ash {
class WindowState;
}  // namespace ash

namespace views {
class ClientView;
}  // namespace views

namespace base {
namespace trace_event {
class TracedValue;
}
}  // namespace base

namespace exo {
class Surface;

// This class provides functions for treating a surfaces like toplevel,
// fullscreen or popup widgets, move, resize or maximize them, associate
// metadata like title and class, etc.
class ShellSurfaceBase : public SurfaceTreeHost,
                         public SurfaceObserver,
                         public aura::WindowObserver,
                         public aura::client::CaptureClientObserver,
                         public views::WidgetDelegate,
                         public views::WidgetObserver,
                         public views::View,
                         public wm::ActivationChangeObserver,
                         public wm::TooltipObserver {
 public:
  // The |origin| is the initial position in screen coordinates. The position
  // specified as part of the geometry is relative to the shell surface.
  ShellSurfaceBase(Surface* surface,
                   const gfx::Point& origin,
                   bool can_minimize,
                   int container);

  ShellSurfaceBase(const ShellSurfaceBase&) = delete;
  ShellSurfaceBase& operator=(const ShellSurfaceBase&) = delete;

  ~ShellSurfaceBase() override;

  // Set the callback to run when the user wants the shell surface to be closed.
  // The receiver can chose to not close the window on this signal.
  void set_close_callback(const base::RepeatingClosure& close_callback) {
    close_callback_ = close_callback;
  }

  // Set the callback to run when the user has requested to close the surface.
  // This runs before the normal |close_callback_| and should not be used to
  // actually close the surface.
  void set_pre_close_callback(const base::RepeatingClosure& close_callback) {
    pre_close_callback_ = close_callback;
  }

  // Set the callback to run when the surface is destroyed.
  void set_surface_destroyed_callback(
      base::OnceClosure surface_destroyed_callback) {
    surface_destroyed_callback_ = std::move(surface_destroyed_callback);
  }

  // Whether the connected client supports setting window bounds and is
  // expecting to receive window origin in configure updates.
  bool client_supports_window_bounds() const {
    return client_supports_window_bounds_;
  }

  void set_client_supports_window_bounds(bool enable) {
    client_supports_window_bounds_ = enable;
  }

  // Activates the shell surface. Brings it to the foreground.
  void Activate();
  void RequestActivation();

  // Deactivates the shell surface. Makes it not the foreground.
  void Deactivate();
  void RequestDeactivation();

  // Set title for the surface.
  void SetTitle(const std::u16string& title);

  // Set icon for the surface.
  void SetIcon(const gfx::ImageSkia& icon);

  // Set the application ID for the surface.
  void SetApplicationId(const char* application_id);

  // Set the startup ID for the surface.
  void SetStartupId(const char* startup_id);

  // Set the child ax tree ID for the surface.
  void SetChildAxTreeId(ui::AXTreeID child_ax_tree_id);

  // Set geometry for surface. The geometry represents the "visible bounds"
  // for the surface from the user's perspective.
  void SetGeometry(const gfx::Rect& geometry);

  // If set, geometry is in display rather than window or screen coordinates.
  void SetDisplay(int64_t display_id);

  // Set origin in screen coordinate space.
  void SetOrigin(const gfx::Point& origin);

  // Set activatable state for surface.
  void SetActivatable(bool activatable);

  // Set container for surface.
  void SetContainer(int container);

  // Set the maximum size for the surface. If the size smaller than the minimum
  // size is given, it's ignored. However, the given maximum size is restored
  // once the minimum size is set to that smaller than the maximum size.
  void SetMaximumSize(const gfx::Size& size);

  // Set the miniumum size for the surface.
  void SetMinimumSize(const gfx::Size& size);

  // Set the flag if the surface can maximize or not.
  void SetCanMinimize(bool can_minimize);

  // Set whether the window is persistable.  This should be called before the
  // widget is created.
  void SetPersistable(bool persistable);

  // Sets the window corner radii.
  void SetWindowCornersRadii(const gfx::RoundedCornersF& radii);

  // Sets the shadow corner radii.
  void SetShadowCornersRadii(const gfx::RoundedCornersF& radii);

  // Set normal shadow bounds, |shadow_bounds_|, to |bounds| to be used and
  // applied via `UpdateShadow()`. Set and update resize shadow bounds with
  // |widget_|'s origin and |bounds| via `UpdateResizeShadowBoundsOfWindow()`.
  void SetBoundsForShadows(const std::optional<gfx::Rect>& bounds);

  // Make the shell surface menu type.
  void SetMenu();

  // Prevents shell surface from being moved.
  void DisableMovement();

  // Update the resizability for the surface.
  virtual void UpdateResizability();

  // Rebind a surface as the root surface of the shell surface.
  void RebindRootSurface(Surface* root_surface,
                         bool can_minimize,
                         int container);

  // Set the window bounds. The bounds specify 'visible bounds' of the
  // shell surface.
  void SetWindowBounds(const gfx::Rect& bounds_in_screen);

  // Set `restore_session_id_` and `restore_window_id_` to be the browser
  // session id and restore id, respectively.
  void SetRestoreInfo(int32_t restore_id, int32_t restore_window_id);

  // Set `restore_window_id_source` to be the app id for Restore to fetch window
  // id for.
  void SetRestoreInfoWithWindowIdSource(
      int32_t restore_id,
      const std::string& restore_window_id_source);

  // Unfloats the shell surface.
  void UnsetFloat();

  // Returns a trace value representing the state of the surface.
  std::unique_ptr<base::trace_event::TracedValue> AsTracedValue() const;

  // An overlay creation parameters. The view is owned by the
  // overlay.
  struct OverlayParams {
    explicit OverlayParams(std::unique_ptr<views::View> overlay);
    ~OverlayParams();

    bool translucent = false;
    bool overlaps_frame = true;
    std::optional<bool> can_resize;
    // TODO(oshima): It's unlikely for overlay not to request focus.
    // Remove this.
    bool focusable = true;
    std::optional<gfx::RoundedCornersF> corners_radii;
    std::unique_ptr<views::View> contents_view;
  };

  // Add a new overlay. Currently only one overlay is supported.
  // It is caller's responsibility to make sure there is no overlay
  // before calling this.
  void AddOverlay(OverlayParams&& params);

  // Remove the current overlay. This is no-op if there is no overlay.
  void RemoveOverlay();

  bool HasOverlay() const { return !!overlay_widget_; }

  // Set specific orientation lock for this surface. When this surface is in
  // foreground and the display can be rotated (e.g. tablet mode), apply the
  // behavior defined by |orientation_lock|. See more details in
  // //ash/display/screen_orientation_controller.h.
  void SetOrientationLock(chromeos::OrientationType orientation_lock);

  // Sets the z order for the window. If the window's widget has not yet been
  // initialized, it saves `z_order` for when it is initialized.
  void SetZOrder(ui::ZOrderLevel z_order);

  // Sets the shape of the toplevel window, applied on commit. If shape is null
  // this will unset the window shape.
  void SetShape(std::optional<cc::Region> shape);

  // SurfaceDelegate:
  void OnSurfaceCommit() override;
  bool IsInputEnabled(Surface* surface) const override;
  void OnSetFrame(SurfaceFrameType type) override;
  void OnSetFrameColors(SkColor active_color, SkColor inactive_color) override;
  void OnSetStartupId(const char* startup_id) override;
  void OnSetApplicationId(const char* application_id) override;
  void SetUseImmersiveForFullscreen(bool value) override;
  void ShowSnapPreviewToPrimary() override;
  void ShowSnapPreviewToSecondary() override;
  void HideSnapPreview() override;
  void SetSnapPrimary(float snap_ratio) override;
  void SetSnapSecondary(float snap_ratio) override;
  void UnsetSnap() override;
  void OnActivationRequested() override;
  void OnSetServerStartResize() override;
  void SetCanGoBack() override;
  void UnsetCanGoBack() override;
  void SetPip() override;
  void UnsetPip() override;
  void SetFloatToLocation(
      chromeos::FloatStartLocation float_start_location) override;
  void SetAspectRatio(const gfx::SizeF& aspect_ratio) override;
  void MoveToDesk(int desk_index) override;
  void SetVisibleOnAllWorkspaces() override;
  void SetInitialWorkspace(const char* initial_workspace) override;
  void Pin(bool trusted) override;
  void Unpin() override;
  void SetSystemModal(bool system_modal) override;
  void SetTopInset(int height) override;

  // SurfaceObserver:
  void OnSurfaceDestroying(Surface* surface) override;
  void OnFrameLockingChanged(Surface*, bool) override {}
  void OnDeskChanged(Surface*, int) override {}
  void OnTooltipShown(Surface* surface,
                      const std::u16string& text,
                      const gfx::Rect& bounds) override {}
  void OnTooltipHidden(Surface* surface) override {}

  // CaptureClientObserver:
  void OnCaptureChanged(aura::Window* lost_capture,
                        aura::Window* gained_capture) override;

  // views::WidgetDelegate:
  bool OnCloseRequested(views::Widget::ClosedReason close_reason) override;
  void WindowClosing() override;
  views::Widget* GetWidget() override;
  const views::Widget* GetWidget() const override;
  views::View* GetContentsView() override;
  std::unique_ptr<views::NonClientFrameView> CreateNonClientFrameView(
      views::Widget* widget) override;
  bool ShouldSaveWindowPlacement() const override;
  bool WidgetHasHitTestMask() const override;
  void GetWidgetHitTestMask(SkPath* mask) const override;
  views::ClientView* CreateClientView(views::Widget* widget) override;

  // views::WidgetObserver:
  void OnWidgetClosing(views::Widget* widget) override;

  // views::View:
  gfx::Size CalculatePreferredSize(
      const views::SizeBounds& available_size) const override;
  // This returns the surface's min/max size. If you want to know the
  // widget/window's min/mx size, you must use
  // ShellSurfaceBase::GetWidget()->GetXxxSize.
  gfx::Size GetMinimumSize() const override;
  gfx::Size GetMaximumSize() const override;
  views::FocusTraversable* GetFocusTraversable() override;

  // aura::WindowObserver:
  void OnWindowDestroying(aura::Window* window) override;
  void OnWindowPropertyChanged(aura::Window* window,
                               const void* key,
                               intptr_t old_value) override;
  void OnWindowAddedToRootWindow(aura::Window* window) override;
  void OnWindowParentChanged(aura::Window* window,
                             aura::Window* parent) override;

  // wm::ActivationChangeObserver:
  void OnWindowActivated(ActivationReason reason,
                         aura::Window* gained_active,
                         aura::Window* lost_active) override;

  // wm::TooltipObserver:
  void OnTooltipShown(aura::Window* target,
                      const std::u16string& text,
                      const gfx::Rect& bounds) override;
  void OnTooltipHidden(aura::Window* target) override;

  // ui::AcceleratorTarget:
  bool AcceleratorPressed(const ui::Accelerator& accelerator) override;

  // SurfaceTreeHost:
  void SetRootSurface(Surface* root_surface) override;
  float GetPendingScaleFactor() const override;

  bool frame_enabled() const {
    return frame_type_ != SurfaceFrameType::NONE &&
           frame_type_ != SurfaceFrameType::SHADOW;
  }

  bool frame_overlapped() const {
    return frame_type_ == SurfaceFrameType::OVERLAP;
  }

  Surface* surface_for_testing() { return root_surface(); }
  bool get_shadow_bounds_changed_for_testing() {
    return shadow_bounds_changed_;
  }

  bool server_side_resize() const { return server_side_resize_; }

  const views::Widget* overlay_widget_for_testing() const {
    return overlay_widget_.get();
  }

  // Returns true if surface is currently being dragged.
  bool IsDragged() const;

  void set_in_extended_drag(bool in_extended_drag) {
    in_extended_drag_ = in_extended_drag;
  }

  const std::optional<cc::Region>& shape_dp() const { return shape_dp_; }

  // Window corners radii in dps.
  const std::optional<gfx::RoundedCornersF>& window_corners_radii() const {
    return window_corners_radii_dp_;
  }

  const std::optional<gfx::RoundedCornersF>& shadow_corner_radii() const {
    return shadow_corners_radii_dp_;
  }

  // Returns true if the shell surface has completed its initial configure
  // and commit sequence (i.e. is ready and initialized).
  bool IsReady() const;

 protected:
  bool has_frame_colors() const { return has_frame_colors_; }

  // Creates the |widget_| for |surface_|. |show_state| is the initial state
  // of the widget (e.g. maximized).
  void CreateShellSurfaceWidget(ui::WindowShowState show_state);

  // Returns true if the window is the ShellSurface's widget's window.
  bool IsShellSurfaceWindow(const aura::Window* window) const;

  // Lets subclasses modify Widget parameters immediately before widget
  // creation.
  virtual void OverrideInitParams(views::Widget::InitParams* params) {}

  // Returns true if surface is currently being resized.
  bool IsResizing() const;

  // Updates the bounds of widget to match the current surface bounds.
  void UpdateWidgetBounds();

  // Returns a bounds that WindowManager might have applied the constraints to.
  virtual gfx::Rect ComputeAdjustedBounds(const gfx::Rect& bounds) const;

  // Called by UpdateWidgetBounds to set widget bounds. If the
  // `adjusted_by_server` is true, the bounds requested by a client is updated
  // to satisfy the constraints.
  virtual void SetWidgetBounds(const gfx::Rect& bounds,
                               bool adjusted_by_server) = 0;

  // Updates the bounds of host window to match the current widget bounds.
  void UpdateHostWindowOrigin();

  // Creates, deletes and update the shadow bounds based on
  // |shadow_bounds_|.
  void UpdateShadow();

  virtual void UpdateFrameType();

  void UpdateWindowRoundedCorners();

  // Applies |system_modal_| to |widget_|.
  void UpdateSystemModal();

  // Applies `shape_rects_dp_` to the host window's layer.
  void UpdateShape();

  // Returns the "visible bounds" for the surface from the user's perspective.
  // TODO(b/299688152): Make this non virtual back once ARC is updated not to
  // use geometry to specify the window bounds.
  virtual gfx::Rect GetVisibleBounds() const;

  // Returns the bounds of the client area.
  gfx::Rect GetClientViewBounds() const;

  // Computes the widget bounds using visible bounds.
  gfx::Rect GetWidgetBoundsFromVisibleBounds() const;

  // In the local coordinate system of the window.
  virtual gfx::Rect GetShadowBounds() const;

  // Start the event capture on this surface.
  void StartCapture();

  const gfx::Rect& geometry() const { return geometry_; }
  aura::Window* parent() const { return parent_; }

  // Install custom window targeter. Used to restore window targeter.
  void InstallCustomWindowTargeter();

  // Creates a NonClientFrameView for shell surface.
  std::unique_ptr<views::NonClientFrameView> CreateNonClientFrameViewInternal(
      views::Widget* widget);

  virtual void OnPostWidgetCommit();
  virtual void ShowWidget(bool active);

  void SetParentInternal(aura::Window* window);
  void SetContainerInternal(int container);

  // Converts min/max sizes to resizeability. This needs to be overridden as
  // different clients have different default min/max values.
  virtual bool GetCanResizeFromSizeConstraints() const = 0;

  // Returns true if this surface will exit fullscreen from a restore or
  // maximize request. Currently only true for Lacros.
  bool ShouldExitFullscreenFromRestoreOrMaximized();

  static bool IsPopupWithGrab(aura::Window* window);

  raw_ptr<views::Widget> widget_ = nullptr;
  bool movement_disabled_ = false;
  // This value is in the screen coordinates.
  gfx::Point origin_;

  // Container Window Id (see ash/public/cpp/shell_window_ids.h)
  int container_;
  gfx::Rect geometry_;
  gfx::Rect pending_geometry_;
  std::optional<gfx::Rect> initial_bounds_;
  std::optional<cc::Region> shape_dp_;
  std::optional<cc::Region> pending_shape_dp_;

  // Radii of window corners in dps. Currently only specified by clients that do
  // server-side rounded windows.
  std::optional<gfx::RoundedCornersF> window_corners_radii_dp_;
  std::optional<gfx::RoundedCornersF> pending_window_corners_radii_dp_;

  // Radii of shadow corners in dps.
  std::optional<gfx::RoundedCornersF> shadow_corners_radii_dp_;
  std::optional<gfx::RoundedCornersF> pending_shadow_corners_radii_dp_;

  int64_t display_id_ = display::kInvalidDisplayId;
  int64_t pending_display_id_ = display::kInvalidDisplayId;
  std::optional<gfx::Rect> shadow_bounds_;
  bool shadow_bounds_changed_ = false;
  SurfaceFrameType frame_type_ = SurfaceFrameType::NONE;
  bool is_popup_ = false;
  bool is_menu_ = false;
  bool has_grab_ = false;
  bool server_side_resize_ = false;
  bool needs_layout_on_show_ = false;
  bool client_supports_window_bounds_ = false;

  // The requested size constraint for this window.
  // Actual return value of GetMaximumSize() may be modified to make sure it's
  // equal to or greater than the minimum size constraint.
  gfx::Size requested_minimum_size_;
  gfx::Size requested_maximum_size_;

  // Effective and pending top inset (header) heights, that are reserved or
  // occupied by the top window frame.
  int top_inset_height_ = 0;
  int pending_top_inset_height_ = 0;

  // The orientation to be applied when widget is being created. Only set when
  // widget is not created yet orientation lock is being set. This is currently
  // only used by ClientControlledShellSurface.
  chromeos::OrientationType initial_orientation_lock_ =
      chromeos::OrientationType::kAny;

 private:
  FRIEND_TEST_ALL_PREFIXES(ShellSurfaceTest,
                           HostWindowBoundsUpdatedAfterCommitWidget);
  FRIEND_TEST_ALL_PREFIXES(ShellSurfaceTest,
                           HostWindowBoundsUpdatedWithNegativeCoordinate);
  FRIEND_TEST_ALL_PREFIXES(ShellSurfaceTest,
                           HostWindowIncludesAllSubSurfacesWithScaleFactor);
  FRIEND_TEST_ALL_PREFIXES(ShellSurfaceTest,
                           ShadowBoundsWithNegativeCoordinate);
  FRIEND_TEST_ALL_PREFIXES(ShellSurfaceTest, ShadowBoundsWithScaleFactor);
  FRIEND_TEST_ALL_PREFIXES(ShellSurfaceTest,
                           LocalSurfaceIdUpdatedOnHostWindowOriginChanged);
  FRIEND_TEST_ALL_PREFIXES(
      ShellSurfaceTest,
      LocalSurfaceIdUpdatedOnHostWindowOriginChangedWithScaleFactor);
  FRIEND_TEST_ALL_PREFIXES(ShellSurfaceTest, SubpixelPositionOffset);

  // Updates the shadow's rounded corner associated with the `widget_`.
  void UpdateShadowRoundedCorners();

  // Called on widget creation to initialize its window state.
  // TODO(reveman): Remove virtual functions below to avoid FBC problem.
  virtual void InitializeWindowState(ash::WindowState* window_state) = 0;

  // Returns the scale of the surface tree relative to the shell surface.
  virtual float GetScale() const;

  // Return the bounds of the widget/origin of surface taking visible
  // bounds and current resize direction into account.
  virtual std::optional<gfx::Rect> GetWidgetBounds() const = 0;
  virtual gfx::Point GetSurfaceOrigin() const = 0;

  // Commit is deferred if this returns false.
  virtual bool OnPreWidgetCommit() = 0;

  void CommitWidget();

  bool IsFrameDecorationSupported(SurfaceFrameType frame_type);

  void UpdatePinned();

  void UpdateTopInset();

  // Returns the resizability of the window. Useful to get the resizability
  // without actually updating it.
  bool CalculateCanResize() const;

  raw_ptr<aura::Window> parent_ = nullptr;
  bool activatable_ = true;
  bool can_minimize_ = true;
  bool has_frame_colors_ = false;
  SkColor active_frame_color_ = SK_ColorBLACK;
  SkColor inactive_frame_color_ = SK_ColorBLACK;
  bool pending_show_widget_ = false;
  std::optional<std::string> application_id_;
  std::optional<std::string> startup_id_;
  bool immersive_implied_by_fullscreen_ = true;
  base::RepeatingClosure close_callback_;
  base::RepeatingClosure pre_close_callback_;
  base::OnceClosure surface_destroyed_callback_;
  bool system_modal_ = false;
  bool non_system_modal_window_was_active_ = false;
  gfx::Size pending_minimum_size_;
  gfx::Size pending_maximum_size_;
  gfx::SizeF pending_aspect_ratio_;
  bool pending_pip_ = false;
  bool in_extended_drag_ = false;
  std::optional<std::string> initial_workspace_;
  std::optional<ui::ZOrderLevel> initial_z_order_;

  // Restore members. These pass window restore related ids from exo clients,
  // e.g. Lacros, so that the window can be created with the correct restore
  // info looked up using the ids.
  std::optional<int32_t> restore_session_id_;
  std::optional<int32_t> restore_window_id_;
  std::optional<std::string> restore_window_id_source_;

  // Member determines if the owning process is persistable.
  bool persistable_ = true;

  // Overlay members.
  std::unique_ptr<views::Widget> overlay_widget_;
  bool skip_ime_processing_ = false;
  bool overlay_overlaps_frame_ = true;
  std::optional<bool> overlay_can_resize_;

  // We independently store whether a widget should be activated on creation.
  // The source of truth is on widget, but there are two problems:
  //   (1) The widget has no activation state before it has shown.
  //   (2) In the wayland protocol, asynchronous buffer-commit causes a surface
  //   to be shown. Because this is asynchronous, it's possible for a surface to
  //   be deactivated before shown.
  bool initially_activated_ = true;

  // Pin members.
  chromeos::WindowPinType current_pinned_state_ =
      chromeos::WindowPinType::kNone;
  chromeos::WindowPinType pending_pinned_state_ =
      chromeos::WindowPinType::kNone;
};

}  // namespace exo

#endif  // COMPONENTS_EXO_SHELL_SURFACE_BASE_H_