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

#import "components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h"

#import <AppKit/AppKit.h>
#include <Foundation/Foundation.h>
#include <Security/Security.h>
#import <SecurityInterface/SecurityInterface.h>
#import <objc/runtime.h>
#include <stddef.h>
#include <stdint.h>

#include <algorithm>
#include <cmath>
#include <memory>

#include "base/apple/bridging.h"
#import "base/apple/foundation_util.h"
#include "base/apple/scoped_cftyperef.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/mac/mac_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/no_destructor.h"
#include "base/strings/sys_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#import "components/remote_cocoa/app_shim/NSToolbar+Private.h"
#import "components/remote_cocoa/app_shim/bridged_content_view.h"
#import "components/remote_cocoa/app_shim/browser_native_widget_window_mac.h"
#import "components/remote_cocoa/app_shim/context_menu_runner.h"
#import "components/remote_cocoa/app_shim/mouse_capture.h"
#import "components/remote_cocoa/app_shim/native_widget_mac_frameless_nswindow.h"
#import "components/remote_cocoa/app_shim/native_widget_mac_nswindow.h"
#import "components/remote_cocoa/app_shim/native_widget_mac_overlay_nswindow.h"
#import "components/remote_cocoa/app_shim/native_widget_ns_window_host_helper.h"
#include "components/remote_cocoa/app_shim/select_file_dialog_bridge.h"
#import "components/remote_cocoa/app_shim/views_nswindow_delegate.h"
#import "components/remote_cocoa/app_shim/window_move_loop.h"
#include "components/remote_cocoa/common/native_widget_ns_window_host.mojom.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "net/cert/x509_util_apple.h"
#include "ui/accelerated_widget_mac/window_resize_helper_mac.h"
#include "ui/base/cocoa/animation_utils.h"
#import "ui/base/cocoa/constrained_window/constrained_window_animation.h"
#include "ui/base/cocoa/cursor_utils.h"
#include "ui/base/cocoa/remote_accessibility_api.h"
#import "ui/base/cocoa/window_size_constants.h"
#include "ui/base/emoji/emoji_panel_helper.h"
#include "ui/base/hit_test.h"
#include "ui/base/mojom/ui_base_types.mojom-shared.h"
#include "ui/base/ui_base_features.h"
#include "ui/base/ui_base_switches.h"
#include "ui/color/color_provider_key.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/events/cocoa/cocoa_event_utils.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/dip_util.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gfx/image/image_skia_util_mac.h"
#import "ui/gfx/mac/coordinate_conversion.h"
#import "ui/gfx/mac/menu_text_elider_mac.h"
#import "ui/gfx/mac/nswindow_frame_controls.h"
#include "ui/gfx/native_ui_types.h"

using remote_cocoa::mojom::VisibilityTransition;
using remote_cocoa::mojom::WindowVisibilityState;

// Undocumented API used to prevent a window region from being screen captured.
using CGSConnectionID = uint32_t;
using CGSWindowID = NSInteger;
using CGRegionRef = CFTypeRef;

CG_EXTERN CGSConnectionID CGSMainConnectionID(void);
CG_EXTERN CGError CGSSetWindowCaptureExcludeShape(CGSConnectionID cid,
                                                  CGSWindowID wid,
                                                  CGRegionRef region);
CG_EXTERN CGRegionRef CGRegionCreateWithRect(CGRect rect);

namespace {
constexpr auto kUIPaintTimeout = base::Milliseconds(500);

void ApplyCaptureExclusion(NSWindow* window, bool allow) {
  CGSConnectionID connection_id = CGSMainConnectionID();
  CGSWindowID window_id = window.windowNumber;
  CGRect frame = window.frame;
  frame.origin = CGPointZero;
  base::apple::ScopedCFTypeRef<CGRegionRef> region;
  if (!allow) {
    region.reset(CGRegionCreateWithRect(frame));
  }
  CGSSetWindowCaptureExcludeShape(connection_id, window_id, region.get());
}

// Returns the display that the specified window is on.
display::Display GetDisplayForWindow(NSWindow* window) {
  return display::Screen::Get()->GetDisplayNearestWindow(
      gfx::NativeWindow(window));
}

bool IsBackgroundEffectView(NSView* view) {
  if ([view isKindOfClass:[NSVisualEffectView class]]) {
    return true;
  }

  if (@available(macOS 26, *)) {
    return [view isKindOfClass:[NSGlassEffectView class]];
  }

  return false;
}

}  // namespace

@class NSWindowRestorationOptions;

// An NSKeyedUnarchiver that can optionally vend NSWindowRestorationOptions.
@interface RestorationOptionsKeyedUnarchiver : NSKeyedUnarchiver
@property BOOL returnRestorationOptions;
@end

@implementation RestorationOptionsKeyedUnarchiver

@synthesize returnRestorationOptions;

- (NSWindowRestorationOptions*)_windowRestorationOptions {
  // Returning a default-initialized NSWindowRestorationOptions from
  // _windowRestorationOptions is enough to get windows to restore on the spaces
  // whence they came.
  if (self.returnRestorationOptions) {
    return [[NSClassFromString(@"NSWindowRestorationOptions") alloc] init];
  }

  return nil;
}

@end

// The NSView that hosts the composited CALayer drawing the UI. It fills the
// window but is not hittable so that accessibility hit tests always go to the
// BridgedContentView.
@interface ViewsCompositorSuperview : NSView
@end

@implementation ViewsCompositorSuperview
- (NSView*)hitTest:(NSPoint)aPoint {
  return nil;
}
@end

// Self-owning animation delegate that starts a hide animation, then calls
// -[NSWindow close] when the animation ends, releasing itself.
@interface ViewsNSWindowCloseAnimator : NSObject <NSAnimationDelegate> {
 @private
  NSWindow* __strong _window;
  NSAnimation* __strong _animation;
}
+ (void)closeWindowWithAnimation:(NSWindow*)window;

- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithWindow:(NSWindow*)window NS_UNAVAILABLE;

@end

@implementation ViewsNSWindowCloseAnimator

- (instancetype)initWithWindow:(NSWindow*)window {
  if ((self = [super init])) {
    _window = window;
    _animation = [[ConstrainedWindowAnimationHide alloc] initWithWindow:window];
    [_animation setDelegate:self];
    [_animation setAnimationBlockingMode:NSAnimationNonblocking];
    [_animation startAnimation];
  }
  return self;
}

+ (NSMutableSet<ViewsNSWindowCloseAnimator*>*)allAnimators {
  static NSMutableSet<ViewsNSWindowCloseAnimator*>* set = [NSMutableSet set];
  return set;
}

+ (void)closeWindowWithAnimation:(NSWindow*)window {
  ViewsNSWindowCloseAnimator* animator =
      [[ViewsNSWindowCloseAnimator alloc] initWithWindow:window];
  if (animator) {
    [[ViewsNSWindowCloseAnimator allAnimators] addObject:animator];
  }
}

- (void)animationDidEnd:(NSAnimation*)animation {
  [_window close];
  [_animation setDelegate:nil];
  [[ViewsNSWindowCloseAnimator allAnimators]
      performSelector:@selector(removeObject:)
           withObject:self
           afterDelay:0];
}
@end

// This class overrides NSAnimation methods to invalidate the shadow for each
// frame. It is required because the show animation uses CGSSetWindowWarp()
// which is touchy about the consistency of the points it is given. The show
// animation includes a translate, which fails to apply properly to the window
// shadow, when that shadow is derived from a layer-hosting view. So invalidate
// it. This invalidation is only needed to cater for the translate. It is not
// required if CGSSetWindowWarp() is used in a way that keeps the center point
// of the window stationary (e.g. a scale). It's also not required for the hide
// animation: in that case, the shadow is never invalidated so retains the
// shadow calculated before a translate is applied.
@interface ModalShowAnimationWithLayer
    : ConstrainedWindowAnimationShow <NSAnimationDelegate>
@end

@implementation ModalShowAnimationWithLayer {
  // This is the "real" delegate, but this class acts as the NSAnimationDelegate
  // to avoid a separate object.
  raw_ptr<remote_cocoa::NativeWidgetNSWindowBridge> _bridgedNativeWidget;
}
- (instancetype)initWithBridgedNativeWidget:
    (remote_cocoa::NativeWidgetNSWindowBridge*)widget {
  if ((self = [super initWithWindow:widget->ns_window()])) {
    _bridgedNativeWidget = widget;
    CHECK(_bridgedNativeWidget);
    self.delegate = self;
  }
  return self;
}
- (void)dealloc {
  CHECK(!_bridgedNativeWidget);
}
- (void)animationDidEnd:(NSAnimation*)animation {
  CHECK(_bridgedNativeWidget);
  // The call to `OnShowAnimationComplete()` will immediately reset an owning
  // pointer of this object. Therefore, make sure all the invariants of the
  // `-dealloc` method above are satisfied now by moving the pointer value to be
  // local.
  remote_cocoa::NativeWidgetNSWindowBridge* bridgedNativeWidget =
      _bridgedNativeWidget;
  _bridgedNativeWidget = nullptr;
  bridgedNativeWidget->OnShowAnimationComplete();
  self.delegate = nil;
}
- (void)stopAnimation {
  [super stopAnimation];
  [self.window invalidateShadow];
}
- (void)setCurrentProgress:(NSAnimationProgress)progress {
  [super setCurrentProgress:progress];
  [self.window invalidateShadow];
}
@end

namespace remote_cocoa {

namespace {

using RankMap = std::map<NSView*, int>;

// Return the content size for a minimum or maximum widget size.
gfx::Size GetClientSizeForWindowSize(NSWindow* window,
                                     const gfx::Size& window_size) {
  NSRect frame_rect =
      NSMakeRect(0, 0, window_size.width(), window_size.height());
  // Note gfx::Size will prevent dimensions going negative. They are allowed to
  // be zero at this point, because Widget::GetMinimumSize() may later increase
  // the size.
  return gfx::Size([window contentRectForFrameRect:frame_rect].size);
}

NSComparisonResult SubviewSorter(__kindof NSView* lhs,
                                 __kindof NSView* rhs,
                                 void* rank_as_void) {
  DCHECK_NE(lhs, rhs);

  // Put background effect views before `ViewsCompositorSuperview`, otherwise
  // they can cover content displayed by the compositor.
  bool lhs_is_bg = IsBackgroundEffectView(lhs);
  bool rhs_is_bg = IsBackgroundEffectView(rhs);
  if (lhs_is_bg != rhs_is_bg) {
    return lhs_is_bg ? NSOrderedAscending : NSOrderedDescending;
  }
  bool lhs_is_comp = [lhs isKindOfClass:[ViewsCompositorSuperview class]];
  bool rhs_is_comp = [rhs isKindOfClass:[ViewsCompositorSuperview class]];
  if (lhs_is_comp != rhs_is_comp) {
    return lhs_is_comp ? NSOrderedAscending : NSOrderedDescending;
  }

  const RankMap* rank = static_cast<const RankMap*>(rank_as_void);
  auto left_rank = rank->find(lhs);
  auto right_rank = rank->find(rhs);
  bool left_found = left_rank != rank->end();
  bool right_found = right_rank != rank->end();

  // Sort unassociated views above associated views.
  if (left_found != right_found)
    return left_found ? NSOrderedAscending : NSOrderedDescending;

  if (left_found) {
    return left_rank->second < right_rank->second ? NSOrderedAscending
                                                  : NSOrderedDescending;
  }

  // If both are unassociated, consider that order is not important
  return NSOrderedSame;
}

// Counts windows managed by a NativeWidgetNSWindowBridge instance in the
// |child_windows| array ignoring the windows added by AppKit.
NSUInteger CountBridgedWindows(NSArray* child_windows) {
  NSUInteger count = 0;
  for (NSWindow* child in child_windows) {
    if ([[child delegate] isKindOfClass:[ViewsNSWindowDelegate class]]) {
      ++count;
    }
  }

  return count;
}

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

std::map<NSWindow*, std::u16string>& GetPendingWindowTitleMap() {
  static base::NoDestructor<std::map<NSWindow*, std::u16string>> map;
  return *map;
}

}  // namespace

// static
gfx::Size NativeWidgetNSWindowBridge::GetWindowSizeForClientSize(
    NSWindow* window,
    const gfx::Size& content_size) {
  NSRect content_rect =
      NSMakeRect(0, 0, content_size.width(), content_size.height());
  NSRect frame_rect = [window frameRectForContentRect:content_rect];
  return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect));
}

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

