#include "ui/aura/window_tree_host.h"

#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/memory/ptr_util.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/field_trial_params.h"
#include "base/no_destructor.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "build/chromeos_buildflags.h"
#include "components/viz/common/features.h"
#include "components/viz/common/surfaces/frame_sink_id.h"
#include "components/viz/host/host_frame_sink_manager.h"
#include "ui/aura/client/capture_client.h"
#include "ui/aura/client/cursor_client.h"
#include "ui/aura/env.h"
#include "ui/aura/native_window_occlusion_tracker.h"
#include "ui/aura/scoped_keyboard_hook.h"
#include "ui/aura/scoped_simple_keyboard_hook.h"
#include "ui/aura/window.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/aura/window_targeter.h"
#include "ui/aura/window_tree_host_observer.h"
#include "ui/base/cursor/mojom/cursor_type.mojom-shared.h"
#include "ui/base/ime/init/input_method_factory.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/layout.h"
#include "ui/base/ui_base_features.h"
#include "ui/base/view_prop.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/compositor_switches.h"
#include "ui/compositor/layer.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/point3_f.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gfx/icc_profile.h"
#include "ui/gfx/switches.h"
#include "ui/platform_window/platform_window_init_properties.h"

namespace aura {

namespace {

const char kWindowTreeHostForAcceleratedWidget[] =
    "__AURA_WINDOW_TREE_HOST_ACCELERATED_WIDGET__";

cc::Layer* ccLayerFromUiLayer(ui::Layer* layer) {
  return static_cast<ui::LayerAnimationDelegate*>(layer)->GetCcLayer();
}

#if DCHECK_IS_ON()
class ScopedLocalSurfaceIdValidator {
 public:
  explicit ScopedLocalSurfaceIdValidator(Window* window)
      : window_(window),
        local_surface_id_(window ? window->GetLocalSurfaceId()
                                 : viz::LocalSurfaceId()) {}

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

  ~ScopedLocalSurfaceIdValidator() {
    if (window_) {
      DCHECK_EQ(local_surface_id_, window_->GetLocalSurfaceId());
    }
  }

 private:
  Window* const window_;
  const viz::LocalSurfaceId local_surface_id_;
};
#else
class ScopedLocalSurfaceIdValidator {
 public:
  explicit ScopedLocalSurfaceIdValidator(Window* window) {}
  ~ScopedLocalSurfaceIdValidator() {}
};
#endif

class HostThrottler {
 public:
  static HostThrottler& GetInstance() {
    static base::NoDestructor<HostThrottler> instance;
    return *instance;
  }

  void AddHost(WindowTreeHost* host) {
    if (base::Contains(hosts_, host))
      return;
    hosts_.insert(host);
    UpdateHostFrameSinkManager();
  }

  void RemoveHost(WindowTreeHost* host) {
    if (!base::Contains(hosts_, host))
      return;
    hosts_.erase(host);
    UpdateHostFrameSinkManager();
  }

  const base::flat_set<WindowTreeHost*>& hosts() const { return hosts_; }

 private:
  friend class base::NoDestructor<HostThrottler>;

  HostThrottler() = default;
  ~HostThrottler() = default;

  void UpdateHostFrameSinkManager() {
    std::vector<viz::FrameSinkId> ids;
    ids.reserve(hosts_.size());
    for (WindowTreeHost* host : hosts_)
      ids.push_back(host->compositor()->frame_sink_id());
    Env::GetInstance()->context_factory()->GetHostFrameSinkManager()->Throttle(
        ids, base::Seconds(1));
  }

  base::flat_set<WindowTreeHost*> hosts_;
};

}  

class WindowTreeHost::HideHelper {
 public:
  explicit HideHelper(WindowTreeHost* host)
      : host_(host),
        compositor_root_layer_(
            ccLayerFromUiLayer(host->window()->layer())->parent()),
        layer_for_transition_(
            std::make_unique<ui::Layer>(ui::LAYER_SOLID_COLOR)) {
    layer_for_transition_->SetColor(SK_ColorWHITE);
    aura::Window* host_window = host_->window();

    ccLayerFromUiLayer(host_window->layer())->RemoveFromParent();
    layer_for_transition_->SetBounds(host_window->bounds());
    compositor_root_layer_->AddChild(
        ccLayerFromUiLayer(layer_for_transition_.get()));
    layer_for_transition_->OnDeviceScaleFactorChanged(
        host_window->layer()->device_scale_factor());

    host_->compositor()->RequestPresentationTimeForNextFrame(base::BindOnce(
        &HideHelper::OnFramePresented, weak_ptr_factory_.GetWeakPtr()));
  }

  ~HideHelper() {
    ui::Layer* host_window_layer = host_->window()->layer();
    compositor_root_layer_->AddChild(ccLayerFromUiLayer(host_window_layer));
    host_window_layer->OnDeviceScaleFactorChanged(
        layer_for_transition_->device_scale_factor());
  }

 private:
  void OnFramePresented(const gfx::PresentationFeedback& feedback) {
    host_->FinishHideTransition();

  }

