chromium/ash/wm/raster_scale/raster_scale_layer_observer.cc

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

#include "ash/wm/raster_scale/raster_scale_layer_observer.h"

#include "ash/shell.h"
#include "ash/wm/raster_scale/raster_scale_controller.h"
#include "ash/wm/window_util.h"
#include "base/logging.h"
#include "ui/aura/client/transient_window_client.h"
#include "ui/compositor/layer_animation_element.h"
#include "ui/compositor/layer_animation_sequence.h"
#include "ui/compositor/property_change_reason.h"
#include "ui/wm/core/window_util.h"

namespace ash {

namespace {

#if DCHECK_IS_ON()
gfx::Transform AncestorTransform(aura::Window* window) {
  gfx::Transform transform;
  while (window->parent() != nullptr) {
    window = window->parent();
    transform = window->transform() * transform;
  }
  return transform;
}
#endif

void DLogIfAncestorTransformIsNotIdentity(aura::Window* window) {
  // Currently, TSR computes the raster scale purely from the local transform.
  // We don't expect there to be a non-identity transform for ancestors of TSR
  // applied windows, but check it in debug builds here. If there is a
  // non-identity transform, we need to include it somehow. The full (and
  // overcomplex) fix for this would be observing animations and window
  // transforms on every ancestor window and computing the maximum raster scale
  // for each span between each event point we are okay taking the compositor
  // lock at (e.g. animation start/stop on any ancestor window).
  DLOG_IF(ERROR, !AncestorTransform(window).IsIdentity())
      << "Unexpected non-identity transform from parent window coordinates to "
         "root window coordinates";
}

}  // namespace

ScopedRasterScaleLayerObserverLock::ScopedRasterScaleLayerObserverLock(
    base::WeakPtr<RasterScaleLayerObserver> observer)
    : observer_(observer) {
  observer_->IncrementRefCount();
}

ScopedRasterScaleLayerObserverLock::~ScopedRasterScaleLayerObserverLock() {
  if (observer_) {
    observer_->DecrementRefCount();
  }
}

ScopedRasterScaleLayerObserverLock::ScopedRasterScaleLayerObserverLock(
    ScopedRasterScaleLayerObserverLock&&) = default;
ScopedRasterScaleLayerObserverLock&
ScopedRasterScaleLayerObserverLock::operator=(
    ScopedRasterScaleLayerObserverLock&&) = default;

RasterScaleLayerObserver::RasterScaleLayerObserver(aura::Window* observe_window,
                                                   ui::Layer* observe_layer,
                                                   aura::Window* apply_window)
    : observe_window_(observe_window),
      observe_layer_(observe_layer),
      apply_window_(apply_window) {
  for (aura::Window* transient_child :
       GetTransientTreeIterator(apply_window_)) {
    transient_windows_.insert(transient_child);
  }
  if (observe_window->IsVisible()) {
    UpdateRasterScaleFromTransform(observe_layer_->transform());
  }
  if (!windows_observation_.IsObservingSource(observe_window_)) {
    windows_observation_.AddObservation(observe_window_);
  }
  if (!windows_observation_.IsObservingSource(apply_window_)) {
    windows_observation_.AddObservation(apply_window_);
  }
  observe_layer_->AddObserver(this);
  observe_layer_->GetAnimator()->AddObserver(this);
  aura::client::GetTransientWindowClient()->AddObserver(this);
}

RasterScaleLayerObserver::~RasterScaleLayerObserver() {
  aura::client::GetTransientWindowClient()->RemoveObserver(this);
  if (observe_layer_) {
    observe_layer_->GetAnimator()->RemoveObserver(this);
    observe_layer_->RemoveObserver(this);
  }
}

void RasterScaleLayerObserver::OnLayerAnimationStarted(
    ui::LayerAnimationSequence* sequence) {
  animation_count_++;

  if (observe_window_ == nullptr || observe_layer_ == nullptr ||
      apply_window_ == nullptr) {
    return;
  }

  // It's complex to support more than one element in the
  // LayerAnimationSequence, because we would need to match raster scale
  // updates with the progression of the animation, which would need
  // framework changes. Also, it would introduce jank at each new element
  // during the animation, since changing the raster scale will take a
  // compositor lock to synchronize the raster scale update. Currently, the
  // only animation with at least one element affecting the transform and
  // with at least two elements that would apply to windows is the window
  // bounce animation, which we do not want to update raster scale for,
  // since it's very transient and doesn't change the scale much. So, we
  // will do nothing here if there is more than one element in the animation
  // sequence.
  if (sequence->size() > 1) {
    return;
  }

  ui::LayerAnimationElement::TargetValue value;
  sequence->FirstElement()->GetTargetValue(&value);

  // Don't do anything if we will never be visible.
  if (!observe_window_->IsVisible() && !value.visibility) {
    return;
  }

  // Don't do anything for things not animating the transform.
  if (!(sequence->properties() &
        ui::LayerAnimationElement::AnimatableProperty::TRANSFORM)) {
    return;
  }

  // Don't update if nothing happens until the animation is done:
  if (sequence->FirstElement()->tween_type() == gfx::Tween::ZERO) {
    return;
  }

  const auto new_scale =
      RasterScaleController::RasterScaleFromTransform(value.transform);
  const auto old_scale =
      Shell::Get()->raster_scale_controller()->ComputeRasterScaleForWindow(
          apply_window_);
  if (new_scale >= old_scale) {
    SetRasterScales(new_scale);
  }
}

void RasterScaleLayerObserver::OnLayerAnimationEnded(
    ui::LayerAnimationSequence* sequence) {
  animation_count_--;

  if (animation_count_ == 0 && ref_count_ != 0) {
    // We can always apply no matter what here, since the animation is done.
    UpdateRasterScaleFromTransform(observe_layer_->transform());
  }

  MaybeShutdown();
}

void RasterScaleLayerObserver::OnLayerAnimationWillRepeat(
    ui::LayerAnimationSequence* sequence) {
  DLOG(ERROR) << "Unexpected repeating layer animation.";
}

void RasterScaleLayerObserver::OnLayerAnimationAborted(
    ui::LayerAnimationSequence* sequence) {
  animation_count_--;
  MaybeShutdown();
}

void RasterScaleLayerObserver::OnLayerAnimationScheduled(
    ui::LayerAnimationSequence* sequence) {}

void RasterScaleLayerObserver::OnWindowVisibilityChanged(aura::Window* window,
                                                         bool visible) {
  if (observe_window_ == nullptr || observe_layer_ == nullptr ||
      apply_window_ == nullptr) {
    return;
  }

  if (window != observe_window_) {
    return;
  }

  UpdateRasterScaleFromTransform(observe_layer_->transform());
}

void RasterScaleLayerObserver::OnWindowDestroying(aura::Window* window) {
  windows_observation_.RemoveObservation(window);
  if (window == observe_window_) {
    observe_window_ = nullptr;
  }
  if (window == apply_window_) {
    apply_window_ = nullptr;
  }
  MaybeShutdown();
}

void RasterScaleLayerObserver::OnWindowBoundsChanged(
    aura::Window* window,
    const gfx::Rect& old_bounds,
    const gfx::Rect& new_bounds,
    ui::PropertyChangeReason reason) {
  if (observe_window_ == nullptr || observe_layer_ == nullptr ||
      apply_window_ == nullptr) {
    return;
  }

  if (window != observe_window_) {
    return;
  }

  // Needed for showing minimized windows via mirror view.
  if (reason == ui::PropertyChangeReason::NOT_FROM_ANIMATION) {
    UpdateRasterScaleFromTransform(observe_layer_->transform());
  }
}

void RasterScaleLayerObserver::OnWindowTransformed(
    aura::Window* window,
    ui::PropertyChangeReason reason) {
  if (observe_window_ == nullptr || observe_layer_ == nullptr ||
      apply_window_ == nullptr) {
    return;
  }

  if (window != observe_window_) {
    return;
  }

  // Transformations happening in an animation are handled separately.
  if (reason == ui::PropertyChangeReason::NOT_FROM_ANIMATION) {
    UpdateRasterScaleFromTransform(observe_layer_->transform());
  }
}

void RasterScaleLayerObserver::OnWindowLayerRecreated(aura::Window* window) {
  if (window != observe_window_) {
    return;
  }

  // OnWindowLayerRecreated shouldn't be called for mirror layers, so safe to
  // update here.
  if (observe_layer_) {
    observe_layer_->GetAnimator()->RemoveObserver(this);
    observe_layer_->RemoveObserver(this);
  }

  // Animations are not carried over to the new layer.
  animation_count_ = 0;

  observe_layer_ = window->layer();
  observe_layer_->AddObserver(this);
  observe_layer_->GetAnimator()->AddObserver(this);

  MaybeShutdown();
}

void RasterScaleLayerObserver::LayerDestroyed(ui::Layer* layer) {
  CHECK_EQ(layer, observe_layer_);
  observe_layer_ = nullptr;
  animation_count_ = 0;
  MaybeShutdown();
}

void RasterScaleLayerObserver::OnTransientChildWindowAdded(
    aura::Window* parent,
    aura::Window* transient_child) {
  if (parent != apply_window_ &&
      !::wm::HasTransientAncestor(parent, apply_window_)) {
    return;
  }

  transient_windows_.insert(transient_child);

  // Apply the existing raster scale to the new transient window.
  auto iter = raster_scales_.find(apply_window_);
  if (iter == raster_scales_.end()) {
    return;
  }

  const float raster_scale = iter->second->raster_scale();
  ScopedSetRasterScale::SetOrUpdateRasterScale(
      transient_child, raster_scale, &raster_scales_[transient_child]);
}

void RasterScaleLayerObserver::OnTransientChildWindowRemoved(
    aura::Window* parent,
    aura::Window* transient_child) {
  if (parent != apply_window_ &&
      !wm::HasTransientAncestor(parent, apply_window_)) {
    return;
  }

  // Stop applying raster scale to the transient window and forget it.
  transient_windows_.erase(transient_child);
  raster_scales_.erase(transient_child);
}

void RasterScaleLayerObserver::SetRasterScales(float raster_scale) {
  DLogIfAncestorTransformIsNotIdentity(observe_window_);

  ScopedSetRasterScale::SetOrUpdateRasterScale(apply_window_, raster_scale,
                                               &raster_scales_[apply_window_]);
  for (aura::Window* window : transient_windows_) {
    ScopedSetRasterScale::SetOrUpdateRasterScale(window, raster_scale,
                                                 &raster_scales_[window]);
  }
}

void RasterScaleLayerObserver::UpdateRasterScaleFromTransform(
    const gfx::Transform& transform) {
  if (observe_window_ == nullptr || observe_layer_ == nullptr ||
      apply_window_ == nullptr) {
    return;
  }

  if (observe_window_->IsVisible()) {
    const auto raster_scale =
        RasterScaleController::RasterScaleFromTransform(transform);
    SetRasterScales(raster_scale);
  } else {
    // This content is not shown via the window, so stop holding a raster scale
    // lock on it.
    raster_scales_.clear();
  }
}

void RasterScaleLayerObserver::IncrementRefCount() {
  ref_count_++;
}

void RasterScaleLayerObserver::DecrementRefCount() {
  ref_count_--;
  MaybeShutdown();
}

void RasterScaleLayerObserver::MaybeShutdown() {
  // If there are no animations (we won't do anything in the future) and no more
  // refs, we can delete.
  if (ref_count_ != 0 || animation_count_ != 0) {
    return;
  }

  delete this;
}

}  // namespace ash