// static
NativeWidgetNSWindowBridge* NativeWidgetNSWindowBridge::GetFromNSWindow(
    NSWindow* window) {
  if (NativeWidgetMacNSWindow* widget_window =
          base::apple::ObjCCast<NativeWidgetMacNSWindow>(window)) {
    return GetFromId([widget_window bridgedNativeWidgetId]);
  }
  return nullptr;
}

// static
NativeWidgetMacNSWindow* NativeWidgetNSWindowBridge::CreateNSWindow(
    const mojom::CreateWindowParams* params) {
  NativeWidgetMacNSWindow* ns_window;
  switch (params->window_class) {
    case mojom::WindowClass::kDefault:
      ns_window = [[NativeWidgetMacNSWindow alloc]
          initWithContentRect:ui::kWindowSizeDeterminedLater
                    styleMask:params->style_mask
                      backing:NSBackingStoreBuffered
                        defer:NO];
      break;
    case mojom::WindowClass::kBrowser:
      ns_window = [[BrowserNativeWidgetWindow alloc]
          initWithContentRect:ui::kWindowSizeDeterminedLater
                    styleMask:params->style_mask
                      backing:NSBackingStoreBuffered
                        defer:NO];
      break;
    case mojom::WindowClass::kFrameless:
      ns_window = [[NativeWidgetMacFramelessNSWindow alloc]
          initWithContentRect:ui::kWindowSizeDeterminedLater
                    styleMask:params->style_mask
                      backing:NSBackingStoreBuffered
                        defer:NO];
      break;
    case mojom::WindowClass::kOverlay:
      ns_window = [[NativeWidgetMacOverlayNSWindow alloc]
          initWithContentRect:ui::kWindowSizeDeterminedLater
                    styleMask:params->style_mask
                      backing:NSBackingStoreBuffered
                        defer:NO];
      break;
  }
  ns_window.releasedWhenClosed = NO;

  if (params->titlebar_appears_transparent) {
    ns_window.titlebarAppearsTransparent = YES;
  }
  if (params->window_title_hidden) {
    ns_window.titleVisibility = NSWindowTitleHidden;
  }
  if (params->animation_enabled) {
    ns_window.animationBehavior = NSWindowAnimationBehaviorDocumentWindow;
  }

  return ns_window;
}

NativeWidgetNSWindowBridge::NativeWidgetNSWindowBridge(
    uint64_t bridged_native_widget_id,
    NativeWidgetNSWindowHost* host,
    NativeWidgetNSWindowHostHelper* host_helper,
    mojom::TextInputHost* text_input_host)
    : id_(bridged_native_widget_id),
      host_(host),
      host_helper_(host_helper),
      text_input_host_(text_input_host) {
  DCHECK(GetIdToWidgetImplMap().find(id_) == GetIdToWidgetImplMap().end());
  GetIdToWidgetImplMap().insert(std::make_pair(id_, this));
}

NativeWidgetNSWindowBridge::~NativeWidgetNSWindowBridge() {
  SetLocalEventMonitorEnabled(false);
  DCHECK(!local_event_monitor_);
  GetPendingWindowTitleMap().erase(window_);
  // The delegate should be cleared already. Note this enforces the precondition
  // that -[NSWindow close] is invoked on the hosted window before the
  // destructor is called.
  DCHECK(![window_ delegate]);
  DCHECK(child_windows_.empty());
  DestroyContentView();
}

void NativeWidgetNSWindowBridge::BindReceiver(
    mojo::PendingAssociatedReceiver<mojom::NativeWidgetNSWindow> receiver,
    base::OnceClosure connection_closed_callback) {
  bridge_mojo_receiver_.Bind(std::move(receiver),
                             ui::WindowResizeHelperMac::Get()->task_runner());
  bridge_mojo_receiver_.set_disconnect_handler(
      std::move(connection_closed_callback));
}

void NativeWidgetNSWindowBridge::SetWindow(NativeWidgetMacNSWindow* window) {
  DCHECK(!window_);
  window_delegate_ =
      [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this];
  window_ = window;
  window_.bridge = this;
  window_.bridgedNativeWidgetId = id_;
  window_.releasedWhenClosed = NO;
  window_.delegate = window_delegate_;
  ui::CATransactionCoordinator::Get().AddPreCommitObserver(this);
}

void NativeWidgetNSWindowBridge::SetCommandDispatcher(
    NSObject<CommandDispatcherDelegate>* delegate,
    id<UserInterfaceItemCommandHandler> command_handler) {
  window_command_dispatcher_delegate_ = delegate;
  [window_ setCommandDispatcherDelegate:delegate];
  [window_ setCommandHandler:command_handler];
}

void NativeWidgetNSWindowBridge::SetParent(uint64_t new_parent_id) {
  // Remove from the old parent.
  if (parent_) {
    parent_->RemoveChildWindow(this);
    parent_ = nullptr;
  }

  // Strip the managed/transient collection behavior bits; they will be reset
  // later.
  NSWindowCollectionBehavior collectionBehavior =
      window_.collectionBehavior & ~(NSWindowCollectionBehaviorManaged |
                                     NSWindowCollectionBehaviorTransient);

  // If `new_parent_id` is 0 or is invalid, leave the window as a top-level
  // window.
  //
  // Details: When the OS tells us a window is closing it is removed from the id
  // map. Since nothing is stopping the browser process from still trying to use
  // that id until the browser process has been informed that the window is
  // gone, it is totally possible to be passed no longer valid ids.
  NativeWidgetNSWindowBridge* new_parent =
      new_parent_id ? NativeWidgetNSWindowBridge::GetFromId(new_parent_id)
                    : nil;
  if (!new_parent_id || !new_parent) {
    // When a window doesn't have a parent, it should have normal managed
    // collection behavior.
    window_.collectionBehavior =
        collectionBehavior | NSWindowCollectionBehaviorManaged;
    return;
  }

  parent_ = new_parent;
  parent_->child_windows_.push_back(this);

  // When a window has a parent, it must not have a fixed Space, otherwise
  // Widget::ShowInactive() could result in a Space switch when the widget has a
  // parent, and we're calling -orderWindow:relativeTo:. Therefore, give it
  // transient collection behavior. See https://crbug.com/41305285.
  window_.collectionBehavior =
      collectionBehavior | NSWindowCollectionBehaviorTransient;

  if (wants_to_be_visible_) {
    parent_->OrderChildren();
  }
}

void NativeWidgetNSWindowBridge::CreateSelectFileDialog(
    mojo::PendingReceiver<mojom::SelectFileDialog> receiver) {
  mojo::MakeSelfOwnedReceiver(
      std::make_unique<remote_cocoa::SelectFileDialogBridge>(window_),
      std::move(receiver));
}

void NativeWidgetNSWindowBridge::ShowCertificateViewer(
    const scoped_refptr<net::X509Certificate>& certificate) {
  NSArray* cert_chain = base::apple::CFToNSOwnershipCast(
      net::x509_util::CreateSecCertificateArrayForX509Certificate(
          certificate.get())
          .release());
  if (!cert_chain) {
    return;
  }

  [[[SFCertificatePanel alloc] init] beginSheetForWindow:window_
                                           modalDelegate:nil
                                          didEndSelector:nil
                                             contextInfo:nil
                                            certificates:cert_chain
                                               showGroup:YES];
}

void NativeWidgetNSWindowBridge::StackAbove(uint64_t sibling_id) {
  NativeWidgetNSWindowBridge* sibling_bridge =
      NativeWidgetNSWindowBridge::GetFromId(sibling_id);
  if (!sibling_bridge) {
    // When the OS tells us a window is closing it is removed from the id map.
    // Since nothing is stopping the browser process from still trying to use
    // that id until the browser process has been informed that the window is
    // gone, it is totally possible to be passed no longer valid ids here.
    return;
  }

  NSInteger sibling = sibling_bridge->ns_window().windowNumber;
  [window_ orderWindowByShuffling:NSWindowAbove relativeTo:sibling];
}

void NativeWidgetNSWindowBridge::StackAtTop() {
  [window_ orderWindowByShuffling:NSWindowAbove relativeTo:0];
}

void NativeWidgetNSWindowBridge::ShowEmojiPanel() {
  ui::ShowEmojiPanel();
}

void NativeWidgetNSWindowBridge::CreateWindow(
    mojom::CreateWindowParamsPtr params) {
  SetWindow(CreateNSWindow(params.get()));
  if (window_set_callback_) {
    std::move(window_set_callback_).Run(ns_window());
  }
}

void NativeWidgetNSWindowBridge::OnWindowSetForTesting(  // IN-TEST
    base::OnceCallback<void(NativeWidgetMacNSWindow*)> callback) {
  window_set_callback_ = std::move(callback);
}

void NativeWidgetNSWindowBridge::InitWindow(
    mojom::NativeWidgetNSWindowInitParamsPtr params) {
  modal_type_ = params->modal_type;
  is_translucent_window_ = params->is_translucent;
  pending_restoration_data_ = params->state_restoration_data.Clone();

  if (display::Screen::Get()->IsHeadless()) {
    [window_ setIsHeadless:YES];
  }

  // Register for application hide notifications so that visibility can be
  // properly tracked. This is not done in the delegate so that the lifetime is
  // tied to the C++ object, rather than the delegate (which may be reference
  // counted). This is required since the application hides do not send an
  // orderOut: to individual windows. Unhide, however, does send an order
  // message.
  [[NSNotificationCenter defaultCenter]
      addObserver:window_delegate_
         selector:@selector(onWindowOrderChanged:)
             name:NSApplicationDidHideNotification
           object:nil];

  [[NSNotificationCenter defaultCenter]
      addObserver:window_delegate_
         selector:@selector(onSystemColorsChanged:)
             name:NSSystemColorsDidChangeNotification
           object:nil];

  [NSWorkspace.sharedWorkspace.notificationCenter
      addObserver:window_delegate_
         selector:@selector(onActiveSpaceChanged:)
             name:NSWorkspaceActiveSpaceDidChangeNotification
           object:nil];

  // Force update on initialization because the notification won't send
  // until the active space changes.
  OnSpaceActivationMayHaveChanged();

  // Validate the window's initial state, otherwise the bridge's initial
  // tracking state will be incorrect.
  DCHECK(![window_ isVisible]);
  DCHECK_EQ(0u, [window_ styleMask] & NSWindowStyleMaskFullScreen);

  // Include "regular" windows without the standard frame in the window cycle.
  // These use NSWindowStyleMaskBorderless so do not get it by default.
  if (params->force_into_collection_cycle) {
    [window_
        setCollectionBehavior:[window_ collectionBehavior] |
                              NSWindowCollectionBehaviorParticipatesInCycle];
  }

  [window_ setHasShadow:params->has_window_server_shadow];

  // Don't allow dragging sheets.
  if (params->modal_type == ui::mojom::ModalType::kWindow) {
    [window_ setMovable:NO];
  }
  [window_ setIsTooltip:params->is_tooltip];
  CheckAndNotifyAllWorkspacesStateChanged();
}

