// Copyright 2012 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/widget/desktop_aura/desktop_window_tree_host_win.h"

#include <dwmapi.h>

#include <algorithm>
#include <string_view>
#include <utility>
#include <vector>

#include "base/check_op.h"
#include "base/command_line.h"
#include "base/containers/flat_set.h"
#include "base/debug/dump_without_crashing.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "base/win/win_util.h"
#include "base/win/windows_version.h"
#include "skia/ext/skia_utils_win.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/core/SkRegion.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/cursor_client.h"
#include "ui/aura/client/focus_client.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/base/class_property.h"
#include "ui/base/cursor/cursor.h"
#include "ui/base/cursor/platform_cursor.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/mojom/menu_source_type.mojom-shared.h"
#include "ui/base/mojom/ui_base_types.mojom-shared.h"
#include "ui/base/mojom/window_show_state.mojom.h"
#include "ui/base/win/event_creation_utils.h"
#include "ui/base/win/hwnd_metrics.h"
#include "ui/base/win/win_cursor.h"
#include "ui/color/color_id.h"
#include "ui/color/color_provider_key.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/paint_context.h"
#include "ui/display/win/dpi.h"
#include "ui/display/win/screen_win.h"
#include "ui/events/keyboard_hook.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/dom_keyboard_layout_map.h"
#include "ui/events/platform/platform_event_source.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/gfx/native_ui_types.h"
#include "ui/gfx/path_win.h"
#include "ui/views/corewm/tooltip_aura.h"
#include "ui/views/views_features.h"
#include "ui/views/views_switches.h"
#include "ui/views/widget/desktop_aura/desktop_drag_drop_client_win.h"
#include "ui/views/widget/desktop_aura/desktop_native_cursor_manager.h"
#include "ui/views/widget/desktop_aura/desktop_native_cursor_manager_win.h"
#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
#include "ui/views/widget/root_view.h"
#include "ui/views/widget/widget_activation_delegate.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/views/widget/widget_hwnd_utils.h"
#include "ui/views/win/fullscreen_handler.h"
#include "ui/views/win/hwnd_message_handler.h"
#include "ui/views/win/hwnd_util.h"
#include "ui/views/window/native_frame_view.h"
#include "ui/wm/core/compound_event_filter.h"
#include "ui/wm/core/window_animations.h"
#include "ui/wm/public/scoped_tooltip_disabler.h"

DEFINE_UI_CLASS_PROPERTY_TYPE(views::DesktopWindowTreeHostWin*)

