chromium/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h

// Copyright 2014 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_REMOTE_COCOA_APP_SHIM_NATIVE_WIDGET_NS_WINDOW_BRIDGE_H_
#define COMPONENTS_REMOTE_COCOA_APP_SHIM_NATIVE_WIDGET_NS_WINDOW_BRIDGE_H_

#import <Cocoa/Cocoa.h>

#include <memory>
#include <optional>
#include <vector>

#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "components/remote_cocoa/app_shim/immersive_mode_controller_cocoa.h"
#include "components/remote_cocoa/app_shim/immersive_mode_tabbed_controller_cocoa.h"
#import "components/remote_cocoa/app_shim/mouse_capture_delegate.h"
#include "components/remote_cocoa/app_shim/native_widget_ns_window_fullscreen_controller.h"
#include "components/remote_cocoa/app_shim/ns_view_ids.h"
#include "components/remote_cocoa/app_shim/remote_cocoa_app_shim_export.h"
#include "components/remote_cocoa/common/native_widget_ns_window.mojom-shared.h"
#include "components/remote_cocoa/common/native_widget_ns_window.mojom.h"
#include "components/remote_cocoa/common/text_input_host.mojom.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "ui/accelerated_widget_mac/ca_transaction_observer.h"
#include "ui/accelerated_widget_mac/display_ca_layer_tree.h"
#include "ui/base/cocoa/command_dispatcher.h"
#include "ui/base/cursor/cursor.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/base/mojom/ui_base_types.mojom-shared.h"
#include "ui/display/display_observer.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"

@class BridgedContentView;
@class ModalShowAnimationWithLayer;
@class NativeWidgetMacNSWindow;
@class ViewsNSWindowDelegate;

namespace views::test {
class BridgedNativeWidgetTestApi;
}  // namespace views::test