void NativeWidgetNSWindowBridge::SetInitialBounds(
    const gfx::Rect& new_bounds,
    const gfx::Size& minimum_content_size) {
  gfx::Rect adjusted_bounds = new_bounds;
  if (new_bounds.IsEmpty()) {
    // If a position is set, but no size, complain. Otherwise, a 1x1 window
    // would appear there, which might be unexpected.
    DCHECK(new_bounds.origin().IsOrigin())
        << "Zero-sized windows not supported on Mac.";

    // Otherwise, bounds is all zeroes. Cocoa will currently have the window at
    // the bottom left of the screen. To support a client calling SetSize() only
    // (and for consistency across platforms) put it at the top-left instead.
    // Read back the current frame: it will be a 1x1 context rect but the frame
    // size also depends on the window style.
    NSRect frame_rect = [window_ frame];
    adjusted_bounds = gfx::Rect(
        gfx::Point(), gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect)));
  }
  SetBounds(adjusted_bounds, minimum_content_size, std::nullopt);
}

void NativeWidgetNSWindowBridge::SetBounds(
    const gfx::Rect& new_bounds,
    const gfx::Size& minimum_content_size,
    const std::optional<gfx::Size>& maximum_content_size) {
  // Ensure that any changes to the window frame be atomic with the updates to
  // their content.
  if (!ca_transaction_sync_suppressed_ &&
      base::FeatureList::IsEnabled(features::kCATransactionV2) &&
      !base::FeatureList::IsEnabled(features::kAsyncLiveResize)) {
    ui::CATransactionCoordinator::Get().Synchronize();
  }

  // Discard any pending live resizes.
  live_resize_.pending_window_frame = std::nullopt;
  live_resize_.queued_pending_window_frame = std::nullopt;

  // -[NSWindow contentMinSize] and [NSWindow contentMaxSize] are only checked
  // by Cocoa for user-initiated resizes. This is not what toolkit-views
  // expects, so clamp.
  gfx::Size clamped_content_size =
      GetClientSizeForWindowSize(window_, new_bounds.size());
  clamped_content_size.SetToMax(minimum_content_size);

  if (maximum_content_size.has_value()) {
    clamped_content_size.SetToMin(*maximum_content_size);
  }

  // A contentRect with zero width or height is a banned practice in ChromeMac,
  // due to unpredictable macOS treatment.
  DCHECK(!clamped_content_size.IsEmpty())
      << "Zero-sized windows not supported on Mac";

  if (!window_visible_ && IsWindowModalSheet()) {
    // Window-Modal dialogs (i.e. sheets) are positioned by Cocoa when shown for
    // the first time. They also have no frame, so just update the content size.
    [window_ setContentSize:NSMakeSize(clamped_content_size.width(),
                                       clamped_content_size.height())];
    return;
  }
  gfx::Rect actual_new_bounds(
      new_bounds.origin(),
      GetWindowSizeForClientSize(window_, clamped_content_size));

  NSScreen* previous_screen = [window_ screen];

  [window_ setFrame:gfx::ScreenRectToNSRect(actual_new_bounds)
            display:YES
            animate:NO];

  if (window_move_loop_) {
    // If the window bounds are updated programmatically during an active move
    // loop (e.g. by TabDragController when crossing display boundaries),
    // update the move loop baseline so subsequent mouse drag events calculate
    // deltas against the new frame rather than the stale initial frame.
    window_move_loop_->SetBaseFrame([window_ frame], [NSEvent mouseLocation]);
  }

  // If the window has focus but is not on the active space and the window was
  // moved to a different display, re-activate it to switch the space to the
  // active window. (crbug.com/1316543)
  if ([window_ isKeyWindow] && ![window_ isOnActiveSpace] &&
      [window_ screen] != previous_screen) {
    SetVisibilityState(WindowVisibilityState::kShowAndActivateWindow);
  }
}

void NativeWidgetNSWindowBridge::SetSize(
    const gfx::Size& new_size,
    const gfx::Size& minimum_content_size) {
  // Ensure the top-left corner stays in-place (rather than the bottom-left,
  // which -[NSWindow setContentSize:] would do).
  gfx::Rect new_window_bounds = gfx::ScreenRectFromNSRect([window_ frame]);
  new_window_bounds.set_size(new_size);
  SetBounds(new_window_bounds, minimum_content_size, std::nullopt);
}

void NativeWidgetNSWindowBridge::SetSizeAndCenter(
    const gfx::Size& content_size,
    const gfx::Size& minimum_content_size) {
  gfx::Rect new_window_bounds = gfx::ScreenRectFromNSRect([window_ frame]);
  new_window_bounds.set_size(GetWindowSizeForClientSize(window_, content_size));
  SetBounds(new_window_bounds, minimum_content_size, std::nullopt);

  if (!parent_) {
    // Note that this is not the precise center of screen, but it is the
    // standard location for windows like dialogs to appear on screen for Mac.
    [window_ center];
  } else {
    NSWindow* parent_window = parent_->ns_view().window;
    NSRect parent_rect = [parent_window frame];
    NSRect window_rect = [window_ frame];

    // Clamp to the visible area of the screen where the parent window resides.
    // This ensures the window stays within viewable bounds and respects
    // the menu bar and dock.
    NSScreen* screen = [parent_window screen];
    if (screen) {
      NSRect visible_frame = [screen visibleFrame];
      parent_rect = NSIntersectionRect(parent_rect, visible_frame);

      // Calculate the centered position relative to the parent's visible area.
      CGFloat x = NSMidX(parent_rect) - window_rect.size.width / 2;
      CGFloat y = NSMidY(parent_rect) - window_rect.size.height / 2;

      // Ensure the child window stays completely within the visible frame,
      // even if the parent's visible area is smaller than the child window.
      x = std::max(x, NSMinX(visible_frame));
      x = std::min(x, NSMaxX(visible_frame) - window_rect.size.width);

      y = std::max(y, NSMinY(visible_frame));
      y = std::min(y, NSMaxY(visible_frame) - window_rect.size.height);

      [window_ setFrameOrigin:NSMakePoint(x, y)];
    } else {
      // Fallback if no screen is available.
      [window_ center];
    }
  }
}

void NativeWidgetNSWindowBridge::DestroyContentView() {
  if (!bridged_view_)
    return;
  [bridged_view_ clearView];
  bridged_view_id_mapping_.reset();
  bridged_view_ = nil;
  [window_ setContentView:nil];
}

void NativeWidgetNSWindowBridge::CreateContentView(
    uint64_t ns_view_id,
    const gfx::Rect& bounds,
    std::optional<int> corner_radius) {
  DCHECK(!bridged_view_);

  bridged_view_ = [[BridgedContentView alloc] initWithBridge:this
                                                      bounds:bounds];
  bridged_view_id_mapping_ =
      std::make_unique<ScopedNSViewIdMapping>(ns_view_id, bridged_view_);

  // Objective C initializers can return nil. However, if |view| is non-NULL
  // this should be treated as an error and caught early.
  CHECK(bridged_view_);

  // Send the accessibility tokens for the NSView now that it exists.
  host_->SetRemoteAccessibilityTokens(
      ui::RemoteAccessibility::GetTokenForLocalElement(window_),
      ui::RemoteAccessibility::GetTokenForLocalElement(bridged_view_));

  // Beware: This view was briefly removed (in favor of a bare CALayer) in
  // https://crrev.com/c/1236675. The ordering of unassociated layers relative
  // to NSView layers is undefined on macOS 10.12 and earlier, so the compositor
  // layer ended up covering up subviews (see https://crbug.com/899499).
  NSView* compositor_view =
      [[ViewsCompositorSuperview alloc] initWithFrame:[bridged_view_ bounds]];
  [compositor_view
      setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
  auto* background_layer = [CALayer layer];
  display_ca_layer_tree_ =
      std::make_unique<ui::DisplayCALayerTree>(background_layer);
  [compositor_view setLayer:background_layer];
  [compositor_view setWantsLayer:YES];
  [bridged_view_ addSubview:compositor_view];

  [bridged_view_ setWantsLayer:YES];
  if (corner_radius) {
    bridged_view_.layer.cornerRadius = *corner_radius;
    bridged_view_.layer.masksToBounds = YES;
  }

  [window_ setContentView:bridged_view_];
}

void NativeWidgetNSWindowBridge::CloseWindow() {
  if (fullscreen_controller_.HasDeferredWindowClose())
    return;

  // Make a local variable of the window on the stack so that the block can
  // capture a reference to it.
  NSWindow* window = ns_window();

  if (IsWindowModalSheet() && window.sheet) {
    // Sheets can't be closed normally. This starts the sheet closing. Once the
    // sheet has finished animating, it will call the end-sheet block defined
    // when the sheet was displayed. Note it still needs to be asynchronous,
    // since code calling Widget::Close() doesn't expect things to be deleted
    // upon return. Ensure |window| is retained by a block. Note in some cases
    // during teardown, [window sheetParent] may be nil.
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(^{
          [NSApp endSheet:window];
        }));
    return;
  }

  // For other modal types, animate the close.
  if (ShouldRunCustomAnimationFor(VisibilityTransition::kHide) &&
      [ns_window() isVisible]) {
    [ViewsNSWindowCloseAnimator closeWindowWithAnimation:window];
    return;
  }

  // If the window wants to be visible and has a parent, then the parent may
  // order it back in (in the period between orderOut: and close).
  wants_to_be_visible_ = false;

  // Widget::Close() ensures [Non]ClientView::CanClose() returns true, so there
  // is no need to call the NSWindow or its delegate's -windowShouldClose:
  // implementation in the manner of -[NSWindow performClose:]. But,
  // like -performClose:, first remove the window from AppKit's display
  // list to avoid crashes like http://crbug.com/156101.
  [window orderOut:nil];

  // Defer closing windows until after fullscreen transitions complete.
  fullscreen_controller_.OnWindowWantsToClose();
  if (fullscreen_controller_.HasDeferredWindowClose())
    return;

  // Many tests assume that base::RunLoop().RunUntilIdle() is always sufficient
  // to execute a close. However, in rare cases, -performSelector:..afterDelay:0
  // does not do this. So post a regular task.
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
                                                              base::BindOnce(^{
                                                                [window close];
                                                              }));
}

void NativeWidgetNSWindowBridge::CloseWindowNow() {
  // NSWindows must be retained until -[NSWindow close] returns.
  NS_VALID_UNTIL_END_OF_SCOPE NSWindow* window_retain = window_;

  // If there's a bridge at this point, it means there must be a window as well.
  DCHECK(window_);
  [window_ close];
  // Note: |this| will be deleted here.
}