namespace views {

namespace {

// While the mouse is locked we want the invisible mouse to stay within the
// confines of the screen so we keep it in a capture region the size of the
// screen.  However, on windows when the mouse hits the edge of the screen some
// events trigger and cause strange issues to occur. To stop those events from
// occurring we add a small border around the edge of the capture region.
// This constant controls how many pixels wide that border is.
const int kMouseCaptureRegionBorder = 5;

// Updates the cursor clip region. Used for mouse locking.
void UpdateMouseLockRegion(aura::Window* window, bool locked) {
  if (!locked) {
    ::ClipCursor(nullptr);
    return;
  }

  RECT window_rect =
      display::Screen::Get()
          ->DIPToScreenRectInWindow(window, window->GetBoundsInScreen())
          .ToRECT();
  window_rect.left += kMouseCaptureRegionBorder;
  window_rect.right -= kMouseCaptureRegionBorder;
  window_rect.top += kMouseCaptureRegionBorder;
  window_rect.bottom -= kMouseCaptureRegionBorder;
  ::ClipCursor(&window_rect);
}

// Applies display affinity to all active Win32 native system/popup menu
// windows on the current thread.
void ApplyAffinityToActiveSystemMenus(DWORD affinity) {
  ::EnumThreadWindows(
      ::GetCurrentThreadId(),
      [](HWND hwnd, LPARAM lParam) -> BOOL {
        constexpr wchar_t kSystemMenuClassName[] = L"#32768";
        wchar_t class_name[32];
        const int len = ::GetClassName(hwnd, class_name, std::size(class_name));
        if (len > 0 &&
            std::wstring_view(class_name, static_cast<size_t>(len)) ==
                kSystemMenuClassName) {
          ::SetWindowDisplayAffinity(hwnd, static_cast<DWORD>(lParam));
        }
        return TRUE;
      },
      static_cast<LPARAM>(affinity));
}

// Returns the display affinity value to use for excluding a window from screen
// capture, based on the Windows OS version.
DWORD GetExclusionAffinity() {
  return (base::win::GetVersion() >= base::win::Version::WIN10_20H1)
             ? WDA_EXCLUDEFROMCAPTURE
             : WDA_MONITOR;
}

// Enumerates all top-level windows owned by the given hwnd on the current
// thread and applies the given display affinity to them.
void ApplyAffinityToOwnedWindows(HWND owner_hwnd, DWORD affinity) {
  struct EnumData {
    HWND owner;
    DWORD affinity;
  };
  EnumData data{owner_hwnd, affinity};

  ::EnumThreadWindows(
      ::GetCurrentThreadId(),
      [](HWND hwnd, LPARAM lParam) -> BOOL {
        EnumData* enum_data = reinterpret_cast<EnumData*>(lParam);
        HWND current_owner = ::GetWindow(hwnd, GW_OWNER);
        // Guard against infinite loops if an external process or cyclic
        // ownership breaks the assumption that the owner chain ends with null.
        constexpr int kMaxOwnerDepth = 32;
        for (int depth = 0; current_owner && depth < kMaxOwnerDepth; ++depth) {
          if (current_owner == enum_data->owner) {
            ::SetWindowDisplayAffinity(hwnd, enum_data->affinity);
            break;
          }
          current_owner = ::GetWindow(current_owner, GW_OWNER);
        }
        return TRUE;
      },
      reinterpret_cast<LPARAM>(&data));
}

}  // namespace

DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(base::WeakPtr<aura::Window>,
                                   kContentWindowForRootWindow)

// Identifies the DesktopWindowTreeHostWin associated with the
// WindowEventDispatcher.
DEFINE_UI_CLASS_PROPERTY_KEY(DesktopWindowTreeHostWin*,
                             kDesktopWindowTreeHostKey,
                             NULL)

////////////////////////////////////////////////////////////////////////////////
// DesktopWindowTreeHostWin, public:

bool DesktopWindowTreeHostWin::is_cursor_visible_ = true;

DesktopWindowTreeHostWin::DesktopWindowTreeHostWin(
    internal::NativeWidgetDelegate* native_widget_delegate,
    DesktopNativeWidgetAura* desktop_native_widget_aura)
    : native_widget_delegate_(native_widget_delegate->AsWidget()->GetWeakPtr()),
      desktop_native_widget_aura_(desktop_native_widget_aura),
      drag_drop_client_(nullptr),
      should_animate_window_close_(false),
      pending_close_(false),
      has_non_client_view_(false),
      is_modal_(native_widget_delegate_->IsModal()) {}

DesktopWindowTreeHostWin::~DesktopWindowTreeHostWin() {
  ClearBackgroundPaintBrush();
  desktop_native_widget_aura_->OnDesktopWindowTreeHostDestroyed(this);
  // Normally HandleDestroying() destroys the compositor (which is called
  // from WM_DESTROY) but it appears in some situations we can get
  // WM_NCDESTROY (which calls this function) without a WM_DESTROY. As a result
  // DestroyCompositor() is called from both places.
  DestroyCompositor();
  DestroyDispatcher();

  if (HWNDMessageHandler* raw_handler = message_handler_.release()) {
    raw_handler->DestroyHandler();
  }
}

// static
aura::Window* DesktopWindowTreeHostWin::GetContentWindowForHWND(HWND hwnd) {
  // All HWND's we create should have WindowTreeHost instances associated with
  // them. There are exceptions like the content layer creating HWND's which
  // are not associated with WindowTreeHost instances.
  aura::WindowTreeHost* host =
      aura::WindowTreeHost::GetForAcceleratedWidget(hwnd);
  if (!host) {
    return nullptr;
  }
  base::WeakPtr<aura::Window>* weak_ptr =
      host->window()->GetProperty(kContentWindowForRootWindow);
  return weak_ptr ? weak_ptr->get() : nullptr;
}

void DesktopWindowTreeHostWin::StartTouchDrag(gfx::Point screen_point) {
  // Send a mouse down and mouse move before do drag drop runs its own event
  // loop. This is required for ::DoDragDrop to start the drag.
  ui::SendMouseEvent(screen_point,
                     (MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_VIRTUALDESK));
  ui::SendMouseEvent(screen_point,
                     (MOUSEEVENTF_MOVE | MOUSEEVENTF_VIRTUALDESK));
  in_touch_drag_ = true;
}

void DesktopWindowTreeHostWin::FinishTouchDrag(gfx::Point screen_point) {
  if (in_touch_drag_) {
    in_touch_drag_ = false;
    ui::SendMouseEvent(screen_point,
                       (MOUSEEVENTF_LEFTUP | MOUSEEVENTF_VIRTUALDESK));
  }
}

bool DesktopWindowTreeHostWin::IsInNativeMoveResizeLoop() const {
  return message_handler_ && (message_handler_->IsInNativeMoveResizeLoop() ||
                              message_handler_->IsInNativeMenuLoop());
}

// DesktopWindowTreeHostWin, DesktopWindowTreeHost implementation:

void DesktopWindowTreeHostWin::Init(const Widget::InitParams& params) {
  wm::SetAnimationHost(content_window(), this);
  if (params.type == Widget::InitParams::TYPE_WINDOW &&
      !params.remove_standard_frame) {
    content_window()->SetProperty(aura::client::kAnimationsDisabledKey, true);
  }

  message_handler_ = HWNDMessageHandler::Create(
      this, native_widget_delegate_->AsWidget()->GetName());

  ConfigureWindowStyles(message_handler_.get(), params,
                        GetWidget()->widget_delegate(),
                        native_widget_delegate_.get());

  HWND parent_hwnd = nullptr;
  if (params.parent && params.parent->GetHost()) {
    parent_hwnd = params.parent->GetHost()->GetAcceleratedWidget();
  } else if (params.context && params.context->GetHost() &&
             params.type != Widget::InitParams::TYPE_WINDOW &&
             params.type != Widget::InitParams::TYPE_WINDOW_FRAMELESS) {
    // Establish a Win32 owner (GW_OWNER) for subordinate popups (e.g. tooltips,
    // menus, bubbles) to inherit display affinity and proper z-order stacking.
    parent_hwnd = params.context->GetHost()->GetAcceleratedWidget();
  }

  remove_standard_frame_ = params.remove_standard_frame;
  has_non_client_view_ = Widget::RequiresNonClientView(params.type);
  z_order_ = params.EffectiveZOrderLevel();

  // We don't have an HWND yet, so scale relative to the nearest screen.
  gfx::Rect pixel_bounds =
      display::win::GetScreenWin()->DIPToScreenRect(nullptr, params.bounds);
  message_handler_->Init(parent_hwnd, pixel_bounds);

  if (ShouldAddDWMBackdrop()) {
    DWM_SYSTEMBACKDROP_TYPE backdrop = DWMSBT_MAINWINDOW;
    HRESULT hr = DwmSetWindowAttribute(GetHWND(), DWMWA_SYSTEMBACKDROP_TYPE,
                                       &backdrop, sizeof(backdrop));
    if (FAILED(hr)) {
      // If DwmSetWindowAttribute fails, it indicates that there was a problem
      // setting the system backdrop type. In this state, the backdrop is not
      // applied, and the worst that can happen is a transparent window appears
      // while the GPU process is being started.
      LOG(ERROR) << "Failed to set DWM system backdrop type: "
                 << logging::SystemErrorCodeToString(
                        static_cast<logging::SystemErrorCode>(hr));
    }
  }

  UpdateBackdropColorMode();

  CreateCompositor(params.force_software_compositing);
  OnAcceleratedWidgetAvailable();
  InitHost();
  window()->Show();

  // Stack immediately above its parent so that it does not cover other
  // root-level windows, with the exception of menus, to allow them to be
  // displayed on top of other windows.
  if (params.parent && params.type != views::Widget::InitParams::TYPE_MENU) {
    StackAbove(params.parent);
  }
}

void DesktopWindowTreeHostWin::OnNativeWidgetCreated(
    const Widget::InitParams& params) {
  // The cursor is not necessarily visible when the root window is created.
  aura::client::CursorClient* cursor_client =
      aura::client::GetCursorClient(window());
  if (cursor_client) {
    is_cursor_visible_ = cursor_client->IsCursorVisible();
  }

  window()->SetProperty(kContentWindowForRootWindow,
                        content_window()->GetWeakPtrAsWindow());
  window()->SetProperty(kDesktopWindowTreeHostKey, this);

  should_animate_window_close_ =
      content_window()->GetType() != aura::client::WINDOW_TYPE_NORMAL &&
      !wm::WindowAnimationsDisabled(content_window());
}

void DesktopWindowTreeHostWin::OnActiveWindowChanged(bool active) {}

void DesktopWindowTreeHostWin::OnWidgetInitDone() {}

void DesktopWindowTreeHostWin::SetBackgroundColor(SkColor background_color) {
  UpdateBackdropColorMode();
  ClearBackgroundPaintBrush();
  background_paint_brush_ =
      CreateSolidBrush(skia::SkColorToCOLORREF(background_color));
}

std::unique_ptr<corewm::Tooltip> DesktopWindowTreeHostWin::CreateTooltip() {
  return std::make_unique<corewm::TooltipAura>();
}

std::unique_ptr<aura::client::DragDropClient>
DesktopWindowTreeHostWin::CreateDragDropClient() {
  auto res =
      std::make_unique<DesktopDragDropClientWin>(window(), GetHWND(), this);
  drag_drop_client_ = res->GetWeakPtr();
  return std::move(res);
}

void DesktopWindowTreeHostWin::Close() {
  // Do not generate synthesized events during shutdown.
  dispatcher()->Shutdown();

  // Calling Hide() can detach the content window's layer, so store it
  // beforehand so we can access it below.
  auto* window_layer = content_window()->layer();

  content_window()->Hide();
  // TODO(beng): Move this entire branch to DNWA so it can be shared with X11.
  if (should_animate_window_close_) {
    pending_close_ = true;
    // Animation may not start for a number of reasons.
    if (!window_layer->GetAnimator()->is_animating()) {
      message_handler_->Close();
    }
    // else case, OnWindowHidingAnimationCompleted does the actual Close.
  } else {
    message_handler_->Close();
  }
}

void DesktopWindowTreeHostWin::CloseNow() {
  message_handler_->CloseNow();
}

aura::WindowTreeHost* DesktopWindowTreeHostWin::AsWindowTreeHost() {
  return this;
}

DesktopWindowTreeHost::WindowTreeHosts
DesktopWindowTreeHostWin::GetOwnedWindowTreeHosts() {
  WindowTreeHosts window_tree_hosts;
  std::vector<HWND> owned_hwns = message_handler_->GetOwnedWindows();
  for (HWND hwnd : owned_hwns) {
    if (aura::WindowTreeHost* host =
            aura::WindowTreeHost::GetForAcceleratedWidget(hwnd)) {
      window_tree_hosts.insert(host);
    }
  }
  return window_tree_hosts;
}

void DesktopWindowTreeHostWin::Show(ui::mojom::WindowShowState show_state,
                                    const gfx::Rect& restore_bounds) {
  OnAcceleratedWidgetMadeVisible(true);

  gfx::Rect pixel_restore_bounds;
  if (show_state == ui::mojom::WindowShowState::kMaximized) {
    // The window parameter is intentionally passed as nullptr because a
    // non-null window parameter causes errors when restoring windows to saved
    // positions in variable-DPI situations. See https://crbug.com/1252564 for
    // details.
    pixel_restore_bounds =
        display::win::GetScreenWin()->DIPToScreenRect(nullptr, restore_bounds);
  }

  // Ensure the display affinity is updated on the HWND before ShowWindow is
  // called so that DWM is already aware of exclusion when the window appears.
  UpdateDisplayAffinity();

  // Show content window first so that Widget::IsVisible() returns true during
  // the synchronous HandleVisibilityChanged(true) triggered by ShowWindow().
  content_window()->Show();
  message_handler_->Show(show_state, pixel_restore_bounds);

  if (WidgetActivationDelegate::Get()) {
    WidgetActivationDelegate::Get()->MaybeActivate(
        GetWidget(), GetWidget()->CanActivate() &&
                         show_state != ui::mojom::WindowShowState::kInactive &&
                         show_state != ui::mojom::WindowShowState::kMinimized);
  }
}

bool DesktopWindowTreeHostWin::IsVisible() const {
  return message_handler_->IsVisible();
}

void DesktopWindowTreeHostWin::SetSize(const gfx::Size& size) {
  const gfx::Size size_in_pixels =
      display::win::GetScreenWin()->DIPToScreenSize(GetHWND(), size);
  message_handler_->SetSize(size_in_pixels);
}

void DesktopWindowTreeHostWin::StackAbove(aura::Window* window) {
  HWND hwnd = HWNDForNativeView(window);
  if (hwnd) {
    message_handler_->StackAbove(hwnd);
  }
}

void DesktopWindowTreeHostWin::StackAtTop() {
  message_handler_->StackAtTop();
}

void DesktopWindowTreeHostWin::CenterWindow(const gfx::Size& size) {
  const gfx::Size size_in_pixels =
      display::win::GetScreenWin()->DIPToScreenSize(GetHWND(), size);
  message_handler_->CenterWindow(size_in_pixels);
}

void DesktopWindowTreeHostWin::GetWindowPlacement(
    gfx::Rect* bounds,
    ui::mojom::WindowShowState* show_state) const {
  message_handler_->GetWindowPlacement(bounds, show_state);
  *bounds = display::win::GetScreenWin()->ScreenToDIPRect(GetHWND(), *bounds);
}

gfx::Rect DesktopWindowTreeHostWin::GetWindowBoundsInScreen() const {
  gfx::Rect pixel_bounds = message_handler_->GetWindowBoundsInScreen();
  return display::win::GetScreenWin()->ScreenToDIPRect(GetHWND(), pixel_bounds);
}

gfx::Rect DesktopWindowTreeHostWin::GetClientAreaBoundsInScreen() const {
  gfx::Rect pixel_bounds = message_handler_->GetClientAreaBoundsInScreen();
  return display::win::GetScreenWin()->ScreenToDIPRect(GetHWND(), pixel_bounds);
}

gfx::Rect DesktopWindowTreeHostWin::GetRestoredBounds() const {
  gfx::Rect pixel_bounds = message_handler_->GetRestoredBounds();
  return display::win::GetScreenWin()->ScreenToDIPRect(GetHWND(), pixel_bounds);
}

std::string DesktopWindowTreeHostWin::GetWorkspace() const {
  return std::string();
}

gfx::Rect DesktopWindowTreeHostWin::GetWorkAreaBoundsInScreen() const {
  MONITORINFO monitor_info;
  monitor_info.cbSize = sizeof(monitor_info);
  GetMonitorInfo(
      MonitorFromWindow(message_handler_->hwnd(), MONITOR_DEFAULTTONEAREST),
      &monitor_info);
  gfx::Rect pixel_bounds = gfx::Rect(monitor_info.rcWork);
  return display::win::GetScreenWin()->ScreenToDIPRect(GetHWND(), pixel_bounds);
}

void DesktopWindowTreeHostWin::SetShape(
    std::unique_ptr<Widget::ShapeRects> native_shape) {
  if (!native_shape || native_shape->empty()) {
    message_handler_->SetRegion(nullptr);
    return;
  }

  // TODO(wez): This would be a lot simpler if we were passed an SkPath.
  // See crbug.com/410593.
  SkRegion shape;
  const float scale =
      display::win::GetScreenWin()->GetScaleFactorForHWND(GetHWND());
  if (scale > 1.0) {
    std::vector<SkIRect> sk_rects;
    for (const gfx::Rect& rect : *native_shape) {
      const SkIRect sk_rect = gfx::RectToSkIRect(rect);
      SkRect scaled_rect =
          SkRect::MakeLTRB(sk_rect.left() * scale, sk_rect.top() * scale,
                           sk_rect.right() * scale, sk_rect.bottom() * scale);
      SkIRect rounded_scaled_rect;
      scaled_rect.roundOut(&rounded_scaled_rect);
      sk_rects.push_back(rounded_scaled_rect);
    }
    shape.setRects(&sk_rects[0], static_cast<int>(sk_rects.size()));
  } else {
    for (const gfx::Rect& rect : *native_shape) {
      shape.op(gfx::RectToSkIRect(rect), SkRegion::kUnion_Op);
    }
  }

  message_handler_->SetRegion(gfx::CreateHRGNFromSkRegion(shape));
}

void DesktopWindowTreeHostWin::SetParent(gfx::AcceleratedWidget parent) {
  message_handler_->SetParentOrOwner(parent);
}

void DesktopWindowTreeHostWin::Activate() {
  if (WidgetActivationDelegate::Get()) {
    WidgetActivationDelegate::Get()->MaybeActivate(GetWidget(),
                                                   /*activate=*/true);
  }
  message_handler_->Activate();
}

void DesktopWindowTreeHostWin::Deactivate() {
  if (WidgetActivationDelegate::Get()) {
    WidgetActivationDelegate::Get()->Deactivate(GetWidget());
  } else {
    message_handler_->Deactivate();
  }
}

bool DesktopWindowTreeHostWin::IsActive() const {
  if (WidgetActivationDelegate::Get()) {
    return WidgetActivationDelegate::Get()->IsActive(GetWidget());
  }
  return message_handler_->IsActive();
}

void DesktopWindowTreeHostWin::PaintAsActiveChanged() {
  message_handler_->PaintAsActiveChanged();
}

void DesktopWindowTreeHostWin::Maximize() {
  message_handler_->Maximize();
}

void DesktopWindowTreeHostWin::Minimize() {
  message_handler_->Minimize();
}

void DesktopWindowTreeHostWin::Restore() {
  message_handler_->Restore();
}

bool DesktopWindowTreeHostWin::IsMaximized() const {
  return message_handler_->IsMaximized();
}

bool DesktopWindowTreeHostWin::IsMinimized() const {
  return message_handler_->IsMinimized();
}

bool DesktopWindowTreeHostWin::HasCapture() const {
  return message_handler_->HasCapture();
}

void DesktopWindowTreeHostWin::SetZOrderLevel(ui::ZOrderLevel order) {
  z_order_ = order;
  // Emulate the multiple window levels provided by other platforms by
  // collapsing the z-order enum into kNormal = normal, everything else = always
  // on top.
  message_handler_->SetAlwaysOnTop(order != ui::ZOrderLevel::kNormal);
}

ui::ZOrderLevel DesktopWindowTreeHostWin::GetZOrderLevel() const {
  bool window_always_on_top = message_handler_->IsAlwaysOnTop();
  bool level_always_on_top = z_order_ != ui::ZOrderLevel::kNormal;

  if (window_always_on_top == level_always_on_top) {
    return z_order_;
  }

  // If something external has forced a window to be always-on-top, map it to
  // kFloatingWindow as a reasonable equivalent.
  return window_always_on_top ? ui::ZOrderLevel::kFloatingWindow
                              : ui::ZOrderLevel::kNormal;
}

bool DesktopWindowTreeHostWin::IsStackedAbove(aura::Window* window) {
  HWND above = GetHWND();
  HWND below = window->GetHost()->GetAcceleratedWidget();

  // Child windows are always above their parent windows.
  // Check to see if HWNDs have a Parent-Child relationship.
  if (IsChild(below, above)) {
    return true;
  }

  if (IsChild(above, below)) {
    return false;
  }

  // Check all HWNDs with lower z order than current HWND
  // to see if it matches or is a parent to the "below" HWND.
  bool result = false;
  HWND parent = above;
  while (parent && parent != GetDesktopWindow()) {
    HWND next = parent;
    while (next) {
      // GW_HWNDNEXT retrieves the next HWND below z order level.
      next = GetWindow(next, GW_HWNDNEXT);
      if (next == below || IsChild(next, below)) {
        result = true;
        break;
      }
    }
    parent = GetAncestor(parent, GA_PARENT);
  }

  return result;
}

void DesktopWindowTreeHostWin::SetVisibleOnAllWorkspaces(bool always_visible) {
  // Chrome does not yet support Windows 10 desktops.
}

bool DesktopWindowTreeHostWin::IsVisibleOnAllWorkspaces() const {
  return false;
}

bool DesktopWindowTreeHostWin::SetWindowTitle(const std::u16string& title) {
  return message_handler_->SetTitle(title);
}

void DesktopWindowTreeHostWin::ClearNativeFocus() {
  message_handler_->ClearNativeFocus();
}

Widget::MoveLoopResult DesktopWindowTreeHostWin::RunMoveLoop(
    const gfx::Vector2d& drag_offset,
    Widget::MoveLoopSource source,
    Widget::MoveLoopEscapeBehavior escape_behavior) {
  const bool hide_on_escape =
      escape_behavior == Widget::MoveLoopEscapeBehavior::kHide;
  return message_handler_->RunMoveLoop(drag_offset, hide_on_escape)
             ? Widget::MoveLoopResult::kSuccessful
             : Widget::MoveLoopResult::kCanceled;
}

void DesktopWindowTreeHostWin::EndMoveLoop() {
  message_handler_->EndMoveLoop();
}

void DesktopWindowTreeHostWin::SetVisibilityChangedAnimationsEnabled(
    bool value) {
  message_handler_->SetVisibilityChangedAnimationsEnabled(value);
  if (desktop_native_widget_aura_->widget_type() !=
          Widget::InitParams::TYPE_WINDOW ||
      remove_standard_frame_) {
    content_window()->SetProperty(aura::client::kAnimationsDisabledKey, !value);
  }
}

std::unique_ptr<FrameView> DesktopWindowTreeHostWin::CreateFrameView() {
  return (ShouldUseNativeFrame() && native_widget_delegate_)
             ? std::make_unique<NativeFrameView>(
                   native_widget_delegate_->AsWidget())
             : nullptr;
}

bool DesktopWindowTreeHostWin::ShouldUseNativeFrame() const {
  return true;
}

bool DesktopWindowTreeHostWin::ShouldWindowContentsBeTransparent() const {
  // The window contents need to be transparent when the titlebar area is drawn
  // by the DWM rather than Chrome, so that area can show through.  This
  // function does not describe the transparency of the whole window appearance,
  // but merely of the content Chrome draws, so even when the system titlebars
  // appear opaque (Win 8+), the content above them needs to be transparent, or
  // they'll be covered by a black (undrawn) region.
  return ShouldUseNativeFrame() && !IsFullscreen();
}

void DesktopWindowTreeHostWin::FrameTypeChanged() {
  message_handler_->FrameTypeChanged();
}

void DesktopWindowTreeHostWin::SetFullscreen(bool fullscreen,
                                             int64_t target_display_id) {
  auto weak_ptr = GetWeakPtr();
  message_handler_->SetFullscreen(fullscreen, target_display_id);
  if (!weak_ptr) {
    return;
  }
  // TODO(sky): workaround for ScopedFullscreenVisibility showing window
  // directly. Instead of this should listen for visibility changes and then
  // update window.
  if (message_handler_->IsVisible() && !content_window()->TargetVisibility()) {
    OnAcceleratedWidgetMadeVisible(true);
    content_window()->Show();
  }
  desktop_native_widget_aura_->UpdateWindowTransparency();
}

bool DesktopWindowTreeHostWin::IsFullscreen() const {
  return message_handler_->IsFullscreen();
}

void DesktopWindowTreeHostWin::SetOpacity(float opacity) {
  content_window()->layer()->SetOpacity(opacity);
}

void DesktopWindowTreeHostWin::SetAspectRatio(
    const gfx::SizeF& aspect_ratio,
    const gfx::Size& excluded_margin) {
  DCHECK(!aspect_ratio.IsEmpty());
  message_handler_->SetAspectRatio(aspect_ratio.width() / aspect_ratio.height(),
                                   excluded_margin);
}

void DesktopWindowTreeHostWin::SetWindowIcons(const gfx::ImageSkia& window_icon,
                                              const gfx::ImageSkia& app_icon) {
  message_handler_->SetWindowIcons(window_icon, app_icon);
}

void DesktopWindowTreeHostWin::InitModalType(ui::mojom::ModalType modal_type) {
  message_handler_->InitModalType(modal_type);
}

void DesktopWindowTreeHostWin::FlashFrame(bool flash_frame) {
  message_handler_->FlashFrame(flash_frame);
}

bool DesktopWindowTreeHostWin::IsAnimatingClosed() const {
  return pending_close_;
}

void DesktopWindowTreeHostWin::SizeConstraintsChanged() {
  message_handler_->SizeConstraintsChanged();
}

bool DesktopWindowTreeHostWin::ShouldUpdateWindowTransparency() const {
  return true;
}

bool DesktopWindowTreeHostWin::ShouldUseDesktopNativeCursorManager() const {
  return true;
}

bool DesktopWindowTreeHostWin::ShouldCreateVisibilityController() const {
  return true;
}

////////////////////////////////////////////////////////////////////////////////
// DesktopWindowTreeHostWin, WindowTreeHost implementation:

ui::EventSource* DesktopWindowTreeHostWin::GetEventSource() {
  return this;
}

gfx::AcceleratedWidget DesktopWindowTreeHostWin::GetAcceleratedWidget() {
  return message_handler_->hwnd();
}

void DesktopWindowTreeHostWin::ShowImpl() {
  Show(ui::mojom::WindowShowState::kNormal, gfx::Rect());
}

void DesktopWindowTreeHostWin::HideImpl() {
  if (!pending_close_) {
    message_handler_->Hide();
  }
}

// GetBoundsInPixels and SetBoundsInPixels work in pixel coordinates, whereas
// other get/set methods work in DIP.

gfx::Rect DesktopWindowTreeHostWin::GetBoundsInPixels() const {
  const gfx::Rect bounds_px(message_handler_->GetClientAreaBounds());
  // If the window bounds were expanded we need to return the original bounds
  // To achieve this we do the reverse of the expansion, i.e. add the
  // window_expansion_top_left_delta_ to the origin and subtract the
  // window_expansion_bottom_right_delta_ from the width and height.
  const gfx::Rect without_expansion_bounds_px(
      bounds_px.x() + window_expansion_top_left_delta_.x(),
      bounds_px.y() + window_expansion_top_left_delta_.y(),
      bounds_px.width() - window_expansion_bottom_right_delta_.x(),
      bounds_px.height() - window_expansion_bottom_right_delta_.y());
  return without_expansion_bounds_px;
}

void DesktopWindowTreeHostWin::SetBoundsInPixels(
    const gfx::Rect& bounds_in_pixels) {
  // If the window bounds have to be expanded we need to subtract the
  // window_expansion_top_left_delta_ from the origin and add the
  // window_expansion_bottom_right_delta_ to the width and height
  const gfx::Size old_content_size_px = GetBoundsInPixels().size();

  const gfx::Rect expanded_bounds_px(
      bounds_in_pixels.x() - window_expansion_top_left_delta_.x(),
      bounds_in_pixels.y() - window_expansion_top_left_delta_.y(),
      bounds_in_pixels.width() + window_expansion_bottom_right_delta_.x(),
      bounds_in_pixels.height() + window_expansion_bottom_right_delta_.y());

  // When `expanded_bounds_px` causes the window to be moved to a display with a
  // different DSF, HWNDMessageHandler::OnDpiChanged() will be called and the
  // window size will be scaled automatically.
  message_handler_->SetBounds(expanded_bounds_px,
                              old_content_size_px != bounds_in_pixels.size());
}

gfx::Rect
DesktopWindowTreeHostWin::GetBoundsInAcceleratedWidgetPixelCoordinates() {
  if (message_handler_->IsMinimized()) {
    return gfx::Rect();
  }
  const gfx::Rect client_bounds =
      message_handler_->GetClientAreaBoundsInScreen();
  const gfx::Rect window_bounds = message_handler_->GetWindowBoundsInScreen();
  if (window_bounds == client_bounds) {
    return gfx::Rect(window_bounds.size());
  }
  const gfx::Vector2d offset = client_bounds.origin() - window_bounds.origin();
  DCHECK_GE(offset.x(), 0);
  DCHECK_GE(offset.y(), 0);
  return gfx::Rect(gfx::Point() + offset, client_bounds.size());
}

gfx::Point DesktopWindowTreeHostWin::GetLocationOnScreenInPixels() const {
  return GetBoundsInPixels().origin();
}

void DesktopWindowTreeHostWin::SetCapture() {
  message_handler_->SetCapture();
}

void DesktopWindowTreeHostWin::ReleaseCapture() {
  message_handler_->ReleaseCapture();
}

bool DesktopWindowTreeHostWin::CaptureSystemKeyEventsImpl(
    std::optional<base::flat_set<ui::DomCode>> dom_codes) {
  // Only one KeyboardHook should be active at a time, otherwise there will be
  // problems with event routing (i.e. which Hook takes precedence) and
  // destruction ordering.
  DCHECK(!keyboard_hook_);
  keyboard_hook_ = ui::KeyboardHook::CreateModifierKeyboardHook(
      std::move(dom_codes), GetAcceleratedWidget(),
      base::BindRepeating(&DesktopWindowTreeHostWin::HandleKeyEvent,
                          base::Unretained(this)));

  return keyboard_hook_ != nullptr;
}

void DesktopWindowTreeHostWin::ReleaseSystemKeyEventCapture() {
  keyboard_hook_.reset();
}

bool DesktopWindowTreeHostWin::IsKeyLocked(ui::DomCode dom_code) {
  return keyboard_hook_ && keyboard_hook_->IsKeyLocked(dom_code);
}

base::flat_map<std::string, std::string>
DesktopWindowTreeHostWin::GetKeyboardLayoutMap() {
  return ui::GenerateDomKeyboardLayoutMap();
}

void DesktopWindowTreeHostWin::SetCursorNative(gfx::NativeCursor cursor) {
  TRACE_EVENT1("ui,input", "DesktopWindowTreeHostWin::SetCursorNative",
               "cursor", cursor.type());

  message_handler_->SetCursor(
      ui::WinCursor::FromPlatformCursor(cursor.platform()));
}

void DesktopWindowTreeHostWin::OnCursorVisibilityChangedNative(bool show) {
  if (is_cursor_visible_ == show) {
    return;
  }
  is_cursor_visible_ = show;
  ::ShowCursor(!!show);
}

void DesktopWindowTreeHostWin::MoveCursorToScreenLocationInPixels(
    const gfx::Point& location_in_pixels) {
  POINT cursor_location = location_in_pixels.ToPOINT();
  ::ClientToScreen(GetHWND(), &cursor_location);
  ::SetCursorPos(cursor_location.x, cursor_location.y);
}

std::unique_ptr<aura::ScopedEnableUnadjustedMouseEvents>
DesktopWindowTreeHostWin::RequestUnadjustedMovement() {
  return message_handler_->RegisterUnadjustedMouseEvent();
}

void DesktopWindowTreeHostWin::LockMouse(aura::Window* window) {
  UpdateMouseLockRegion(window, true /*locked*/);
  message_handler_->set_mouse_locked(true);
  WindowTreeHost::LockMouse(window);
}

void DesktopWindowTreeHostWin::UnlockMouse(aura::Window* window) {
  UpdateMouseLockRegion(window, false /*locked*/);
  message_handler_->set_mouse_locked(false);
  WindowTreeHost::UnlockMouse(window);
}

////////////////////////////////////////////////////////////////////////////////
// DesktopWindowTreeHostWin, wm::AnimationHost implementation:

void DesktopWindowTreeHostWin::SetHostTransitionOffsets(
    const gfx::Vector2d& top_left_delta,
    const gfx::Vector2d& bottom_right_delta) {
  gfx::Rect bounds_without_expansion = GetBoundsInPixels();
  window_expansion_top_left_delta_ = top_left_delta;
  window_expansion_bottom_right_delta_ = bottom_right_delta;
  SetBoundsInPixels(bounds_without_expansion);
}

void DesktopWindowTreeHostWin::OnWindowHidingAnimationCompleted() {
  if (pending_close_) {
    message_handler_->Close();
  }
}

////////////////////////////////////////////////////////////////////////////////
// DesktopWindowTreeHostWin, HWNDMessageHandlerDelegate implementation:

ui::InputMethod* DesktopWindowTreeHostWin::GetHWNDMessageDelegateInputMethod() {
  return GetInputMethod();
}

bool DesktopWindowTreeHostWin::HasNonClientView() const {
  return has_non_client_view_;
}

FrameMode DesktopWindowTreeHostWin::GetFrameMode() const {
  if (const Widget* widget = GetWidget()) {
    return widget->ShouldUseNativeFrame() ? FrameMode::SYSTEM_DRAWN
                                          : FrameMode::CUSTOM_DRAWN;
  }
  return FrameMode::SYSTEM_DRAWN;
}

void DesktopWindowTreeHostWin::ShowCustomSystemMenu(
    const gfx::Point& screen_point) {}

bool DesktopWindowTreeHostWin::UsesNativeSystemMenu() const {
  return true;
}

bool DesktopWindowTreeHostWin::HasFrame() const {
  return !remove_standard_frame_;
}

void DesktopWindowTreeHostWin::SchedulePaint() {
  if (Widget* widget = GetWidget()) {
    widget->GetRootView()->SchedulePaint();
  }
}

bool DesktopWindowTreeHostWin::ShouldPaintAsActive() const {
  if (const Widget* widget = GetWidget()) {
    return widget->ShouldPaintAsActive();
  }
  return false;
}

bool DesktopWindowTreeHostWin::CanResize() const {
  if (const Widget* widget = GetWidget(); widget && widget->widget_delegate()) {
    return widget->widget_delegate()->CanResize();
  }
  return false;
}

bool DesktopWindowTreeHostWin::CanMaximize() const {
  if (const Widget* widget = GetWidget(); widget && widget->widget_delegate()) {
    return widget->widget_delegate()->CanMaximize();
  }
  return false;
}

bool DesktopWindowTreeHostWin::CanMinimize() const {
  if (const Widget* widget = GetWidget(); widget && widget->widget_delegate()) {
    return widget->widget_delegate()->CanMinimize();
  }
  return false;
}

bool DesktopWindowTreeHostWin::CanActivate() const {
  if (IsModalWindowActive()) {
    return true;
  }
  return native_widget_delegate_ ? native_widget_delegate_->CanActivate()
                                 : false;
}

bool DesktopWindowTreeHostWin::WidgetSizeIsClientSize() const {
  if (IsMaximized()) {
    return true;
  }
  if (const Widget* widget = GetWidget()) {
    return widget->ShouldUseNativeFrame();
  }
  return false;
}

bool DesktopWindowTreeHostWin::IsModal() const {
  return is_modal_;
}

int DesktopWindowTreeHostWin::GetInitialShowState() const {
  return CanActivate() ? SW_SHOWNORMAL : SW_SHOWNOACTIVATE;
}

int DesktopWindowTreeHostWin::GetNonClientComponent(
    const gfx::Point& point) const {
  if (!native_widget_delegate_) {
    return HTTRANSPARENT;
  }
  gfx::Point dip_position =
      display::win::GetScreenWin()->ClientToDIPPoint(GetHWND(), point);
  return native_widget_delegate_->GetNonClientComponent(dip_position);
}

void DesktopWindowTreeHostWin::GetWindowMask(const gfx::Size& size_px,
                                             SkPath* path) {
  Widget* widget = GetWidget();
  if (!widget || !widget->non_client_view()) {
    return;
  }

  widget->non_client_view()->GetWindowMask(
      display::win::GetScreenWin()->ScreenToDIPSize(GetHWND(), size_px), path);
  // Convert path in DIPs to pixels.
  if (!path->isEmpty()) {
    const float scale =
        display::win::GetScreenWin()->GetScaleFactorForHWND(GetHWND());
    *path = path->makeTransform(SkMatrix::Scale(scale, scale));
  }
}

bool DesktopWindowTreeHostWin::GetClientAreaInsets(gfx::Insets* insets,
                                                   int frame_thickness) const {
  // WS_THICKFRAME style has a system titlebar. Remove this titlebar for
  // borderless windows.
  if (desktop_native_widget_aura_->widget_type() ==
          Widget::InitParams::TYPE_WINDOW_FRAMELESS &&
      (GetWindowLong(GetHWND(), GWL_STYLE) & WS_THICKFRAME)) {
    *insets = gfx::Insets(frame_thickness);
    // In non-maximized window, the top-border inset must be zero, otherwise
    // Windows will draw a full native titlebar.
    if (!IsMaximized()) {
      insets->set_top(0);
    }

    return true;
  }

  return false;
}

bool DesktopWindowTreeHostWin::GetDwmFrameInsetsInPixels(
    gfx::Insets* insets) const {
  return false;
}

void DesktopWindowTreeHostWin::GetMinMaxSize(gfx::Size* min_size,
                                             gfx::Size* max_size) const {
  if (!native_widget_delegate_) {
    return;
  }

  *min_size = native_widget_delegate_->GetMinimumSize();
  *max_size = native_widget_delegate_->GetMaximumSize();
}

gfx::Size DesktopWindowTreeHostWin::GetRootViewSize() const {
  if (const Widget* widget = GetWidget()) {
    return widget->GetRootView()->size();
  }
  return gfx::Size();
}

gfx::Size DesktopWindowTreeHostWin::DIPToScreenSize(
    const gfx::Size& dip_size) const {
  return display::win::GetScreenWin()->DIPToScreenSize(GetHWND(), dip_size);
}

void DesktopWindowTreeHostWin::ResetWindowControls() {
  if (Widget* widget = GetWidget(); widget && widget->non_client_view()) {
    widget->non_client_view()->ResetWindowControls();
  }
}

gfx::NativeViewAccessible DesktopWindowTreeHostWin::GetNativeViewAccessible() {
  // This function may be called during shutdown when the |RootView| is nullptr.
  if (Widget* widget = GetWidget()) {
    return widget->GetRootView()->GetNativeViewAccessible();
  }
  return nullptr;
}

gfx::NativeViewAccessible
DesktopWindowTreeHostWin::GetParentNativeViewAccessible() {
  views::Widget* widget = GetWidget();
  if (!widget) {
    return nullptr;
  }

  views::Widget* parent_widget = widget->parent();
  if (!parent_widget) {
    return nullptr;
  }

  views::View* parent_root = parent_widget->GetRootView();
  return parent_root ? parent_root->GetNativeViewAccessible() : nullptr;
}

void DesktopWindowTreeHostWin::HandleActivationChanged(bool active) {
  if (WidgetActivationDelegate::Get()) {
    return;
  }

  // This can be invoked from HWNDMessageHandler::Init(), at which point we're
  // not in a good state and need to ignore it.
  // TODO(beng): Do we need this still now the host owns the dispatcher?
  if (!dispatcher()) {
    return;
  }

  desktop_native_widget_aura_->HandleActivationChanged(active);
}

bool DesktopWindowTreeHostWin::HandleAppCommand(int command) {
  // We treat APPCOMMAND ids as an extension of our command namespace, and just
  // let the delegate figure out what to do...
  if (Widget* widget = GetWidget(); widget && widget->widget_delegate()) {
    return widget->widget_delegate()->ExecuteWindowsCommand(command);
  }
  return false;
}

void DesktopWindowTreeHostWin::HandleCancelMode() {
  dispatcher()->DispatchCancelModeEvent();
}

void DesktopWindowTreeHostWin::HandleCaptureLost() {
  OnHostLostWindowCapture();
}

void DesktopWindowTreeHostWin::HandleClose() {
  if (Widget* widget = GetWidget()) {
    widget->Close();
  }
}

bool DesktopWindowTreeHostWin::HandleCommand(int command) {
  if (Widget* widget = GetWidget(); widget && widget->widget_delegate()) {
    return widget->widget_delegate()->ExecuteWindowsCommand(command);
  }
  return false;
}

void DesktopWindowTreeHostWin::HandleAccelerator(
    const ui::Accelerator& accelerator) {
  if (Widget* widget = GetWidget()) {
    widget->GetFocusManager()->ProcessAccelerator(accelerator);
  }
}

void DesktopWindowTreeHostWin::HandleCreate() {
  if (native_widget_delegate_) {
    native_widget_delegate_->OnNativeWidgetCreated();
  }
}

void DesktopWindowTreeHostWin::HandleDestroying() {
  drag_drop_client_->OnNativeWidgetDestroying(GetHWND());
  if (native_widget_delegate_) {
    native_widget_delegate_->OnNativeWidgetDestroying();
  }

  // Destroy the compositor before destroying the HWND since shutdown
  // may try to swap to the window.
  DestroyCompositor();
}

void DesktopWindowTreeHostWin::HandleDestroyed() {
  desktop_native_widget_aura_->OnHostClosed();
}

bool DesktopWindowTreeHostWin::HandleInitialFocus(
    ui::mojom::WindowShowState show_state) {
  if (Widget* widget = GetWidget()) {
    return widget->SetInitialFocus(show_state);
  }
  return false;
}

void DesktopWindowTreeHostWin::HandleDisplayChange() {
  if (Widget* widget = GetWidget(); widget && widget->widget_delegate()) {
    widget->widget_delegate()->OnDisplayChanged();
  }
}

void DesktopWindowTreeHostWin::HandleBeginWMSizeMove() {
  if (native_widget_delegate_) {
    native_widget_delegate_->OnNativeWidgetBeginUserBoundsChange();
  }
}

void DesktopWindowTreeHostWin::HandleEndWMSizeMove() {
  if (native_widget_delegate_) {
    native_widget_delegate_->OnNativeWidgetEndUserBoundsChange();
  }
}

void DesktopWindowTreeHostWin::HandleBeginUserResize() {
  if (native_widget_delegate_) {
    native_widget_delegate_->OnNativeWidgetUserResizeStarted();
  }
}

void DesktopWindowTreeHostWin::HandleEndUserResize() {
  if (native_widget_delegate_) {
    native_widget_delegate_->OnNativeWidgetUserResizeEnded();
  }
}

void DesktopWindowTreeHostWin::HandleBeginUserDrag() {
  if (native_widget_delegate_) {
    native_widget_delegate_->OnNativeWidgetUserDragStarted();
  }
}

void DesktopWindowTreeHostWin::HandleEndUserDrag() {
  if (native_widget_delegate_) {
    native_widget_delegate_->OnNativeWidgetUserDragEnded();
  }
}

void DesktopWindowTreeHostWin::HandleMove() {
  // Adding/removing a monitor, or changing the primary monitor can cause a
  // WM_MOVE message before `OnDisplayChanged()`. Without this call, we would
  // DCHECK due to stale `DisplayInfo`s. See https:://crbug.com/1413940.
  auto weak_ptr = GetWeakPtr();
  display::win::GetScreenWin()->UpdateDisplayInfosIfNeeded();
  if (!weak_ptr) {
    return;
  }
  CheckForMonitorChange();
  OnHostMovedInPixels();
}

void DesktopWindowTreeHostWin::HandleWorkAreaChanged() {
  CheckForMonitorChange();
  if (Widget* widget = GetWidget(); widget && widget->widget_delegate()) {
    widget->widget_delegate()->OnWorkAreaChanged();
  }
}

void DesktopWindowTreeHostWin::HandleVisibilityChanged(bool visible) {
  if (native_widget_delegate_) {
    native_widget_delegate_->OnNativeWidgetVisibilityChanged(visible);
  }
  if (visible) {
    UpdateDisplayAffinity();
  }
}

void DesktopWindowTreeHostWin::HandleWindowMinimizedOrRestored(bool restored) {
  // Ignore minimize/restore events that happen before widget initialization is
  // done. If a window is created minimized, and then activated, restoring
  // focus will fail because the root window is not visible, which is exposed by
  // ExtensionWindowCreateTest.AcceptState.
  if (!native_widget_delegate_ ||
      !native_widget_delegate_->IsNativeWidgetInitialized()) {
    return;
  }

  if (restored) {
    window()->Show();
  } else {
    window()->Hide();
  }
}

void DesktopWindowTreeHostWin::HandleClientSizeChanged(
    const gfx::Size& new_size) {
  CheckForMonitorChange();
  if (dispatcher()) {
    OnHostResizedInPixels(new_size);
  }
}

void DesktopWindowTreeHostWin::HandleFrameChanged() {
  // Replace the frame and layout the contents.
  if (Widget* widget = GetWidget(); widget && widget->non_client_view()) {
    widget->non_client_view()->UpdateFrame();
  }
}

void DesktopWindowTreeHostWin::HandleNativeFocus(HWND last_focused_window) {
  // TODO(beng): inform the native_widget_delegate_.
}

void DesktopWindowTreeHostWin::HandleNativeBlur(HWND focused_window) {
  // TODO(beng): inform the native_widget_delegate_.
}

bool DesktopWindowTreeHostWin::HandleMouseEvent(ui::MouseEvent* event) {
  // Ignore native platform events for test purposes
  if (ui::PlatformEventSource::ShouldIgnoreNativePlatformEvents()) {
    return true;
  }

  SendEventToSink(event);
  return event->handled();
}

void DesktopWindowTreeHostWin::HandleKeyEvent(ui::KeyEvent* event) {
  // Bypass normal handling of alt-space, which would otherwise consume the
  // corresponding WM_SYSCHAR.  This allows HandleIMEMessage() to show the
  // system menu in this case.  If we instead showed the system menu here, the
  // WM_SYSCHAR would trigger a beep when processed by the native event handler.
  if ((event->type() == ui::EventType::kKeyPressed) &&
      (event->key_code() == ui::VKEY_SPACE) &&
      (event->flags() & ui::EF_ALT_DOWN) &&
      !(event->flags() & ui::EF_CONTROL_DOWN)) {
    if (Widget* widget = GetWidget(); widget && widget->non_client_view()) {
      if (!UsesNativeSystemMenu()) {
        // Show the Views version of the window frame context menu if it should
        // be used instead of the OS native version. Default location for the
        // menu is the origin (0, 0) of the browser.
        gfx::Point point = widget->non_client_view()
                               ->frame_view()
                               ->GetKeyboardContextMenuLocation();
        ShowCustomSystemMenu(point);
        event->SetHandled();
        return;
      }

      return;
    }
  }

  SendEventToSink(event);
}

void DesktopWindowTreeHostWin::HandleTouchEvent(ui::TouchEvent* event) {
  // HWNDMessageHandler asynchronously processes touch events. Because of this
  // it's possible for the aura::WindowEventDispatcher to have been destroyed
  // by the time we attempt to process them.
  Widget* widget = GetWidget();
  if (!widget || !widget->GetNativeView()) {
    return;
  }
  if (in_touch_drag_) {
    POINT event_point(event->location().x(), event->location().y());
    ::ClientToScreen(GetHWND(), &event_point);
    gfx::Point screen_point(event_point);
    // When dragging, Windows requires that touch pointer events are translated
    // to mouse pointer events. The drag controller (`DesktopDragDropClientWin`)
    // will manage gesture states until a drop happens.
    if (event->type() == ui::EventType::kTouchMoved) {
      ui::SendMouseEvent(screen_point,
                         (MOUSEEVENTF_MOVE | MOUSEEVENTF_VIRTUALDESK));
    } else if (event->type() == ui::EventType::kTouchReleased) {
      FinishTouchDrag(screen_point);
    }
    return;
  }
  if (event->type() == ui::EventType::kTouchPressed) {
    display::Screen* screen = display::Screen::Get();
    CHECK(screen);
    aura::Window* window =
        screen->GetWindowAtScreenPoint(screen->GetCursorScreenPoint());
    bool touch_drag_cursor_sync =
        base::FeatureList::IsEnabled(features::kEnableTouchDragCursorSync);
    // If a window is not found at the cursor's screen point, then the mouse is
    // outside of the browser's window and we need to sync the pointer to enable
    // drag and drop. Setting the cursor to the touch location doesn't move the
    // mouse pointer. If the user has their mouse outside of the window when
    // this sync happens, when they move their mouse again it will show up in
    // it's original location.
    if (touch_drag_cursor_sync && !window) {
      POINT event_point(event->location().x(), event->location().y());
      ::ClientToScreen(GetHWND(), &event_point);
      ::SetCursorPos(event_point.x, event_point.y);
    }
  }
  // Currently we assume the window that has capture gets touch events too.
  aura::WindowTreeHost* host =
      aura::WindowTreeHost::GetForAcceleratedWidget(GetCapture());
  if (host) {
    DesktopWindowTreeHostWin* target =
        host->window()->GetProperty(kDesktopWindowTreeHostKey);
    if (target && target->HasCapture() && target != this) {
      POINT target_location(event->location().ToPOINT());
      ClientToScreen(GetHWND(), &target_location);
      ScreenToClient(target->GetHWND(), &target_location);
      ui::TouchEvent target_event(*event, static_cast<View*>(nullptr),
                                  static_cast<View*>(nullptr));
      target_event.set_location(gfx::Point(target_location));
      target_event.set_root_location(target_event.location());
      target->SendEventToSink(&target_event);
      return;
    }
  }
  SendEventToSink(event);
}

bool DesktopWindowTreeHostWin::HandleIMEMessage(UINT message,
                                                WPARAM w_param,
                                                LPARAM l_param,
                                                LRESULT* result) {
  // Show the system menu at an appropriate location on alt-space.
  if ((message == WM_SYSCHAR) && (w_param == VK_SPACE)) {
    if (Widget* widget = GetWidget(); widget && widget->non_client_view()) {
      const auto* frame = GetWidget()->non_client_view()->frame_view();
      ShowSystemMenuAtScreenPixelLocation(
          GetHWND(), frame->GetSystemMenuScreenPixelLocation());
      return true;
    }
  }

  CHROME_MSG msg = {};
  msg.hwnd = GetHWND();
  msg.message = message;
  msg.wParam = w_param;
  msg.lParam = l_param;
  return GetInputMethod()->OnUntranslatedIMEMessage(msg, result);
}

void DesktopWindowTreeHostWin::HandleInputLanguageChange(
    DWORD character_set,
    HKL input_language_id) {
  GetInputMethod()->OnInputLocaleChanged();
}

void DesktopWindowTreeHostWin::HandlePaintAccelerated(
    const gfx::Rect& invalid_rect) {
  if (compositor()) {
    compositor()->ScheduleRedrawRect(invalid_rect);
  }
}

void DesktopWindowTreeHostWin::HandleMenuLoop(bool in_menu_loop) {
  if (in_menu_loop) {
    tooltip_disabler_ = std::make_unique<wm::ScopedTooltipDisabler>(window());
  } else {
    tooltip_disabler_.reset();
  }
}

bool DesktopWindowTreeHostWin::PreHandleMSG(UINT message,
                                            WPARAM w_param,
                                            LPARAM l_param,
                                            LRESULT* result) {
  if (message == WM_INITMENUPOPUP) {
    // Intercept native popup menu initialization to propagate our capture
    // exclusion state to the native menu window. Since sub-menu windows are
    // created by Win32 after WM_INITMENUPOPUP returns, post a task to apply
    // the affinity once the menu window has been created.
    if (exclude_from_capture_ && IsCaptureExclusionAllowed()) {
      base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE, base::BindOnce(&ApplyAffinityToActiveSystemMenus,
                                    GetExclusionAffinity()));
    }
  }
  return false;
}