  WindowTreeHost* host_;
  scoped_refptr<cc::Layer> compositor_root_layer_;
  std::unique_ptr<ui::Layer> layer_for_transition_;
  base::WeakPtrFactory<HideHelper> weak_ptr_factory_{this};
};

WindowTreeHost::VideoCaptureLock::~VideoCaptureLock() {
  if (host_)
    host_->DecrementVideoCaptureCount();
}

WindowTreeHost::VideoCaptureLock::VideoCaptureLock(WindowTreeHost* host)
    : host_(host->GetWeakPtr()) {}

WindowTreeHost::~WindowTreeHost() {
  if (display::Screen::GetScreen())
  DCHECK(!compositor_) << "compositor must be destroyed before root window";
  if (owned_input_method_) {
    delete input_method_;
    input_method_ = nullptr;
  }
}

WindowTreeHost* WindowTreeHost::GetForAcceleratedWidget(
    gfx::AcceleratedWidget widget) {
  return reinterpret_cast<WindowTreeHost*>(
      ui::ViewProp::GetValue(widget, kWindowTreeHostForAcceleratedWidget));
}

void WindowTreeHost::InitHost() {
  display::Display display =
      display::Screen::GetScreen()->GetDisplayNearestWindow(window());
  device_scale_factor_ = display.device_scale_factor();

  UpdateRootWindowSizeInPixels();
  InitCompositor();
  Env::GetInstance()->NotifyHostInitialized(this);
}

void WindowTreeHost::AddObserver(WindowTreeHostObserver* observer) {
  observers_.AddObserver(observer);
}

void WindowTreeHost::RemoveObserver(WindowTreeHostObserver* observer) {
  observers_.RemoveObserver(observer);
}

bool WindowTreeHost::HasObserver(const WindowTreeHostObserver* observer) const {
  return observers_.HasObserver(observer);
}

base::WeakPtr<WindowTreeHost> WindowTreeHost::GetWeakPtr() {
  return weak_factory_.GetWeakPtr();
}

gfx::Transform WindowTreeHost::GetRootTransform() const {
  gfx::Transform transform;
  transform.Scale(device_scale_factor_, device_scale_factor_);
  transform *= window()->layer()->transform();
  return transform;
}

void WindowTreeHost::SetRootTransform(const gfx::Transform& transform) {
  window()->SetTransform(transform);
  UpdateRootWindowSizeInPixels();
}

gfx::Transform WindowTreeHost::GetInverseRootTransform() const {
  gfx::Transform invert;
  gfx::Transform transform = GetRootTransform();
  if (!transform.GetInverse(&invert))
    return transform;
  return invert;
}

void WindowTreeHost::SetDisplayTransformHint(gfx::OverlayTransform transform) {
  if (compositor()->display_transform_hint() == transform)
    return;

  compositor()->SetDisplayTransformHint(transform);
  UpdateCompositorScaleAndSize(GetBoundsInPixels().size());
}

gfx::Transform WindowTreeHost::GetRootTransformForLocalEventCoordinates()
    const {
  return GetRootTransform();
}

gfx::Transform WindowTreeHost::GetInverseRootTransformForLocalEventCoordinates()
    const {
  gfx::Transform invert;
  gfx::Transform transform = GetRootTransformForLocalEventCoordinates();
  if (!transform.GetInverse(&invert))
    return transform;
  return invert;
}

void WindowTreeHost::UpdateRootWindowSizeInPixels() {

  bool compositor_inited = !!compositor()->root_layer();
  ScopedLocalSurfaceIdValidator lsi_validator(compositor_inited ? window()
                                                                : nullptr);
  gfx::Rect transformed_bounds_in_pixels =
      GetTransformedRootWindowBoundsInPixels(GetBoundsInPixels().size());
  window()->SetBounds(transformed_bounds_in_pixels);
}

void WindowTreeHost::UpdateCompositorScaleAndSize(
    const gfx::Size& new_size_in_pixels) {
  gfx::Rect new_bounds(new_size_in_pixels);
  if (compositor_->display_transform_hint() ==
          gfx::OVERLAY_TRANSFORM_ROTATE_90 ||
      compositor_->display_transform_hint() ==
          gfx::OVERLAY_TRANSFORM_ROTATE_270) {
    new_bounds.Transpose();
  }

  window_->AllocateLocalSurfaceId();
  ScopedLocalSurfaceIdValidator lsi_validator(window());
  compositor_->SetScaleAndSize(device_scale_factor_, new_bounds.size(),
                               window_->GetLocalSurfaceId());
}

void WindowTreeHost::ConvertDIPToScreenInPixels(gfx::Point* point) const {
  ConvertDIPToPixels(point);
  gfx::Point location = GetLocationOnScreenInPixels();
  point->Offset(location.x(), location.y());
}

void WindowTreeHost::ConvertScreenInPixelsToDIP(gfx::Point* point) const {
  gfx::Point location = GetLocationOnScreenInPixels();
  point->Offset(-location.x(), -location.y());
  ConvertPixelsToDIP(point);
}

void WindowTreeHost::ConvertDIPToPixels(gfx::Point* point) const {
  auto point_3f = gfx::Point3F(gfx::PointF(*point));
  GetRootTransform().TransformPoint(&point_3f);
  *point = gfx::ToFlooredPoint(point_3f.AsPointF());
}

void WindowTreeHost::ConvertPixelsToDIP(gfx::Point* point) const {
  auto point_3f = gfx::Point3F(gfx::PointF(*point));
  GetInverseRootTransform().TransformPoint(&point_3f);
  *point = gfx::ToFlooredPoint(point_3f.AsPointF());
}

void WindowTreeHost::SetCursor(gfx::NativeCursor cursor) {
  last_cursor_ = cursor;

  SetCursorNative(cursor);
}

void WindowTreeHost::OnCursorVisibilityChanged(bool show) {

  if (!show) {
    ui::EventDispatchDetails details = dispatcher()->DispatchMouseExitAtPoint(
        nullptr, dispatcher()->GetLastMouseLocationInRoot(),
        ui::EF_CURSOR_HIDE);
    if (details.dispatcher_destroyed)
      return;
  }

  OnCursorVisibilityChangedNative(show);
}

void WindowTreeHost::MoveCursorToLocationInDIP(
    const gfx::Point& location_in_dip) {
  gfx::Point host_location(location_in_dip);
  ConvertDIPToPixels(&host_location);
  MoveCursorToInternal(location_in_dip, host_location);
}

void WindowTreeHost::MoveCursorToLocationInPixels(
    const gfx::Point& location_in_pixels) {
  gfx::Point root_location(location_in_pixels);
  ConvertPixelsToDIP(&root_location);
  MoveCursorToInternal(root_location, location_in_pixels);
}

ui::InputMethod* WindowTreeHost::GetInputMethod() {
  if (!input_method_) {
    input_method_ =
        ui::CreateInputMethod(this, GetAcceleratedWidget()).release();
    owned_input_method_ = true;
  }
  return input_method_;
}

void WindowTreeHost::SetSharedInputMethod(ui::InputMethod* input_method) {
  if (input_method_ && owned_input_method_)
    delete input_method_;
  input_method_ = input_method;
  owned_input_method_ = false;
}

ui::EventDispatchDetails WindowTreeHost::DispatchKeyEventPostIME(
    ui::KeyEvent* event) {

  DCHECK(!dispatcher_->should_skip_ime());
  dispatcher_->set_skip_ime(true);

  ui::Event::DispatcherApi(event).set_target(nullptr);

  ui::EventDispatchDetails dispatch_details =
      GetEventSink()->OnEventFromSource(event);
  if (!dispatch_details.dispatcher_destroyed)
    dispatcher_->set_skip_ime(false);
  return dispatch_details;
}

ui::EventSink* WindowTreeHost::GetEventSink() {
  return dispatcher_.get();
}

int64_t WindowTreeHost::GetDisplayId() {
  return display::Screen::GetScreen()->GetDisplayNearestWindow(window()).id();
}

void WindowTreeHost::Show() {

  DCHECK(compositor());
  DCHECK_EQ(compositor()->root_layer(), window()->layer());
  OnAcceleratedWidgetMadeVisible(true);
  ShowImpl();
  window()->Show();
}

void WindowTreeHost::Hide() {
  HideImpl();
  OnAcceleratedWidgetMadeVisible(false);
}

gfx::Rect WindowTreeHost::GetBoundsInAcceleratedWidgetPixelCoordinates() {
  return gfx::Rect(GetBoundsInPixels().size());
}

std::unique_ptr<ScopedKeyboardHook> WindowTreeHost::CaptureSystemKeyEvents(
    absl::optional<base::flat_set<ui::DomCode>> dom_codes) {

  if (!base::FeatureList::IsEnabled(features::kSystemKeyboardLock))
    return std::make_unique<ScopedSimpleKeyboardHook>(std::move(dom_codes));

  if (CaptureSystemKeyEventsImpl(std::move(dom_codes)))
    return std::make_unique<ScopedKeyboardHook>(weak_factory_.GetWeakPtr());
  return nullptr;
}

bool WindowTreeHost::ShouldSendKeyEventToIme() {
  return true;
}

bool WindowTreeHost::IsNativeWindowOcclusionEnabled() const {
  return native_window_occlusion_enabled_;
}

void WindowTreeHost::SetNativeWindowOcclusionState(
    Window::OcclusionState state,
    const SkRegion& occluded_region) {
  if (occlusion_state_ == state && occluded_region_ == occluded_region)
    return;

  occlusion_state_ = state;

  if (compositor() && accelerated_widget_made_visible_ &&
      NativeWindowOcclusionTracker::
          IsNativeWindowOcclusionTrackingAlwaysEnabled(this)) {
    if (ShouldThrottleWhenOccluded()) {

      if (occlusion_state_ == Window::OcclusionState::OCCLUDED)
        HostThrottler::GetInstance().AddHost(this);
      else
        HostThrottler::GetInstance().RemoveHost(this);
    } else {
      const bool visible = CalculateCompositorVisibilityFromOcclusionState();

      if (visible != compositor()->IsVisible() || is_transitioning_to_hidden())
        UpdateCompositorVisibility(visible);
    }
  }

  for (WindowTreeHostObserver& observer : observers_)
    observer.OnOcclusionStateChanged(this, state, occluded_region);
}

std::unique_ptr<ScopedEnableUnadjustedMouseEvents>
WindowTreeHost::RequestUnadjustedMovement() {
  NOTIMPLEMENTED();
  return nullptr;
}

bool WindowTreeHost::SupportsMouseLock() {
  return false;
}

void WindowTreeHost::LockMouse(Window* window) {
  Window* root_window = window->GetRootWindow();
  DCHECK(root_window);

  auto* cursor_client = client::GetCursorClient(root_window);
  if (cursor_client) {
    cursor_client->HideCursor();
    cursor_client->LockCursor();
  }
}

void WindowTreeHost::UnlockMouse(Window* window) {
  Window* root_window = window->GetRootWindow();
  DCHECK(root_window);

  if (window->HasCapture())
    window->ReleaseCapture();

  auto* cursor_client = client::GetCursorClient(root_window);
  if (cursor_client) {
    cursor_client->UnlockCursor();
    cursor_client->ShowCursor();
  }
}

std::unique_ptr<WindowTreeHost::VideoCaptureLock>
WindowTreeHost::CreateVideoCaptureLock() {
  if (!NativeWindowOcclusionTracker::
          IsNativeWindowOcclusionTrackingAlwaysEnabled(this)) {
    return nullptr;
  }

  if (ShouldThrottleWhenOccluded())
    return nullptr;

  ++video_capture_count_;
  MaybeUpdateComposibleVisibilityForVideoLockCountChange();

  return base::WrapUnique(new VideoCaptureLock(this));
}

WindowTreeHost::WindowTreeHost(std::unique_ptr<Window> window)
    : window_(window.release()) {  
  if (!window_)
    window_ = new Window(nullptr);
  auto display = display::Screen::GetScreen()->GetDisplayNearestWindow(window_);
  device_scale_factor_ = display.device_scale_factor();
#if defined(OS_WIN)

  native_window_occlusion_enabled_ =
      !base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kHeadless) &&
      base::FeatureList::IsEnabled(features::kCalculateNativeWinOcclusion);
#endif
}