void NativeWidgetNSWindowBridge::SetVisibilityState(
    WindowVisibilityState new_state) {
  // During session restore this method gets called from RestoreTabsToBrowser()
  // with new_state = kShowAndActivateWindow. We consume restoration data on our
  // first time through this method so we can use its existence as an
  // indication that session restoration is underway. We'll use this later to
  // decide whether or not to actually honor the WindowVisibilityState change
  // request. This window may live in the dock, for example, in which case we
  // don't really want to kShowAndActivateWindow. Even if the window is on the
  // desktop we still won't want to kShowAndActivateWindow because doing so
  // might trigger a transition to a different space (and we don't want to
  // switch spaces on start-up). When session restore determines the Active
  // window it will also call SetVisibilityState(), on that pass the window
  // can/will be activated.
  bool session_restore_in_progress = false;

  // Restore Cocoa window state.
  if (pending_restoration_data_) {
    NSData* restore_ns_data = [NSData
        dataWithBytes:pending_restoration_data_->appkit_restoration_data.data()
               length:pending_restoration_data_->appkit_restoration_data
                          .size()];
    RestorationOptionsKeyedUnarchiver* decoder =
        [[RestorationOptionsKeyedUnarchiver alloc]
            initForReadingFromData:restore_ns_data
                             error:nil];

    // Standard macOS behavior is: if a user quits and relaunches an app, window
    // restoration code puts all restored windows onto the primary space. If the
    // system quits and relaunches an app during, say, a system restart, window
    // restoration code puts the restored windows back onto the spaces whence
    // they came.
    //
    // Chromium wants to do the same for its restarts: if it was restarted
    // (rather than just quit), it wants to return windows to their spaces.
    // There is no API to do this (FB22128442), so two different approaches are
    // used.
    //
    // Prior to macOS 15, the defaults key NSWindowRestoresWorkspaceAtLaunch is
    // used. That triggers window restoration code to put windows back.
    //
    // NSWindowRestoresWorkspaceAtLaunch stopped working in macOS 15
    // (FB15644170). However, if the decoder passed to -restoreStateWithCoder:
    // has a method _windowRestorationOptions which returns a
    // default-initialized NSWindowRestorationOptions object, that will cause
    // the window restoration code to put windows back.
    //
    // Note that, in theory, -restoreStateWithCoder: shouldn't be used at all;
    // the NSWindowRestoration API should be used (https://crbug.com/376834368).
    // However, that API is insufficiently flexible enough for Chromium's usage
    // (FB22128526) so it is not used. When that API is revised so that it is
    // flexible enough, Chromium should switch to it.

    if (base::mac::MacOSMajorVersion() >= 15) {
      decoder.returnRestorationOptions =
          pending_restoration_data_->restore_space;
    } else if (pending_restoration_data_->restore_space) {
      [NSUserDefaults.standardUserDefaults registerDefaults:@{
        @"NSWindowRestoresWorkspaceAtLaunch" : @YES
      }];
    }

    [window_ restoreStateWithCoder:decoder];

    pending_restoration_data_.reset();

    session_restore_in_progress = true;
  }

  // Ensure that:
  //  - A window with an invisible parent is not made visible.
  //  - A parent changing visibility updates child window visibility.
  //    * But only when changed via this function - ignore changes via the
  //      NSWindow API, or changes propagating out from here.
  wants_to_be_visible_ = new_state != WindowVisibilityState::kHideWindow &&
                         new_state != WindowVisibilityState::kMiniaturizeWindow;

  [show_animation_ stopAnimation];  // If set, calls OnShowAnimationComplete().
  CHECK(!show_animation_);

  if (new_state == WindowVisibilityState::kHideWindow) {
    // Calling -orderOut: on a window with an attached sheet encounters broken
    // AppKit behavior. The sheet effectively becomes "lost".
    // See http://crbug.com/667602. Alternatives: call -setAlphaValue:0 and
    // -setIgnoresMouseEvents:YES on the NSWindow, or dismiss the sheet before
    // hiding.
    //
    // TODO(ellyjones): Sort this entire situation out. This DCHECK doesn't
    // trigger in shipped builds, but it does trigger when the browser exits
    // "abnormally" (not via one of the UI paths to exiting), such as in browser
    // tests, so this breaks a slew of browser tests in MacViews mode. See also
    // https://crbug.com/834926.
    // DCHECK(![window_ attachedSheet]);

    [window_ orderOut:nil];
    DCHECK(!window_visible_);
    return;
  } else if (new_state == WindowVisibilityState::kMiniaturizeWindow) {
    [window_ miniaturize:nil];
    return;
  }

  DCHECK(wants_to_be_visible_);

  if (!ca_transaction_sync_suppressed_) {
    if (base::FeatureList::IsEnabled(features::kAlphaInsteadOfCATransaction)) {
      // If the window isn't visible and a compositor frame for the window size
      // hasn't come in yet, keep the window effectively invisible via
      // `alphaValue`. It will be made visible when a correctly sized compositor
      // frame arrives.
      if (!window_.visible && compositor_frame_dip_size_ != content_dip_size_) {
        if (!pending_alpha_value_.has_value()) {
          pending_alpha_value_ = window_.alphaValue;
        }
        window_.alphaValue = 0.0;
      }
    } else {
      ui::CATransactionCoordinator::Get().Synchronize();
    }
  }

  // If the parent (or an ancestor) is hidden, return and wait for it to become
  // visible.
  for (auto* ancestor = parent_.get(); ancestor; ancestor = ancestor->parent_) {
    if (!ancestor->window_visible_)
      return;
  }

  // Don't activate a window during session restore, to avoid switching spaces
  // (or pulling it out of the dock) during startup.
  if (session_restore_in_progress &&
      new_state == WindowVisibilityState::kShowAndActivateWindow) {
    new_state = WindowVisibilityState::kShowInactive;
  }

  if (IsWindowModalSheet()) {
    ShowAsModalSheet();
    return;
  }

  if (parent_)
    parent_->OrderChildren();

  if (new_state == WindowVisibilityState::kShowAndActivateWindow) {
    // Activate the app before `makeKeyAndOrderFront:`. If the order is
    // reversed while the app is inactive, AppKit will not send
    // NSWindowDidBecomeKeyNotification, causing Widget::IsActive() to
    // incorrectly return false.
    if (![window_ activationIndependence]) {
      [NSApp activateIgnoringOtherApps:YES];
    }
    [window_ makeKeyAndOrderFront:nil];
  } else if (new_state == WindowVisibilityState::kShowInactive && !parent_ &&
             ![window_ isMiniaturized]) {
    if ([[NSApp mainWindow] screen] == [window_ screen] ||
        ![[NSApp mainWindow] isKeyWindow]) {
      // When the new window is on the same display as the main window or the
      // main window is inactive, order the window relative to the main window.
      // Avoid making it the front window (with e.g. orderFront:), which can
      // cause a space switch.
      [window_ orderWindow:NSWindowBelow
                relativeTo:NSApp.mainWindow.windowNumber];
    } else {
      // When opening an inactive window on another screen, put the window at
      // the front and trigger a space switch.
      [window_ orderFrontKeepWindowKeyState];
    }
  }

  // For non-sheet modal types, use the constrained window animations to make
  // the window appear.
  if (ShouldRunCustomAnimationFor(VisibilityTransition::kShow)) {
    show_animation_ =
        [[ModalShowAnimationWithLayer alloc] initWithBridgedNativeWidget:this];

    // The default mode is blocking, which would block the UI thread for the
    // duration of the animation, but would keep it smooth. The window also
    // hasn't yet received a frame from the compositor at this stage, so it is
    // fully transparent until the GPU sends a frame swap IPC. For the blocking
    // option, the animation needs to wait until
    // AcceleratedWidgetCALayerParamsUpdated has been called at least once,
    // otherwise it will animate nothing.
    [show_animation_ setAnimationBlockingMode:NSAnimationNonblocking];
    [show_animation_ startAnimation];
  }
}

void NativeWidgetNSWindowBridge::SetTransitionsToAnimate(
    VisibilityTransition transitions) {
  // TODO(tapted): Use scoping to disable native animations at appropriate
  // times as well.
  transitions_to_animate_ = transitions;
}

void NativeWidgetNSWindowBridge::AcquireCapture() {
  if (HasCapture())
    return;
  if (!window_visible_)
    return;  // Capture on hidden windows is disallowed.

  mouse_capture_ = std::make_unique<CocoaMouseCapture>(this);
  host_->OnMouseCaptureActiveChanged(true);

  // Initiating global event capture with addGlobalMonitorForEventsMatchingMask:
  // will reset the mouse cursor to an arrow. Asking the window for an update
  // here will restore what we want. However, it can sometimes cause the cursor
  // to flicker, once, on the initial mouseDown.
  // TODO(tapted): Make this unnecessary by only asking for global mouse capture
  // for the cases that need it (e.g. menus, but not drag and drop).
  [window_ cursorUpdate:[NSApp currentEvent]];
}

void NativeWidgetNSWindowBridge::ReleaseCapture() {
  mouse_capture_.reset();
}

bool NativeWidgetNSWindowBridge::HasCapture() {
  return mouse_capture_ && mouse_capture_->IsActive();
}

void NativeWidgetNSWindowBridge::SetLocalEventMonitorEnabled(bool enabled) {
  if (enabled) {
    // Create the event monitor if it does not exist yet.
    if (local_event_monitor_) {
      return;
    }

    base::WeakPtr<NativeWidgetNSWindowBridge> weak_ptr = factory_.GetWeakPtr();

    auto block = ^NSEvent*(NSEvent* event) {
      if (!weak_ptr) {
        return event;
      }

      std::unique_ptr<ui::Event> ui_event =
          ui::EventFromNative(base::apple::OwnedNSEvent(event));
      bool event_handled = false;
      if (ui_event && ui_event->type() != ui::EventType::kUnknown) {
        // Pass up whether or not the event is for this specific window. This
        // allows consumers to filter events that are not for this window.
        bool target_is_this_window = event.window == weak_ptr->window_;
        weak_ptr->host_->DispatchMonitorEvent(
            std::move(ui_event), target_is_this_window, &event_handled);
      }

      return event_handled ? nil : event;
    };
    local_event_monitor_ =
        [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskAny
                                              handler:block];
  } else {
    // Destroy the event monitor if it exists.
    if (!local_event_monitor_) {
      return;
    }

    [NSEvent removeMonitor:local_event_monitor_];
    local_event_monitor_ = nil;
  }
}

bool NativeWidgetNSWindowBridge::HasWindowRestorationData() {
  return !pending_restoration_data_.is_null();
}

void NativeWidgetNSWindowBridge::RestoreCollectionBehavior() {
  if (collection_behavior_to_restore_.has_value() && window_) {
    window_.collectionBehavior = *collection_behavior_to_restore_;
    collection_behavior_to_restore_.reset();
  }
}

bool NativeWidgetNSWindowBridge::RunMoveLoop(const gfx::Vector2d& drag_offset) {
  // https://crbug.com/876493
  CHECK(!HasCapture());
  // Does some *other* widget have capture?
  CHECK(!CocoaMouseCapture::GetGlobalCaptureWindow());
  CHECK(!window_move_loop_);

  // RunMoveLoop caller is responsible for updating the window to be under the
  // mouse, but it does this using possibly outdated coordinate from the mouse
  // event, and mouse is very likely moved beyond that point.

  // Compensate for mouse drift by shifting the initial mouse position we pass
  // to CocoaWindowMoveLoop, so as it handles incoming move events the window's
  // top left corner will be |drag_offset| from the current mouse position.

  const gfx::Rect frame = gfx::ScreenRectFromNSRect([window_ frame]);
  const gfx::Point mouse_in_screen(frame.x() + drag_offset.x(),
                                   frame.y() + drag_offset.y());
  window_move_loop_ = std::make_unique<CocoaWindowMoveLoop>(
      this, gfx::ScreenPointToNSPoint(mouse_in_screen));

  return window_move_loop_->Run();

  // |this| may be destroyed during the RunLoop, causing it to exit early.
  // Even if that doesn't happen, CocoaWindowMoveLoop will clean itself up by
  // calling EndMoveLoop(). So window_move_loop_ will always be null before the
  // function returns. But don't DCHECK since |this| might not be valid.
}