void DesktopWindowTreeHostWin::PostHandleMSG(UINT message,
                                             WPARAM w_param,
                                             LPARAM l_param) {}

bool DesktopWindowTreeHostWin::HandleScrollEvent(ui::ScrollEvent* event) {
  SendEventToSink(event);
  return event->handled();
}

bool DesktopWindowTreeHostWin::HandleGestureEvent(ui::GestureEvent* event) {
  SendEventToSink(event);
  return event->handled();
}

void DesktopWindowTreeHostWin::HandleWindowScaleFactorChanged(
    float window_scale_factor) {
  // TODO(ccameron): This will violate surface invariants, and is insane.
  // Shouldn't the scale factor and window pixel size changes be sent
  // atomically? And how does this interact with updates to display::Display?
  // Should we expect the display::Display to be updated before this? If so,
  // why can't we use the DisplayObserver that the base WindowTreeHost is
  // using?
  if (compositor()) {
    compositor()->SetScaleAndSize(
        window_scale_factor, message_handler_->GetClientAreaBounds().size(),
        window()->GetLocalSurfaceId());
  }
}

void DesktopWindowTreeHostWin::HandleHeadlessWindowBoundsChanged(
    const gfx::Rect& bounds) {
  window()->SetProperty(aura::client::kHeadlessBoundsKey, bounds);
}