namespace remote_cocoa {

namespace mojom {
class NativeWidgetNSWindowHost;
class TextInputHost;
}  // namespace mojom

class NativeWidgetNSWindowHostHelper;
class CocoaMouseCapture;
class CocoaWindowMoveLoop;
class DragDropClient;

using remote_cocoa::mojom::NativeWidgetNSWindowHost;
using remote_cocoa::NativeWidgetNSWindowHostHelper;
using remote_cocoa::CocoaMouseCapture;
using remote_cocoa::CocoaMouseCaptureDelegate;

// A bridge to an NSWindow managed by an instance of NativeWidgetMac or
// DesktopNativeWidgetMac. Serves as a helper class to bridge requests from the
// NativeWidgetMac to the Cocoa window. Behaves a bit like an aura::Window.
class REMOTE_COCOA_APP_SHIM_EXPORT NativeWidgetNSWindowBridge
    : public remote_cocoa::mojom::NativeWidgetNSWindow,
      public display::DisplayObserver,
      public NativeWidgetNSWindowFullscreenController::Client,
      public ui::CATransactionCoordinator::PreCommitObserver,
      public CocoaMouseCaptureDelegate {
 public:
  // Return the size that |window| will take for the given client area |size|,
  // based on its current style mask.
  static gfx::Size GetWindowSizeForClientSize(NSWindow* window,
                                              const gfx::Size& size);

  // Retrieve a NativeWidgetNSWindowBridge* from its id or window.
  static NativeWidgetNSWindowBridge* GetFromId(
      uint64_t bridged_native_widget_id);
  static NativeWidgetNSWindowBridge* GetFromNativeWindow(
      gfx::NativeWindow window);

  // Create an NSWindow for the specified parameters.
  static NativeWidgetMacNSWindow* CreateNSWindow(
      const remote_cocoa::mojom::CreateWindowParams* params);

  // Creates one side of the bridge. |host| and |parent| must not be NULL.
  NativeWidgetNSWindowBridge(
      uint64_t bridged_native_widget_id,
      NativeWidgetNSWindowHost* host,
      NativeWidgetNSWindowHostHelper* host_helper,
      remote_cocoa::mojom::TextInputHost* text_input_host);

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

  ~NativeWidgetNSWindowBridge() override;

  // Bind |bridge_mojo_receiver_| to |receiver|, and set the connection error
  // callback for |bridge_mojo_receiver_| to |connection_closed_callback| (which
  // will delete |this| when the connection is closed).
  void BindReceiver(mojo::PendingAssociatedReceiver<
                        remote_cocoa::mojom::NativeWidgetNSWindow> receiver,
                    base::OnceClosure connection_closed_callback);

  // Initialize the NSWindow. TODO(ccameron): When a NativeWidgetNSWindowBridge
  // is allocated across a process boundary, it will not be possible to
  // explicitly set an NSWindow in this way.
  void SetWindow(NativeWidgetMacNSWindow* window);

  // Set the command dispatcher delegate for the window. This will retain
  // |delegate| for the lifetime of |this|.
  void SetCommandDispatcher(
      NSObject<CommandDispatcherDelegate>* delegate,
      id<UserInterfaceItemCommandHandler> command_handler);

  // Start moving the window, pinned to the mouse cursor, and monitor events.
  // Return true on mouse up or false on premature termination via EndMoveLoop()
  // or when window is destroyed during the drag.
  bool RunMoveLoop(const gfx::Vector2d& drag_offset);
  void EndMoveLoop();

  // Sets the cursor associated with the NSWindow. Retains |cursor|.
  void SetCursor(NSCursor* cursor);

  // Called internally by the NSWindowDelegate when the window is closing.
  void OnWindowWillClose();

  // Called by the NSWindowDelegate when the size of the window changes.
  void OnSizeChanged();

  // Called once by the NSWindowDelegate when the position of the window has
  // changed.
  void OnPositionChanged();

  // Called by the NSWindowDelegate when the visibility of the window may have
  // changed. For example, due to a (de)miniaturize operation, or the window
  // being reordered in (or out of) the screen list.
  void OnVisibilityChanged();

  // Called by the NSWindowDelegate when the system colors change.
  void OnSystemColorsChanged();

  // Called by the NSWindowDelegate on screen, scale, or color space changes.
  void OnScreenOrBackingPropertiesChanged();

  // Called by the NSWindowDelegate when the window becomes or resigns key.
  void OnWindowKeyStatusChangedTo(bool is_key);

  // Called by the window show animation when it completes and wants to destroy
  // itself.
  void OnShowAnimationComplete();

  BridgedContentView* ns_view() { return bridged_view_; }
  NativeWidgetNSWindowHost* host() { return host_; }
  NativeWidgetNSWindowHostHelper* host_helper() { return host_helper_; }
  remote_cocoa::mojom::TextInputHost* text_input_host() const {
    return text_input_host_;
  }
  NSWindow* ns_window();

  remote_cocoa::DragDropClient* drag_drop_client();
  bool is_translucent_window() const { return is_translucent_window_; }

  // The parent widget specified in Widget::InitParams::parent. If non-null, the
  // parent will close children before the parent closes, and children will be
  // raised above their parent when window z-order changes.
  NativeWidgetNSWindowBridge* parent() { return parent_; }
  const std::vector<NativeWidgetNSWindowBridge*>& child_windows() {
    return child_windows_;
  }

  NativeWidgetNSWindowFullscreenController& fullscreen_controller() {
    return fullscreen_controller_;
  }
  bool target_fullscreen_state() const {
    return fullscreen_controller_.GetTargetFullscreenState();
  }
  bool window_visible() const;
  bool wants_to_be_visible() const { return wants_to_be_visible_; }
  bool in_fullscreen_transition() const {
    return fullscreen_controller_.IsInFullscreenTransition();
  }

  bool CanGoBack() const { return can_go_back_; }
  bool CanGoForward() const { return can_go_forward_; }

  // Whether to run a custom animation for the provided |transition|.
  bool ShouldRunCustomAnimationFor(
      remote_cocoa::mojom::VisibilityTransition transition) const;

  // Redispatch a keyboard event using the widget's window's CommandDispatcher.
  // Return true if the event is handled.
  bool RedispatchKeyEvent(NSEvent* event);

  // display::DisplayObserver:
  void OnDisplayAdded(const display::Display& new_display) override;
  void OnDisplaysRemoved(const display::Displays& removed_displays) override;
  void OnDisplayMetricsChanged(const display::Display& display,
                               uint32_t metrics) override;

  // NativeWidgetNSWindowFullscreenController::Client:
  void FullscreenControllerTransitionStart(bool is_target_fullscreen) override;
  void FullscreenControllerTransitionComplete(bool is_fullscreen) override;
  void FullscreenControllerSetFrame(
      const gfx::Rect& frame,
      bool animate,
      base::OnceCallback<void()> completion_callback) override;
  void FullscreenControllerToggleFullscreen() override;
  void FullscreenControllerCloseWindow() override;
  int64_t FullscreenControllerGetDisplayId() const override;
  gfx::Rect FullscreenControllerGetFrameForDisplay(
      int64_t display_id) const override;
  gfx::Rect FullscreenControllerGetFrame() const override;

  // ui::CATransactionCoordinator::PreCommitObserver:
  bool ShouldWaitInPreCommit() override;
  base::TimeDelta PreCommitTimeout() override;

  // remote_cocoa::mojom::NativeWidgetNSWindow:
  void CreateWindow(mojom::CreateWindowParamsPtr params) override;
  void SetParent(uint64_t parent_id) override;
  void CreateSelectFileDialog(
      mojo::PendingReceiver<mojom::SelectFileDialog> receiver) override;
  void ShowCertificateViewer(
      const scoped_refptr<net::X509Certificate>& certificate) override;
  void StackAbove(uint64_t sibling_id) override;
  void StackAtTop() override;
  void ShowEmojiPanel() override;
  void InitWindow(
      remote_cocoa::mojom::NativeWidgetNSWindowInitParamsPtr params) override;
  void InitCompositorView(InitCompositorViewCallback callback) override;
  void CreateContentView(uint64_t ns_view_id, const gfx::Rect& bounds) override;
  void DestroyContentView() override;
  void CloseWindow() override;
  void CloseWindowNow() override;
  void SetInitialBounds(const gfx::Rect& new_bounds,
                        const gfx::Size& minimum_content_size) override;
  void SetBounds(const gfx::Rect& new_bounds,
                 const gfx::Size& minimum_content_size,
                 const std::optional<gfx::Size>& maximum_content_size) override;
  void SetSize(const gfx::Size& new_size,
               const gfx::Size& minimum_content_size) override;
  void SetSizeAndCenter(const gfx::Size& content_size,
                        const gfx::Size& minimum_content_size) override;
  void SetVisibilityState(
      remote_cocoa::mojom::WindowVisibilityState new_state) override;
  void SetAnimationEnabled(bool animation_enabled) override;
  void SetTransitionsToAnimate(
      remote_cocoa::mojom::VisibilityTransition transitions) override;
  void SetVisibleOnAllSpaces(bool always_visible) override;
  void SetZoomed(bool zoomed) override;
  void EnterFullscreen(int64_t target_display_id) override;
  void ExitFullscreen() override;
  void SetCanAppearInExistingFullscreenSpaces(
      bool can_appear_in_existing_fullscreen_spaces) override;
  void SetMiniaturized(bool miniaturized) override;
  void SetSizeConstraints(const gfx::Size& min_size,
                          const gfx::Size& max_size,
                          bool is_resizable,
                          bool is_maximizable) override;
  void SetOpacity(float opacity) override;
  void SetWindowLevel(int32_t level) override;
  void SetAspectRatio(const gfx::SizeF& aspect_ratio,
                      const gfx::Size& excluded_margin) override;
  void SetCALayerParams(const gfx::CALayerParams& ca_layer_params) override;
  void SetWindowTitle(const std::u16string& title) override;
  void SetIgnoresMouseEvents(bool ignores_mouse_events) override;
  void MakeFirstResponder() override;
  void SortSubviews(
      const std::vector<uint64_t>& associated_subview_ids) override;
  void ClearTouchBar() override;
  void UpdateTooltip() override;
  void AcquireCapture() override;
  void ReleaseCapture() override;
  void RedispatchKeyEvent(
      const std::vector<uint8_t>& native_event_data) override;
  void SetLocalEventMonitorEnabled(bool enable) override;
  void SetCursor(const ui::Cursor& cursor) override;
  void EnableImmersiveFullscreen(uint64_t fullscreen_overlay_widget_id,
                                 uint64_t tab_widget_id) override;
  void DisableImmersiveFullscreen() override;
  void UpdateToolbarVisibility(
      remote_cocoa::mojom::ToolbarVisibilityStyle style) override;
  void OnTopContainerViewBoundsChanged(const gfx::Rect& bounds) override;
  void ImmersiveFullscreenRevealLock() override;
  void ImmersiveFullscreenRevealUnlock() override;
  void SetCanGoBack(bool can_go_back) override;
  void SetCanGoForward(bool can_go_back) override;
  void DisplayContextMenu(mojom::ContextMenuPtr menu,
                          mojo::PendingRemote<mojom::MenuHost> host,
                          mojo::PendingReceiver<mojom::Menu> receiver) override;
  void SetAllowScreenshots(bool allow) override;

  // Return true if [NSApp updateWindows] needs to be called after updating the
  // TextInputClient.
  bool NeedsUpdateWindows();

  // Compute the window and content size, and forward them to |host_|. This will
  // update widget and compositor size.
  void UpdateWindowGeometry();

  bool ShouldUseCustomTitlebarHeightForFullscreen() const;

  // Called by the ImmersiveModeController when the toolbar reveal status
  // changes. Note that the toolbar may be revealed while the menubar is hidden,
  // e.g. when "Always Show Toolbar in Full Screen" is enabled or there're
  // reveal locks.
  void OnImmersiveFullscreenToolbarRevealChanged(bool is_revealed);

  // Called by the ImmersiveModeController when the menubar reveal status
  // changes. `reveal_amount` ranges in [0, 1]. This is the opacity of the
  // menubar and the browser window traffic lights.
  void OnImmersiveFullscreenMenuBarRevealChanged(float reveal_amount);

  BOOL ImmersiveFullscreenEnabled() { return !!immersive_mode_controller_; }

  // Called by the ImmersiveModeController at the end of fullscreen transition
  // with the height of the menu bar if it autohides, or 0 if it doesn't.
  void OnAutohidingMenuBarHeightChanged(int menu_bar_height);

 private:
  friend class views::test::BridgedNativeWidgetTestApi;

  // Attach child windows, if the window is visible (see comment inline).
  void OrderChildren();

  // Closes all child windows. NativeWidgetNSWindowBridge children will be
  // destroyed.
  void RemoveOrDestroyChildren();

  // Remove the specified child window without closing it.
  void RemoveChildWindow(NativeWidgetNSWindowBridge* child);

  // Check if the window's zoomed state has changed. If changes happen, notify
  // the clients.
  void CheckAndNotifyZoomedStateChanged();

  // Notify descendants of a visibility change.
  void NotifyVisibilityChangeDown();

  // Query the display properties of the monitor that |window_| is on, and
  // forward them to |host_|.
  void UpdateWindowDisplay();

  // Return true if the delegate's modal type is window-modal. These display as
  // a native window "sheet", and have a different lifetime to regular windows.
  bool IsWindowModalSheet() const;

  // Show the window using -[NSApp beginSheet:..], modal for the parent window.
  void ShowAsModalSheet();

  // Returns true if capture exists and is currently active.
  bool HasCapture();

  // Returns true if window restoration data exists from session restore.
  bool HasWindowRestorationData();

  // CocoaMouseCaptureDelegate:
  bool PostCapturedEvent(NSEvent* event) override;
  void OnMouseCaptureLost() override;
  NSWindow* GetWindow() const override;

  const uint64_t id_;
  const raw_ptr<NativeWidgetNSWindowHost> host_;  // Weak. Owns this.
  const raw_ptr<NativeWidgetNSWindowHostHelper>
      host_helper_;  // Weak, owned by |host_|.
  const raw_ptr<remote_cocoa::mojom::TextInputHost>
      text_input_host_;  // Weak, owned by |host_|.

  NativeWidgetMacNSWindow* __strong window_;
  ViewsNSWindowDelegate* __strong window_delegate_;
  NSObject<CommandDispatcherDelegate>* window_command_dispatcher_delegate_;

  BridgedContentView* __strong bridged_view_;
  std::unique_ptr<remote_cocoa::ScopedNSViewIdMapping> bridged_view_id_mapping_;
  ModalShowAnimationWithLayer* __strong show_animation_;
  std::unique_ptr<CocoaMouseCapture> mouse_capture_;
  std::unique_ptr<CocoaWindowMoveLoop> window_move_loop_;
  ui::mojom::ModalType modal_type_ = ui::mojom::ModalType::kNone;
  bool is_translucent_window_ = false;
  id __strong key_down_event_monitor_;

  raw_ptr<NativeWidgetNSWindowBridge> parent_ =
      nullptr;  // Weak. If non-null, owns this.
  std::vector<NativeWidgetNSWindowBridge*> child_windows_;

  // The size of the content area of the window most recently sent to |host_|
  // (and its compositor).
  gfx::Size content_dip_size_;

  // The size of the frame most recently *received from* the compositor. Note
  // that during resize (and showing new windows), this will lag behind
  // |content_dip_size_|, which is the frame size most recently *sent to* the
  // compositor.
  gfx::Size compositor_frame_dip_size_;
  std::unique_ptr<ui::DisplayCALayerTree> display_ca_layer_tree_;

  // Tracks the bounds when the window last started entering fullscreen. Used to
  // provide an answer for GetRestoredBounds(), but not ever sent to Cocoa (it
  // has its own copy, but doesn't provide access to it).
  gfx::Rect bounds_before_fullscreen_;

  // The transition types to animate when not relying on native NSWindow
  // animation behaviors.
  remote_cocoa::mojom::VisibilityTransition transitions_to_animate_ =
      remote_cocoa::mojom::VisibilityTransition::kBoth;

  // Manager of fullscreen state transitions.
  NativeWidgetNSWindowFullscreenController fullscreen_controller_{this};

  // Stores the value last read from -[NSWindow isVisible], to detect visibility
  // changes.
  bool window_visible_ = false;

  // Stores the value last read from -[NSWindow isZoomed], to detect zoomed
  // state changes.
  bool window_zoomed_ = false;

  // If true, the window is either visible, or wants to be visible but is
  // currently hidden due to having a hidden parent.
  bool wants_to_be_visible_ = false;

  // If true, then ignore interactions with CATransactionCoordinator until the
  // first frame arrives.
  bool ca_transaction_sync_suppressed_ = false;

  // If true, the window has been made visible or changed shape and the window
  // shadow needs to be invalidated when a frame is received for the new shape.
  bool invalidate_shadow_on_frame_swap_ = false;

  // A blob representing the window's saved state, which is applied and cleared
  // on the first call to SetVisibilityState().
  std::vector<uint8_t> pending_restoration_data_;

  // Manages immersive mode when in fullscreen.
  std::unique_ptr<ImmersiveModeControllerCocoa> immersive_mode_controller_;

  // This tracks headless window visibility and fullscreen states.
  // In headless mode the platform window is never made visible or change its
  // state, so this structure holds the requested state for reporting.
  struct HeadlessModeWindow {
    bool visibility_state = false;
    bool fullscreen_state = false;
  };

  // This is present iff the window has been created in headless mode.
  std::optional<HeadlessModeWindow> headless_mode_window_;

  // This tracks whether current window can go back or go forward.
  bool can_go_back_ = false;
  bool can_go_forward_ = false;

  display::ScopedDisplayObserver display_observer_{this};

  mojo::AssociatedReceiver<remote_cocoa::mojom::NativeWidgetNSWindow>
      bridge_mojo_receiver_{this};

  // Keep track of ImmersiveFullscreenRevealLock() and
  // ImmersiveFullscreenRevealUnlock() calls so locks can persist across
  // immersive_mode_controller_ resets.
  int immersive_fullscreen_reveal_lock_count_ = 0;

  base::WeakPtrFactory<NativeWidgetNSWindowBridge> factory_{this};
};

}  // namespace remote_cocoa

#endif  // COMPONENTS_REMOTE_COCOA_APP_SHIM_NATIVE_WIDGET_NS_WINDOW_BRIDGE_H_