void WindowTreeHost::IntializeDeviceScaleFactor(float device_scale_factor) {
  DCHECK(!compositor_->root_layer()) << "Only call this before InitHost()";
  device_scale_factor_ = device_scale_factor;
}

void WindowTreeHost::UpdateCompositorVisibility(bool visible) {
  if (!compositor())
    return;

  if (ShouldThrottleWhenOccluded()) {

    HostThrottler::GetInstance().RemoveHost(this);
  }

  if (visible) {
    if (is_transitioning_to_hidden())
      RestoreHideTransitionState();
  } else {
    if (is_transitioning_to_hidden())
      return;
    if (ShouldReleaseResourcesWhenHidden()) {
      StartReleasingResourcesForHide();

      return;
    }
  }
  compositor()->SetVisible(visible);
}

void WindowTreeHost::DestroyCompositor() {
  if (!compositor_)
    return;

  if (ShouldThrottleWhenOccluded())
    HostThrottler::GetInstance().RemoveHost(this);

  hide_helper_.reset();

  compositor_->RemoveObserver(this);
  compositor_.reset();
  if (NativeWindowOcclusionTracker::
          IsNativeWindowOcclusionTrackingAlwaysEnabled(this)) {
    NativeWindowOcclusionTracker::DisableNativeWindowOcclusionTracking(this);
  }
}

void WindowTreeHost::DestroyDispatcher() {
  Env::GetInstance()->NotifyHostDestroyed(this);
  delete window_;
  window_ = nullptr;
  dispatcher_.reset();

}

void WindowTreeHost::OnAcceleratedWidgetMadeVisible(bool value) {
  if (accelerated_widget_made_visible_ == value)
    return;

  accelerated_widget_made_visible_ = value;

  UpdateCompositorVisibility(value);
}

void WindowTreeHost::CreateCompositor(
    bool force_software_compositor,
    bool use_external_begin_frame_control,
    bool enable_compositing_based_throttling) {
  Env* env = Env::GetInstance();
  ui::ContextFactory* context_factory = env->context_factory();
  DCHECK(context_factory);
  compositor_ = std::make_unique<ui::Compositor>(
      context_factory->AllocateFrameSinkId(), context_factory,
      base::ThreadTaskRunnerHandle::Get(), ui::IsPixelCanvasRecordingEnabled(),
      use_external_begin_frame_control, force_software_compositor,
      enable_compositing_based_throttling);
#if BUILDFLAG(IS_CHROMEOS_ASH)
  compositor_->AddObserver(this);
#endif
  if (!dispatcher()) {
    window()->Init(ui::LAYER_NOT_DRAWN);
    window()->set_host(this);
    window()->SetName("RootWindow");
    dispatcher_ = std::make_unique<WindowEventDispatcher>(this);
  }
}

void WindowTreeHost::InitCompositor() {
  DCHECK(!compositor_->root_layer());
  compositor_->SetScaleAndSize(device_scale_factor_, GetBoundsInPixels().size(),
                               window()->GetLocalSurfaceId());
  compositor_->SetRootLayer(window()->layer());

  display::Display display =
      display::Screen::GetScreen()->GetDisplayNearestWindow(window());
  compositor_->SetDisplayColorSpaces(display.color_spaces());
}

void WindowTreeHost::OnAcceleratedWidgetAvailable() {
  compositor_->SetAcceleratedWidget(GetAcceleratedWidget());
  prop_ = std::make_unique<ui::ViewProp>(
      GetAcceleratedWidget(), kWindowTreeHostForAcceleratedWidget, this);
  if (NativeWindowOcclusionTracker::
          IsNativeWindowOcclusionTrackingAlwaysEnabled(this)) {
    NativeWindowOcclusionTracker::EnableNativeWindowOcclusionTracking(this);
  }
}

void WindowTreeHost::OnHostMovedInPixels(
    const gfx::Point& new_location_in_pixels) {
  TRACE_EVENT1("ui", "WindowTreeHost::OnHostMovedInPixels", "origin",
               new_location_in_pixels.ToString());

  for (WindowTreeHostObserver& observer : observers_)
    observer.OnHostMovedInPixels(this, new_location_in_pixels);
}

void WindowTreeHost::OnHostResizedInPixels(
    const gfx::Size& new_size_in_pixels) {

  if (!compositor_)
    return;

  display::Display display =
      display::Screen::GetScreen()->GetDisplayNearestWindow(window());
  device_scale_factor_ = display.device_scale_factor();
  UpdateRootWindowSizeInPixels();

  UpdateCompositorScaleAndSize(new_size_in_pixels);

  for (WindowTreeHostObserver& observer : observers_)
    observer.OnHostResized(this);
}

void WindowTreeHost::OnHostWorkspaceChanged() {
  for (WindowTreeHostObserver& observer : observers_)
    observer.OnHostWorkspaceChanged(this);
}

void WindowTreeHost::OnHostDisplayChanged() {
  if (!compositor_)
    return;
  display::Display display =
      display::Screen::GetScreen()->GetDisplayNearestWindow(window());
  compositor_->SetDisplayColorSpaces(display.color_spaces());
}

void WindowTreeHost::OnHostCloseRequested() {
  for (WindowTreeHostObserver& observer : observers_)
    observer.OnHostCloseRequested(this);
}

void WindowTreeHost::OnHostLostWindowCapture() {

  if (!window())
    return;
  Window* capture_window = client::GetCaptureWindow(window());
  if (capture_window && capture_window->GetRootWindow() == window())
    capture_window->ReleaseCapture();
}

void WindowTreeHost::OnDisplayMetricsChanged(const display::Display& display,
                                             uint32_t metrics) {
  if (metrics & DisplayObserver::DISPLAY_METRIC_COLOR_SPACE && compositor_ &&
      display.id() == GetDisplayId())
    compositor_->SetDisplayColorSpaces(display.color_spaces());

#if !BUILDFLAG(IS_CHROMEOS_ASH)
  if (metrics & DISPLAY_METRIC_DEVICE_SCALE_FACTOR &&
      display.id() == GetDisplayId())
    OnHostResizedInPixels(GetBoundsInPixels().size());
#endif
}