HBRUSH DesktopWindowTreeHostWin::GetBackgroundPaintBrush() {
  return background_paint_brush_;
}

DesktopNativeCursorManager*
DesktopWindowTreeHostWin::GetSingletonDesktopNativeCursorManager() {
  return new DesktopNativeCursorManagerWin();
}

void DesktopWindowTreeHostWin::SetBoundsInDIP(const gfx::Rect& bounds) {
  // The window parameter is intentionally passed as nullptr on Windows because
  // a non-null window parameter causes errors when restoring windows to saved
  // positions in variable-DPI situations. See https://crbug.com/1224715 for
  // details.
  aura::Window* root = nullptr;
  const gfx::Rect bounds_in_pixels =
      display::Screen::Get()->DIPToScreenRectInWindow(
          root, AdjustedContentBounds(bounds));
  AsWindowTreeHost()->SetBoundsInPixels(bounds_in_pixels);
}

void DesktopWindowTreeHostWin::SetAllowScreenshots(bool allow) {
  if (allow_screenshots_ == allow) {
    return;
  }

  allow_screenshots_ = allow;

  // If the window is not visible, do not set the window display affinity
  // because `SetWindowDisplayAffinity` will attempt to compose the window,
  // resulting in a blank window. Instead, we will update it in the `Show`
  // function.
  if (!IsVisible()) {
    return;
  }

  UpdateDisplayAffinity();
}