void NativeWidgetNSWindowBridge::EndMoveLoop() {
  DCHECK(window_move_loop_);
  window_move_loop_->End();
  window_move_loop_.reset();
}

void NativeWidgetNSWindowBridge::SetWindowMoveLoopForTesting(
    std::unique_ptr<CocoaWindowMoveLoop> move_loop) {
  window_move_loop_ = std::move(move_loop);
}

void NativeWidgetNSWindowBridge::SetCursor(NSCursor* cursor) {
  [window_delegate_ setCursor:cursor];
}

void NativeWidgetNSWindowBridge::SetCursor(const ui::Cursor& cursor) {
  SetCursor(ui::GetNativeCursor(cursor));
}

void NativeWidgetNSWindowBridge::EnableImmersiveFullscreen(
    uint64_t fullscreen_overlay_widget_id,
    uint64_t tab_widget_id) {
  NativeWidgetNSWindowBridge* tab_widget_bridge = GetFromId(tab_widget_id);
  if (tab_widget_bridge) {
    NSWindow* tab_window = tab_widget_bridge->ns_window();
    immersive_mode_controller_ =
        std::make_unique<ImmersiveModeTabbedControllerCocoa>(
            ns_window(), GetFromId(fullscreen_overlay_widget_id)->ns_window(),
            tab_window);
  } else {
    immersive_mode_controller_ = std::make_unique<ImmersiveModeControllerCocoa>(
        ns_window(), GetFromId(fullscreen_overlay_widget_id)->ns_window());
  }
  immersive_mode_controller_->Init();

  // It is possible for the fullscreen transition to complete before the
  // immersive mode controller is created. Mark the transition as complete as
  // needed here.
  if (!fullscreen_controller_.IsInFullscreenTransition() &&
      fullscreen_controller_.GetTargetFullscreenState()) {
    immersive_mode_controller_->FullscreenTransitionCompleted();
  }

  // Reveal locks can outlive immersive_mode_controller_, re-establish any
  // outstanding locks.
  for (int i = 0; i < immersive_fullscreen_reveal_lock_count_; ++i) {
    immersive_mode_controller_->RevealLock();
  }
}

void NativeWidgetNSWindowBridge::DisableImmersiveFullscreen() {
  immersive_mode_controller_.reset();
}

void NativeWidgetNSWindowBridge::UpdateToolbarVisibility(
    remote_cocoa::mojom::ToolbarVisibilityStyle style) {
  if (immersive_mode_controller_) {
    immersive_mode_controller_->UpdateToolbarVisibility(style);
  }
}

void NativeWidgetNSWindowBridge::OnTopContainerViewBoundsChanged(
    const gfx::Rect& bounds) {
  if (immersive_mode_controller_) {
    immersive_mode_controller_->OnTopViewBoundsChanged(bounds);
  }
}

void NativeWidgetNSWindowBridge::ImmersiveFullscreenRevealLock() {
  ++immersive_fullscreen_reveal_lock_count_;
  if (immersive_mode_controller_) {
    immersive_mode_controller_->RevealLock();
  }
}

void NativeWidgetNSWindowBridge::ImmersiveFullscreenRevealUnlock() {
  --immersive_fullscreen_reveal_lock_count_;
  DCHECK(immersive_fullscreen_reveal_lock_count_ >= 0);
  if (immersive_mode_controller_) {
    immersive_mode_controller_->RevealUnlock();
  }
}

bool NativeWidgetNSWindowBridge::ShouldUseCustomTitlebarHeightForFullscreen()
    const {
  return immersive_mode_controller_ &&
         immersive_mode_controller_->is_initialized() &&
         immersive_mode_controller_->IsTabbed() &&
         !immersive_mode_controller_->IsContentFullscreen();
}

void NativeWidgetNSWindowBridge::OnImmersiveFullscreenToolbarRevealChanged(
    bool is_revealed) {
  host_->OnImmersiveFullscreenToolbarRevealChanged(is_revealed);
}

void NativeWidgetNSWindowBridge::OnImmersiveFullscreenMenuBarRevealChanged(
    float reveal_amount) {
  host_->OnImmersiveFullscreenMenuBarRevealChanged(reveal_amount);
}

void NativeWidgetNSWindowBridge::OnAutohidingMenuBarHeightChanged(
    int menu_bar_height) {
  host_->OnAutohidingMenuBarHeightChanged(menu_bar_height);
}

base::WeakPtr<NativeWidgetNSWindowBridge>
NativeWidgetNSWindowBridge::GetWeakPtr() {
  return factory_.GetWeakPtr();
}

void NativeWidgetNSWindowBridge::SetCanGoBack(bool can_go_back) {
  can_go_back_ = can_go_back;
}

void NativeWidgetNSWindowBridge::SetCanGoForward(bool can_go_forward) {
  can_go_forward_ = can_go_forward;
}

void NativeWidgetNSWindowBridge::DisplayContextMenu(
    mojom::ContextMenuPtr menu,
    mojo::PendingRemote<mojom::MenuHost> host,
    mojo::PendingReceiver<mojom::Menu> receiver) {
  ContextMenuRunner runner(std::move(host), std::move(receiver));
  NSView* target_view = GetNSViewFromId(menu->target_view_id);
  runner.ShowMenu(std::move(menu), GetWindow(), target_view);
}

void NativeWidgetNSWindowBridge::SetAllowScreenshots(bool allow) {
  allow_screenshots_ = allow;
  if (capture_exclusion_applier_for_testing_) {
    capture_exclusion_applier_for_testing_.Run(ns_window(), allow);
  } else {
    ApplyCaptureExclusion(ns_window(), allow);
  }
}

void NativeWidgetNSWindowBridge::SetColorMode(
    ui::ColorProviderKey::ColorMode color_mode) {
  NSAppearance* appearance =
      color_mode == ui::ColorProviderKey::ColorMode::kLight
          ? [NSAppearance appearanceNamed:NSAppearanceNameAqua]
          : [NSAppearance appearanceNamed:NSAppearanceNameDarkAqua];
  [window_ setAppearance:appearance];
}

void NativeWidgetNSWindowBridge::BeginFileDrag(
    mojom::FileDragDataPtr file_drag_data,
    const gfx::PointF& mouse_location) {
  if (!window_ || !bridged_view_) {
    DLOG(WARNING) << "BeginFileDrag failed: window or bridged_view is null";
    return;
  }

  NSURL* file_url = base::apple::FilePathToNSURL(file_drag_data->file_path);
  if (!file_url) {
    return;
  }

  NSDraggingItem* file_item =
      [[NSDraggingItem alloc] initWithPasteboardWriter:file_url];

  // Align to backing pixels for Retina displays.
  NSPoint mouse_point = NSMakePoint(mouse_location.x(), mouse_location.y());
  NSPoint current_position =
      [bridged_view_
          backingAlignedRect:NSMakeRect(mouse_point.x, mouse_point.y, 0, 0)
                     options:NSAlignAllEdgesOutward]
          .origin;

  if (!file_drag_data->drag_image.isNull()) {
    NSImage* image = gfx::NSImageFromImageSkia(file_drag_data->drag_image);
    NSSize image_size = image.size;
    gfx::Vector2d offset = file_drag_data->image_offset;
    NSRect image_rect = NSMakeRect(current_position.x - offset.x(),
                                   current_position.y - offset.y(),
                                   image_size.width, image_size.height);
    [file_item setDraggingFrame:image_rect contents:image];
  } else {
    // 16x16 placeholder corresponding to IconLoader::IconSize::SMALL.
    NSRect placeholder_rect =
        NSMakeRect(current_position.x - 8, current_position.y - 8, 16, 16);
    [file_item setDraggingFrame:placeholder_rect contents:nil];
  }

  // Synthesize a drag event
  NSEvent* dragEvent = [NSEvent mouseEventWithType:NSEventTypeLeftMouseDragged
                                          location:current_position
                                     modifierFlags:0
                                         timestamp:NSApp.currentEvent.timestamp
                                      windowNumber:window_.windowNumber
                                           context:nil
                                       eventNumber:0
                                        clickCount:1
                                          pressure:1.0];

  [bridged_view_ beginDraggingSessionWithItems:@[ file_item ]
                                         event:dragEvent
                                        source:bridged_view_];
}

void NativeWidgetNSWindowBridge::OnWindowWillClose() {
  fullscreen_controller_.OnWindowWillClose();
  // Immersive full screen needs to be disabled synchronously when the window
  // is closing. So disable it right away, rather than waiting for the browser
  // process to signal us to disable immersive fullscreen after being informed
  // of the window closing.
  DisableImmersiveFullscreen();

  [window_ setCommandHandler:nil];
  [window_ setCommandDispatcherDelegate:nil];

  ui::CATransactionCoordinator::Get().RemovePreCommitObserver(this);
  // This is the standard teardown path when the NSWindow is closing.
  // Notify the host process that the window is closing so that it can
  // tear down the browser-side widget/window structures.
  host_->OnWindowWillClose();

  // Ensure NativeWidgetNSWindowBridge does not have capture, otherwise
  // OnMouseCaptureLost() may reference a deleted |host_| when called via
  // ~CocoaMouseCapture() upon the destruction of |mouse_capture_|. See
  // https://crbug.com/622201. Also we do this before setting the delegate to
  // nil, because this may lead to callbacks to bridge which rely on a valid
  // delegate.
  ReleaseCapture();

  if (parent_) {
    parent_->RemoveChildWindow(this);
    parent_ = nullptr;
  }
  [[NSNotificationCenter defaultCenter] removeObserver:window_delegate_];
  [NSWorkspace.sharedWorkspace.notificationCenter
      removeObserver:window_delegate_];

  [show_animation_ stopAnimation];  // If set, calls OnShowAnimationComplete().
  CHECK(!show_animation_);

  [window_ setDelegate:nil];
  [window_ setBridge:nullptr];

  // Ensure that |this| cannot be reached by its id while it is being destroyed.
  size_t erased = GetIdToWidgetImplMap().erase(id_);
  DCHECK_EQ(1u, erased);

  RemoveOrDestroyChildren();
  DCHECK(child_windows_.empty());

  host_->OnWindowHasClosed();
  // Note: |this| and its host will be deleted here.
}

void NativeWidgetNSWindowBridge::OnSizeChanged() {
  UpdateWindowGeometry();
  if (!allow_screenshots_) {
    SetAllowScreenshots(false);
  }
}

void NativeWidgetNSWindowBridge::OnPositionChanged() {
  UpdateWindowGeometry();
}

void NativeWidgetNSWindowBridge::OnWindowWillMove() {
  host_->OnWindowWillMove();
}

void NativeWidgetNSWindowBridge::OnWindowDidEndMove() {
  host_->OnWindowDidEndMove();
}

void NativeWidgetNSWindowBridge::OnWindowWillStartLiveResize() {
  host_->OnWindowWillStartLiveResize();
}