gfx::Rect WindowTreeHost::GetTransformedRootWindowBoundsInPixels(
    const gfx::Size& size_in_pixels) const {
  gfx::RectF new_bounds = gfx::RectF(gfx::Rect(size_in_pixels));
  GetInverseRootTransform().TransformRect(&new_bounds);
  return gfx::ToEnclosingRect(new_bounds);
}

void WindowTreeHost::SetNativeWindowOcclusionEnabled(bool enable) {
  native_window_occlusion_enabled_ = enable;

}

void WindowTreeHost::DecrementVideoCaptureCount() {
  DCHECK_GT(video_capture_count_, 0);
  --video_capture_count_;
  MaybeUpdateComposibleVisibilityForVideoLockCountChange();
}

void WindowTreeHost::MaybeUpdateComposibleVisibilityForVideoLockCountChange() {

  DCHECK(!ShouldThrottleWhenOccluded());

  if (video_capture_count_ > 1 || !compositor() ||
      !accelerated_widget_made_visible_) {
    return;
  }
  const bool visible = CalculateCompositorVisibilityFromOcclusionState();
  if (visible != compositor()->IsVisible() || is_transitioning_to_hidden())
    UpdateCompositorVisibility(visible);
}

bool WindowTreeHost::CalculateCompositorVisibilityFromOcclusionState() const {
  switch (occlusion_state_) {
    case Window::OcclusionState::UNKNOWN:
      return true;
    case Window::OcclusionState::VISIBLE:
      return true;
    case Window::OcclusionState::OCCLUDED: {

      return video_capture_count_ != 0;
    }
    case Window::OcclusionState::HIDDEN:

      return false;
  }
}

bool WindowTreeHost::ShouldReleaseResourcesWhenHidden() const {
#if defined(OS_WIN)
  if (!base::FeatureList::IsEnabled(
          features::kApplyNativeOcclusionToCompositor) ||
      !IsNativeWindowOcclusionEnabled()) {
    return false;
  }

  const std::string type = base::GetFieldTrialParamValueByFeature(
      features::kApplyNativeOcclusionToCompositor,
      features::kApplyNativeOcclusionToCompositorType);
  return type == features::kApplyNativeOcclusionToCompositorTypeRelease;
#else
  return false;
#endif
}

bool WindowTreeHost::ShouldThrottleWhenOccluded() const {
#if defined(OS_WIN)
  if (!base::FeatureList::IsEnabled(
          features::kApplyNativeOcclusionToCompositor) ||
      !IsNativeWindowOcclusionEnabled()) {
    return false;
  }

  const std::string type = base::GetFieldTrialParamValueByFeature(
      features::kApplyNativeOcclusionToCompositor,
      features::kApplyNativeOcclusionToCompositorType);
  return type == features::kApplyNativeOcclusionToCompositorTypeThrottle;
#else
  return false;
#endif
}

void WindowTreeHost::RestoreHideTransitionState() {
  DCHECK(is_transitioning_to_hidden());
  hide_helper_.reset();
}

void WindowTreeHost::FinishHideTransition() {
  DCHECK(is_transitioning_to_hidden());
  compositor_->SetVisible(false);
  RestoreHideTransitionState();
}

const base::flat_set<WindowTreeHost*>&
WindowTreeHost::GetThrottledHostsForTesting() {
  return HostThrottler::GetInstance().hosts();
}

void WindowTreeHost::StartReleasingResourcesForHide() {
  DCHECK(!is_transitioning_to_hidden());
  if (compositor_->size().IsEmpty()) {

    compositor_->SetVisible(false);
    return;
  }
  hide_helper_ = std::make_unique<HideHelper>(this);
}

void WindowTreeHost::MoveCursorToInternal(const gfx::Point& root_location,
                                          const gfx::Point& host_location) {
  last_cursor_request_position_in_host_ = host_location;
  MoveCursorToScreenLocationInPixels(host_location);
  client::CursorClient* cursor_client = client::GetCursorClient(window());
  if (cursor_client) {
    const display::Display& display =
        display::Screen::GetScreen()->GetDisplayNearestWindow(window());
    cursor_client->SetDisplay(display);
  }
  dispatcher()->OnCursorMovedToRootLocation(root_location);
}

void WindowTreeHost::OnCompositingEnded(ui::Compositor* compositor) {
  if (!holding_pointer_moves_)
    return;

  dispatcher_->ReleasePointerMoves();
  holding_pointer_moves_ = false;
}

void WindowTreeHost::OnCompositingChildResizing(ui::Compositor* compositor) {
  if (!Env::GetInstance()->throttle_input_on_resize() || holding_pointer_moves_)
    return;
  dispatcher_->HoldPointerMoves();
  holding_pointer_moves_ = true;
}

void WindowTreeHost::OnFrameSinksToThrottleUpdated(
    const base::flat_set<viz::FrameSinkId>& ids) {
  for (auto& observer : observers_)
    observer.OnCompositingFrameSinksToThrottleUpdated(this, ids);
}

}
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/aura/window_tree_host.h"

#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/memory/ptr_util.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/field_trial_params.h"
#include "base/no_destructor.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "build/chromeos_buildflags.h"
#include "components/viz/common/features.h"
#include "components/viz/common/surfaces/frame_sink_id.h"
#include "components/viz/host/host_frame_sink_manager.h"
#include "ui/aura/client/capture_client.h"
#include "ui/aura/client/cursor_client.h"
#include "ui/aura/env.h"
#include "ui/aura/native_window_occlusion_tracker.h"
#include "ui/aura/scoped_keyboard_hook.h"
#include "ui/aura/scoped_simple_keyboard_hook.h"
#include "ui/aura/window.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/aura/window_targeter.h"
#include "ui/aura/window_tree_host_observer.h"
#include "ui/base/cursor/mojom/cursor_type.mojom-shared.h"
#include "ui/base/ime/init/input_method_factory.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/layout.h"
#include "ui/base/ui_base_features.h"
#include "ui/base/view_prop.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/compositor_switches.h"
#include "ui/compositor/layer.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/point3_f.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gfx/icc_profile.h"
#include "ui/gfx/switches.h"
#include "ui/platform_window/platform_window_init_properties.h"

namespace aura {

namespace {

const char kWindowTreeHostForAcceleratedWidget[] =
    "__AURA_WINDOW_TREE_HOST_ACCELERATED_WIDGET__";

// Returns the cc::Layer used by a ui::Layer. This is a bit ugly, and is done to
// avoid exposing cc::Layer outside of ui::Layer.
cc::Layer* ccLayerFromUiLayer(ui::Layer* layer) {
  return static_cast<ui::LayerAnimationDelegate*>(layer)->GetCcLayer();
}

#if DCHECK_IS_ON()
class ScopedLocalSurfaceIdValidator {
 public:
  explicit ScopedLocalSurfaceIdValidator(Window* window)
      : window_(window),
        local_surface_id_(window ? window->GetLocalSurfaceId()
                                 : viz::LocalSurfaceId()) {}

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

  ~ScopedLocalSurfaceIdValidator() {
    if (window_) {
      DCHECK_EQ(local_surface_id_, window_->GetLocalSurfaceId());
    }
  }

 private:
  Window* const window_;
  const viz::LocalSurfaceId local_surface_id_;
};
#else
class ScopedLocalSurfaceIdValidator {
 public:
  explicit ScopedLocalSurfaceIdValidator(Window* window) {}
  ~ScopedLocalSurfaceIdValidator() {}
};
#endif

// Used to throttle the frame rate of hosts that are occluded.
class HostThrottler {
 public:
  static HostThrottler& GetInstance() {
    static base::NoDestructor<HostThrottler> instance;
    return *instance;
  }