bool DesktopWindowTreeHostWin::AreScreenshotsAllowed() {
  return allow_screenshots_;
}

void DesktopWindowTreeHostWin::SetExcludeFromScreenCapture(bool exclude) {
  if (exclude_from_capture_ == exclude) {
    return;
  }

  exclude_from_capture_ = exclude;

  if (!IsVisible()) {
    return;
  }

  UpdateDisplayAffinity();
}

void DesktopWindowTreeHostWin::ClientDestroyedWidget() {
  if (native_widget_delegate_) {
    // Send an explicit visibility change event when the delegate is reset. This
    // ensures delegate's visibility state is updated appropriately before the
    // delegate pointer is nullified.
    native_widget_delegate_->OnNativeWidgetVisibilityChanged(false);
    native_widget_delegate_ = nullptr;
  }
  DesktopWindowTreeHost::ClientDestroyedWidget();
}

////////////////////////////////////////////////////////////////////////////////
// DesktopWindowTreeHostWin, private:

Widget* DesktopWindowTreeHostWin::GetWidget() {
  return native_widget_delegate_ ? native_widget_delegate_->AsWidget()
                                 : nullptr;
}

const Widget* DesktopWindowTreeHostWin::GetWidget() const {
  return native_widget_delegate_ ? native_widget_delegate_->AsWidget()
                                 : nullptr;
}

