chromium/ui/views/cocoa/native_widget_mac_ns_window_host.mm

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/views/cocoa/native_widget_mac_ns_window_host.h"

#include <tuple>
#include <utility>

#include "base/apple/foundation_util.h"
#include "base/base64.h"
#include "base/containers/contains.h"
#include "base/no_destructor.h"
#include "base/numerics/safe_conversions.h"
#include "base/ranges/algorithm.h"
#include "base/strings/sys_string_conversions.h"
#include "base/time/time.h"
#include "components/remote_cocoa/app_shim/immersive_mode_delegate_mac.h"
#include "components/remote_cocoa/app_shim/mouse_capture.h"
#include "components/remote_cocoa/app_shim/native_widget_mac_nswindow.h"
#include "components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h"
#include "components/remote_cocoa/browser/ns_view_ids.h"
#include "components/remote_cocoa/browser/window.h"
#include "mojo/public/cpp/bindings/self_owned_associated_receiver.h"
#include "ui/accelerated_widget_mac/window_resize_helper_mac.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/base/cocoa/animation_utils.h"
#include "ui/base/cocoa/nswindow_test_util.h"
#include "ui/base/cocoa/remote_accessibility_api.h"
#include "ui/base/cocoa/remote_layer_api.h"
#include "ui/base/hit_test.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/mojom/dialog_button.mojom.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/recyclable_compositor_mac.h"
#include "ui/display/screen.h"
#include "ui/events/cocoa/cocoa_event_utils.h"
#include "ui/gfx/geometry/dip_util.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gfx/mac/coordinate_conversion.h"
#include "ui/native_theme/native_theme_mac.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/cocoa/immersive_mode_reveal_client.h"
#include "ui/views/cocoa/text_input_host.h"
#include "ui/views/cocoa/tooltip_manager_mac.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/menu/menu_config.h"
#include "ui/views/controls/menu/menu_controller.h"
#include "ui/views/view_utils.h"
#include "ui/views/views_delegate.h"
#include "ui/views/widget/native_widget_mac.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/views/window/dialog_delegate.h"
#include "ui/views/word_lookup_client.h"

using remote_cocoa::mojom::NativeWidgetNSWindowInitParams;
using remote_cocoa::mojom::WindowVisibilityState;