  void AddHost(WindowTreeHost* host) {
    if (base::Contains(hosts_, host))
      return;
    hosts_.insert(host);
    UpdateHostFrameSinkManager();
  }

  void RemoveHost(WindowTreeHost* host) {
    if (!base::Contains(hosts_, host))
      return;
    hosts_.erase(host);
    UpdateHostFrameSinkManager();
  }

  const base::flat_set<WindowTreeHost*>& hosts() const { return hosts_; }

 private:
  friend class base::NoDestructor<HostThrottler>;

  HostThrottler() = default;
  ~HostThrottler() = default;

  void UpdateHostFrameSinkManager() {
    std::vector<viz::FrameSinkId> ids;
    ids.reserve(hosts_.size());
    for (WindowTreeHost* host : hosts_)
      ids.push_back(host->compositor()->frame_sink_id());
    Env::GetInstance()->context_factory()->GetHostFrameSinkManager()->Throttle(
        ids, base::Seconds(1));
  }

  // Set of hosts that are currently throttled.
  base::flat_set<WindowTreeHost*> hosts_;
};

}  // namespace

// In order for viz to drop all references to resources used by the browser
// (and renderers) a CompositorFrame with *no* references to other surfaces
// must be generated. To do this a solid color layer is inserted into the
// layer tree, and the existing root layer is removed. Adding a layer at the top
// (keeping existing layer tree) is not enough, as the CompositorFrame still
// contains information about the surfaces/resources that are obscured. Once the
// frame is generated, the root layer (from `window_`) is inserted back in the
// Compositor's root layer and then the Compositor is hidden.
class WindowTreeHost::HideHelper {
 public:
  explicit HideHelper(WindowTreeHost* host)
      : host_(host),
        compositor_root_layer_(
            ccLayerFromUiLayer(host->window()->layer())->parent()),
        layer_for_transition_(
            std::make_unique<ui::Layer>(ui::LAYER_SOLID_COLOR)) {
    layer_for_transition_->SetColor(SK_ColorWHITE);
    aura::Window* host_window = host_->window();
    // SetRootLayer() resets the `compositor_` member in Layer. If
    // SetRootLayer() were used, it would mean the existing layer hierarchy
    // would no longer think it is in a compositor. As this state is temporary,
    // and purely to release resources, SetRootLayer() is not used.
    ccLayerFromUiLayer(host_window->layer())->RemoveFromParent();
    layer_for_transition_->SetBounds(host_window->bounds());
    compositor_root_layer_->AddChild(
        ccLayerFromUiLayer(layer_for_transition_.get()));
    layer_for_transition_->OnDeviceScaleFactorChanged(
        host_window->layer()->device_scale_factor());
    // Request a presentation frame. Once the frame is generated the real root
    // layer is added back (from the destructor).
    host_->compositor()->RequestPresentationTimeForNextFrame(base::BindOnce(
        &HideHelper::OnFramePresented, weak_ptr_factory_.GetWeakPtr()));
  }

  ~HideHelper() {
    ui::Layer* host_window_layer = host_->window()->layer();
    compositor_root_layer_->AddChild(ccLayerFromUiLayer(host_window_layer));
    host_window_layer->OnDeviceScaleFactorChanged(
        layer_for_transition_->device_scale_factor());
  }

 private:
  void OnFramePresented(const gfx::PresentationFeedback& feedback) {
    host_->FinishHideTransition();
    // WARNING: this has been deleted.
  }