HWND DesktopWindowTreeHostWin::GetHWND() const {
  return message_handler_->hwnd();
}

bool DesktopWindowTreeHostWin::IsModalWindowActive() const {
  // This function can get called during window creation which occurs before
  // dispatcher() has been created.
  if (!dispatcher()) {
    return false;
  }

  const auto is_active = [](const aura::Window* child) {
    return child->GetProperty(aura::client::kModalKey) !=
               ui::mojom::ModalType::kNone &&
           child->TargetVisibility();
  };
  return std::ranges::any_of(window()->children(), is_active);
}

void DesktopWindowTreeHostWin::CheckForMonitorChange() {
  display::Display nearest_display =
      display::Screen::Get()->GetDisplayNearestWindow(window());
  if (nearest_display == last_nearest_display_) {
    return;
  }
  last_nearest_display_ = nearest_display;

  OnHostDisplayChanged();
}

gfx::Rect DesktopWindowTreeHostWin::AdjustedContentBounds(
    const gfx::Rect& bounds) {
  gfx::Size minimum_size;
  gfx::Size maximum_size;
  GetMinMaxSize(&minimum_size, &maximum_size);

  if (WidgetSizeIsClientSize()) {
    // Constraints are sized to the client area, not the HWND (see
    // OnGetMinMaxInfo), so inflate otherwise the max size will be too small.
    display::win::ScreenWin* screen = display::win::GetScreenWin();
    gfx::Size min_px = screen->DIPToScreenSize(GetHWND(), minimum_size);
    gfx::Size max_px = screen->DIPToScreenSize(GetHWND(), maximum_size);
    InflateClientSizeConstraintsInPixels(GetHWND(), min_px, max_px);
    minimum_size = screen->ScreenToDIPSize(GetHWND(), min_px);
    maximum_size = screen->ScreenToDIPSize(GetHWND(), max_px);
  }

  gfx::Size bounds_size = bounds.size();

  if (!maximum_size.IsEmpty()) {
    bounds_size.SetToMin(maximum_size);
  }

  if (!minimum_size.IsEmpty()) {
    bounds_size.SetToMax(minimum_size);
  }

  gfx::Rect adjusted_bounds = bounds;
  adjusted_bounds.set_size(bounds_size);
  return adjusted_bounds;
}