void NativeWidgetNSWindowBridge::OnWindowDidEndLiveResize() {
  host_->OnWindowDidEndLiveResize();
}

void NativeWidgetNSWindowBridge::OnVisibilityChanged() {
  const bool window_visible = [window_ isVisible];
  if (window_visible_ == window_visible)
    return;

  window_visible_ = window_visible;

  // If arriving via SetVisible(), |wants_to_be_visible_| should already be set.
  // If made visible externally (e.g. Cmd+H), just roll with it. Don't try (yet)
  // to distinguish being *hidden* externally from being hidden by a parent
  // window - we might not need that.
  if (window_visible_) {
    wants_to_be_visible_ = true;
    if (parent_ && !window_visible_)
      parent_->OrderChildren();
  } else {
    ReleaseCapture();  // Capture on hidden windows is not permitted.

    // When becoming invisible, remove the entry in any parent's childWindow
    // list. Cocoa's childWindow management breaks down when child windows are
    // hidden.
    if (parent_)
      [parent_->ns_window() removeChildWindow:window_];
  }

  // Showing a translucent window after hiding it should trigger shadow
  // invalidation.
  if (window_visible && ![window_ isOpaque])
    invalidate_shadow_on_frame_swap_ = true;

  NotifyVisibilityChangeDown();
  host_->OnVisibilityChanged(window_visible_);
}

void NativeWidgetNSWindowBridge::OnSpaceActivationMayHaveChanged() {
  host_->OnSpaceActivationChanged(window_.onActiveSpace);
}

void NativeWidgetNSWindowBridge::OnSystemColorsChanged() {
  host_->OnWindowNativeThemeChanged();
}

void NativeWidgetNSWindowBridge::OnScreenOrBackingPropertiesChanged() {
  UpdateWindowDisplay();
}

void NativeWidgetNSWindowBridge::OnWindowKeyStatusChangedTo(bool is_key) {
  host_->OnWindowKeyStatusChanged(
      is_key, [window_ contentView] == [window_ firstResponder],
      [NSApp isFullKeyboardAccessEnabled]);
  // If the window just became key, this is a good chance to add child windows
  // from when the window wasn't on the current space.
  if (is_key)
    OrderChildren();
}

void NativeWidgetNSWindowBridge::SetSizeConstraints(const gfx::Size& min_size,
                                                    const gfx::Size& max_size,
                                                    bool is_resizable,
                                                    bool is_maximizable) {
  // Don't modify the size constraints or fullscreen collection behavior while
  // in fullscreen or during a transition.
  if (!fullscreen_controller_.CanResize())
    return;

  bool shows_resize_controls =
      is_resizable && (min_size.IsEmpty() || min_size != max_size);
  bool shows_fullscreen_controls = is_resizable && is_maximizable;

  gfx::ApplyNSWindowSizeConstraints(window_, min_size, max_size,
                                    shows_resize_controls,
                                    shows_fullscreen_controls);
}

void NativeWidgetNSWindowBridge::OnShowAnimationComplete() {
  show_animation_ = nil;
}

void NativeWidgetNSWindowBridge::InitCompositorView(
    InitCompositorViewCallback callback) {
  // Use the regular window background for window modal sheets. The layer will
  // still paint over most of it, but the native -[NSApp beginSheet:] animation
  // blocks the UI thread, so there's no way to invalidate the shadow to match
  // the composited layer. This assumes the native window shape is a good match
  // for the composited NonClientFrameView, which should be the case since the
  // native shape is what's most appropriate for displaying sheets on Mac.
  if (is_translucent_window_ && !IsWindowModalSheet()) {
    [window_ setOpaque:NO];
    // A completely transparent background ([NSColor clearColor]) causes AppKit
    // to continuously invalidate the window surface, resulting in high CPU
    // and energy usage. Using an almost-transparent color (alpha 0.001) avoids
    // this performance issue while remaining visually indistinguishable.
    [window_ setBackgroundColor:[[NSColor windowBackgroundColor]
                                    colorWithAlphaComponent:0.001]];

    // Don't block waiting for the initial frame of completely transparent
    // windows. This allows us to avoid blocking on the UI thread e.g, while
    // typing in the omnibox. Note window modal sheets _must_ wait: there is no
    // way for a frame to arrive during AppKit's sheet animation.
    // https://crbug.com/712268
    ca_transaction_sync_suppressed_ = true;
  } else {
    DCHECK(!ca_transaction_sync_suppressed_);
  }

  // Send the initial window geometry and screen properties. Any future changes
  // will be forwarded.
  UpdateWindowDisplay();
  UpdateWindowGeometry();

  // Inform the browser of the CGWindowID for this NSWindow.
  std::move(callback).Run([window_ windowNumber]);
}

void NativeWidgetNSWindowBridge::SortSubviews(
    const std::vector<uint64_t>& attached_subview_ids) {
  // Ignore layer manipulation during a Close(). This can be reached during the
  // orderOut: in Close(), which notifies visibility changes to Views.
  if (!bridged_view_)
    return;
  RankMap rank;
  for (uint64_t subview_id : attached_subview_ids) {
    if (NSView* subview = remote_cocoa::GetNSViewFromId(subview_id))
      rank[subview] = rank.size() + 1;
  }
  [bridged_view_ sortSubviewsUsingFunction:&SubviewSorter context:&rank];
}

void NativeWidgetNSWindowBridge::SetAnimationEnabled(bool animate) {
  [window_
      setAnimationBehavior:(animate ? NSWindowAnimationBehaviorDocumentWindow
                                    : NSWindowAnimationBehaviorNone)];
}

bool NativeWidgetNSWindowBridge::ShouldRunCustomAnimationFor(
    VisibilityTransition transition) const {
  // The logic around this needs to change if new transition types are set.
  // E.g. it would be nice to distinguish "hide" from "close". Mac currently
  // treats "hide" only as "close". Hide (e.g. Cmd+h) should not animate on Mac.
  if (transitions_to_animate_ != transition &&
      transitions_to_animate_ != VisibilityTransition::kBoth) {
    return false;
  }

  // Custom animations are only used for tab-modals.
  bool widget_is_modal = false;
  host_->GetWidgetIsModal(&widget_is_modal);
  if (!widget_is_modal)
    return false;

  // Note this also checks the native animation property. Clearing that will
  // also disable custom animations to ensure that the views::Widget API
  // behaves consistently.
  if ([window_ animationBehavior] == NSWindowAnimationBehaviorNone)
    return false;

  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kDisableModalAnimations)) {
    return false;
  }

  return true;
}

bool NativeWidgetNSWindowBridge::RedispatchKeyEvent(NSEvent* event) {
  return [[window_ commandDispatcher] redispatchKeyEvent:event];
}

NativeWidgetMacNSWindow* NativeWidgetNSWindowBridge::ns_window() {
  return window_;
}

DragDropClient* NativeWidgetNSWindowBridge::drag_drop_client() {
  return host_helper_->GetDragDropClient();
}

////////////////////////////////////////////////////////////////////////////////
// NativeWidgetNSWindowBridge, display::DisplayObserver:

void NativeWidgetNSWindowBridge::OnDisplayAdded(
    const display::Display& display) {
  UpdateWindowDisplay();
}

void NativeWidgetNSWindowBridge::OnDisplaysRemoved(
    const display::Displays& removed_displays) {
  UpdateWindowDisplay();
}

void NativeWidgetNSWindowBridge::OnDisplayMetricsChanged(
    const display::Display& display,
    uint32_t metrics) {
  UpdateWindowDisplay();
}

////////////////////////////////////////////////////////////////////////////////
// NativeWidgetNSWindowBridge, NativeWidgetNSWindowFullscreenController::Client:

void NativeWidgetNSWindowBridge::FullscreenControllerTransitionStart(
    bool is_target_fullscreen) {
  host_->OnWindowFullscreenTransitionStart(is_target_fullscreen);
  if (!is_target_fullscreen) {
    // Immersive full screen needs to be disabled synchronously during the
    // fullscreen transition. So disable it right away, rather than waiting for
    // the browser process to signal us to disable immersive fullscreen after
    // being informed of the start of the transition.
    DisableImmersiveFullscreen();
  }
}

void NativeWidgetNSWindowBridge::FullscreenControllerTransitionComplete(
    bool is_fullscreen) {
  DCHECK(!fullscreen_controller_.IsInFullscreenTransition());
  UpdateWindowGeometry();
  UpdateWindowDisplay();

  // Add any children that were skipped during the fullscreen transition.
  OrderChildren();

  host_->OnWindowFullscreenTransitionComplete(is_fullscreen);
  if (is_fullscreen && immersive_mode_controller_) {
    immersive_mode_controller_->FullscreenTransitionCompleted();
  }
}

void NativeWidgetNSWindowBridge::FullscreenControllerSetFrame(
    const gfx::Rect& frame,
    bool animate,
    base::OnceCallback<void()> completion_callback) {
  NSRect ns_frame = gfx::ScreenRectToNSRect(frame);
  base::TimeDelta transition_time = base::Seconds(0);
  if (animate)
    transition_time = base::Seconds([window_ animationResizeTime:ns_frame]);

  __block base::OnceCallback<void()> complete = std::move(completion_callback);
  [NSAnimationContext
      runAnimationGroup:^(NSAnimationContext* context) {
        [context setDuration:transition_time.InSecondsF()];
        [[window_ animator] setFrame:ns_frame display:YES animate:animate];
      }
      completionHandler:^{
        std::move(complete).Run();
      }];
}

void NativeWidgetNSWindowBridge::FullscreenControllerToggleFullscreen() {
  bool is_key_window = [window_ isKeyWindow];

  // If a request to close the window comes in during the nested loop of
  // -[NSWindow toggleFullScreen:], `this` may be destroyed when the call
  // returns (see NativeWidgetNSWindowFullscreenController::
  // HandleDeferredClose). Use a weak pointer to check for this case.
  // https://crbug.com/503792787
  auto weak_ptr = factory_.GetWeakPtr();
  [window_ toggleFullScreen:nil];
  if (!weak_ptr) {
    return;
  }

  // Ensure the transitioning window and any companion windows (such as speaker
  // notes) maintain focus and Z order when moving to fullscreen. When a key
  // window becomes fullscreen in a different space, AppKit will sometimes make
  // different window on the previously focused space the key window, which can
  // break cross-display fullscreen transitions by losing focus of the
  // transitioning window (crbug.com/40229685) or changing the z-order of
  // windows on the previous space (crbug.com/40247797). This is only done when
  // transitioning to fullscreen, as changes to window order during the
  // transition from fullscreen can break the transition animation
  // (crbug.com/503845404)
  if (is_key_window && [window_ styleMask] & NSWindowStyleMaskFullScreen) {
    [window_ makeKeyAndOrderFront:nil];
  }
}

void NativeWidgetNSWindowBridge::FullscreenControllerCloseWindow() {
  [window_ close];
}

int64_t NativeWidgetNSWindowBridge::FullscreenControllerGetDisplayId() const {
  return GetDisplayForWindow(window_).id();
}

gfx::Rect NativeWidgetNSWindowBridge::FullscreenControllerGetFrameForDisplay(
    int64_t display_id) const {
  display::Display display;
  if (display::Screen::Get()->GetDisplayWithDisplayId(display_id, &display)) {
    // Use the current window size to avoid unexpected window resizes on
    // subsequent cross-screen window drag and drops; see crbug.com/1338664
    return gfx::Rect(display.work_area().origin(),
                     FullscreenControllerGetFrame().size());
  }
  return gfx::Rect();
}