  WindowTreeHost* host_;
  scoped_refptr<cc::Layer> compositor_root_layer_;
  std::unique_ptr<ui::Layer> layer_for_transition_;
  base::WeakPtrFactory<HideHelper> weak_ptr_factory_{this};
};

WindowTreeHost::VideoCaptureLock::~VideoCaptureLock() {
  if (host_)
    host_->DecrementVideoCaptureCount();
}

WindowTreeHost::VideoCaptureLock::VideoCaptureLock(WindowTreeHost* host)
    : host_(host->GetWeakPtr()) {}

////////////////////////////////////////////////////////////////////////////////
// WindowTreeHost, public:

WindowTreeHost::~WindowTreeHost() {
  if (display::Screen::GetScreen())
  DCHECK(!compositor_) << "compositor must be destroyed before root window";
  if (owned_input_method_) {
    delete input_method_;
    input_method_ = nullptr;
  }
}

// static
WindowTreeHost* WindowTreeHost::GetForAcceleratedWidget(
    gfx::AcceleratedWidget widget) {
  return reinterpret_cast<WindowTreeHost*>(
      ui::ViewProp::GetValue(widget, kWindowTreeHostForAcceleratedWidget));
}

void WindowTreeHost::InitHost() {
  display::Display display =
      display::Screen::GetScreen()->GetDisplayNearestWindow(window());
  device_scale_factor_ = display.device_scale_factor();

  UpdateRootWindowSizeInPixels();
  InitCompositor();
  Env::GetInstance()->NotifyHostInitialized(this);
}

void WindowTreeHost::AddObserver(WindowTreeHostObserver* observer) {
  observers_.AddObserver(observer);
}

void WindowTreeHost::RemoveObserver(WindowTreeHostObserver* observer) {
  observers_.RemoveObserver(observer);
}

bool WindowTreeHost::HasObserver(const WindowTreeHostObserver* observer) const {
  return observers_.HasObserver(observer);
}

base::WeakPtr<WindowTreeHost> WindowTreeHost::GetWeakPtr() {
  return weak_factory_.GetWeakPtr();
}

gfx::Transform WindowTreeHost::GetRootTransform() const {
  gfx::Transform transform;
  transform.Scale(device_scale_factor_, device_scale_factor_);
  transform *= window()->layer()->transform();
  return transform;
}

void WindowTreeHost::SetRootTransform(const gfx::Transform& transform) {
  window()->SetTransform(transform);
  UpdateRootWindowSizeInPixels();
}

gfx::Transform WindowTreeHost::GetInverseRootTransform() const {
  gfx::Transform invert;
  gfx::Transform transform = GetRootTransform();
  if (!transform.GetInverse(&invert))
    return transform;
  return invert;
}

void WindowTreeHost::SetDisplayTransformHint(gfx::OverlayTransform transform) {
  if (compositor()->display_transform_hint() == transform)
    return;

  compositor()->SetDisplayTransformHint(transform);
  UpdateCompositorScaleAndSize(GetBoundsInPixels().size());
}

gfx::Transform WindowTreeHost::GetRootTransformForLocalEventCoordinates()
    const {
  return GetRootTransform();
}

gfx::Transform WindowTreeHost::GetInverseRootTransformForLocalEventCoordinates()
    const {
  gfx::Transform invert;
  gfx::Transform transform = GetRootTransformForLocalEventCoordinates();
  if (!transform.GetInverse(&invert))
    return transform;
  return invert;
}

void WindowTreeHost::UpdateRootWindowSizeInPixels() {
  // Validate that the LocalSurfaceId does not change.
  bool compositor_inited = !!compositor()->root_layer();
  ScopedLocalSurfaceIdValidator lsi_validator(compositor_inited ? window()
                                                                : nullptr);
  gfx::Rect transformed_bounds_in_pixels =
      GetTransformedRootWindowBoundsInPixels(GetBoundsInPixels().size());
  window()->SetBounds(transformed_bounds_in_pixels);
}

void WindowTreeHost::UpdateCompositorScaleAndSize(
    const gfx::Size& new_size_in_pixels) {
  gfx::Rect new_bounds(new_size_in_pixels);
  if (compositor_->display_transform_hint() ==
          gfx::OVERLAY_TRANSFORM_ROTATE_90 ||
      compositor_->display_transform_hint() ==
          gfx::OVERLAY_TRANSFORM_ROTATE_270) {
    new_bounds.Transpose();
  }

  // Allocate a new LocalSurfaceId for the new size or scale factor.
  window_->AllocateLocalSurfaceId();
  ScopedLocalSurfaceIdValidator lsi_validator(window());
  compositor_->SetScaleAndSize(device_scale_factor_, new_bounds.size(),
                               window_->GetLocalSurfaceId());
}

void WindowTreeHost::ConvertDIPToScreenInPixels(gfx::Point* point) const {
  ConvertDIPToPixels(point);
  gfx::Point location = GetLocationOnScreenInPixels();
  point->Offset(location.x(), location.y());
}

void WindowTreeHost::ConvertScreenInPixelsToDIP(gfx::Point* point) const {
  gfx::Point location = GetLocationOnScreenInPixels();
  point->Offset(-location.x(), -location.y());
  ConvertPixelsToDIP(point);
}

void WindowTreeHost::ConvertDIPToPixels(gfx::Point* point) const {
  auto point_3f = gfx::Point3F(gfx::PointF(*point));
  GetRootTransform().TransformPoint(&point_3f);
  *point = gfx::ToFlooredPoint(point_3f.AsPointF());
}

void WindowTreeHost::ConvertPixelsToDIP(gfx::Point* point) const {
  auto point_3f = gfx::Point3F(gfx::PointF(*point));
  GetInverseRootTransform().TransformPoint(&point_3f);
  *point = gfx::ToFlooredPoint(point_3f.AsPointF());
}

void WindowTreeHost::SetCursor(gfx::NativeCursor cursor) {
  last_cursor_ = cursor;
  // A lot of code seems to depend on NULL cursors actually showing an arrow,
  // so just pass everything along to the host.
  SetCursorNative(cursor);
}

void WindowTreeHost::OnCursorVisibilityChanged(bool show) {
  // Clear any existing mouse hover effects when the cursor becomes invisible.
  // Note we do not need to dispatch a mouse enter when the cursor becomes
  // visible because that can only happen in response to a mouse event, which
  // will trigger its own mouse enter.
  if (!show) {
    ui::EventDispatchDetails details = dispatcher()->DispatchMouseExitAtPoint(
        nullptr, dispatcher()->GetLastMouseLocationInRoot(),
        ui::EF_CURSOR_HIDE);
    if (details.dispatcher_destroyed)
      return;
  }

  OnCursorVisibilityChangedNative(show);
}

void WindowTreeHost::MoveCursorToLocationInDIP(
    const gfx::Point& location_in_dip) {
  gfx::Point host_location(location_in_dip);
  ConvertDIPToPixels(&host_location);
  MoveCursorToInternal(location_in_dip, host_location);
}

void WindowTreeHost::MoveCursorToLocationInPixels(
    const gfx::Point& location_in_pixels) {
  gfx::Point root_location(location_in_pixels);
  ConvertPixelsToDIP(&root_location);
  MoveCursorToInternal(root_location, location_in_pixels);
}

ui::InputMethod* WindowTreeHost::GetInputMethod() {
  if (!input_method_) {
    input_method_ =
        ui::CreateInputMethod(this, GetAcceleratedWidget()).release();
    owned_input_method_ = true;
  }
  return input_method_;
}

void WindowTreeHost::SetSharedInputMethod(ui::InputMethod* input_method) {
  if (input_method_ && owned_input_method_)
    delete input_method_;
  input_method_ = input_method;
  owned_input_method_ = false;
}

ui::EventDispatchDetails WindowTreeHost::DispatchKeyEventPostIME(
    ui::KeyEvent* event) {
  // If dispatch to IME is already disabled we shouldn't reach here.
  DCHECK(!dispatcher_->should_skip_ime());
  dispatcher_->set_skip_ime(true);

  // InputMethod::DispatchKeyEvent() is called in PRE_DISPATCH phase, so event
  // target is reset here to avoid issues in subsequent processing phases.
  ui::Event::DispatcherApi(event).set_target(nullptr);

  // We should bypass event rewriters here as they've been tried before.
  ui::EventDispatchDetails dispatch_details =
      GetEventSink()->OnEventFromSource(event);
  if (!dispatch_details.dispatcher_destroyed)
    dispatcher_->set_skip_ime(false);
  return dispatch_details;
}

ui::EventSink* WindowTreeHost::GetEventSink() {
  return dispatcher_.get();
}

int64_t WindowTreeHost::GetDisplayId() {
  return display::Screen::GetScreen()->GetDisplayNearestWindow(window()).id();
}

void WindowTreeHost::Show() {
  // Ensure that compositor has been properly initialized, see InitCompositor()
  // and InitHost().
  DCHECK(compositor());
  DCHECK_EQ(compositor()->root_layer(), window()->layer());
  OnAcceleratedWidgetMadeVisible(true);
  ShowImpl();
  window()->Show();
}

void WindowTreeHost::Hide() {
  HideImpl();
  OnAcceleratedWidgetMadeVisible(false);
}

gfx::Rect WindowTreeHost::GetBoundsInAcceleratedWidgetPixelCoordinates() {
  return gfx::Rect(GetBoundsInPixels().size());
}

std::unique_ptr<ScopedKeyboardHook> WindowTreeHost::CaptureSystemKeyEvents(
    absl::optional<base::flat_set<ui::DomCode>> dom_codes) {
  // TODO(joedow): Remove the simple hook class/logic once this flag is removed.
  if (!base::FeatureList::IsEnabled(features::kSystemKeyboardLock))
    return std::make_unique<ScopedSimpleKeyboardHook>(std::move(dom_codes));

  if (CaptureSystemKeyEventsImpl(std::move(dom_codes)))
    return std::make_unique<ScopedKeyboardHook>(weak_factory_.GetWeakPtr());
  return nullptr;
}

bool WindowTreeHost::ShouldSendKeyEventToIme() {
  return true;
}

bool WindowTreeHost::IsNativeWindowOcclusionEnabled() const {
  return native_window_occlusion_enabled_;
}

void WindowTreeHost::SetNativeWindowOcclusionState(
    Window::OcclusionState state,
    const SkRegion& occluded_region) {
  if (occlusion_state_ == state && occluded_region_ == occluded_region)
    return;

  occlusion_state_ = state;

  if (compositor() && accelerated_widget_made_visible_ &&
      NativeWindowOcclusionTracker::
          IsNativeWindowOcclusionTrackingAlwaysEnabled(this)) {
    if (ShouldThrottleWhenOccluded()) {
      // Throttling doesn't update the visibility.
      if (occlusion_state_ == Window::OcclusionState::OCCLUDED)
        HostThrottler::GetInstance().AddHost(this);
      else
        HostThrottler::GetInstance().RemoveHost(this);
    } else {
      const bool visible = CalculateCompositorVisibilityFromOcclusionState();
      // Transitioning to hidden means the compositor state hasn't been updated
      // yet, but will. In this case, always route through
      // UpdateCompositorVisibility() to ensure state is correctly cleaned up.
      if (visible != compositor()->IsVisible() || is_transitioning_to_hidden())
        UpdateCompositorVisibility(visible);
    }
  }

  for (WindowTreeHostObserver& observer : observers_)
    observer.OnOcclusionStateChanged(this, state, occluded_region);
}

std::unique_ptr<ScopedEnableUnadjustedMouseEvents>
WindowTreeHost::RequestUnadjustedMovement() {
  NOTIMPLEMENTED();
  return nullptr;
}

bool WindowTreeHost::SupportsMouseLock() {
  return false;
}

void WindowTreeHost::LockMouse(Window* window) {
  Window* root_window = window->GetRootWindow();
  DCHECK(root_window);

  auto* cursor_client = client::GetCursorClient(root_window);
  if (cursor_client) {
    cursor_client->HideCursor();
    cursor_client->LockCursor();
  }
}

void WindowTreeHost::UnlockMouse(Window* window) {
  Window* root_window = window->GetRootWindow();
  DCHECK(root_window);

  if (window->HasCapture())
    window->ReleaseCapture();

  auto* cursor_client = client::GetCursorClient(root_window);
  if (cursor_client) {
    cursor_client->UnlockCursor();
    cursor_client->ShowCursor();
  }
}

std::unique_ptr<WindowTreeHost::VideoCaptureLock>
WindowTreeHost::CreateVideoCaptureLock() {
  if (!NativeWindowOcclusionTracker::
          IsNativeWindowOcclusionTrackingAlwaysEnabled(this)) {
    return nullptr;
  }
  // Throtting doesn't actually change the visibility, so no need for the lock.
  if (ShouldThrottleWhenOccluded())
    return nullptr;

  ++video_capture_count_;
  MaybeUpdateComposibleVisibilityForVideoLockCountChange();
  // WrapUnique() is used as constructor is private.
  return base::WrapUnique(new VideoCaptureLock(this));
}

////////////////////////////////////////////////////////////////////////////////
// WindowTreeHost, protected:

WindowTreeHost::WindowTreeHost(std::unique_ptr<Window> window)
    : window_(window.release()) {  // See header for details on ownership.
  if (!window_)
    window_ = new Window(nullptr);
  auto display = display::Screen::GetScreen()->GetDisplayNearestWindow(window_);
  device_scale_factor_ = display.device_scale_factor();
#if defined(OS_WIN)
  // The feature state is neccessary but not sufficient for checking if
  // occlusion is enabled. It may be disabled by other means (e.g., policy).
  native_window_occlusion_enabled_ =
      !base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kHeadless) &&
      base::FeatureList::IsEnabled(features::kCalculateNativeWinOcclusion);
#endif
}