namespace views {

namespace {

// Dummy implementation of the BridgedNativeWidgetHost interface. This structure
// exists to work around a bug wherein synchronous mojo calls to an associated
// interface can hang if the interface request is unbound. This structure is
// bound to the real host's interface, and then deletes itself only once the
// underlying connection closes.
// https://crbug.com/915572
class BridgedNativeWidgetHostDummy
    : public remote_cocoa::mojom::NativeWidgetNSWindowHost {
 public:
  BridgedNativeWidgetHostDummy() = default;
  ~BridgedNativeWidgetHostDummy() override = default;

 private:
  void OnVisibilityChanged(bool visible) override {}
  void OnWindowNativeThemeChanged() override {}
  void OnViewSizeChanged(const gfx::Size& new_size) override {}
  void SetKeyboardAccessible(bool enabled) override {}
  void OnIsFirstResponderChanged(bool is_first_responder) override {}
  void OnMouseCaptureActiveChanged(bool capture_is_active) override {}
  void OnScrollEvent(std::unique_ptr<ui::Event> event) override {}
  void OnMouseEvent(std::unique_ptr<ui::Event> event) override {}
  void OnGestureEvent(std::unique_ptr<ui::Event> event) override {}
  void OnWindowGeometryChanged(
      const gfx::Rect& window_bounds_in_screen_dips,
      const gfx::Rect& content_bounds_in_screen_dips) override {}
  void OnWindowFullscreenTransitionStart(
      bool target_fullscreen_state) override {}
  void OnWindowFullscreenTransitionComplete(bool is_fullscreen) override {}
  void OnWindowMiniaturizedChanged(bool miniaturized) override {}
  void OnWindowZoomedChanged(bool zoomed) override {}
  void OnWindowDisplayChanged(const display::Display& display) override {}
  void OnWindowWillClose() override {}
  void OnWindowHasClosed() override {}
  void OnWindowKeyStatusChanged(bool is_key,
                                bool is_content_first_responder,
                                bool full_keyboard_access_enabled) override {}
  void OnWindowStateRestorationDataChanged(
      const std::vector<uint8_t>& data) override {}
  void OnImmersiveFullscreenToolbarRevealChanged(bool is_revealed) override {}
  void OnImmersiveFullscreenMenuBarRevealChanged(float reveal_amount) override {
  }
  void OnAutohidingMenuBarHeightChanged(int menu_bar_height) override {}
  void DoDialogButtonAction(ui::mojom::DialogButton button) override {}
  void OnFocusWindowToolbar() override {}
  void SetRemoteAccessibilityTokens(
      const std::vector<uint8_t>& window_token,
      const std::vector<uint8_t>& view_token) override {}
  void GetSheetOffsetY(GetSheetOffsetYCallback callback) override {
    float offset_y = 0;
    std::move(callback).Run(offset_y);
  }
  void DispatchKeyEventRemote(
      std::unique_ptr<ui::Event> event,
      DispatchKeyEventRemoteCallback callback) override {
    bool event_handled = false;
    std::move(callback).Run(event_handled);
  }
  void DispatchKeyEventToMenuControllerRemote(
      std::unique_ptr<ui::Event> event,
      DispatchKeyEventToMenuControllerRemoteCallback callback) override {
    ui::KeyEvent* key_event = event->AsKeyEvent();
    bool event_swallowed = false;
    std::move(callback).Run(event_swallowed, key_event->handled());
  }
  void DispatchMonitorEvent(std::unique_ptr<ui::Event> event,
                            DispatchMonitorEventCallback callback) override {
    bool event_handled = false;
    std::move(callback).Run(event_handled);
  }
  void GetHasMenuController(GetHasMenuControllerCallback callback) override {
    bool has_menu_controller = false;
    std::move(callback).Run(has_menu_controller);
  }
  void GetIsDraggableBackgroundAt(
      const gfx::Point& location_in_content,
      GetIsDraggableBackgroundAtCallback callback) override {
    bool is_draggable_background = false;
    std::move(callback).Run(is_draggable_background);
  }
  void GetTooltipTextAt(const gfx::Point& location_in_content,
                        GetTooltipTextAtCallback callback) override {
    std::u16string new_tooltip_text;
    std::move(callback).Run(new_tooltip_text);
  }
  void GetIsFocusedViewTextual(
      GetIsFocusedViewTextualCallback callback) override {
    bool is_textual = false;
    std::move(callback).Run(is_textual);
  }
  void GetWidgetIsModal(GetWidgetIsModalCallback callback) override {
    bool widget_is_modal = false;
    std::move(callback).Run(widget_is_modal);
  }
  void GetDialogButtonInfo(ui::mojom::DialogButton button,
                           GetDialogButtonInfoCallback callback) override {
    bool exists = false;
    std::u16string label;
    bool is_enabled = false;
    bool is_default = false;
    std::move(callback).Run(exists, label, is_enabled, is_default);
  }
  void GetDoDialogButtonsExist(
      GetDoDialogButtonsExistCallback callback) override {
    bool buttons_exist = false;
    std::move(callback).Run(buttons_exist);
  }
  void GetShouldShowWindowTitle(
      GetShouldShowWindowTitleCallback callback) override {
    bool should_show_window_title = false;
    std::move(callback).Run(should_show_window_title);
  }
  void GetCanWindowBecomeKey(GetCanWindowBecomeKeyCallback callback) override {
    bool can_window_become_key = false;
    std::move(callback).Run(can_window_become_key);
  }
  void GetAlwaysRenderWindowAsKey(
      GetAlwaysRenderWindowAsKeyCallback callback) override {
    bool always_render_as_key = false;
    std::move(callback).Run(always_render_as_key);
  }
  void OnWindowCloseRequested(
      OnWindowCloseRequestedCallback callback) override {
    bool can_window_close = false;
    std::move(callback).Run(can_window_close);
  }
  void GetWindowFrameTitlebarHeight(
      GetWindowFrameTitlebarHeightCallback callback) override {
    bool override_titlebar_height = false;
    float titlebar_height = 0;
    std::move(callback).Run(override_titlebar_height, titlebar_height);
  }
  void GetRootViewAccessibilityToken(
      GetRootViewAccessibilityTokenCallback callback) override {
    std::vector<uint8_t> token;
    base::ProcessId pid = base::kNullProcessId;
    std::move(callback).Run(pid, token);
  }
  void ValidateUserInterfaceItem(
      int32_t command,
      ValidateUserInterfaceItemCallback callback) override {
    remote_cocoa::mojom::ValidateUserInterfaceItemResultPtr result;
    std::move(callback).Run(std::move(result));
  }
  void WillExecuteCommand(int32_t command,
                          WindowOpenDisposition window_open_disposition,
                          bool is_before_first_responder,
                          ExecuteCommandCallback callback) override {
    bool will_execute = false;
    std::move(callback).Run(will_execute);
  }
  void ExecuteCommand(int32_t command,
                      WindowOpenDisposition window_open_disposition,
                      bool is_before_first_responder,
                      ExecuteCommandCallback callback) override {
    bool was_executed = false;
    std::move(callback).Run(was_executed);
  }
  void HandleAccelerator(const ui::Accelerator& accelerator,
                         bool require_priority_handler,
                         HandleAcceleratorCallback callback) override {
    bool was_handled = false;
    std::move(callback).Run(was_handled);
  }
};

std::map<uint64_t, NativeWidgetMacNSWindowHost*>& GetIdToWidgetHostImplMap() {
  static base::NoDestructor<std::map<uint64_t, NativeWidgetMacNSWindowHost*>>
      id_map;
  return *id_map;
}

uint64_t g_last_bridged_native_widget_id = 0;

NSWindow* OriginalHostingWindowFromFullScreenWindow(
    NSWindow* full_screen_window) {
  if ([full_screen_window.delegate
          conformsToProtocol:@protocol(ImmersiveModeDelegate)]) {
    return base::apple::ObjCCastStrict<NSObject<ImmersiveModeDelegate>>(
               full_screen_window.delegate)
        .originalHostingWindow;
  }
  return nullptr;
}

}  // namespace

// static
NativeWidgetMacNSWindowHost* NativeWidgetMacNSWindowHost::GetFromNativeWindow(
    gfx::NativeWindow native_window) {
  NSWindow* window = native_window.GetNativeNSWindow();

  if (NativeWidgetMacNSWindow* widget_window =
          base::apple::ObjCCast<NativeWidgetMacNSWindow>(window)) {
    return GetFromId([widget_window bridgedNativeWidgetId]);
  }

  // If the window is a system created NSToolbarFullScreenWindow we need to do
  // some additional work to find the original window.
  // TODO(mek): Figure out how to make this work with remote remote_cocoa
  // windows.
  if (remote_cocoa::IsNSToolbarFullScreenWindow(window)) {
    NSWindow* original = OriginalHostingWindowFromFullScreenWindow(window);
    if (NativeWidgetMacNSWindow* widget_window =
            base::apple::ObjCCast<NativeWidgetMacNSWindow>(original)) {
      return GetFromId([widget_window bridgedNativeWidgetId]);
    }
  }

  return nullptr;  // Not created by NativeWidgetMac.
}

// static
NativeWidgetMacNSWindowHost* NativeWidgetMacNSWindowHost::GetFromNativeView(
    gfx::NativeView native_view) {
  return GetFromNativeWindow(native_view.GetNativeNSView().window);
}

// static
const char NativeWidgetMacNSWindowHost::kMovedContentNSView[] =
    "kMovedContentNSView";

// static
NativeWidgetMacNSWindowHost* NativeWidgetMacNSWindowHost::GetFromId(
    uint64_t bridged_native_widget_id) {
  auto found = GetIdToWidgetHostImplMap().find(bridged_native_widget_id);
  if (found == GetIdToWidgetHostImplMap().end())
    return nullptr;
  return found->second;
}

NativeWidgetMacNSWindowHost::NativeWidgetMacNSWindowHost(NativeWidgetMac* owner)
    : widget_id_(++g_last_bridged_native_widget_id),
      native_widget_mac_(owner),
      root_view_id_(remote_cocoa::GetNewNSViewId()),
      accessibility_focus_overrider_(this),
      text_input_host_(new TextInputHost(this)) {
  DCHECK(GetIdToWidgetHostImplMap().find(widget_id_) ==
         GetIdToWidgetHostImplMap().end());
  GetIdToWidgetHostImplMap().emplace(widget_id_, this);
  DCHECK(owner);
}

NativeWidgetMacNSWindowHost::~NativeWidgetMacNSWindowHost() {
  DCHECK(children_.empty());
  native_window_mapping_.reset();
  if (application_host_) {
    remote_ns_window_remote_.reset();
    application_host_->RemoveObserver(this);
    application_host_ = nullptr;
  }

  // Workaround for https://crbug.com/915572
  if (remote_ns_window_host_receiver_.is_bound()) {
    auto receiver = remote_ns_window_host_receiver_.Unbind();
    if (receiver.is_valid()) {
      mojo::MakeSelfOwnedAssociatedReceiver(
          std::make_unique<BridgedNativeWidgetHostDummy>(),
          std::move(receiver));
    }
  }

  // Ensure that |this| cannot be reached by its id while it is being destroyed.
  auto found = GetIdToWidgetHostImplMap().find(widget_id_);
  DCHECK(found != GetIdToWidgetHostImplMap().end());
  DCHECK_EQ(found->second, this);
  GetIdToWidgetHostImplMap().erase(found);

  // Destroy the bridge first to prevent any calls back into this during
  // destruction.
  // TODO(ccameron): When all communication from |bridge_| to this goes through
  // the BridgedNativeWidgetHost, this can be replaced with closing that pipe.
  in_process_ns_window_bridge_.reset();
  DestroyCompositor();
}

Widget* NativeWidgetMacNSWindowHost::GetWidget() {
  return native_widget_mac_ ? native_widget_mac_->GetWidget() : nullptr;
}

NativeWidgetMacNSWindow* NativeWidgetMacNSWindowHost::GetInProcessNSWindow()
    const {
  return in_process_ns_window_;
}

gfx::NativeViewAccessible
NativeWidgetMacNSWindowHost::GetNativeViewAccessibleForNSView() const {
  if (in_process_ns_window_bridge_)
    return in_process_ns_window_bridge_->ns_view();
  return remote_view_accessible_;
}

gfx::NativeViewAccessible
NativeWidgetMacNSWindowHost::GetNativeViewAccessibleForNSWindow() const {
  if (in_process_ns_window_bridge_) {
    // AppKit requires the return of the NSWindow that contains `ns_view()`,
    // Failure to do so will result in VoiceOver not announcing focus changes.
    // Typically, this would be `ns_window()`, but in fullscreen mode,
    // the overlay window's contentView is moved to NSToolbarFullScreenWindow.
    // Regardless of the mode (fullscreen or not), `[ns_view() window]` would
    // always yield the correct NSWindow that contains `ns_view()`.
    return [in_process_ns_window_bridge_->ns_view() window];
  }

  return remote_window_accessible_;
}

remote_cocoa::mojom::NativeWidgetNSWindow*
NativeWidgetMacNSWindowHost::GetNSWindowMojo() const {
  if (remote_ns_window_remote_)
    return remote_ns_window_remote_.get();
  if (in_process_ns_window_bridge_)
    return in_process_ns_window_bridge_.get();
  return nullptr;
}

void NativeWidgetMacNSWindowHost::CreateInProcessNSWindowBridge(
    NativeWidgetMacNSWindow* window) {
  in_process_ns_window_ = window;
  in_process_ns_window_bridge_ =
      std::make_unique<remote_cocoa::NativeWidgetNSWindowBridge>(
          widget_id_, this, this, text_input_host_.get());
  in_process_ns_window_bridge_->SetWindow(in_process_ns_window_);
}

void NativeWidgetMacNSWindowHost::CreateRemoteNSWindow(
    remote_cocoa::ApplicationHost* application_host,
    remote_cocoa::mojom::CreateWindowParamsPtr window_create_params) {
  accessibility_focus_overrider_.SetAppIsRemote(true);
  application_host_ = application_host;
  application_host_->AddObserver(this);

  // Create a local invisible window that will be used as the gfx::NativeWindow
  // handle to track this window in this process.
  {
    auto in_process_ns_window_create_params =
        remote_cocoa::mojom::CreateWindowParams::New();
    in_process_ns_window_create_params->style_mask =
        NSWindowStyleMaskBorderless;
    in_process_ns_window_ =
        remote_cocoa::NativeWidgetNSWindowBridge::CreateNSWindow(
            in_process_ns_window_create_params.get());
    [in_process_ns_window_ setBridgedNativeWidgetId:widget_id_];
    [in_process_ns_window_ setAlphaValue:0.0];
    in_process_view_id_mapping_ =
        std::make_unique<remote_cocoa::ScopedNSViewIdMapping>(
            root_view_id_, [in_process_ns_window_ contentView]);
    [in_process_ns_window_ enforceNeverMadeVisible];
  }

  // Initialize |remote_ns_window_remote_| to point to a bridge created by
  // |factory|.
  mojo::PendingAssociatedRemote<remote_cocoa::mojom::TextInputHost>
      text_input_host_remote;
  text_input_host_->BindReceiver(
      text_input_host_remote.InitWithNewEndpointAndPassReceiver());
  application_host_->GetApplication()->CreateNativeWidgetNSWindow(
      widget_id_, remote_ns_window_remote_.BindNewEndpointAndPassReceiver(),
      remote_ns_window_host_receiver_.BindNewEndpointAndPassRemote(
          ui::WindowResizeHelperMac::Get()->task_runner()),
      std::move(text_input_host_remote));

  // Create the window in its process, and attach it to its parent window.
  GetNSWindowMojo()->CreateWindow(std::move(window_create_params));
}

void NativeWidgetMacNSWindowHost::InitWindow(
    const Widget::InitParams& params,
    const gfx::Rect& initial_bounds_in_screen) {
  native_window_mapping_ =
      std::make_unique<remote_cocoa::ScopedNativeWindowMapping>(
          gfx::NativeWindow(in_process_ns_window_), application_host_,
          in_process_ns_window_bridge_.get(), GetNSWindowMojo());

  Widget* widget = GetWidget();
  widget_type_ = params.type;
  bool is_tooltip = params.type == Widget::InitParams::TYPE_TOOLTIP;
  if (!is_tooltip)
    tooltip_manager_ = std::make_unique<TooltipManagerMac>(GetNSWindowMojo());
  is_headless_mode_window_ = params.ShouldInitAsHeadless();

  if (params.workspace.length()) {
    std::string restoration_data;
    if (base::Base64Decode(params.workspace, &restoration_data)) {
      state_restoration_data_ = std::vector<uint8_t>(restoration_data.begin(),
                                                     restoration_data.end());
    } else {
      DLOG(ERROR) << "Failed to decode a window's state restoration data.";
    }
  }

  // Initialize the window.
  {
    auto window_params = NativeWidgetNSWindowInitParams::New();
    window_params->modal_type = widget->widget_delegate()->GetModalType();
    window_params->is_translucent =
        params.opacity == Widget::InitParams::WindowOpacity::kTranslucent;
    window_params->is_headless_mode_window = is_headless_mode_window_;
    window_params->is_tooltip = is_tooltip;

    // macOS likes to put shadows on most things. However, frameless windows
    // (with styleMask = NSWindowStyleMaskBorderless) default to no shadow. So
    // change that. ShadowType::kDrop is used for Menus, which get the same
    // shadow style on Mac.
    switch (params.shadow_type) {
      case Widget::InitParams::ShadowType::kNone:
        window_params->has_window_server_shadow = false;
        break;
      case Widget::InitParams::ShadowType::kDefault:
        // Controls should get views shadows instead of native shadows.
        window_params->has_window_server_shadow =
            params.type != Widget::InitParams::TYPE_CONTROL;
        break;
      case Widget::InitParams::ShadowType::kDrop:
        window_params->has_window_server_shadow = true;
        break;
    }  // No default case, to pick up new types.

    // Include "regular" windows without the standard frame in the window cycle.
    // These use NSWindowStyleMaskBorderless so do not get it by default.
    window_params->force_into_collection_cycle =
        widget_type_ == Widget::InitParams::TYPE_WINDOW &&
        params.remove_standard_frame;
    window_params->state_restoration_data = state_restoration_data_;

    GetNSWindowMojo()->InitWindow(std::move(window_params));
  }

  // Set a meaningful initial bounds. Note that except for frameless widgets
  // with no WidgetDelegate, the bounds will be set again by Widget after
  // initializing the non-client view. In the former case, if bounds were not
  // set at all, the creator of the Widget is expected to call SetBounds()
  // before calling Widget::Show() to avoid a kWindowSizeDeterminedLater-sized
  // (i.e. 1x1) window appearing.
  UpdateLocalWindowFrame(initial_bounds_in_screen);
  GetNSWindowMojo()->SetInitialBounds(initial_bounds_in_screen,
                                      widget->GetMinimumSize());

  // Widgets for UI controls (usually layered above web contents) start visible.
  if (widget_type_ == Widget::InitParams::TYPE_CONTROL)
    SetVisibilityState(WindowVisibilityState::kShowInactive);
}

void NativeWidgetMacNSWindowHost::CloseWindowNow() {
  bool is_out_of_process = !in_process_ns_window_bridge_;
  // Note that CloseWindowNow may delete |this| for in-process windows.
  if (GetNSWindowMojo())
    GetNSWindowMojo()->CloseWindowNow();

  // If it is out-of-process, then simulate the calls that would have been
  // during window closure.
  if (is_out_of_process) {
    OnWindowWillClose();
    while (!children_.empty())
      children_.front()->CloseWindowNow();
    OnWindowHasClosed();
  }
}

void NativeWidgetMacNSWindowHost::SetBoundsInScreen(const gfx::Rect& bounds) {
  Widget* widget = GetWidget();
  if (!widget) {
    return;
  }
  DCHECK(!bounds.IsEmpty() || !widget->GetMinimumSize().IsEmpty())
      << "Zero-sized windows are not supported on Mac";
  UpdateLocalWindowFrame(bounds);

  // `SetBounds()` accepts an optional maximum size, while
  // `Widget::GetMaximumSize()` uses an empty size to represent "no maximum
  // size", so we convert between those here.
  std::optional<gfx::Size> maximum_size = widget->GetMaximumSize();
  if (maximum_size->IsEmpty()) {
    maximum_size = std::nullopt;
  }

  GetNSWindowMojo()->SetBounds(bounds, widget->GetMinimumSize(), maximum_size);

  if (remote_ns_window_remote_) {
    gfx::Rect window_in_screen =
        gfx::ScreenRectFromNSRect([in_process_ns_window_ frame]);
    gfx::Rect content_in_screen = GetAdjustedContentBoundsInScreen();

    OnWindowGeometryChanged(window_in_screen, content_in_screen);
  }
}

void NativeWidgetMacNSWindowHost::SetSize(const gfx::Size& size) {
  Widget* widget = GetWidget();
  if (!widget) {
    return;
  }
  DCHECK(!size.IsEmpty() || !widget->GetMinimumSize().IsEmpty())
      << "Zero-sized windows are not supported on Mac";
  GetNSWindowMojo()->SetSize(size, widget->GetMinimumSize());

  if (remote_ns_window_remote_) {
    // Reflecting the logic above in SetBoundsInScreen, update our local
    // version of what we think the bounds of the window are. These bounds
    // are going to be not quite correct until OnWindowGeometryChanged is
    // called by the remote process but this is better than keeping the old
    // bounds around as code might try to make decisions based on the current
    // perceived bounds of the window.
    gfx::Rect bounds(GetWindowBoundsInScreen().origin(), size);
    UpdateLocalWindowFrame(bounds);

    gfx::Rect window_in_screen =
        gfx::ScreenRectFromNSRect([in_process_ns_window_ frame]);
    gfx::Rect content_in_screen = GetAdjustedContentBoundsInScreen();

    OnWindowGeometryChanged(window_in_screen, content_in_screen);
  }
}

void NativeWidgetMacNSWindowHost::SetFullscreen(bool fullscreen,
                                                int64_t target_display_id) {
  // Note that when the NSWindow begins a fullscreen transition, the value of
  // |target_fullscreen_state_| updates via OnWindowFullscreenTransitionStart.
  // The update here is necessary for the case where we are currently in
  // transition (and therefore OnWindowFullscreenTransitionStart will not be
  // called until the current transition completes).
  target_fullscreen_state_ = fullscreen;

  if (target_fullscreen_state_)
    GetNSWindowMojo()->EnterFullscreen(target_display_id);
  else
    GetNSWindowMojo()->ExitFullscreen();
}

void NativeWidgetMacNSWindowHost::SetRootView(views::View* root_view) {
  if (root_view_) {
    DropRootViewReferences();
  }
  root_view_ = root_view;
  if (root_view_) {
    root_view_observation_.Observe(root_view);
    // TODO(ccameron): Drag-drop functionality does not yet run over mojo.
    if (in_process_ns_window_bridge_) {
      drag_drop_client_ = std::make_unique<DragDropClientMac>(
          in_process_ns_window_bridge_.get(), root_view_);
    }
  }
}

void NativeWidgetMacNSWindowHost::CreateCompositor(
    const Widget::InitParams& params) {
  DCHECK(!compositor_);
  DCHECK(!layer());
  DCHECK(ViewsDelegate::GetInstance());

  // "Infer" must be handled by ViewsDelegate::OnBeforeWidgetInit().
  DCHECK_NE(Widget::InitParams::WindowOpacity::kInferred, params.opacity);
  bool translucent =
      params.opacity == Widget::InitParams::WindowOpacity::kTranslucent;

  // Create the layer.
  SetLayer(std::make_unique<ui::Layer>(params.layer_type));
  layer()->set_delegate(this);
  layer()->SetFillsBoundsOpaquely(!translucent);

  // Create the compositor and attach the layer to it.
  ui::ContextFactory* context_factory =
      ViewsDelegate::GetInstance()->GetContextFactory();
  DCHECK(context_factory);
  compositor_ = std::make_unique<ui::RecyclableCompositorMac>(context_factory);
  compositor_->widget()->SetNSView(this);
  compositor_->compositor()->SetBackgroundColor(
      translucent ? SK_ColorTRANSPARENT : SK_ColorWHITE);
  compositor_->compositor()->SetRootLayer(layer());

  // The compositor is locked (prevented from producing frames) until the widget
  // is made visible unless the window was created in headless mode in which
  // case it will never become visible but we want its compositor to produce
  // frames for screenshooting and screencasting.
  UpdateCompositorProperties();
  layer()->SetVisible(is_visible_);
  if (is_visible_ || is_headless_mode_window_)
    compositor_->Unsuspend();

  // Register the CGWindowID (used to identify this window for video capture)
  // when it is received. Note that this is done at this moment (as opposed to
  // as a callback to CreateWindow) so that we can associate the CGWindowID with
  // the (now existing) compositor.
  auto lambda = [](NativeWidgetMacNSWindowHost* host, uint32_t cg_window_id) {
    if (!host->compositor_)
      return;
    host->scoped_cg_window_id_ =
        std::make_unique<remote_cocoa::ScopedCGWindowID>(
            cg_window_id, host->compositor_->compositor()->frame_sink_id());
  };
  GetNSWindowMojo()->InitCompositorView(
      base::BindOnce(lambda, base::Unretained(this)));
}

void NativeWidgetMacNSWindowHost::UpdateCompositorProperties() {
  if (!compositor_)
    return;
  layer()->SetBounds(gfx::Rect(content_bounds_in_screen_.size()));
  // Mac device scale factor is always an integer so the result here is an
  // integer pixel size.
  gfx::Size content_bounds_in_pixels =
      gfx::ToRoundedSize(gfx::ConvertSizeToPixels(
          content_bounds_in_screen_.size(), display_.device_scale_factor()));
  compositor_->UpdateSurface(content_bounds_in_pixels,
                             display_.device_scale_factor(),
                             display_.GetColorSpaces(), display_.id());
}

void NativeWidgetMacNSWindowHost::DestroyCompositor() {
  if (layer()) {
    // LayerOwner supports a change in ownership, e.g., to animate a closing
    // window, but that won't work as expected for the root layer in
    // NativeWidgetNSWindowBridge.
    DCHECK_EQ(this, layer()->owner());
    layer()->CompleteAllAnimations();
  }
  DestroyLayer();
  if (!compositor_)
    return;
  compositor_->widget()->ResetNSView();
  compositor_->compositor()->SetRootLayer(nullptr);
  compositor_.reset();
}

bool NativeWidgetMacNSWindowHost::SetWindowTitle(const std::u16string& title) {
  if (window_title_ == title)
    return false;
  window_title_ = title;
  GetNSWindowMojo()->SetWindowTitle(window_title_);
  return true;
}

void NativeWidgetMacNSWindowHost::OnWidgetInitDone() {
  Widget* widget = GetWidget();
  if (widget) {
    if (DialogDelegate* dialog =
            widget->widget_delegate()->AsDialogDelegate()) {
      dialog->AddObserver(this);
    }
  }
}

bool NativeWidgetMacNSWindowHost::RedispatchKeyEvent(NSEvent* event) {
  // If the target window is in-process, then redispatch the event directly,
  // and give an accurate return value.
  if (in_process_ns_window_bridge_)
    return in_process_ns_window_bridge_->RedispatchKeyEvent(event);

  // If the target window is out of process then always report the event as
  // handled (because it should never be handled in this process).
  GetNSWindowMojo()->RedispatchKeyEvent(ui::EventToData(event));
  return true;
}

gfx::Rect NativeWidgetMacNSWindowHost::GetContentBoundsInScreen() const {
  return content_bounds_in_screen_;
}

gfx::Rect NativeWidgetMacNSWindowHost::GetRestoredBounds() const {
  if (target_fullscreen_state_ || in_fullscreen_transition_)
    return window_bounds_before_fullscreen_;
  return window_bounds_in_screen_;
}

const std::vector<uint8_t>&
NativeWidgetMacNSWindowHost::GetWindowStateRestorationData() const {
  return state_restoration_data_;
}

void NativeWidgetMacNSWindowHost::SetNativeWindowProperty(const char* name,
                                                          void* value) {
  if (value)
    native_window_properties_[name] = value;
  else
    native_window_properties_.erase(name);
}

void* NativeWidgetMacNSWindowHost::GetNativeWindowProperty(
    const char* name) const {
  auto found = native_window_properties_.find(name);
  if (found == native_window_properties_.end())
    return nullptr;
  return found->second;
}

void NativeWidgetMacNSWindowHost::SetParent(
    NativeWidgetMacNSWindowHost* new_parent) {
  if (new_parent == parent_)
    return;

  if (parent_) {
    auto found = base::ranges::find(parent_->children_, this);
    DCHECK(found != parent_->children_.end());
    parent_->children_.erase(found);
    parent_ = nullptr;
  }

  // We can only re-parent to another Widget if that Widget is hosted in the
  // same process that we were already hosted by. If this is not the case, just
  // close the Widget.
  // https://crbug.com/957927
  remote_cocoa::ApplicationHost* new_application_host =
      new_parent ? new_parent->application_host() : application_host_.get();
  if (new_application_host != application_host_) {
    DLOG(ERROR) << "Cannot migrate views::NativeWidget to another process, "
                   "closing it instead.";
    GetNSWindowMojo()->CloseWindow();
    return;
  }

  parent_ = new_parent;
  if (parent_) {
    parent_->children_.push_back(this);
    GetNSWindowMojo()->SetParent(parent_->bridged_native_widget_id());
  } else {
    GetNSWindowMojo()->SetParent(0);
  }
}

void NativeWidgetMacNSWindowHost::OnNativeViewHostAttach(const View* view,
                                                         NSView* native_view) {
  DCHECK_EQ(0u, attached_native_view_host_views_.count(view));
  attached_native_view_host_views_[view] = native_view;
  if (Widget* widget = GetWidget()) {
    widget->ReorderNativeViews();
  }
}

void NativeWidgetMacNSWindowHost::OnNativeViewHostDetach(const View* view) {
  auto it = attached_native_view_host_views_.find(view);
  DCHECK(it != attached_native_view_host_views_.end());
  attached_native_view_host_views_.erase(it);
}

void NativeWidgetMacNSWindowHost::ReorderChildViews() {
  if (!root_view_) {
    return;
  }

  // Get the ordering for the NSViews in |attached_native_view_host_views_|.
  std::vector<NSView*> attached_subviews;
  GetAttachedNativeViewHostViewsRecursive(root_view_, &attached_subviews);

  // Convert to NSView ids that can go over mojo. If need be, create temporary
  // NSView ids.
  std::vector<uint64_t> attached_subview_ids;
  std::list<remote_cocoa::ScopedNSViewIdMapping> temp_ids;
  for (NSView* subview : attached_subviews) {
    uint64_t ns_view_id = remote_cocoa::GetIdFromNSView(subview);
    if (!ns_view_id) {
      // Subviews that do not already have an id will not work if the target is
      // in a different process.
      DCHECK(in_process_ns_window_bridge_);
      ns_view_id = remote_cocoa::GetNewNSViewId();
      temp_ids.emplace_back(ns_view_id, subview);
    }
    attached_subview_ids.push_back(ns_view_id);
  }
  GetNSWindowMojo()->SortSubviews(attached_subview_ids);
}

void NativeWidgetMacNSWindowHost::SetVisibilityState(
    remote_cocoa::mojom::WindowVisibilityState new_state) {
  // On macOS 14 an application can't generally activate themselves. If we're
  // trying to activate a window in a remote application host, this yield
  // should make sure this works as long as chrome is the currently active
  // application.
  if (@available(macOS 14, *)) {
    if (application_host_ &&
        new_state == WindowVisibilityState::kShowAndActivateWindow) {
      [NSApp yieldActivationToApplicationWithBundleIdentifier:
                 base::SysUTF8ToNSString(application_host_->bundle_id())];
    }
  }
  GetNSWindowMojo()->SetVisibilityState(new_state);
}

void NativeWidgetMacNSWindowHost::GetAttachedNativeViewHostViewsRecursive(
    View* view,
    std::vector<NSView*>* order) const {
  auto found = attached_native_view_host_views_.find(view);
  if (found != attached_native_view_host_views_.end())
    order->push_back(found->second);
  for (View* child : view->children())
    GetAttachedNativeViewHostViewsRecursive(child, order);
}

void NativeWidgetMacNSWindowHost::UpdateLocalWindowFrame(
    const gfx::Rect& frame) {
  if (!remote_ns_window_remote_)
    return;
  [in_process_ns_window_ setFrame:gfx::ScreenRectToNSRect(frame)
                          display:NO
                          animate:NO];
}

void NativeWidgetMacNSWindowHost::DropRootViewReferences() {
  root_view_ = nullptr;
  root_view_observation_.Reset();
  drag_drop_client_.reset();
}

gfx::Rect NativeWidgetMacNSWindowHost::GetAdjustedContentBoundsInScreen() {
  gfx::Rect content_in_screen = gfx::ScreenRectFromNSRect([in_process_ns_window_
      contentRectForFrameRect:[in_process_ns_window_ frame]]);
  gfx::Size content_in_screen_size = content_in_screen.size();

  const std::optional<gfx::Size> maximum_size =
      native_widget_mac_->GetWidget()->GetMaximumSize();
  if (maximum_size.has_value() && !maximum_size->IsEmpty()) {
    content_in_screen_size.SetToMin(maximum_size.value());
  }

  const std::optional<gfx::Size> minimum_size =
      native_widget_mac_->GetWidget()->GetMinimumSize();
  if (minimum_size.has_value() && !minimum_size->IsEmpty()) {
    content_in_screen_size.SetToMax(minimum_size.value());
  }

  content_in_screen.set_size(content_in_screen_size);
  return content_in_screen;
}

std::unique_ptr<NativeWidgetMacEventMonitor>
NativeWidgetMacNSWindowHost::AddEventMonitor(
    NativeWidgetMacEventMonitor::Client* client) {
  // Enable the local event monitor if this is the first registered monitor.
  if (event_monitors_.empty())
    GetNSWindowMojo()->SetLocalEventMonitorEnabled(true);

  // Add the new monitor to `event_monitors_`.
  auto* monitor = new NativeWidgetMacEventMonitor(client);
  event_monitors_.push_back(monitor);

  // Set up `monitor`'s remove closure to remove it from `event_monitors_`.
  auto remove_lambda = [](base::WeakPtr<NativeWidgetMacNSWindowHost> weak_this,
                          NativeWidgetMacEventMonitor* monitor) {
    if (!weak_this)
      return;
    auto found = base::ranges::find(weak_this->event_monitors_, monitor);
    CHECK(found != weak_this->event_monitors_.end());
    weak_this->event_monitors_.erase(found);

    // If this was the last monitor to be removed, disable the local
    // event monitor.
    if (weak_this->event_monitors_.empty())
      weak_this->GetNSWindowMojo()->SetLocalEventMonitorEnabled(false);
  };
  monitor->remove_closure_runner_.ReplaceClosure(
      base::BindOnce(remove_lambda, weak_factory_.GetWeakPtr(), monitor));

  return base::WrapUnique<NativeWidgetMacEventMonitor>(monitor);
}

// static
NSView* NativeWidgetMacNSWindowHost::GetGlobalCaptureView() {
  // TODO(ccameron): This will not work across process boundaries.
  return
      [remote_cocoa::CocoaMouseCapture::GetGlobalCaptureWindow() contentView];
}

void NativeWidgetMacNSWindowHost::CanGoBack(bool can_go_back) {
  GetNSWindowMojo()->SetCanGoBack(can_go_back);
}

void NativeWidgetMacNSWindowHost::CanGoForward(bool can_go_forward) {
  GetNSWindowMojo()->SetCanGoForward(can_go_forward);
}

void NativeWidgetMacNSWindowHost::SetAllowScreenshots(bool allow) {
  GetNSWindowMojo()->SetAllowScreenshots(allow);
  allow_screenshots_ = allow;
}

bool NativeWidgetMacNSWindowHost::AllowScreenshots() const {
  // The `setSharingType` call in `SetAllowScreenshots()` doesn't actually
  // change the value returned by NSWindow::sharingType. The official
  // documentation doesn't even mention setSharingType, see
  // https://developer.apple.com/documentation/appkit/nswindow/1419729-sharingtype
  //
  // Using `allow_screenshots_` is a workaround to be able to know the actual
  // value `SetAllowScreenshots()` was called with.
  return allow_screenshots_;
}

////////////////////////////////////////////////////////////////////////////////
// NativeWidgetMacNSWindowHost, remote_cocoa::BridgedNativeWidgetHostHelper:

id NativeWidgetMacNSWindowHost::GetNativeViewAccessible() {
  return root_view_ ? root_view_->GetNativeViewAccessible() : nil;
}

void NativeWidgetMacNSWindowHost::DispatchKeyEvent(ui::KeyEvent* event) {
  if (root_view_) {
    std::ignore =
        root_view_->GetWidget()->GetInputMethod()->DispatchKeyEvent(event);
  }
}

bool NativeWidgetMacNSWindowHost::DispatchKeyEventToMenuController(
    ui::KeyEvent* event) {
  MenuController* menu_controller = MenuController::GetActiveInstance();
  if (menu_controller && root_view_ &&
      menu_controller->owner() == root_view_->GetWidget()) {
    return menu_controller->OnWillDispatchKeyEvent(event) ==
           ui::POST_DISPATCH_NONE;
  }
  return false;
}

remote_cocoa::DragDropClient* NativeWidgetMacNSWindowHost::GetDragDropClient() {
  return drag_drop_client_.get();
}

ui::TextInputClient* NativeWidgetMacNSWindowHost::GetTextInputClient() {
  return text_input_host_->GetTextInputClient();
}

bool NativeWidgetMacNSWindowHost::MustPostTaskToRunModalSheetAnimation() const {
  return false;
}

////////////////////////////////////////////////////////////////////////////////
// NativeWidgetMacNSWindowHost, remote_cocoa::ApplicationHost::Observer:
void NativeWidgetMacNSWindowHost::OnApplicationHostDestroying(
    remote_cocoa::ApplicationHost* host) {
  DCHECK_EQ(host, application_host_);
  application_host_->RemoveObserver(this);
  application_host_ = nullptr;

  // Because the process hosting this window has ended, close the window by
  // sending the window close messages that the bridge would have sent.
  OnWindowWillClose();
  // Explicitly propagate this message to all children (they are also observers,
  // but may not be destroyed before |this| is destroyed, which would violate
  // tear-down assumptions). This would have been done by the bridge, had it
  // shut down cleanly.
  while (!children_.empty())
    children_.front()->OnApplicationHostDestroying(host);
  OnWindowHasClosed();
}

////////////////////////////////////////////////////////////////////////////////
// NativeWidgetMacNSWindowHost,
// remote_cocoa::mojom::NativeWidgetNSWindowHost:

void NativeWidgetMacNSWindowHost::OnVisibilityChanged(bool window_visible) {
  is_visible_ = window_visible;
  if (compositor_) {
    layer()->SetVisible(window_visible);
    if (window_visible) {
      compositor_->Unsuspend();
      layer()->SchedulePaint(layer()->bounds());
    } else {
      compositor_->Suspend();
    }
  }
  if (Widget* widget = GetWidget()) {
    widget->OnNativeWidgetVisibilityChanged(window_visible);
  }
}

void NativeWidgetMacNSWindowHost::OnWindowNativeThemeChanged() {
  ui::NativeTheme::GetInstanceForNativeUi()->NotifyOnNativeThemeUpdated();
}

void NativeWidgetMacNSWindowHost::OnScrollEvent(
    std::unique_ptr<ui::Event> event) {
  if (root_view_) {
    root_view_->GetWidget()->OnScrollEvent(event->AsScrollEvent());
  }
}

void NativeWidgetMacNSWindowHost::OnMouseEvent(
    std::unique_ptr<ui::Event> event) {
  if (!root_view_) {
    return;
  }
  ui::MouseEvent* mouse_event = event->AsMouseEvent();
  if (scoped_cg_window_id_) {
    scoped_cg_window_id_->OnMouseMoved(mouse_event->location_f(),
                                       window_bounds_in_screen_.size());
  }
  root_view_->GetWidget()->OnMouseEvent(mouse_event);
  // Note that |this| may be destroyed by the above call to OnMouseEvent.
  // https://crbug.com/1193454
}

void NativeWidgetMacNSWindowHost::OnGestureEvent(
    std::unique_ptr<ui::Event> event) {
  if (root_view_) {
    root_view_->GetWidget()->OnGestureEvent(event->AsGestureEvent());
  }
}

bool NativeWidgetMacNSWindowHost::DispatchKeyEventRemote(
    std::unique_ptr<ui::Event> event,
    bool* event_handled) {
  DispatchKeyEvent(event->AsKeyEvent());
  *event_handled = event->handled();
  return true;
}

bool NativeWidgetMacNSWindowHost::DispatchKeyEventToMenuControllerRemote(
    std::unique_ptr<ui::Event> event,
    bool* event_swallowed,
    bool* event_handled) {
  *event_swallowed = DispatchKeyEventToMenuController(event->AsKeyEvent());
  *event_handled = event->handled();
  return true;
}

bool NativeWidgetMacNSWindowHost::DispatchMonitorEvent(
    std::unique_ptr<ui::Event> event,
    bool* event_handled) {
  // The calls to NativeWidgetMacEventMonitorOnEvent can add or remove monitors,
  // so take a snapshot of `event_monitors_` before making any calls.
  auto event_monitors_snapshot = event_monitors_;

  // The calls to NativeWidgetMacEventMonitorOnEvent can delete `this`. Use
  // `weak_this` to detect that.
  auto weak_this = weak_factory_.GetWeakPtr();

  *event_handled = false;
  for (auto* event_monitor : event_monitors_snapshot) {
    // Ensure `event_monitor` was not removed from `event_monitors_` by a
    // previous call to NativeWidgetMacEventMonitorOnEvent.
    if (!base::Contains(event_monitors_, event_monitor))
      continue;
    event_monitor->client_->NativeWidgetMacEventMonitorOnEvent(event.get(),
                                                               event_handled);
    if (!weak_this)
      return true;
  }
  return true;
}

bool NativeWidgetMacNSWindowHost::GetHasMenuController(
    bool* has_menu_controller) {
  MenuController* menu_controller = MenuController::GetActiveInstance();
  *has_menu_controller = menu_controller && root_view_ &&
                         menu_controller->owner() == root_view_->GetWidget() &&
                         // The editable combobox menu does not swallow keys.
                         !menu_controller->IsEditableCombobox();
  return true;
}

void NativeWidgetMacNSWindowHost::OnViewSizeChanged(const gfx::Size& new_size) {
  if (root_view_) {
    root_view_->SetSize(new_size);
  }
}

bool NativeWidgetMacNSWindowHost::GetSheetOffsetY(int32_t* offset_y) {
  *offset_y = native_widget_mac_->SheetOffsetY();
  return true;
}

void NativeWidgetMacNSWindowHost::SetKeyboardAccessible(bool enabled) {
  if (!root_view_) {
    return;
  }
  views::FocusManager* focus_manager =
      root_view_->GetWidget()->GetFocusManager();
  if (focus_manager)
    focus_manager->SetKeyboardAccessible(enabled);
}

void NativeWidgetMacNSWindowHost::OnIsFirstResponderChanged(
    bool is_first_responder) {
  if (!root_view_) {
    return;
  }
  accessibility_focus_overrider_.SetViewIsFirstResponder(is_first_responder);

  FocusManager* focus_manager = root_view_->GetWidget()->GetFocusManager();
  if (focus_manager->IsSettingFocusedView()) {
    // This first responder change is not initiated by the os,
    // but by the focus change within the browser (e.g. tab switch),
    // so skip setting the focus.
    return;
  }

  if (is_first_responder) {
    focus_manager->RestoreFocusedView();
  } else {
    // Do not call ClearNativeFocus because that will re-make the
    // BridgedNativeWidget first responder (and this is called to indicate that
    // it is no longer first responder).
    focus_manager->StoreFocusedView(false /* clear_native_focus */);
  }
}

void NativeWidgetMacNSWindowHost::OnMouseCaptureActiveChanged(bool is_active) {
  DCHECK_NE(is_mouse_capture_active_, is_active);
  is_mouse_capture_active_ = is_active;
  if (!is_mouse_capture_active_ && GetWidget()) {
    GetWidget()->OnMouseCaptureLost();
  }
}

bool NativeWidgetMacNSWindowHost::GetIsDraggableBackgroundAt(
    const gfx::Point& location_in_content,
    bool* is_draggable_background) {
  if (!root_view_) {
    return false;
  }
  int component =
      root_view_->GetWidget()->GetNonClientComponent(location_in_content);
  *is_draggable_background = component == HTCAPTION;
  return true;
}

void NativeWidgetMacNSWindowHost::GetWordAt(
    const gfx::Point& location_in_content,
    bool* found_word,
    gfx::DecoratedText* decorated_word,
    gfx::Point* baseline_point) {
  *found_word = false;
  if (!root_view_) {
    return;
  }
  views::View* target =
      root_view_->GetEventHandlerForPoint(location_in_content);
  if (!target)
    return;

  views::WordLookupClient* word_lookup_client = target->GetWordLookupClient();
  if (!word_lookup_client)
    return;

  gfx::Point location_in_target = location_in_content;
  views::View::ConvertPointToTarget(root_view_, target, &location_in_target);
  gfx::Rect rect;
  if (!word_lookup_client->GetWordLookupDataAtPoint(location_in_target,
                                                    decorated_word, &rect)) {
    return;
  }

  // We only care about the baseline of the glyph, not the space it occupies.
  *baseline_point = rect.origin();
  // Convert |baselinePoint| to the coordinate system of |root_view_|.
  views::View::ConvertPointToTarget(target, root_view_, baseline_point);
  *found_word = true;
}

bool NativeWidgetMacNSWindowHost::GetWidgetIsModal(bool* widget_is_modal) {
  if (Widget* widget = GetWidget()) {
    *widget_is_modal = widget->IsModal();
  }
  return true;
}

bool NativeWidgetMacNSWindowHost::GetIsFocusedViewTextual(bool* is_textual) {
  views::FocusManager* focus_manager =
      root_view_ ? root_view_->GetWidget()->GetFocusManager() : nullptr;
  *is_textual = focus_manager && focus_manager->GetFocusedView() &&
                IsViewClass<views::Label>(focus_manager->GetFocusedView());
  return true;
}

void NativeWidgetMacNSWindowHost::OnWindowGeometryChanged(
    const gfx::Rect& new_window_bounds_in_screen,
    const gfx::Rect& new_content_bounds_in_screen) {
  UpdateLocalWindowFrame(new_window_bounds_in_screen);

  bool window_has_moved =
      new_window_bounds_in_screen.origin() != window_bounds_in_screen_.origin();
  bool content_has_resized =
      new_content_bounds_in_screen.size() != content_bounds_in_screen_.size();

  window_bounds_in_screen_ = new_window_bounds_in_screen;
  content_bounds_in_screen_ = new_content_bounds_in_screen;

  Widget* widget = GetWidget();
  // When a window grows vertically, the AppKit origin changes, but as far as
  // toolkit-views is concerned, the window hasn't moved. Suppress these.
  if (window_has_moved && widget) {
    widget->OnNativeWidgetMove();
  }

  // Note we can't use new_window_bounds_in_screen.size(), since it includes the
  // titlebar for the purposes of detecting a window move.
  if (content_has_resized && widget) {
    widget->OnNativeWidgetSizeChanged(content_bounds_in_screen_.size());

    // Update the compositor surface and layer size.
    UpdateCompositorProperties();
  }
}

void NativeWidgetMacNSWindowHost::OnWindowFullscreenTransitionStart(
    bool target_fullscreen_state) {
  target_fullscreen_state_ = target_fullscreen_state;
  in_fullscreen_transition_ = true;

  // If going into fullscreen, store an answer for GetRestoredBounds().
  if (target_fullscreen_state)
    window_bounds_before_fullscreen_ = window_bounds_in_screen_;

  // Notify that fullscreen state is changing.
  native_widget_mac_->OnWindowFullscreenTransitionStart();
}

void NativeWidgetMacNSWindowHost::OnWindowFullscreenTransitionComplete(
    bool actual_fullscreen_state) {
  in_fullscreen_transition_ = false;

  // Notify that fullscreen state has changed.
  native_widget_mac_->OnWindowFullscreenTransitionComplete();

  // Ensure constraints are re-applied when completing a transition.
  native_widget_mac_->OnSizeConstraintsChanged();

  ui::NSWindowFullscreenNotificationWaiter::NotifyFullscreenTransitionComplete(
      native_widget_mac_->GetNativeWindow(), actual_fullscreen_state);
}

void NativeWidgetMacNSWindowHost::OnWindowMiniaturizedChanged(
    bool miniaturized) {
  is_miniaturized_ = miniaturized;
  if (Widget* widget = GetWidget()) {
    widget->OnNativeWidgetWindowShowStateChanged();
  }
}

void NativeWidgetMacNSWindowHost::OnWindowZoomedChanged(bool zoomed) {
  is_zoomed_ = zoomed;
}

void NativeWidgetMacNSWindowHost::OnWindowDisplayChanged(
    const display::Display& new_display) {
  display_ = new_display;
  if (!compositor_) {
    return;
  }
  // Mac device scale factor is always an integer so the result here is an
  // integer pixel size.
  gfx::Size content_bounds_in_pixels =
      gfx::ToRoundedSize(gfx::ConvertSizeToPixels(
          content_bounds_in_screen_.size(), display_.device_scale_factor()));
  compositor_->UpdateSurface(content_bounds_in_pixels,
                             display_.device_scale_factor(),
                             display_.GetColorSpaces(), display_.id());
}

void NativeWidgetMacNSWindowHost::OnWindowWillClose() {
  Widget* widget = GetWidget();
  if (widget && widget->widget_delegate() &&
      widget->widget_delegate()->AsDialogDelegate()) {
    widget->widget_delegate()->AsDialogDelegate()->RemoveObserver(this);
  }
  native_widget_mac_->WindowDestroying();
  // Remove |this| from the parent's list of children.
  SetParent(nullptr);
}

void NativeWidgetMacNSWindowHost::OnWindowHasClosed() {
  // OnWindowHasClosed will be called only after all child windows have had
  // OnWindowWillClose called on them.
  DCHECK(children_.empty());
  native_widget_mac_->WindowDestroyed();
}

void NativeWidgetMacNSWindowHost::OnWindowKeyStatusChanged(
    bool is_key,
    bool is_content_first_responder,
    bool full_keyboard_access_enabled) {
  // We need `setRemoteUIApp` to YES to support some accessibility
  // features on out-of-process remote cocoa windows like those used
  // for PWAs. However this breaks accessibility on in-process windows,
  // so set it back to NO when a local window gains focus. See
  // https://crbug.com/41485830.
  if (is_key && features::IsAccessibilityRemoteUIAppEnabled()) {
    [NSAccessibilityRemoteUIElement setRemoteUIApp:!!application_host_];
  }
  // Explicitly set the keyboard accessibility state on regaining key
  // window status.
  if (is_key && is_content_first_responder)
    SetKeyboardAccessible(full_keyboard_access_enabled);
  accessibility_focus_overrider_.SetWindowIsKey(is_key);
  is_window_key_ = is_key;
  native_widget_mac_->OnWindowKeyStatusChanged(is_key,
                                               is_content_first_responder);
}

void NativeWidgetMacNSWindowHost::OnWindowStateRestorationDataChanged(
    const std::vector<uint8_t>& data) {
  state_restoration_data_ = data;
  if (Widget* widget = GetWidget()) {
    widget->OnNativeWidgetWorkspaceChanged();
  }
}

void NativeWidgetMacNSWindowHost::OnImmersiveFullscreenToolbarRevealChanged(
    bool is_revealed) {
  if (immersive_mode_reveal_client_) {
    immersive_mode_reveal_client_->OnImmersiveModeToolbarRevealChanged(
        is_revealed);
  }
}

void NativeWidgetMacNSWindowHost::OnImmersiveFullscreenMenuBarRevealChanged(
    float reveal_amount) {
  if (immersive_mode_reveal_client_) {
    immersive_mode_reveal_client_->OnImmersiveModeMenuBarRevealChanged(
        reveal_amount);
  }
}
void NativeWidgetMacNSWindowHost::OnAutohidingMenuBarHeightChanged(
    int menu_bar_height) {
  if (immersive_mode_reveal_client_) {
    immersive_mode_reveal_client_->OnAutohidingMenuBarHeightChanged(
        menu_bar_height);
  }
}

void NativeWidgetMacNSWindowHost::DoDialogButtonAction(
    ui::mojom::DialogButton button) {
  if (!root_view_) {
    return;
  }
  views::DialogDelegate* dialog =
      root_view_->GetWidget()->widget_delegate()->AsDialogDelegate();
  DCHECK(dialog);
  if (button == ui::mojom::DialogButton::kOk) {
    dialog->AcceptDialog();
  } else {
    DCHECK_EQ(button, ui::mojom::DialogButton::kCancel);
    dialog->CancelDialog();
  }
}

bool NativeWidgetMacNSWindowHost::GetDialogButtonInfo(
    ui::mojom::DialogButton button,
    bool* button_exists,
    std::u16string* button_label,
    bool* is_button_enabled,
    bool* is_button_default) {
  *button_exists = false;
  views::DialogDelegate* dialog =
      root_view_
          ? root_view_->GetWidget()->widget_delegate()->AsDialogDelegate()
          : nullptr;
  if (!dialog || !(dialog->buttons() & static_cast<int>(button))) {
    return true;
  }
  *button_exists = true;
  *button_label = dialog->GetDialogButtonLabel(button);
  *is_button_enabled = dialog->IsDialogButtonEnabled(button);
  *is_button_default =
      static_cast<int>(button) == dialog->GetDefaultDialogButton();
  return true;
}

bool NativeWidgetMacNSWindowHost::GetDoDialogButtonsExist(bool* buttons_exist) {
  views::DialogDelegate* dialog =
      root_view_
          ? root_view_->GetWidget()->widget_delegate()->AsDialogDelegate()
          : nullptr;
  *buttons_exist = dialog && dialog->buttons();
  return true;
}

bool NativeWidgetMacNSWindowHost::GetShouldShowWindowTitle(
    bool* should_show_window_title) {
  *should_show_window_title =
      root_view_
          ? root_view_->GetWidget()->widget_delegate()->ShouldShowWindowTitle()
          : true;
  return true;
}

bool NativeWidgetMacNSWindowHost::GetCanWindowBecomeKey(
    bool* can_window_become_key) {
  *can_window_become_key =
      root_view_ ? root_view_->GetWidget()->CanActivate() : false;
  return true;
}

bool NativeWidgetMacNSWindowHost::GetAlwaysRenderWindowAsKey(
    bool* always_render_as_key) {
  *always_render_as_key =
      root_view_ ? root_view_->GetWidget()->ShouldPaintAsActive() : false;
  return true;
}

bool NativeWidgetMacNSWindowHost::OnWindowCloseRequested(
    bool* can_window_close) {
  *can_window_close = true;
  views::NonClientView* non_client_view =
      root_view_ ? root_view_->GetWidget()->non_client_view() : nullptr;
  if (non_client_view)
    *can_window_close = non_client_view->OnWindowCloseRequested() ==
                        CloseRequestResult::kCanClose;
  return true;
}

bool NativeWidgetMacNSWindowHost::GetWindowFrameTitlebarHeight(
    bool* override_titlebar_height,
    float* titlebar_height) {
  native_widget_mac_->GetWindowFrameTitlebarHeight(override_titlebar_height,
                                                   titlebar_height);
  return true;
}

void NativeWidgetMacNSWindowHost::OnFocusWindowToolbar() {
  native_widget_mac_->OnFocusWindowToolbar();
}

void NativeWidgetMacNSWindowHost::SetRemoteAccessibilityTokens(
    const std::vector<uint8_t>& window_token,
    const std::vector<uint8_t>& view_token) {
  remote_window_accessible_ =
      ui::RemoteAccessibility::GetRemoteElementFromToken(window_token);
  remote_view_accessible_ =
      ui::RemoteAccessibility::GetRemoteElementFromToken(view_token);
  [remote_view_accessible_ setWindowUIElement:remote_window_accessible_];
  [remote_view_accessible_ setTopLevelUIElement:remote_window_accessible_];
}

bool NativeWidgetMacNSWindowHost::GetRootViewAccessibilityToken(
    base::ProcessId* pid,
    std::vector<uint8_t>* token) {
  *pid = getpid();
  id element_id = GetNativeViewAccessible();

  if (features::IsAccessibilityRemoteUIAppEnabled()) {
    pid_t client_pid = [remote_view_accessible_ processIdentifier];
    if ([element_id respondsToSelector:@selector
                    (accessibilitySetPresenterProcessIdentifier:)]) {
      [element_id accessibilitySetPresenterProcessIdentifier:client_pid];
    }
  }

  *token = ui::RemoteAccessibility::GetTokenForLocalElement(element_id);
  return true;
}

bool NativeWidgetMacNSWindowHost::ValidateUserInterfaceItem(
    int32_t command,
    remote_cocoa::mojom::ValidateUserInterfaceItemResultPtr* out_result) {
  *out_result = remote_cocoa::mojom::ValidateUserInterfaceItemResult::New();
  native_widget_mac_->ValidateUserInterfaceItem(command, out_result->get());
  return true;
}

bool NativeWidgetMacNSWindowHost::WillExecuteCommand(
    int32_t command,
    WindowOpenDisposition window_open_disposition,
    bool is_before_first_responder,
    bool* will_execute) {
  *will_execute = native_widget_mac_->WillExecuteCommand(
      command, window_open_disposition, is_before_first_responder);
  return true;
}

bool NativeWidgetMacNSWindowHost::ExecuteCommand(
    int32_t command,
    WindowOpenDisposition window_open_disposition,
    bool is_before_first_responder,
    bool* was_executed) {
  *was_executed = native_widget_mac_->ExecuteCommand(
      command, window_open_disposition, is_before_first_responder);
  return true;
}

bool NativeWidgetMacNSWindowHost::HandleAccelerator(
    const ui::Accelerator& accelerator,
    bool require_priority_handler,
    bool* was_handled) {
  *was_handled = false;
  if (Widget* widget = GetWidget()) {
    if (require_priority_handler &&
        !widget->GetFocusManager()->HasPriorityHandler(accelerator)) {
      return true;
    }
    *was_handled = widget->GetFocusManager()->ProcessAccelerator(accelerator);
  }
  return true;
}

////////////////////////////////////////////////////////////////////////////////
// NativeWidgetMacNSWindowHost,
// remote_cocoa::mojom::NativeWidgetNSWindowHost synchronous callbacks:

void NativeWidgetMacNSWindowHost::GetSheetOffsetY(
    GetSheetOffsetYCallback callback) {
  int32_t offset_y = 0;
  GetSheetOffsetY(&offset_y);
  std::move(callback).Run(offset_y);
}

void NativeWidgetMacNSWindowHost::DispatchKeyEventRemote(
    std::unique_ptr<ui::Event> event,
    DispatchKeyEventRemoteCallback callback) {
  bool event_handled = false;
  DispatchKeyEventRemote(std::move(event), &event_handled);
  std::move(callback).Run(event_handled);
}

void NativeWidgetMacNSWindowHost::DispatchKeyEventToMenuControllerRemote(
    std::unique_ptr<ui::Event> event,
    DispatchKeyEventToMenuControllerRemoteCallback callback) {
  ui::KeyEvent* key_event = event->AsKeyEvent();
  bool event_swallowed = DispatchKeyEventToMenuController(key_event);
  std::move(callback).Run(event_swallowed, key_event->handled());
}

void NativeWidgetMacNSWindowHost::DispatchMonitorEvent(
    std::unique_ptr<ui::Event> event,
    DispatchMonitorEventCallback callback) {
  bool event_handled = false;
  DispatchMonitorEvent(std::move(event), &event_handled);
  std::move(callback).Run(event_handled);
}

void NativeWidgetMacNSWindowHost::GetHasMenuController(
    GetHasMenuControllerCallback callback) {
  bool has_menu_controller = false;
  GetHasMenuController(&has_menu_controller);
  std::move(callback).Run(has_menu_controller);
}

void NativeWidgetMacNSWindowHost::GetIsDraggableBackgroundAt(
    const gfx::Point& location_in_content,
    GetIsDraggableBackgroundAtCallback callback) {
  bool is_draggable_background = false;
  GetIsDraggableBackgroundAt(location_in_content, &is_draggable_background);
  std::move(callback).Run(is_draggable_background);
}

void NativeWidgetMacNSWindowHost::GetTooltipTextAt(
    const gfx::Point& location_in_content,
    GetTooltipTextAtCallback callback) {
  std::u16string new_tooltip_text;
  views::View* view =
      root_view_ ? root_view_->GetTooltipHandlerForPoint(location_in_content)
                 : nullptr;
  if (view) {
    gfx::Point view_point = location_in_content;
    views::View::ConvertPointToScreen(root_view_, &view_point);
    views::View::ConvertPointFromScreen(view, &view_point);
    new_tooltip_text = view->GetTooltipText(view_point);
  }
  std::move(callback).Run(new_tooltip_text);
}

void NativeWidgetMacNSWindowHost::GetIsFocusedViewTextual(
    GetIsFocusedViewTextualCallback callback) {
  bool is_textual = false;
  GetIsFocusedViewTextual(&is_textual);
  std::move(callback).Run(is_textual);
}

void NativeWidgetMacNSWindowHost::GetWidgetIsModal(
    GetWidgetIsModalCallback callback) {
  bool widget_is_modal = false;
  GetWidgetIsModal(&widget_is_modal);
  std::move(callback).Run(widget_is_modal);
}

void NativeWidgetMacNSWindowHost::GetDialogButtonInfo(
    ui::mojom::DialogButton button,
    GetDialogButtonInfoCallback callback) {
  bool exists = false;
  std::u16string label;
  bool is_enabled = false;
  bool is_default = false;
  GetDialogButtonInfo(button, &exists, &label, &is_enabled, &is_default);
  std::move(callback).Run(exists, label, is_enabled, is_default);
}

void NativeWidgetMacNSWindowHost::GetDoDialogButtonsExist(
    GetDoDialogButtonsExistCallback callback) {
  bool buttons_exist = false;
  GetDoDialogButtonsExist(&buttons_exist);
  std::move(callback).Run(buttons_exist);
}

void NativeWidgetMacNSWindowHost::GetShouldShowWindowTitle(
    GetShouldShowWindowTitleCallback callback) {
  bool should_show_window_title = false;
  GetShouldShowWindowTitle(&should_show_window_title);
  std::move(callback).Run(should_show_window_title);
}

void NativeWidgetMacNSWindowHost::GetCanWindowBecomeKey(
    GetCanWindowBecomeKeyCallback callback) {
  bool can_window_become_key = false;
  GetCanWindowBecomeKey(&can_window_become_key);
  std::move(callback).Run(can_window_become_key);
}

void NativeWidgetMacNSWindowHost::GetAlwaysRenderWindowAsKey(
    GetAlwaysRenderWindowAsKeyCallback callback) {
  bool always_render_as_key = false;
  GetAlwaysRenderWindowAsKey(&always_render_as_key);
  std::move(callback).Run(always_render_as_key);
}

void NativeWidgetMacNSWindowHost::OnWindowCloseRequested(
    OnWindowCloseRequestedCallback callback) {
  bool can_window_close = false;
  OnWindowCloseRequested(&can_window_close);
  std::move(callback).Run(can_window_close);
}

void NativeWidgetMacNSWindowHost::GetWindowFrameTitlebarHeight(
    GetWindowFrameTitlebarHeightCallback callback) {
  bool override_titlebar_height = false;
  float titlebar_height = 0;
  GetWindowFrameTitlebarHeight(&override_titlebar_height, &titlebar_height);
  std::move(callback).Run(override_titlebar_height, titlebar_height);
}

void NativeWidgetMacNSWindowHost::GetRootViewAccessibilityToken(
    GetRootViewAccessibilityTokenCallback callback) {
  std::vector<uint8_t> token;
  base::ProcessId pid;
  GetRootViewAccessibilityToken(&pid, &token);
  std::move(callback).Run(pid, token);
}

void NativeWidgetMacNSWindowHost::ValidateUserInterfaceItem(
    int32_t command,
    ValidateUserInterfaceItemCallback callback) {
  remote_cocoa::mojom::ValidateUserInterfaceItemResultPtr result;
  ValidateUserInterfaceItem(command, &result);
  std::move(callback).Run(std::move(result));
}

void NativeWidgetMacNSWindowHost::WillExecuteCommand(
    int32_t command,
    WindowOpenDisposition window_open_disposition,
    bool is_before_first_responder,
    ExecuteCommandCallback callback) {
  bool will_execute = false;
  WillExecuteCommand(command, window_open_disposition,
                     is_before_first_responder, &will_execute);
  std::move(callback).Run(will_execute);
}

void NativeWidgetMacNSWindowHost::ExecuteCommand(
    int32_t command,
    WindowOpenDisposition window_open_disposition,
    bool is_before_first_responder,
    ExecuteCommandCallback callback) {
  bool was_executed = false;
  ExecuteCommand(command, window_open_disposition, is_before_first_responder,
                 &was_executed);
  std::move(callback).Run(was_executed);
}

void NativeWidgetMacNSWindowHost::HandleAccelerator(
    const ui::Accelerator& accelerator,
    bool require_priority_handler,
    HandleAcceleratorCallback callback) {
  bool was_handled = false;
  HandleAccelerator(accelerator, require_priority_handler, &was_handled);
  std::move(callback).Run(was_handled);
}

////////////////////////////////////////////////////////////////////////////////
// NativeWidgetMacNSWindowHost, DialogObserver:

void NativeWidgetMacNSWindowHost::OnDialogChanged() {
  // Note it's only necessary to clear the TouchBar. If the OS needs it again,
  // a new one will be created.
  GetNSWindowMojo()->ClearTouchBar();
}

////////////////////////////////////////////////////////////////////////////////
// NativeWidgetMacNSWindowHost, AccessibilityFocusOverrider::Client:

id NativeWidgetMacNSWindowHost::GetAccessibilityFocusedUIElement() {
  return [GetNativeViewAccessible() accessibilityFocusedUIElement];
}

////////////////////////////////////////////////////////////////////////////////
// NativeWidgetMacNSWindowHost, LayerDelegate:

void NativeWidgetMacNSWindowHost::OnPaintLayer(
    const ui::PaintContext& context) {
  if (Widget* widget = GetWidget()) {
    widget->OnNativeWidgetPaint(context);
  }
}

void NativeWidgetMacNSWindowHost::OnDeviceScaleFactorChanged(
    float old_device_scale_factor,
    float new_device_scale_factor) {
  if (Widget* widget = GetWidget()) {
    widget->DeviceScaleFactorChanged(old_device_scale_factor,
                                     new_device_scale_factor);
  }
}

void NativeWidgetMacNSWindowHost::UpdateVisualState() {
  if (Widget* widget = GetWidget()) {
    widget->LayoutRootViewIfNecessary();
  }
}

////////////////////////////////////////////////////////////////////////////////
// NativeWidgetMacNSWindowHost, AcceleratedWidgetMac:

void NativeWidgetMacNSWindowHost::AcceleratedWidgetCALayerParamsUpdated() {
  if (const auto* ca_layer_params = compositor_->widget()->GetCALayerParams())
    GetNSWindowMojo()->SetCALayerParams(*ca_layer_params);
}

////////////////////////////////////////////////////////////////////////////////
// NativeWidgetMacNSWindowHost, ViewObserver:

void NativeWidgetMacNSWindowHost::OnViewIsDeleting(View* observed_view) {
  CHECK_EQ(observed_view, root_view_);
  DropRootViewReferences();
}

}  // namespace views