gfx::Rect NativeWidgetNSWindowBridge::FullscreenControllerGetFrame() const {
  return gfx::ScreenRectFromNSRect([window_ frame]);
}

////////////////////////////////////////////////////////////////////////////////
// NativeWidgetNSWindowBridge, ui::CATransactionObserver

bool NativeWidgetNSWindowBridge::ShouldWaitInPreCommit() {
  if (!window_visible_ || !wants_to_be_visible_) {
    return false;
  }
  if (ca_transaction_sync_suppressed_) {
    return false;
  }
  if (!bridged_view_) {
    return false;
  }
  if (content_dip_size_.IsEmpty()) {
    return false;
  }
  // Suppress synchronous CA transactions during AppKit fullscreen transition
  // since there is no need for updates during such transition.
  // Re-layout and re-paint will be done after the transition. See
  // https://crbug.com/875707 for potential problems if we don't suppress.
  if (fullscreen_controller_.IsInFullscreenTransition()) {
    return false;
  }
  return content_dip_size_ != compositor_frame_dip_size_;
}

base::TimeDelta NativeWidgetNSWindowBridge::PreCommitTimeout() {
  return kUIPaintTimeout;
}

bool NativeWidgetNSWindowBridge::IsWindowInLiveResize() {
  return [window_ inLiveResize];
}

////////////////////////////////////////////////////////////////////////////////
// NativeWidgetNSWindowBridge, CocoaMouseCaptureDelegate:

bool NativeWidgetNSWindowBridge::PostCapturedEvent(NSEvent* event) {
  [bridged_view_ processCapturedMouseEvent:event];
  return true;
}

void NativeWidgetNSWindowBridge::OnMouseCaptureLost() {
  host_->OnMouseCaptureActiveChanged(false);
}

NSWindow* NativeWidgetNSWindowBridge::GetWindow() const {
  return window_;
}

////////////////////////////////////////////////////////////////////////////////
// TODO(ccameron): Update class names to:
// NativeWidgetNSWindowBridge, NativeWidgetNSWindowBridge:

void NativeWidgetNSWindowBridge::SetVisibleOnAllSpaces(bool always_visible) {
  gfx::SetNSWindowVisibleOnAllWorkspaces(window_, always_visible);
  CheckAndNotifyAllWorkspacesStateChanged();
}

void NativeWidgetNSWindowBridge::MoveToActiveFullscreenSpace() {
  // If we're still waiting for a previous collection behavior to be restored,
  // then restore it now before temporarily changing it again.
  RestoreCollectionBehavior();

  // Temporarily change the collection behavior to move the window into the
  // fullscreen space and then move it into the space.
  // `NSWindowCollectionBehaviorMoveToActiveSpace` is incompatible with
  // `NSWindowCollectionBehaviorCanJoinAllSpaces`, so we also disable that if
  // necessary.
  NSWindowCollectionBehavior initial_behavior = window_.collectionBehavior;
  collection_behavior_to_restore_ = initial_behavior;
  NSWindowCollectionBehavior temporary_behavior = initial_behavior;
  temporary_behavior |= NSWindowCollectionBehaviorMoveToActiveSpace;
  temporary_behavior &= ~NSWindowCollectionBehaviorCanJoinAllSpaces;
  window_.collectionBehavior = temporary_behavior;
  [window_ orderFrontRegardless];

  // We can't synchronously restore the collection behavior or else the OS will
  // treat the behavior as `initial_behavior` when performing the
  // `orderFrontRegardless` call, so here we asynchronously restore it.
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&NativeWidgetNSWindowBridge::RestoreCollectionBehavior,
                     factory_.GetWeakPtr()));
}

void NativeWidgetNSWindowBridge::SetZoomed(bool zoomed) {
  const bool window_zoomed = [window_ isZoomed];
  if (window_zoomed == zoomed)
    return;
  [window_ performZoom:nil];
}

void NativeWidgetNSWindowBridge::EnterFullscreen(int64_t target_display_id) {
  // Going fullscreen implicitly makes the window visible. AppKit does this.
  // That is, -[NSWindow isVisible] is always true after a call to -[NSWindow
  // toggleFullScreen:]. Unfortunately, this change happens after AppKit calls
  // -[NSWindowDelegate windowWillEnterFullScreen:], and AppKit doesn't send
  // an orderWindow message. So intercepting the implicit change is hard.
  // Luckily, to trigger externally, the window typically needs to be visible
  // in the first place. So we can just ensure the window is visible here
  // instead of relying on AppKit to do it, and not worry that
  // OnVisibilityChanged() won't be called for externally triggered fullscreen
  // requests.
  if (!window_visible_)
    SetVisibilityState(WindowVisibilityState::kShowInactive);

  // Enable fullscreen collection behavior because:
  // 1: -[NSWindow toggleFullscreen:] would otherwise be ignored,
  // 2: the fullscreen button must be enabled so the user can leave
  // fullscreen. This will be reset when a transition out of fullscreen
  // completes.
  gfx::SetNSWindowCanFullscreen(window_, true);

  fullscreen_controller_.EnterFullscreen(target_display_id);
}

void NativeWidgetNSWindowBridge::ExitFullscreen() {
  fullscreen_controller_.ExitFullscreen();
}

// TODO(https://crbug.com/357082344): Do not set
// `NSWindowCollectionBehaviorPrimary` if the window does not already have this
// flag set by `SetCanAppearInExistingFullscreenSpaces(true)`
void NativeWidgetNSWindowBridge::SetCanAppearInExistingFullscreenSpaces(
    bool can_appear_in_existing_fullscreen_spaces) {
  NSWindowCollectionBehavior collectionBehavior = window_.collectionBehavior;
  if (can_appear_in_existing_fullscreen_spaces) {
    collectionBehavior &= ~NSWindowCollectionBehaviorPrimary;
    collectionBehavior |= NSWindowCollectionBehaviorFullScreenAuxiliary;
    collectionBehavior &= ~NSWindowCollectionBehaviorFullScreenPrimary;
  } else {
    collectionBehavior |= NSWindowCollectionBehaviorPrimary;
    collectionBehavior |= NSWindowCollectionBehaviorFullScreenPrimary;
    collectionBehavior &= ~NSWindowCollectionBehaviorFullScreenAuxiliary;
  }
  window_.collectionBehavior = collectionBehavior;
}

void NativeWidgetNSWindowBridge::SetMiniaturized(bool miniaturized) {

  if (miniaturized) {
    // Calling performMiniaturize: will momentarily highlight the button, but
    // AppKit will reject it if there is no miniaturize button.
    if ([window_ styleMask] & NSWindowStyleMaskMiniaturizable)
      [window_ performMiniaturize:nil];
    else
      [window_ miniaturize:nil];
  } else {
    [window_ deminiaturize:nil];
  }
}

void NativeWidgetNSWindowBridge::SetOpacity(float opacity) {
  // If the window is currently invisible waiting for a compositor frame to
  // arrive, update the value to set once that frame arrives.
  if (pending_alpha_value_.has_value()) {
    pending_alpha_value_ = opacity;
  } else {
    window_.alphaValue = opacity;
  }
}

void NativeWidgetNSWindowBridge::SetWindowLevel(int32_t level) {
  [window_ setLevel:level];
  [bridged_view_ updateCursorTrackingArea];

  // Windows that have a higher window level than NSNormalWindowLevel default to
  // NSWindowCollectionBehaviorTransient. Set the value explicitly here to match
  // normal windows.
  NSWindowCollectionBehavior behavior =
      [window_ collectionBehavior] | NSWindowCollectionBehaviorManaged;
  [window_ setCollectionBehavior:behavior];
}

void NativeWidgetNSWindowBridge::SetActivationIndependence(bool independence) {
  for (NSWindow* window = window_; window; window = window.parentWindow) {
    // This cast may fail, and if so, the message send to the nil pointer will
    // (intentionally) silently fail.
    NativeWidgetMacNSWindow* nwm_window =
        base::apple::ObjCCast<NativeWidgetMacNSWindow>(window);
    [nwm_window setActivationIndependence:independence];
  }
}

void NativeWidgetNSWindowBridge::SetAspectRatio(
    const gfx::SizeF& aspect_ratio,
    const gfx::Size& excluded_margin) {
  DCHECK(!aspect_ratio.IsEmpty());
  [window_delegate_ setAspectRatio:aspect_ratio.width() / aspect_ratio.height()
                    excludedMargin:excluded_margin];
}

void NativeWidgetNSWindowBridge::SetCALayerParams(
    gfx::CALayerParams ca_layer_params) {
  // Ignore frames arriving "late" for an old size. A frame at the new size
  // should arrive soon.
  // TODO(danakj): We should avoid lossy conversions to integer DIPs.
  gfx::Size frame_dip_size = gfx::ToFlooredSize(gfx::ConvertSizeToDips(
      ca_layer_params.pixel_size, ca_layer_params.scale_factor));
  if (content_dip_size_ != frame_dip_size)
    return;
  compositor_frame_dip_size_ = frame_dip_size;

  // Update the contents atomically with the NSWindow frame resize.
  std::optional<ScopedCAActionDisabler> disabler;
  if (live_resize_.pending_window_frame.has_value()) {
    disabler.emplace();
  }

  // Update the DisplayCALayerTree with the most recent CALayerParams, to make
  // the content display on-screen.
  display_ca_layer_tree_->UpdateCALayerTree(std::move(ca_layer_params));

  if (ca_transaction_sync_suppressed_)
    ca_transaction_sync_suppressed_ = false;

  // If the window is being kept invisible by its `alphaValue` waiting for
  // a compositor frame, show it now.
  if (pending_alpha_value_.has_value()) {
    window_.alphaValue = pending_alpha_value_.value();
    pending_alpha_value_ = std::nullopt;
  }

  if (invalidate_shadow_on_frame_swap_) {
    invalidate_shadow_on_frame_swap_ = false;
    [window_ invalidateShadow];
  }

  // If this frame is in response to a live-resize, then update the NSWindow's
  // frame now.
  if (live_resize_.pending_window_frame.has_value()) {
    [window_ setFrame:live_resize_.pending_window_frame.value()
              display:YES
              animate:NO];

    // If a subsequent resize came in, send the new size to the compositor now.
    live_resize_.pending_window_frame =
        std::exchange(live_resize_.queued_pending_window_frame, std::nullopt);
    if (live_resize_.pending_window_frame.has_value()) {
      // The pending size must be different from the current size (otherwise we
      // will never un-set `live_resize_.pending_window_frame`) and hang the
      // resize.
      DCHECK_NE(gfx::Size(live_resize_.pending_window_frame->size),
                content_dip_size_);
      SendWindowFrameChangeToHost(live_resize_.pending_window_frame.value());
    }
  }
}

void NativeWidgetNSWindowBridge::SetIgnoresMouseEvents(
    bool ignores_mouse_events) {
  [window_ setIgnoresMouseEvents:ignores_mouse_events];
}

void NativeWidgetNSWindowBridge::MakeFirstResponder() {
  [window_ makeFirstResponder:bridged_view_];
}