void WindowTreeHost::IntializeDeviceScaleFactor(float device_scale_factor) {
  DCHECK(!compositor_->root_layer()) << "Only call this before InitHost()";
  device_scale_factor_ = device_scale_factor;
}

void WindowTreeHost::UpdateCompositorVisibility(bool visible) {
  if (!compositor())
    return;

  if (ShouldThrottleWhenOccluded()) {
    // If ShouldThrottleWhenOccluded() is true, then this function should only
    // be called if visibility is changed externally. In this case, assume
    // `occlusion_state_` is being ignored, and that throtting should be
    // disabled. For the most part, if ShouldThrottleWhenOccluded() is true,
    // the handling of occlusion changing is done in
    // SetNativeWindowOcclusionState().
    HostThrottler::GetInstance().RemoveHost(this);
  }

  if (visible) {
    if (is_transitioning_to_hidden())
      RestoreHideTransitionState();
  } else {
    if (is_transitioning_to_hidden())
      return;
    if (ShouldReleaseResourcesWhenHidden()) {
      StartReleasingResourcesForHide();
      // Compositor visibility is changed once transition completes.
      return;
    }
  }
  compositor()->SetVisible(visible);
}

void WindowTreeHost::DestroyCompositor() {
  if (!compositor_)
    return;

  if (ShouldThrottleWhenOccluded())
    HostThrottler::GetInstance().RemoveHost(this);

  // Explicitly delete the HideHelper early as it makes use of `compositor_`
  // and `window_`.
  hide_helper_.reset();

  compositor_->RemoveObserver(this);
  compositor_.reset();
  if (NativeWindowOcclusionTracker::
          IsNativeWindowOcclusionTrackingAlwaysEnabled(this)) {
    NativeWindowOcclusionTracker::DisableNativeWindowOcclusionTracking(this);
  }
}

void WindowTreeHost::DestroyDispatcher() {
  Env::GetInstance()->NotifyHostDestroyed(this);
  delete window_;
  window_ = nullptr;
  dispatcher_.reset();

  // TODO(beng): this comment is no longer quite valid since this function
  // isn't called from WED, and WED isn't a subclass of Window. So it seems
  // like we could just rely on ~Window now.
  // Destroy child windows while we're still valid. This is also done by
  // ~Window, but by that time any calls to virtual methods overriden here (such
  // as GetRootWindow()) result in Window's implementation. By destroying here
  // we ensure GetRootWindow() still returns this.
  //window()->RemoveOrDestroyChildren();
}

void WindowTreeHost::OnAcceleratedWidgetMadeVisible(bool value) {
  if (accelerated_widget_made_visible_ == value)
    return;

  accelerated_widget_made_visible_ = value;
  // Always update the compositor (ignoring occlusion-state) as it is entirely
  // possible the occlusion-state is out of date at this point. It is expected
  // that the proper occlusion state is provided soon after this.
  UpdateCompositorVisibility(value);
}

void WindowTreeHost::CreateCompositor(
    bool force_software_compositor,
    bool use_external_begin_frame_control,
    bool enable_compositing_based_throttling) {
  Env* env = Env::GetInstance();
  ui::ContextFactory* context_factory = env->context_factory();
  DCHECK(context_factory);
  compositor_ = std::make_unique<ui::Compositor>(
      context_factory->AllocateFrameSinkId(), context_factory,
      base::ThreadTaskRunnerHandle::Get(), ui::IsPixelCanvasRecordingEnabled(),
      use_external_begin_frame_control, force_software_compositor,
      enable_compositing_based_throttling);
#if BUILDFLAG(IS_CHROMEOS_ASH)
  compositor_->AddObserver(this);
#endif
  if (!dispatcher()) {
    window()->Init(ui::LAYER_NOT_DRAWN);
    window()->set_host(this);
    window()->SetName("RootWindow");
    dispatcher_ = std::make_unique<WindowEventDispatcher>(this);
  }
}

void WindowTreeHost::InitCompositor() {
  DCHECK(!compositor_->root_layer());
  compositor_->SetScaleAndSize(device_scale_factor_, GetBoundsInPixels().size(),
                               window()->GetLocalSurfaceId());
  compositor_->SetRootLayer(window()->layer());

  display::Display display =
      display::Screen::GetScreen()->GetDisplayNearestWindow(window());
  compositor_->SetDisplayColorSpaces(display.color_spaces());
}

void WindowTreeHost::OnAcceleratedWidgetAvailable() {
  compositor_->SetAcceleratedWidget(GetAcceleratedWidget());
  prop_ = std::make_unique<ui::ViewProp>(
      GetAcceleratedWidget(), kWindowTreeHostForAcceleratedWidget, this);
  if (NativeWindowOcclusionTracker::
          IsNativeWindowOcclusionTrackingAlwaysEnabled(this)) {
    NativeWindowOcclusionTracker::EnableNativeWindowOcclusionTracking(this);
  }
}

void WindowTreeHost::OnHostMovedInPixels(
    const gfx::Point& new_location_in_pixels) {
  TRACE_EVENT1("ui", "WindowTreeHost::OnHostMovedInPixels", "origin",
               new_location_in_pixels.ToString());

  for (WindowTreeHostObserver& observer : observers_)
    observer.OnHostMovedInPixels(this, new_location_in_pixels);
}

void WindowTreeHost::OnHostResizedInPixels(
    const gfx::Size& new_size_in_pixels) {
  // The compositor is deleted from WM_DESTROY, but we don't delete things until
  // WM_NCDESTROY, and it must be possible to still get some messages between
  // these two.
  if (!compositor_)
    return;

  display::Display display =
      display::Screen::GetScreen()->GetDisplayNearestWindow(window());
  device_scale_factor_ = display.device_scale_factor();
  UpdateRootWindowSizeInPixels();

  // Passing |new_size_in_pixels| to set compositor size. It could be different
  // from GetBoundsInPixels() on Windows to contain extra space for window
  // transition animations and should be used to set compositor size instead of
  // GetBoundsInPixels() in such case.
  UpdateCompositorScaleAndSize(new_size_in_pixels);

  for (WindowTreeHostObserver& observer : observers_)
    observer.OnHostResized(this);
}