aura::Window* DesktopWindowTreeHostWin::content_window() {
  return desktop_native_widget_aura_->content_window();
}

void DesktopWindowTreeHostWin::UpdateDisplayAffinity() {
  DWORD affinity = WDA_NONE;
  if (exclude_from_capture_ && IsCaptureExclusionAllowed()) {
    // `exclude_from_capture_` is used to exclude the window completely from
    // screen capture. On Windows 10 20H1 and newer, we use
    // WDA_EXCLUDEFROMCAPTURE which hides the window from capture while keeping
    // it visible to the user.
    affinity = GetExclusionAffinity();
  } else if (!allow_screenshots_) {
    // `allow_screenshots_` is used to avoid capturing sensitive content.
    // When screenshots are not allowed, we set the affinity to WDA_MONITOR
    // rather than WDA_EXCLUDEFROMCAPTURE. WDA_MONITOR obscures the window with
    // a black rectangle in the capture, explicitly signaling to the user that
    // the content is intentionally hidden. In contrast, WDA_EXCLUDEFROMCAPTURE
    // completely removes the window from the capture stream, leaving no visual
    // cue.
    affinity = WDA_MONITOR;
  } else {
    // If we don't have our own exclusion state, check if we have a Win32 owner
    // window that is excluded, and inherit its affinity. This ensures that
    // newly created owned windows (like context menus or bubbles) inherit the
    // capture exclusion state of their parent window.
    HWND owner_hwnd = ::GetWindow(GetHWND(), GW_OWNER);
    if (owner_hwnd) {
      DWORD owner_affinity = WDA_NONE;
      if (::GetWindowDisplayAffinity(owner_hwnd, &owner_affinity) &&
          owner_affinity != WDA_NONE) {
        affinity = owner_affinity;
      }
    }
  }

  SetWindowDisplayAffinity(GetHWND(), affinity);

  // Propagate the new display affinity to any active native system menu.
  ApplyAffinityToActiveSystemMenus(affinity);

  // Propagate the new display affinity to all owned windows on the current
  // thread.
  ApplyAffinityToOwnedWindows(GetHWND(), affinity);
}