void NativeWidgetNSWindowBridge::SetWindowTitle(const std::u16string& title) {
  // Truncate long titles to avoid overflow in Dock menu and window title bar.
  std::u16string truncated_title = gfx::ElideMenuItemTitle(title);

  // Delay setting the window title until after any menu tracking is complete.
  if (NSRunLoop.currentRunLoop.currentMode == NSEventTrackingRunLoopMode) {
    // Install one run loop trigger to handle all the pending titles.
    if (GetPendingWindowTitleMap().empty()) {
      CFRunLoopPerformBlock(
          [NSRunLoop.currentRunLoop getCFRunLoop], kCFRunLoopDefaultMode, ^{
            for (const auto& pending_title : GetPendingWindowTitleMap()) {
              pending_title.first.title =
                  base::SysUTF16ToNSString(pending_title.second);
            }

            GetPendingWindowTitleMap().clear();
          });
    }

    GetPendingWindowTitleMap()[window_] = truncated_title;
  } else {
    window_.title = base::SysUTF16ToNSString(truncated_title);

    // In case there is an unfired run loop trigger, erase any pending title so
    // that the new title now being set doesn't get smashed.
    GetPendingWindowTitleMap().erase(window_);
  }
}

void NativeWidgetNSWindowBridge::ClearTouchBar() {
  [bridged_view_ setTouchBar:nil];
}

void NativeWidgetNSWindowBridge::UpdateTooltip() {
  NSPoint nspoint = [window_ convertPointFromScreen:NSEvent.mouseLocation];
  // Note: flip in the view's frame, which matches the window's contentRect.
  gfx::Point point(nspoint.x, NSHeight([bridged_view_ frame]) - nspoint.y);
  [bridged_view_ updateTooltipIfRequiredAt:point bridge:this];
}

bool NativeWidgetNSWindowBridge::NeedsUpdateWindows() {
  return [bridged_view_ needsUpdateWindows];
}

void NativeWidgetNSWindowBridge::RedispatchKeyEvent(
    const std::vector<uint8_t>& native_event_data) {
  RedispatchKeyEvent(ui::EventFromData(native_event_data));
}

////////////////////////////////////////////////////////////////////////////////
// NativeWidgetNSWindowBridge, former BridgedNativeWidgetOwner:

void NativeWidgetNSWindowBridge::RemoveChildWindow(
    NativeWidgetNSWindowBridge* child) {
  auto location = std::ranges::find(child_windows_, child);
  DCHECK(location != child_windows_.end());
  child_windows_.erase(location);

  // Note the child is sometimes removed already by AppKit. This depends on OS
  // version, and possibly some unpredictable reference counting. Removing it
  // here should be safe regardless.
  [window_ removeChildWindow:child->window_];
}

////////////////////////////////////////////////////////////////////////////////
// NativeWidgetNSWindowBridge, private:

void NativeWidgetNSWindowBridge::OrderChildren() {
  // Adding a child to a window that isn't visible on the active space will
  // switch to that space (https://crbug.com/783521, https://crbug.com/798792).
  // Bail here (and call OrderChildren() in a few places) to defer adding
  // children until the window is visible.
  NSWindow* window = window_;
  if (!window.visible || !window.onActiveSpace) {
    return;
  }
  for (auto* child : child_windows_) {
    if (!child->wants_to_be_visible())
      continue;
    NSWindow* child_window = child->window_;
    if (child->IsWindowModalSheet()) {
      if (!child->window_visible_)
        child->ShowAsModalSheet();
      // Sheets don't need a parentWindow set, and setting one causes graphical
      // glitches (http://crbug.com/605098).
    } else {
      if (child_window.parentWindow == window)
        continue;
      if (immersive_mode_controller_ &&
          immersive_mode_controller_->overlay_window() == child_window) {
        continue;
      }
      [window addChildWindow:child_window ordered:NSWindowAbove];
    }
  }
}

void NativeWidgetNSWindowBridge::RemoveOrDestroyChildren() {
  // TODO(tapted): Implement unowned child windows if required.
  while (!child_windows_.empty()) {
    // The NSWindow can only be destroyed after -[NSWindow close] is complete.
    // Retain the window, otherwise the reference count can reach zero when the
    // child calls back into RemoveChildWindow() via its OnWindowWillClose().
    NS_VALID_UNTIL_END_OF_SCOPE NSWindow* child =
        child_windows_.back()->ns_window();
    [child close];
  }
}

void NativeWidgetNSWindowBridge::CheckAndNotifyZoomedStateChanged() {
  host_->OnWindowZoomedChanged(window_.zoomed);
}

void NativeWidgetNSWindowBridge::CheckAndNotifyAllWorkspacesStateChanged() {
  const bool visible_on_all_spaces =
      ([window_ collectionBehavior] &
       NSWindowCollectionBehaviorCanJoinAllSpaces) != 0;
  // Notify that the window's "visible on all spaces" state has changed.
  host_->OnVisibleOnAllWorkspacesChanged(visible_on_all_spaces);
}

void NativeWidgetNSWindowBridge::NotifyVisibilityChangeDown() {
  // Child windows sometimes like to close themselves in response to visibility
  // changes. That's supported, but only with the asynchronous Widget::Close().
  // Perform a heuristic to detect child removal that would break these loops.
  const size_t child_count = child_windows_.size();
  if (!window_visible_) {
    for (NativeWidgetNSWindowBridge* child : child_windows_) {
      if (child->window_visible_) {
        [child->ns_window() orderOut:nil];
      }
      DCHECK(!child->window_visible_);
      CHECK_EQ(child_count, child_windows_.size());
    }
    // The orderOut calls above should result in a call to OnVisibilityChanged()
    // in each child. There, children will remove themselves from the NSWindow
    // childWindow list as well as propagate NotifyVisibilityChangeDown() calls
    // to any children of their own. However this is only true for windows
    // managed by the NativeWidgetNSWindowBridge i.e. windows which have
    // ViewsNSWindowDelegate as the delegate.
    DCHECK_EQ(0u, CountBridgedWindows([window_ childWindows]));
    return;
  }

  OrderChildren();
}

void NativeWidgetNSWindowBridge::OnLiveResizeToFrame(NSRect new_window_frame) {
  // If there is a pending live resize, just queue the resize request (wait for
  // the compositor to produce a frame of the previously-requested size before
  // asking it for a new one).
  if (live_resize_.pending_window_frame.has_value()) {
    if (gfx::Size(new_window_frame.size) ==
        gfx::Size(live_resize_.pending_window_frame->size)) {
      // If this request is the same as the pending live resize request, just
      // discard it.
      live_resize_.queued_pending_window_frame = std::nullopt;
    } else {
      live_resize_.queued_pending_window_frame = new_window_frame;
    }
    return;
  }

  // If we already have a compositor frame of the expected size, then we will
  // not get re-notified of frames of the current size, which will cause us to
  // never un-set `live_resize_.pending_window_frame` and hang the resize.
  // http://crbug.com/510621306
  if (gfx::Size(new_window_frame.size) == content_dip_size_) {
    return;
  }

  // Tell the compositor about the new frame, so it can produce the right
  // sized frame. We will call -[NSWindow setFrame:] when the compositor
  // produces the frame.
  live_resize_.pending_window_frame = new_window_frame;
  SendWindowFrameChangeToHost(new_window_frame);
}

void NativeWidgetNSWindowBridge::UpdateWindowGeometry() {
  // When a live resize is in progress, do not read the window's frame. Continue
  // to use the pending size.
  if (live_resize_.pending_window_frame.has_value()) {
    return;
  }

  const auto content_dip_size_before = content_dip_size_;
  SendWindowFrameChangeToHost([window_ frame]);
  bool content_resized = content_dip_size_before != content_dip_size_;

  CheckAndNotifyZoomedStateChanged();
  CheckAndNotifyAllWorkspacesStateChanged();

  if (content_resized && window_.visible && !ca_transaction_sync_suppressed_) {
    if (!base::FeatureList::IsEnabled(features::kAsyncLiveResize)) {
      ui::CATransactionCoordinator::Get().Synchronize();
    }
  }

  // For a translucent window, the shadow calculation needs to be carried out
  // after the frame from the compositor arrives.
  if (content_resized && ![window_ isOpaque])
    invalidate_shadow_on_frame_swap_ = true;
}

void NativeWidgetNSWindowBridge::SendWindowFrameChangeToHost(
    NSRect new_window_frame) {
  gfx::Rect window_in_screen = gfx::ScreenRectFromNSRect(new_window_frame);
  gfx::Rect content_in_screen = gfx::ScreenRectFromNSRect(
      [window_ contentRectForFrameRect:new_window_frame]);
  content_dip_size_ = content_in_screen.size();
  host_->OnWindowGeometryChanged(window_in_screen, content_in_screen);
}

void NativeWidgetNSWindowBridge::UpdateWindowDisplay() {
  if (fullscreen_controller_.IsInFullscreenTransition())
    return;

  host_->OnWindowDisplayChanged(GetDisplayForWindow(window_));
}

bool NativeWidgetNSWindowBridge::IsWindowModalSheet() const {
  return parent_ && modal_type_ == ui::mojom::ModalType::kWindow;
}

void NativeWidgetNSWindowBridge::ShowAsModalSheet() {
  // -[NSWindow beginSheet:completionHandler:] will block the UI thread while
  // the animation runs. So that it doesn't animate a fully transparent window,
  // first wait for a frame. The first step is to pretend that the window is
  // already visible.
  window_visible_ = true;
  host_->OnVisibilityChanged(window_visible_);

  NSWindow* parent_window = parent_->ns_window();
  if (NativeWidgetMacNSWindow* parent_widget_window =
          base::apple::ObjCCast<NativeWidgetMacNSWindow>(parent_window)) {
    parent_window = [parent_widget_window preferredSheetParent];
  }
  DCHECK(parent_window);
  NSWindow* __weak weak_window = window_;

  // Don't show a sheet twice. If a sheet is shown twice but endSheet: only
  // once it will leave a dangling blank sheet. This happened when the browser
  // is restored from minimization.
  if (parent_window.attachedSheet == window_) {
    return;
  }

  base::WeakPtr<NativeWidgetNSWindowBridge> weak_this = factory_.GetWeakPtr();
  // AppKit's sheet presentation animation runs a nested run loop. We post
  // this task to run the animation asynchronously for two reasons:
  // 1. For remote cocoa (out-of-process), it prevents a deadlock by allowing
  //    the sizing message to be processed before entering the nested run loop.
  // 2. For in-process windows, it prevents Use-After-Free (UAF) bugs and
  //    undefined behavior when returning from the nested run loop if the
  //    window was closed during presentation, causing its bridge (this) to be
  //    destroyed.
  // https://crbug.com/40781530, https://crbug.com/517040438,
  // https://crbug.com/518006007
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(^{
        NativeWidgetNSWindowBridge* bridge = weak_this.get();
        if (!bridge || !bridge->wants_to_be_visible_) {
          return;
        }
        [parent_window beginSheet:bridge->ns_window()
                completionHandler:^(NSModalResponse return_code) {
                  // This class, NativeWidgetNSWindowBridge, clears the window's
                  // delegate as an indication of its death, in which case this
                  // completion handler will no-op. This is necessary to handle
                  // AppKit invoking this selector via a posted task. See
                  // https://crbug.com/41393772
                  NSWindow* window = weak_window;
                  if (!window.delegate) {
                    return;
                  }
                  // Make sure to mark ourselves as not wanting to be visible.
                  // Otherwise if during the orderOut call our parent becomes
                  // the key window, it would try to show us as a new modal
                  // sheet.
                  if (weak_this) {
                    weak_this->wants_to_be_visible_ = false;
                  }
                  [window orderOut:nil];
                  if (weak_this) {
                    weak_this->OnWindowWillClose();
                  }
                }];
      }));
}

}  // namespace remote_cocoa