void WindowTreeHost::OnHostWorkspaceChanged() {
  for (WindowTreeHostObserver& observer : observers_)
    observer.OnHostWorkspaceChanged(this);
}

void WindowTreeHost::OnHostDisplayChanged() {
  if (!compositor_)
    return;
  display::Display display =
      display::Screen::GetScreen()->GetDisplayNearestWindow(window());
  compositor_->SetDisplayColorSpaces(display.color_spaces());
}

void WindowTreeHost::OnHostCloseRequested() {
  for (WindowTreeHostObserver& observer : observers_)
    observer.OnHostCloseRequested(this);
}

void WindowTreeHost::OnHostLostWindowCapture() {
  // It is possible for this function to be called during destruction, after the
  // root window has already been destroyed (e.g. when the ui::PlatformWindow is
  // destroyed, and during destruction, it loses capture. See more details in
  // http://crbug.com/770670)
  if (!window())
    return;
  Window* capture_window = client::GetCaptureWindow(window());
  if (capture_window && capture_window->GetRootWindow() == window())
    capture_window->ReleaseCapture();
}

void WindowTreeHost::OnDisplayMetricsChanged(const display::Display& display,
                                             uint32_t metrics) {
  if (metrics & DisplayObserver::DISPLAY_METRIC_COLOR_SPACE && compositor_ &&
      display.id() == GetDisplayId())
    compositor_->SetDisplayColorSpaces(display.color_spaces());

// Chrome OS is handled in WindowTreeHostManager::OnDisplayMetricsChanged.
// Chrome OS requires additional handling for the bounds that we do not need to
// do for other OSes.
#if !BUILDFLAG(IS_CHROMEOS_ASH)
  if (metrics & DISPLAY_METRIC_DEVICE_SCALE_FACTOR &&
      display.id() == GetDisplayId())
    OnHostResizedInPixels(GetBoundsInPixels().size());
#endif
}

gfx::Rect WindowTreeHost::GetTransformedRootWindowBoundsInPixels(
    const gfx::Size& size_in_pixels) const {
  gfx::RectF new_bounds = gfx::RectF(gfx::Rect(size_in_pixels));
  GetInverseRootTransform().TransformRect(&new_bounds);
  return gfx::ToEnclosingRect(new_bounds);
}

void WindowTreeHost::SetNativeWindowOcclusionEnabled(bool enable) {
  native_window_occlusion_enabled_ = enable;
  // TODO(crbug.com/1051306) If enabled is false, make this
  // turn off native window occlusion on this window. Only Windows has
  // native window occlusion currently.
}

////////////////////////////////////////////////////////////////////////////////
// WindowTreeHost, private:

void WindowTreeHost::DecrementVideoCaptureCount() {
  DCHECK_GT(video_capture_count_, 0);
  --video_capture_count_;
  MaybeUpdateComposibleVisibilityForVideoLockCountChange();
}

void WindowTreeHost::MaybeUpdateComposibleVisibilityForVideoLockCountChange() {
  // Should only be called if the occlusion is applied to the compositor.
  DCHECK(!ShouldThrottleWhenOccluded());

  // Only need to check for changes when transitioning between lock and no lock.
  if (video_capture_count_ > 1 || !compositor() ||
      !accelerated_widget_made_visible_) {
    return;
  }
  const bool visible = CalculateCompositorVisibilityFromOcclusionState();
  if (visible != compositor()->IsVisible() || is_transitioning_to_hidden())
    UpdateCompositorVisibility(visible);
}

bool WindowTreeHost::CalculateCompositorVisibilityFromOcclusionState() const {
  switch (occlusion_state_) {
    case Window::OcclusionState::UNKNOWN:
      return true;
    case Window::OcclusionState::VISIBLE:
      return true;
    case Window::OcclusionState::OCCLUDED: {
      // The compositor needs to be visible when capturing video.
      return video_capture_count_ != 0;
    }
    case Window::OcclusionState::HIDDEN:
      // TODO: this should really return true if `video_capture_count_` is
      // not zero, but this likely needs other changes to really work (such
      // as on windows when an HWND is iconified it is sized to 0x0).
      return false;
  }
}

bool WindowTreeHost::ShouldReleaseResourcesWhenHidden() const {
#if defined(OS_WIN)
  if (!base::FeatureList::IsEnabled(
          features::kApplyNativeOcclusionToCompositor) ||
      !IsNativeWindowOcclusionEnabled()) {
    return false;
  }

  const std::string type = base::GetFieldTrialParamValueByFeature(
      features::kApplyNativeOcclusionToCompositor,
      features::kApplyNativeOcclusionToCompositorType);
  return type == features::kApplyNativeOcclusionToCompositorTypeRelease;
#else
  return false;
#endif
}

bool WindowTreeHost::ShouldThrottleWhenOccluded() const {
#if defined(OS_WIN)
  if (!base::FeatureList::IsEnabled(
          features::kApplyNativeOcclusionToCompositor) ||
      !IsNativeWindowOcclusionEnabled()) {
    return false;
  }

  const std::string type = base::GetFieldTrialParamValueByFeature(
      features::kApplyNativeOcclusionToCompositor,
      features::kApplyNativeOcclusionToCompositorType);
  return type == features::kApplyNativeOcclusionToCompositorTypeThrottle;
#else
  return false;
#endif
}

void WindowTreeHost::RestoreHideTransitionState() {
  DCHECK(is_transitioning_to_hidden());
  hide_helper_.reset();
}

void WindowTreeHost::FinishHideTransition() {
  DCHECK(is_transitioning_to_hidden());
  compositor_->SetVisible(false);
  RestoreHideTransitionState();
}

// static
const base::flat_set<WindowTreeHost*>&
WindowTreeHost::GetThrottledHostsForTesting() {
  return HostThrottler::GetInstance().hosts();
}

void WindowTreeHost::StartReleasingResourcesForHide() {
  DCHECK(!is_transitioning_to_hidden());
  if (compositor_->size().IsEmpty()) {
    // This should generally only happen during startup as Compositor silently
    // ignores changing the size to empty.
    compositor_->SetVisible(false);
    return;
  }
  hide_helper_ = std::make_unique<HideHelper>(this);
}

void WindowTreeHost::MoveCursorToInternal(const gfx::Point& root_location,
                                          const gfx::Point& host_location) {
  last_cursor_request_position_in_host_ = host_location;
  MoveCursorToScreenLocationInPixels(host_location);
  client::CursorClient* cursor_client = client::GetCursorClient(window());
  if (cursor_client) {
    const display::Display& display =
        display::Screen::GetScreen()->GetDisplayNearestWindow(window());
    cursor_client->SetDisplay(display);
  }
  dispatcher()->OnCursorMovedToRootLocation(root_location);
}

void WindowTreeHost::OnCompositingEnded(ui::Compositor* compositor) {
  if (!holding_pointer_moves_)
    return;

  dispatcher_->ReleasePointerMoves();
  holding_pointer_moves_ = false;
}

void WindowTreeHost::OnCompositingChildResizing(ui::Compositor* compositor) {
  if (!Env::GetInstance()->throttle_input_on_resize() || holding_pointer_moves_)
    return;
  dispatcher_->HoldPointerMoves();
  holding_pointer_moves_ = true;
}

void WindowTreeHost::OnFrameSinksToThrottleUpdated(
    const base::flat_set<viz::FrameSinkId>& ids) {
  for (auto& observer : observers_)
    observer.OnCompositingFrameSinksToThrottleUpdated(this, ids);
}

}  // namespace aura