bool DesktopWindowTreeHostWin::IsCaptureExclusionAllowed() const {
  const bool is_remote_session = remote_session_for_testing_.value_or(
      ::GetSystemMetrics(SM_REMOTESESSION) != 0);

  // We allow exclusion if it's a local session, OR if the feature flag
  // overrides the remote session restriction.
  return !is_remote_session ||
         base::FeatureList::IsEnabled(
             views::features::kAllowWindowCaptureExclusionInRemoteSessions);
}

void DesktopWindowTreeHostWin::UpdateBackdropColorMode() {
  // Update backdrop theme using DWMWA_USE_IMMERSIVE_DARK_MODE.
  if (!ShouldAddDWMBackdrop()) {
    return;
  }

  // Ensure that the backdrop honors the OS dark mode setting.
  BOOL use_dark_mode =
      GetWidget()->GetColorMode() == ui::ColorProviderKey::ColorMode::kDark;
  HRESULT hr = DwmSetWindowAttribute(GetHWND(), DWMWA_USE_IMMERSIVE_DARK_MODE,
                                     &use_dark_mode, sizeof(use_dark_mode));
  if (FAILED(hr)) {
    // If DwmSetWindowAttribute fails, it indicates that there was a problem
    // setting dark mode for the window. In this state, the mode change is not
    // applied and the backdrop will remain in its previous state.
    LOG(ERROR) << "Failed to set DWM immersive dark mode: "
               << logging::SystemErrorCodeToString(
                      static_cast<logging::SystemErrorCode>(hr));
  }
}

bool DesktopWindowTreeHostWin::ShouldAddDWMBackdrop() {
  // If the Redirection Surface is removed, there needs to be a replacement
  // "background" of the Chromium window. `DWM_SYSTEMBACKDROP_TYPE` tells DWM
  // to blur the contents behind the chromium window to yield a translucent
  // "frosted glass" effect. This will show whenever the GPU crashes or is not
  // ready by the time the window updates size or shape. Translucent windows
  // do not need a backdrop as it would show up in unexpected ways - i.e. a
  // gutter. Additionally, ensure that this effect is only applied to top level
  // windows since child windows are not supported.
  return ((message_handler_->window_ex_style() & WS_EX_NOREDIRECTIONBITMAP) ==
          WS_EX_NOREDIRECTIONBITMAP) &&
         !message_handler_->is_translucent() &&
         (GetHWND() == GetAncestor(GetHWND(), GA_ROOT));
}

void DesktopWindowTreeHostWin::ClearBackgroundPaintBrush() {
  if (background_paint_brush_) {
    DeleteObject(background_paint_brush_);
    background_paint_brush_ = nullptr;
  }
}

////////////////////////////////////////////////////////////////////////////////
// DesktopWindowTreeHost, public:

// static
DesktopWindowTreeHost* DesktopWindowTreeHost::Create(
    internal::NativeWidgetDelegate* native_widget_delegate,
    DesktopNativeWidgetAura* desktop_native_widget_aura) {
  return new DesktopWindowTreeHostWin(native_widget_delegate,
                                      desktop_native_widget_aura);
}

}  // namespace views
