910e62b5创建于 1月15日历史提交
// Copyright 2019 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/layout/animating_layout_manager.h"

#include <algorithm>
#include <map>
#include <set>
#include <utility>
#include <vector>

#include "base/auto_reset.h"
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/memory/raw_ptr.h"
#include "base/numerics/safe_conversions.h"
#include "base/observer_list.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/animation/animation.h"
#include "ui/gfx/animation/animation_container.h"
#include "ui/gfx/animation/multi_animation.h"
#include "ui/gfx/animation/slide_animation.h"
#include "ui/views/animation/animation_delegate_views.h"
#include "ui/views/layout/normalized_geometry.h"
#include "ui/views/layout/proposed_layout.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/widget/widget.h"

namespace views {

namespace {

// When enabled, a call to gfx::Animation::ShouldRenderRichAnimation() is
// avoided when not needed. Behind a feature to assess impact
// (go/chrome-performance-work-should-be-finched).
// TODO(crbug.com/40897031): Clean up when experiment is complete.
BASE_FEATURE(kAvoidUnnecessaryShouldRenderRichAnimation,
             base::FEATURE_DISABLED_BY_DEFAULT);

// Returns the ChildLayout data for the child view in the proposed layout, or
// nullptr if not found.
const ChildLayout* FindChildViewInLayout(const ProposedLayout& layout,
                                         const View* view) {
  if (!view) {
    return nullptr;
  }

  // The number of children should be small enough that this is more efficient
  // than caching a lookup set.
  for (auto& child_layout : layout.child_layouts) {
    if (child_layout.child_view == view) {
      return &child_layout;
    }
  }
  return nullptr;
}

ChildLayout* FindChildViewInLayout(ProposedLayout* layout, const View* view) {
  return const_cast<ChildLayout*>(FindChildViewInLayout(*layout, view));
}

// Describes the type of fade, used by LayoutFadeInfo (see below).
enum class LayoutFadeType {
  // This view is fading in as part of the current animation.
  kFadingIn,
  // This view is fading out as part of the current animation.
  kFadingOut,
  // This view was fading as part of a previous animation that was interrupted
  // and redirected. No child views in the current animation should base their
  // position off of it.
  kContinuingFade
};

// Makes a copy of the given layout with only visible child views (non-visible
// children are omitted).
ProposedLayout WithOnlyVisibleViews(const ProposedLayout layout) {
  ProposedLayout result;
  result.host_size = layout.host_size;
  std::ranges::copy_if(layout.child_layouts,
                       std::back_inserter(result.child_layouts),
                       &ChildLayout::visible);
  return result;
}

// Returns true if the two proposed layouts have the same visible views, with
// the same parameters, in the same order.
bool HaveSameVisibleViews(const ProposedLayout& l1, const ProposedLayout& l2) {
  // There is an approach that uses nested loops and dual iterators that is more
  // efficient than copying, but since this method is only currently called when
  // views are added to the layout, clarity is more important than speed.
  return WithOnlyVisibleViews(l1) == WithOnlyVisibleViews(l2);
}

}  // namespace

// Holds data about a view that is fading in or out as part of an animation.
struct AnimatingLayoutManager::LayoutFadeInfo {
  // How the child view is fading.
  LayoutFadeType fade_type;
  // The child view which is fading.
  raw_ptr<View> child_view = nullptr;
  // The view previous (leading side) to the fading view which is in both the
  // starting and target layout, or null if none.
  raw_ptr<View> prev_view = nullptr;
  // The view next (trailing side) to the fading view which is in both the
  // starting and target layout, or null if none.
  raw_ptr<View> next_view = nullptr;
  // The full-size bounds, normalized to the orientation of the layout manager,
  // that |child_view| starts with, if fading out, or ends with, if fading in.
  NormalizedRect reference_bounds;
  // The offset from the end of |prev_view| and the start of |next_view|. Insets
  // may be negative if the views overlap.
  Inset1D offsets;
};

// Manages the animation and various callbacks from the animation system that
// are required to update the layout during animations.
class AnimatingLayoutManager::AnimationDelegate
    : public AnimationDelegateViews {
 public:
  explicit AnimationDelegate(AnimatingLayoutManager* layout_manager);
  ~AnimationDelegate() override = default;

  // Returns true after the host view is added to a widget or animation has been
  // enabled by a unit test.
  //
  // Before that, animation is not possible, so all changes to the host view
  // should result in the host view's layout being snapped directly to the
  // target layout.
  bool ready_to_animate() const { return ready_to_animate_; }

  // Pushes animation configuration (tween type, duration) through to the
  // animation itself.
  void UpdateAnimationParameters();

  // Starts the animation.
  void Animate();

  // Cancels and resets the current animation (if any).
  void Reset();

  // If the current layout is not yet ready to animate, transitions into the
  // ready-to-animate state, possibly resetting the current layout and
  // invalidating the host to make sure the layout is up to date.
  void MakeReadyForAnimation();

  // Overrides the default animation container with |container|.
  void SetAnimationContainerForTesting(gfx::AnimationContainer* container) {
    animation_->SetContainer(container);
  }

 private:
  // Observer used to watch for the host view being parented to a widget.
  class ViewWidgetObserver : public ViewObserver {
   public:
    explicit ViewWidgetObserver(AnimationDelegate* animation_delegate)
        : animation_delegate_(animation_delegate) {}

    void OnViewAddedToWidget(View* observed_view) override {
      animation_delegate_->MakeReadyForAnimation();
    }

    void OnViewIsDeleting(View* observed_view) override {
      animation_delegate_->scoped_observation_.Reset();
    }

   private:
    const raw_ptr<AnimationDelegate> animation_delegate_;
  };
  friend class Observer;

  // AnimationDelegateViews:
  void AnimationProgressed(const gfx::Animation* animation) override;
  void AnimationCanceled(const gfx::Animation* animation) override;
  void AnimationEnded(const gfx::Animation* animation) override;

  bool ready_to_animate_ = false;
  bool resetting_animation_ = false;
  const raw_ptr<AnimatingLayoutManager> target_layout_manager_;
  std::unique_ptr<gfx::MultiAnimation> fade_in_opacity_animation_;
  std::unique_ptr<gfx::MultiAnimation> fade_out_opacity_animation_;
  std::unique_ptr<gfx::SlideAnimation> animation_;
  const raw_ptr<gfx::AnimationContainer> container_;
  ViewWidgetObserver view_widget_observer_{this};
  base::ScopedObservation<View, ViewObserver> scoped_observation_{
      &view_widget_observer_};
};

AnimatingLayoutManager::AnimationDelegate::AnimationDelegate(
    AnimatingLayoutManager* layout_manager)
    : AnimationDelegateViews(layout_manager->host_view()),
      target_layout_manager_(layout_manager),
      animation_(std::make_unique<gfx::SlideAnimation>(this)),
      container_(new gfx::AnimationContainer()) {
  animation_->SetContainer(container_);
  View* const host_view = layout_manager->host_view();
  DCHECK(host_view);
  if (host_view->GetWidget()) {
    MakeReadyForAnimation();
  } else {
    scoped_observation_.Observe(host_view);
  }
  UpdateAnimationParameters();
}

void AnimatingLayoutManager::AnimationDelegate::UpdateAnimationParameters() {
  animation_->SetTweenType(target_layout_manager_->tween_type());
  animation_->SetSlideDuration(target_layout_manager_->animation_duration());

  base::TimeDelta opacity_animation_duration =
      std::min(target_layout_manager_->animation_duration(),
               target_layout_manager_->opacity_animation_duration());
  if (!opacity_animation_duration.is_zero()) {
    fade_in_opacity_animation_ =
        std::make_unique<gfx::MultiAnimation>(gfx::MultiAnimation::Parts{
            {target_layout_manager_->animation_duration() -
                 opacity_animation_duration,
             gfx::Tween::Type::LINEAR, 0.0, 0.0},
            {opacity_animation_duration,
             target_layout_manager_->opacity_tween_type(), 0.0, 1.0}});
    fade_in_opacity_animation_->SetContainer(container_);
    fade_in_opacity_animation_->set_continuous(false);
    const base::TimeDelta fade_out_opacity_duration =
        target_layout_manager_->animation_duration() ==
                opacity_animation_duration
            ? opacity_animation_duration
            : target_layout_manager_->animation_duration() -
                  opacity_animation_duration;
    fade_out_opacity_animation_ =
        std::make_unique<gfx::MultiAnimation>(gfx::MultiAnimation::Parts{
            {fade_out_opacity_duration,
             target_layout_manager_->opacity_tween_type(), 1.0, 0.0},
            {target_layout_manager_->animation_duration() -
                 fade_out_opacity_duration,
             gfx::Tween::Type::LINEAR, 0.0, 0.0}});
    fade_out_opacity_animation_->SetContainer(container_);
    fade_out_opacity_animation_->set_continuous(false);
  }
}

void AnimatingLayoutManager::AnimationDelegate::Animate() {
  DCHECK(ready_to_animate_);
  Reset();
  animation_->Show();
  if (target_layout_manager_->opacity_animation_duration() >
      base::Milliseconds(0)) {
    if (fade_in_opacity_animation_.get()) {
      fade_in_opacity_animation_->Start();
    }
    if (fade_out_opacity_animation_.get()) {
      fade_out_opacity_animation_->Start();
    }
  }
}

void AnimatingLayoutManager::AnimationDelegate::Reset() {
  if (!ready_to_animate_) {
    return;
  }
  base::AutoReset<bool> setter(&resetting_animation_, true);
  animation_->Reset();
  if (fade_in_opacity_animation_.get()) {
    fade_in_opacity_animation_->Stop();
  }
  if (fade_out_opacity_animation_.get()) {
    fade_out_opacity_animation_->Stop();
  }
}

void AnimatingLayoutManager::AnimationDelegate::MakeReadyForAnimation() {
  if (!ready_to_animate_) {
    target_layout_manager_->ResetLayout();
    ready_to_animate_ = true;
    scoped_observation_.Reset();
  }
}

void AnimatingLayoutManager::AnimationDelegate::AnimationProgressed(
    const gfx::Animation* animation) {
  DCHECK(animation_.get() == animation);
  double fade_in_opacity = fade_in_opacity_animation_.get()
                               ? fade_in_opacity_animation_->GetCurrentValue()
                               : 1.0;
  double fade_out_opacity = fade_out_opacity_animation_.get()
                                ? fade_out_opacity_animation_->GetCurrentValue()
                                : 0.0;
  target_layout_manager_->AnimateTo(animation->GetCurrentValue(),
                                    fade_in_opacity, fade_out_opacity);
}

void AnimatingLayoutManager::AnimationDelegate::AnimationCanceled(
    const gfx::Animation* animation) {
  AnimationEnded(animation);
}

void AnimatingLayoutManager::AnimationDelegate::AnimationEnded(
    const gfx::Animation* animation) {
  if (resetting_animation_) {
    return;
  }
  DCHECK(animation_.get() == animation);
  target_layout_manager_->AnimateTo(1.0, 1.0, 0.0);
}

// AnimatingLayoutManager:

AnimatingLayoutManager::AnimatingLayoutManager() = default;
AnimatingLayoutManager::~AnimatingLayoutManager() = default;

AnimatingLayoutManager& AnimatingLayoutManager::SetBoundsAnimationMode(
    BoundsAnimationMode bounds_animation_mode) {
  if (bounds_animation_mode_ != bounds_animation_mode) {
    bounds_animation_mode_ = bounds_animation_mode;
    ResetLayout();
  }
  return *this;
}

AnimatingLayoutManager& AnimatingLayoutManager::SetAnimationDuration(
    base::TimeDelta animation_duration) {
  DCHECK_GE(animation_duration, base::TimeDelta());
  animation_duration_ = animation_duration;
  if (animation_delegate_) {
    animation_delegate_->UpdateAnimationParameters();
  }
  return *this;
}

AnimatingLayoutManager& AnimatingLayoutManager::SetTweenType(
    gfx::Tween::Type tween_type) {
  tween_type_ = tween_type;
  if (animation_delegate_) {
    animation_delegate_->UpdateAnimationParameters();
  }
  return *this;
}

AnimatingLayoutManager& AnimatingLayoutManager::SetOpacityAnimationDuration(
    base::TimeDelta animation_duration) {
  DCHECK_GE(animation_duration, base::TimeDelta());
  opacity_animation_duration_ = animation_duration;
  if (animation_delegate_) {
    animation_delegate_->UpdateAnimationParameters();
  }
  return *this;
}

AnimatingLayoutManager& AnimatingLayoutManager::SetOpacityTweenType(
    gfx::Tween::Type tween_type) {
  opacity_tween_type_ = tween_type;
  if (animation_delegate_) {
    animation_delegate_->UpdateAnimationParameters();
  }
  return *this;
}

AnimatingLayoutManager& AnimatingLayoutManager::SetOrientation(
    LayoutOrientation orientation) {
  if (orientation_ != orientation) {
    orientation_ = orientation;
    ResetLayout();
  }
  return *this;
}

AnimatingLayoutManager& AnimatingLayoutManager::SetDefaultFadeMode(
    FadeInOutMode default_fade_mode) {
  default_fade_mode_ = default_fade_mode;
  return *this;
}

void AnimatingLayoutManager::ResetLayout() {
  if (!target_layout_manager()) {
    return;
  }
  ResetLayoutToTargetSize();
  InvalidateHost(false);
}

void AnimatingLayoutManager::FadeOut(View* child_view) {
  DCHECK(child_view);
  DCHECK(child_view->parent());
  DCHECK_EQ(host_view(), child_view->parent());

  // If the view in question is already incapable of being visible, either:
  // 1. the view wasn't capable of being visible in the first place
  // 2. the view is already invisible because the layout has chosen to hide it
  // In either case, it is generally useful to recalculate the layout just in
  // case the caller has made other changes that won't directly cause a layout -
  // for example, the user has changed a layout-affecting class property. Worst
  // case this ends up being a slightly costly no-op but we don't expect this
  // method to be called very often.
  if (!CanBeVisible(child_view)) {
    InvalidateHost(true);
    return;
  }

  // This handles a case where we are in the middle of an animation where we
  // would have hidden the target view, but haven't laid out yet, so haven't
  // actually hidden it yet. Because we plan fade-outs off of the current layout
  // if the view the child view is visible it will not get a proper fade-out and
  // will remain visible but not properly laid out. We remedy this by hiding the
  // view immediately.
  const ChildLayout* const current_layout =
      FindChildViewInLayout(current_layout_, child_view);
  if ((!current_layout || !current_layout->visible) &&
      child_view->GetVisible()) {
    SetViewVisibility(child_view, false);
  }

  // Indicate that the view should become hidden in the layout without
  // immediately changing its visibility. Instead, this triggers an animation
  // which results in the view being hidden.
  //
  // This method is typically only called from View and has a private final
  // implementation in LayoutManagerBase so we have to cast to call it.
  static_cast<LayoutManager*>(this)->ViewVisibilitySet(
      host_view(), child_view, child_view->GetVisible(), false);
}

void AnimatingLayoutManager::FadeIn(View* child_view) {
  DCHECK(child_view);
  DCHECK(child_view->parent());
  DCHECK_EQ(host_view(), child_view->parent());

  // If the view in question is already capable of being visible, either:
  // 1. the view is already visible so this is a no-op
  // 2. the view is not visible because the target layout has chosen to hide it
  // In either case, it is generally useful to recalculate the layout just in
  // case the caller has made other changes that won't directly cause a layout -
  // for example, the user has changed a layout-affecting class property. Worst
  // case this ends up being a slightly costly no-op but we don't expect this
  // method to be called very often.
  if (CanBeVisible(child_view)) {
    InvalidateHost(true);
    return;
  }

  // Indicate that the view should become visible in the layout without
  // immediately changing its visibility. Instead, this triggers an animation
  // which results in the view being shown.
  //
  // This method is typically only called from View and has a private final
  // implementation in LayoutManagerBase so we have to cast to call it.
  static_cast<LayoutManager*>(this)->ViewVisibilitySet(
      host_view(), child_view, child_view->GetVisible(), true);
}

void AnimatingLayoutManager::AddObserver(Observer* observer) {
  if (!observers_.HasObserver(observer)) {
    observers_.AddObserver(observer);
  }
}

void AnimatingLayoutManager::RemoveObserver(Observer* observer) {
  if (observers_.HasObserver(observer)) {
    observers_.RemoveObserver(observer);
  }
}

bool AnimatingLayoutManager::HasObserver(Observer* observer) const {
  return observers_.HasObserver(observer);
}

gfx::Size AnimatingLayoutManager::GetPreferredSize(const View* host) const {
  if (!target_layout_manager()) {
    return gfx::Size();
  }

  // gfx::Animation::ShouldRenderRichAnimation() is a source of jank
  // (go/jank-from-should-render-rich-animation-jun2025). Avoid calling it when
  // `bounds_animation_mode_` is `kUseHostBounds`, since it won't affect the
  // outcome.
  if (base::FeatureList::IsEnabled(
          kAvoidUnnecessaryShouldRenderRichAnimation) &&
      bounds_animation_mode_ == BoundsAnimationMode::kUseHostBounds) {
    return target_layout_manager()->GetPreferredSize(host);
  }

  // If animation is disabled, preferred size does not change with current
  // animation state.
  if (!gfx::Animation::ShouldRenderRichAnimation()) {
    return target_layout_manager()->GetPreferredSize(host);
  }

  switch (bounds_animation_mode_) {
    case BoundsAnimationMode::kUseHostBounds: {
      CHECK(!base::FeatureList::IsEnabled(
          kAvoidUnnecessaryShouldRenderRichAnimation));
      return target_layout_manager()->GetPreferredSize(host);
    }
    case BoundsAnimationMode::kAnimateMainAxis: {
      // Animating only main axis, so cross axis is preferred size.
      gfx::Size result = current_layout_.host_size;
      SetCrossAxis(
          &result, orientation(),
          GetCrossAxis(orientation(),
                       target_layout_manager()->GetPreferredSize(host)));
      return result;
    }
    case BoundsAnimationMode::kAnimateBothAxes: {
      return current_layout_.host_size;
    }
  }
}

gfx::Size AnimatingLayoutManager::GetPreferredSize(
    const View* host,
    const SizeBounds& available_size) const {
  if (!target_layout_manager()) {
    return gfx::Size();
  }

  // If animation is disabled, preferred size does not change with current
  // animation state.
  if (!gfx::Animation::ShouldRenderRichAnimation()) {
    return target_layout_manager()->GetPreferredSize(host, available_size);
  }

  switch (bounds_animation_mode_) {
    case BoundsAnimationMode::kUseHostBounds:
      return target_layout_manager()->GetPreferredSize(host, available_size);
    case BoundsAnimationMode::kAnimateMainAxis: {
      // Animating only main axis, so cross axis is preferred size.
      gfx::Size result = current_layout_.host_size;
      SetCrossAxis(
          &result, orientation(),
          GetCrossAxis(orientation(), target_layout_manager()->GetPreferredSize(
                                          host, available_size)));
      return result;
    }
    case BoundsAnimationMode::kAnimateBothAxes:
      return current_layout_.host_size;
  }
}

gfx::Size AnimatingLayoutManager::GetMinimumSize(const View* host) const {
  if (!target_layout_manager()) {
    return gfx::Size();
  }
  // TODO(dfried): consider cases where the minimum size might not be just the
  // minimum size of the embedded layout.
  gfx::Size minimum_size = target_layout_manager()->GetMinimumSize(host);
  switch (bounds_animation_mode_) {
    case BoundsAnimationMode::kUseHostBounds:
      // No modification required.
      break;
    case BoundsAnimationMode::kAnimateMainAxis:
      SetMainAxis(
          &minimum_size, orientation(),
          std::min(GetMainAxis(orientation(), minimum_size),
                   GetMainAxis(orientation(), current_layout_.host_size)));
      break;
    case BoundsAnimationMode::kAnimateBothAxes:
      minimum_size.SetToMin(current_layout_.host_size);
      break;
  }
  return minimum_size;
}

int AnimatingLayoutManager::GetPreferredHeightForWidth(const View* host,
                                                       int width) const {
  if (!target_layout_manager()) {
    return 0;
  }

  // TODO(dfried): revisit this computation.
  if (bounds_animation_mode_ == BoundsAnimationMode::kAnimateBothAxes ||
      (bounds_animation_mode_ == BoundsAnimationMode::kAnimateMainAxis &&
       orientation() == LayoutOrientation::kVertical)) {
    return current_layout_.host_size.height();
  }
  return target_layout_manager()->GetPreferredHeightForWidth(host, width);
}

std::vector<raw_ptr<View, VectorExperimental>>
AnimatingLayoutManager::GetChildViewsInPaintOrder(const View* host) const {
  DCHECK_EQ(host_view(), host);

  if (!is_animating()) {
    return LayoutManagerBase::GetChildViewsInPaintOrder(host);
  }

  std::vector<raw_ptr<View, VectorExperimental>> result;
  std::set<View*> fading;

  // Put all fading views to the front of the list (back of the Z-order).
  for (const LayoutFadeInfo& fade_info : fade_infos_) {
    result.push_back(fade_info.child_view.get());
    fading.insert(fade_info.child_view);
  }

  // Add the result of the views.
  for (View* child : host->children()) {
    if (!base::Contains(fading, child)) {
      result.push_back(child);
    }
  }

  return result;
}

bool AnimatingLayoutManager::OnViewRemoved(View* host, View* view) {
  // Remove any fade infos corresponding to the removed view.
  std::erase_if(fade_infos_, [view](const LayoutFadeInfo& fade_info) {
    return fade_info.child_view == view;
  });

  // Also delete any references from other fade infos. This prevents dangling
  // partition pointers when the layout is invariably invalidated later.
  for (auto& fade_info : fade_infos_) {
    if (fade_info.next_view == view) {
      fade_info.next_view = nullptr;
    }
    if (fade_info.prev_view == view) {
      fade_info.prev_view = nullptr;
    }
  }

  // Remove any elements in the current layout corresponding to the removed
  // view.
  std::erase_if(current_layout_.child_layouts,
                [view](const ChildLayout& child_layout) {
                  return child_layout.child_view == view;
                });

  return LayoutManagerBase::OnViewRemoved(host, view);
}

void AnimatingLayoutManager::PostOrQueueAction(base::OnceClosure action) {
  queued_actions_.push_back(std::move(action));
  if (!is_animating() && !hold_queued_actions_for_layout_) {
    PostQueuedActions();
  }
}

FlexRule AnimatingLayoutManager::GetDefaultFlexRule() const {
  return base::BindRepeating(&AnimatingLayoutManager::DefaultFlexRuleImpl,
                             base::Unretained(this));
}

gfx::AnimationContainer*
AnimatingLayoutManager::GetAnimationContainerForTesting() {
  DCHECK(animation_delegate_);
  animation_delegate_->MakeReadyForAnimation();
  DCHECK(animation_delegate_->ready_to_animate());
  return animation_delegate_->container();
}

void AnimatingLayoutManager::EnableAnimationForTesting() {
  DCHECK(animation_delegate_);
  animation_delegate_->MakeReadyForAnimation();
  DCHECK(animation_delegate_->ready_to_animate());
}

ProposedLayout AnimatingLayoutManager::CalculateProposedLayout(
    const SizeBounds& size_bounds) const {
  // This class directly overrides Layout() so GetProposedLayout() and
  // CalculateProposedLayout() are not called.
  NOTREACHED();
}

void AnimatingLayoutManager::OnInstalled(View* host) {
  DCHECK(!animation_delegate_);
  animation_delegate_ = std::make_unique<AnimationDelegate>(this);
}

bool AnimatingLayoutManager::OnViewAdded(View* host, View* view) {
  // Handle a case where we add a visible view that shouldn't be visible in the
  // layout. In this case, there is no animation, no invalidation, and we just
  // set the view to not be visible.
  if (IsChildIncludedInLayout(view) && cached_layout_size() && !is_animating_) {
    const gfx::Size target_size = GetAvailableTargetLayoutSize();
    ProposedLayout proposed_layout =
        target_layout_manager()->GetProposedLayout(target_size);
    if (HaveSameVisibleViews(current_layout_, proposed_layout)) {
      SetViewVisibility(view, false);
      current_layout_ = target_layout_ = proposed_layout;
      return false;
    }
  }

  return RecalculateTarget();
}

void AnimatingLayoutManager::OnLayoutChanged() {
  // This replaces the normal behavior of clearing cached layouts.
  RecalculateTarget();
}

void AnimatingLayoutManager::LayoutImpl() {
  // Layouts can be invalidated when views are removed prior to deletion when
  // a view or widget is being destroyed. This is not a good time to recalculate
  // the layout.
  if (check_widget_ &&
      (!host_view()->GetWidget() || host_view()->GetWidget()->IsClosed())) {
    return;
  }

  // Changing the size of a view directly will lead to a layout call rather
  // than an invalidation. This should reset the layout (but see the note in
  // RecalculateTarget() below).
  const gfx::Size host_size = host_view()->size();

  if (bounds_animation_mode_ == BoundsAnimationMode::kUseHostBounds) {
    if (!cached_layout_size()) {
      // No previous layout, so snap to the target.
      ResetLayoutToTargetSize();
    } else if (host_size != *cached_layout_size()) {
      // Host size changed, so animate.
      RecalculateTarget();
    }
  } else {
    const SizeBounds available_size = GetAvailableHostSize();

    if (bounds_animation_mode_ == BoundsAnimationMode::kAnimateMainAxis &&
        (!cached_layout_size() ||
         GetCrossAxis(orientation(), host_size) !=
             GetCrossAxis(orientation(), *cached_layout_size()))) {
      // If we're fixed to the cross-axis size of the host and that size
      // changes, we need to reset the layout.
      last_available_host_size_ = available_size;
      ResetLayoutToSize(host_size);
    } else {
      // Either both axes are animating or only the main axis is animating or
      // the cross axis hasn't changed (because otherwise the previous condition
      // would have executed instead).
      const SizeBound bounds_main = GetMainAxis(orientation(), available_size);
      const int host_main = GetMainAxis(orientation(), host_size);
      const int current_main =
          GetMainAxis(orientation(), current_layout_.host_size);
      if ((current_main > host_main) || (current_main > bounds_main)) {
        // Reset the layout immediately if the current layout exceeds the host
        // size or the available space.
        last_available_host_size_ = available_size;
        ResetLayoutToSize(host_size);
      } else if (available_size != last_available_host_size_) {
        // May need to re-trigger animation if our bounds were relaxed; let us
        // expand into the new available space.
        RecalculateTarget();
      }
    }

    // Verify that the last available size has been updated.
    DCHECK_EQ(available_size, last_available_host_size_);
  }

  ApplyLayout(current_layout_);

  // Send animating stopped events on layout so the current layout during the
  // event represents the final state instead of an intermediate state.
  if (is_animating_ && current_offset_ == 1.0) {
    EndAnimation();
  }

  if (hold_queued_actions_for_layout_ && !is_animating_) {
    hold_queued_actions_for_layout_ = false;
    PostQueuedActions();
  }
}

void AnimatingLayoutManager::EndAnimation() {
  // Make sure an opacity animation is in the correct state before clearing
  // |fade_infos_|.
  for (auto fade_info : fade_infos_) {
    if (fade_info.fade_type != LayoutFadeType::kFadingOut &&
        fade_info.child_view->layer()) {
      fade_info.child_view->layer()->SetOpacity(1);
    }
  }
  fade_infos_.clear();
  hold_queued_actions_for_layout_ = true;
  if (std::exchange(is_animating_, false)) {
    NotifyIsAnimatingChanged();
  }
}

void AnimatingLayoutManager::ResetLayoutToTargetSize() {
  ResetLayoutToSize(GetAvailableTargetLayoutSize());
}

void AnimatingLayoutManager::ResetLayoutToSize(const gfx::Size& target_size) {
  if (animation_delegate_) {
    animation_delegate_->Reset();
  }

  ResolveFades();

  target_layout_ = target_layout_manager()->GetProposedLayout(target_size);
  current_layout_ = target_layout_;
  starting_layout_ = current_layout_;
  current_offset_ = 1.0;
  set_cached_layout_size(target_size);
  EndAnimation();
}

bool AnimatingLayoutManager::RecalculateTarget() {
  constexpr double kResetAnimationThreshold = 0.8;

  if (!target_layout_manager()) {
    return false;
  }

  if (!cached_layout_size() || !animation_delegate_ ||
      !animation_delegate_->ready_to_animate()) {
    ResetLayoutToTargetSize();
    return true;
  }

  const gfx::Size target_size = GetAvailableTargetLayoutSize();
  set_cached_layout_size(target_size);

  // If there has been no appreciable change in layout, there's no reason to
  // start or update an animation.
  const ProposedLayout proposed_layout =
      target_layout_manager()->GetProposedLayout(target_size);

  if (target_layout_ == proposed_layout) {
    return false;
  }

  target_layout_ = proposed_layout;
  if (current_offset_ > kResetAnimationThreshold) {
    starting_layout_ = current_layout_;
    starting_offset_ = 0.0;
    current_offset_ = 0.0;
    animation_delegate_->Animate();
    if (!is_animating_) {
      is_animating_ = true;
      NotifyIsAnimatingChanged();
    }
  } else if (current_offset_ > starting_offset_) {
    // Only update the starting layout if the animation has progressed. This has
    // the effect of "batching up" changes that all happen on the same frame,
    // keeping the same starting point. (A common example of this is multiple
    // child views' visibility changing.)
    starting_layout_ = current_layout_;
    starting_offset_ = current_offset_;
  } else if (starting_layout_ == target_layout_) {
    // If we initiated but did not show any frames of an animation, and we are
    // redirected to our starting layout then just reset the layout.
    ResetLayoutToSize(target_size);
    return false;
  }
  CalculateFadeInfos();

  // We've calculated all of the targets and fades. Start the layout process if
  // we are animating, but if animations are disabled, snap to the final
  // layout.
  if (gfx::Animation::ShouldRenderRichAnimation()) {
    UpdateCurrentLayout(0.0, 0.0, 1.0);
  } else {
    ResetLayoutToSize(target_size);
  }

  return true;
}

void AnimatingLayoutManager::AnimateTo(double value,
                                       double fade_in_opacity,
                                       double fade_out_opacity) {
  DCHECK_GE(value, 0.0);
  DCHECK_LE(value, 1.0);
  DCHECK_GE(value, starting_offset_);
  DCHECK_GE(value, current_offset_);
  if (current_offset_ == value) {
    return;
  }
  current_offset_ = value;
  const double percent =
      (current_offset_ - starting_offset_) / (1.0 - starting_offset_);
  UpdateCurrentLayout(percent, fade_in_opacity, fade_out_opacity);
  InvalidateHost(false);
}

void AnimatingLayoutManager::NotifyIsAnimatingChanged() {
  observers_.Notify(&Observer::OnLayoutIsAnimatingChanged, this,
                    is_animating());
}

void AnimatingLayoutManager::RunQueuedActions() {
  run_queued_actions_is_pending_ = false;
  std::vector<base::OnceClosure> actions = std::move(queued_actions_to_run_);
  for (auto& action : actions) {
    std::move(action).Run();
  }
}

void AnimatingLayoutManager::PostQueuedActions() {
  // Move queued actions over to actions that should run during the next
  // PostTask(). This prevents a race between old PostTask() calls and new
  // delayed actions. See the header for more detail.
  for (auto& action : queued_actions_) {
    queued_actions_to_run_.push_back(std::move(action));
  }
  queued_actions_.clear();

  // Early return to prevent multiple RunQueuedAction() tasks.
  if (run_queued_actions_is_pending_) {
    return;
  }

  // Post to self (instead of posting the queued actions directly) which lets
  // us:
  // * Keep "AnimatingLayoutManager::RunQueuedActions" in the stack frame.
  // * Tie the task lifetimes to AnimatingLayoutManager.
  run_queued_actions_is_pending_ =
      base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE, base::BindOnce(&AnimatingLayoutManager::RunQueuedActions,
                                    weak_ptr_factory_.GetWeakPtr()));
}

void AnimatingLayoutManager::UpdateCurrentLayout(double percent,
                                                 double fade_in_opacity,
                                                 double fade_out_opacity) {
  // This drops out any child view elements that don't exist in the target
  // layout. We'll add them back in later.
  current_layout_ =
      ProposedLayoutBetween(percent, starting_layout_, target_layout_);

  for (const LayoutFadeInfo& fade_info : fade_infos_) {
    // This shouldn't happen but we should ensure that with a check.
    DCHECK(host_view()->GetIndexOf(fade_info.child_view).has_value());

    // Views that were previously fading are animated as normal, so nothing to
    // do here.
    if (fade_info.fade_type == LayoutFadeType::kContinuingFade) {
      continue;
    }

    ChildLayout child_layout;

    if (percent == 1.0) {
      // At the end of the animation snap to the final state of the child view.
      child_layout.child_view = fade_info.child_view;
      switch (fade_info.fade_type) {
        case LayoutFadeType::kFadingIn:
          child_layout.visible = true;
          child_layout.bounds =
              Denormalize(orientation(), fade_info.reference_bounds);
          if (fade_info.child_view->layer()) {
            fade_info.child_view->layer()->SetOpacity(1);
          }
          break;
        case LayoutFadeType::kFadingOut:
          child_layout.visible = false;
          if (child_layout.child_view->layer()) {
            child_layout.child_view->layer()->SetOpacity(0);
          }
          break;
        case LayoutFadeType::kContinuingFade:
          NOTREACHED();
      }
    } else if (default_fade_mode_ == FadeInOutMode::kHide) {
      child_layout.child_view = fade_info.child_view;
      child_layout.visible = false;
    } else {
      const double scale_percent =
          fade_info.fade_type == LayoutFadeType::kFadingIn ? percent
                                                           : 1.0 - percent;
      double opacity_value = fade_info.fade_type == LayoutFadeType::kFadingIn
                                 ? fade_in_opacity
                                 : fade_out_opacity;

      switch (default_fade_mode_) {
        case FadeInOutMode::kHide:
          NOTREACHED();
        case FadeInOutMode::kScaleFromMinimum:
          child_layout = CalculateScaleFade(fade_info, scale_percent,
                                            /* scale_from_zero */ false);
          break;
        case FadeInOutMode::kScaleFromZero:
          child_layout = CalculateScaleFade(fade_info, scale_percent,
                                            /* scale_from_zero */ true);
          break;
        case FadeInOutMode::kSlideFromLeadingEdge:
          child_layout = CalculateSlideFade(fade_info, scale_percent,
                                            /* slide_from_leading */ true);
          break;
        case FadeInOutMode::kSlideFromTrailingEdge:
          child_layout = CalculateSlideFade(fade_info, scale_percent,
                                            /* slide_from_leading */ false);
          break;
        case FadeInOutMode::kFadeAndSlideFromTrailingEdge:
          child_layout = CalculateFadeAndSlideFade(fade_info, scale_percent,
                                                   opacity_value, false);
          break;
      }
    }

    ChildLayout* const to_overwrite =
        FindChildViewInLayout(&current_layout_, fade_info.child_view);
    if (to_overwrite) {
      *to_overwrite = child_layout;
    } else {
      current_layout_.child_layouts.push_back(child_layout);
    }
  }
}

void AnimatingLayoutManager::CalculateFadeInfos() {
  // Save any views that were previously fading so we don't try to key off of
  // them when calculating leading/trailing edge.
  std::set<const View*> previously_fading;
  for (const auto& fade_info : fade_infos_) {
    previously_fading.insert(fade_info.child_view);
  }

  fade_infos_.clear();

  struct ChildInfo {
    std::optional<size_t> start;
    NormalizedRect start_bounds;
    bool start_visible = false;
    std::optional<size_t> target;
    NormalizedRect target_bounds;
    bool target_visible = false;
  };

  std::map<View*, ChildInfo> child_to_info;
  std::map<int, View*> start_leading_edges;
  std::map<int, View*> target_leading_edges;

  // Collect some bookkeping info to prevent linear searches later.

  for (View* child : host_view()->children()) {
    if (IsChildIncludedInLayout(child, /* include hidden */ true)) {
      child_to_info.emplace(child, ChildInfo());
    }
  }

  for (size_t i = 0; i < starting_layout_.child_layouts.size(); ++i) {
    const auto& child_layout = starting_layout_.child_layouts[i];
    auto it = child_to_info.find(child_layout.child_view);
    if (it != child_to_info.end()) {
      it->second.start = i;
      it->second.start_bounds = Normalize(orientation(), child_layout.bounds);
      it->second.start_visible = child_layout.visible;
    }
  }

  for (size_t i = 0; i < target_layout_.child_layouts.size(); ++i) {
    const auto& child_layout = target_layout_.child_layouts[i];
    auto it = child_to_info.find(child_layout.child_view);
    if (it != child_to_info.end()) {
      it->second.target = i;
      it->second.target_bounds = Normalize(orientation(), child_layout.bounds);
      it->second.target_visible = child_layout.visible;
    }
  }

  for (View* child : host_view()->children()) {
    const auto& index = child_to_info[child];
    if (index.start_visible && index.target_visible &&
        !base::Contains(previously_fading, child)) {
      start_leading_edges.emplace(index.start_bounds.origin_main(), child);
      target_leading_edges.emplace(index.target_bounds.origin_main(), child);
    }
  }

  // Build the LayoutFadeInfo data.

  const NormalizedSize start_host_size =
      Normalize(orientation(), starting_layout_.host_size);
  const NormalizedSize target_host_size =
      Normalize(orientation(), target_layout_.host_size);

  for (View* child : host_view()->children()) {
    const auto& current = child_to_info[child];
    if (current.start_visible && !current.target_visible) {
      LayoutFadeInfo fade_info;
      fade_info.fade_type = LayoutFadeType::kFadingOut;
      fade_info.child_view = child;
      fade_info.reference_bounds = current.start_bounds;
      auto next =
          start_leading_edges.upper_bound(current.start_bounds.origin_main());
      if (next == start_leading_edges.end()) {
        fade_info.next_view = nullptr;
        fade_info.offsets.set_trailing(start_host_size.main() -
                                       current.start_bounds.max_main());
      } else {
        fade_info.next_view = next->second;
        fade_info.offsets.set_trailing(next->first -
                                       current.start_bounds.max_main());
      }
      if (next == start_leading_edges.begin()) {
        fade_info.prev_view = nullptr;
        fade_info.offsets.set_leading(current.start_bounds.origin_main());
      } else {
        auto prev = next;
        --prev;
        const auto& prev_info = child_to_info[prev->second];
        fade_info.prev_view = prev->second;
        fade_info.offsets.set_leading(current.start_bounds.origin_main() -
                                      prev_info.start_bounds.max_main());
      }
      fade_infos_.push_back(fade_info);
    } else if (!current.start_visible && current.target_visible) {
      LayoutFadeInfo fade_info;
      fade_info.fade_type = LayoutFadeType::kFadingIn;
      fade_info.child_view = child;
      fade_info.reference_bounds = current.target_bounds;
      auto next =
          target_leading_edges.upper_bound(current.target_bounds.origin_main());
      if (next == target_leading_edges.end()) {
        fade_info.next_view = nullptr;
        fade_info.offsets.set_trailing(target_host_size.main() -
                                       current.target_bounds.max_main());
      } else {
        fade_info.next_view = next->second;
        fade_info.offsets.set_trailing(next->first -
                                       current.target_bounds.max_main());
      }
      if (next == target_leading_edges.begin()) {
        fade_info.prev_view = nullptr;
        fade_info.offsets.set_leading(current.target_bounds.origin_main());
      } else {
        auto prev = next;
        --prev;
        const auto& prev_info = child_to_info[prev->second];
        fade_info.prev_view = prev->second;
        fade_info.offsets.set_leading(current.target_bounds.origin_main() -
                                      prev_info.target_bounds.max_main());
      }
      fade_infos_.push_back(fade_info);
    } else if (base::Contains(previously_fading, child)) {
      // Capture the fact that this view was fading as part of an animation that
      // was interrupted. (It is therefore technically still fading.) This
      // status goes away when the animation ends.
      LayoutFadeInfo fade_info;
      fade_info.fade_type = LayoutFadeType::kContinuingFade;
      fade_info.child_view = child;
      // No reference bounds or offsets since we'll use the normal animation
      // pathway for this view.
      fade_infos_.push_back(fade_info);
    }
  }
}

void AnimatingLayoutManager::ResolveFades() {
  // Views that need faded out are views which were were fading out previously
  // because they were set to not be visible, either by calling SetVisible() or
  // FadeOut(). Those views will not be included in the new layout but may not
  // have been allowed to become invisible yet because of the fade-out
  // animation. Even in the case of FadeInOutMode::kHide, if no frames of the
  // animation have run, the relevant view may still be visible.
  for (const LayoutFadeInfo& fade_info : fade_infos_) {
    View* const child = fade_info.child_view;
    if (fade_info.fade_type == LayoutFadeType::kFadingOut &&
        host_view()->GetIndexOf(child).has_value() &&
        !child->GetProperty(kViewIgnoredByLayoutKey) &&
        !IsChildIncludedInLayout(child)) {
      SetViewVisibility(child, false);
    }
    if (default_fade_mode_ == FadeInOutMode::kFadeAndSlideFromTrailingEdge &&
        fade_info.fade_type == LayoutFadeType::kFadingIn &&
        host_view()->GetIndexOf(child).has_value() && child->layer()) {
      child->layer()->SetOpacity(1);
    }
  }
}

ChildLayout AnimatingLayoutManager::CalculateScaleFade(
    const LayoutFadeInfo& fade_info,
    double scale_percent,
    bool scale_from_zero) const {
  ChildLayout child_layout;

  int leading_reference_point = 0;
  if (fade_info.prev_view) {
    // Since prev/next view is always a view in the start and target layouts, it
    // should also be in the current layout. Therefore this should never return
    // null.
    const ChildLayout* const prev_layout =
        FindChildViewInLayout(current_layout_, fade_info.prev_view);
    leading_reference_point =
        Normalize(orientation(), prev_layout->bounds).max_main();
  }
  leading_reference_point += fade_info.offsets.leading();

  int trailing_reference_point;
  if (fade_info.next_view) {
    // Since prev/next view is always a view in the start and target layouts, it
    // should also be in the current layout. Therefore this should never return
    // null.
    const ChildLayout* const next_layout =
        FindChildViewInLayout(current_layout_, fade_info.next_view);
    trailing_reference_point =
        Normalize(orientation(), next_layout->bounds).origin_main();
  } else {
    trailing_reference_point =
        Normalize(orientation(), current_layout_.host_size).main();
  }
  trailing_reference_point -= fade_info.offsets.trailing();

  const int new_size = std::min(
      base::ClampRound(scale_percent * fade_info.reference_bounds.size_main()),
      trailing_reference_point - leading_reference_point);

  child_layout.child_view = fade_info.child_view;
  if (new_size > 0 &&
      (scale_from_zero ||
       new_size >=
           Normalize(orientation(), fade_info.child_view->GetMinimumSize())
               .main())) {
    child_layout.visible = true;
    NormalizedRect new_bounds = fade_info.reference_bounds;
    switch (fade_info.fade_type) {
      case LayoutFadeType::kFadingIn:
        new_bounds.set_origin_main(leading_reference_point);
        break;
      case LayoutFadeType::kFadingOut:
        new_bounds.set_origin_main(trailing_reference_point - new_size);
        break;
      case LayoutFadeType::kContinuingFade:
        NOTREACHED();
    }
    new_bounds.set_size_main(new_size);
    child_layout.bounds = Denormalize(orientation(), new_bounds);
  }

  return child_layout;
}

ChildLayout AnimatingLayoutManager::CalculateSlideFade(
    const LayoutFadeInfo& fade_info,
    double scale_percent,
    bool slide_from_leading) const {
  // Fall back to kScaleFromMinimum if there is no edge to slide out from.
  if (!fade_info.prev_view && !fade_info.next_view) {
    return CalculateScaleFade(fade_info, scale_percent, false);
  }

  // Slide from the other direction if against the edge of the host view.
  if (slide_from_leading && !fade_info.prev_view) {
    slide_from_leading = false;
  } else if (!slide_from_leading && !fade_info.next_view) {
    slide_from_leading = true;
  }

  NormalizedRect new_bounds = fade_info.reference_bounds;

  // Determine which layout the sliding view will be completely faded in.
  const ProposedLayout* fully_faded_layout;
  switch (fade_info.fade_type) {
    case LayoutFadeType::kFadingIn:
      fully_faded_layout = &starting_layout_;
      break;
    case LayoutFadeType::kFadingOut:
      fully_faded_layout = &target_layout_;
      break;
    case LayoutFadeType::kContinuingFade:
      NOTREACHED();
  }

  if (slide_from_leading) {
    // Get the layout info for the leading child.
    const ChildLayout* const leading_child =
        FindChildViewInLayout(*fully_faded_layout, fade_info.prev_view);

    // This is the right side of the leading control that will eclipse the
    // sliding view at the start/end of the animation.
    const int initial_trailing =
        Normalize(orientation(), leading_child->bounds).max_main();

    // Interpolate between initial and final trailing edge.
    const int new_trailing = gfx::Tween::IntValueBetween(
        scale_percent, initial_trailing, new_bounds.max_main());

    // Adjust the bounding rectangle of the view.
    new_bounds.Offset(new_trailing - new_bounds.max_main(), 0);

  } else {
    // Get the layout info for the trailing child.
    const ChildLayout* const trailing_child =
        FindChildViewInLayout(*fully_faded_layout, fade_info.next_view);

    // This is the left side of the trailing control that will eclipse the
    // sliding view at the start/end of the animation.
    const int initial_leading =
        Normalize(orientation(), trailing_child->bounds).origin_main();

    // Interpolate between initial and final leading edge.
    const int new_leading = gfx::Tween::IntValueBetween(
        scale_percent, initial_leading, new_bounds.origin_main());

    // Adjust the bounding rectangle of the view.
    new_bounds.Offset(new_leading - new_bounds.origin_main(), 0);
  }

  // Actual bounds are a linear interpolation between starting and reference
  // bounds.
  ChildLayout child_layout;
  child_layout.child_view = fade_info.child_view;
  child_layout.visible = true;
  child_layout.bounds = Denormalize(orientation(), new_bounds);

  return child_layout;
}

ChildLayout AnimatingLayoutManager::CalculateFadeAndSlideFade(
    const LayoutFadeInfo& fade_info,
    double scale_percent,
    double opacity_value,
    bool slide_from_leading) const {
  // If not painting to a layer we cannot perform an opacity animation on the
  // view, fall back to just doing a slide animation.
  if (!fade_info.child_view->layer()) {
    return CalculateSlideFade(fade_info, scale_percent, slide_from_leading);
  }

  NormalizedRect new_bounds = fade_info.reference_bounds;

  // Determine which layout the sliding view will be completely faded in.
  const ProposedLayout* fully_faded_layout;
  switch (fade_info.fade_type) {
    case LayoutFadeType::kFadingIn:
      fully_faded_layout = &starting_layout_;
      break;
    case LayoutFadeType::kFadingOut:
      fully_faded_layout = &target_layout_;
      break;
    case LayoutFadeType::kContinuingFade:
      NOTREACHED();
  }

  if (!slide_from_leading) {
    // Find the leading edge of the next child, if there is no next child we use
    // the edge of the host view.
    const ChildLayout* const trailing_child =
        FindChildViewInLayout(*fully_faded_layout, fade_info.next_view);
    const int host_trailing =
        Normalize(orientation(), fully_faded_layout->host_size).main();
    int leading_bound =
        !trailing_child
            ? host_trailing
            : Normalize(orientation(), trailing_child->bounds).origin_main();
    // Interpolate between initial and final leading edge.
    const int new_leading = gfx::Tween::IntValueBetween(
        scale_percent, leading_bound, new_bounds.origin_main());

    new_bounds.Offset(new_leading - new_bounds.origin_main(), 0);
  }

  ChildLayout child_layout;
  child_layout.child_view = fade_info.child_view;
  child_layout.visible = true;
  child_layout.bounds = Denormalize(orientation(), new_bounds);
  fade_info.child_view->layer()->SetOpacity(opacity_value);

  return child_layout;
}

// Returns the space in which to calculate the target layout.
gfx::Size AnimatingLayoutManager::GetAvailableTargetLayoutSize() {
  if (bounds_animation_mode_ == BoundsAnimationMode::kUseHostBounds) {
    return host_view()->size();
  }

  const SizeBounds bounds = GetAvailableHostSize();
  last_available_host_size_ = bounds;
  const gfx::Size preferred_size =
      target_layout_manager()->GetPreferredSize(host_view());

  int width;

  if (orientation() == LayoutOrientation::kVertical &&
      bounds_animation_mode_ == BoundsAnimationMode::kAnimateMainAxis) {
    width = host_view()->width();
  } else {
    width = bounds.width().min_of(preferred_size.width());
  }

  int height;

  if (orientation() == LayoutOrientation::kHorizontal &&
      bounds_animation_mode_ == BoundsAnimationMode::kAnimateMainAxis) {
    height = host_view()->height();
  } else {
    height = width < preferred_size.width()
                 ? target_layout_manager()->GetPreferredHeightForWidth(
                       host_view(), width)
                 : preferred_size.height();
    height = bounds.height().min_of(height);
  }

  return gfx::Size(width, height);
}

// static
gfx::Size AnimatingLayoutManager::DefaultFlexRuleImpl(
    const AnimatingLayoutManager* animating_layout,
    const View* view,
    const SizeBounds& size_bounds) {
  DCHECK_EQ(view->GetLayoutManager(), animating_layout);

  // This is the current preferred size, which takes animation into account.
  const gfx::Size preferred_size = animating_layout->GetPreferredSize(view);

  // Does the preferred size fit in the bounds? If so, return the preferred
  // size. Note that the *target* size might not fit in the bounds, but we'll
  // recalculate that the next time we lay out.
  //
  // The one exception is if the current preferred size is empty. If that's the
  // case, then this check becomes trivial and the layout can get stuck at zero
  // size (which is bad). See crbug.com/1506607 for an example of an empty
  // layout causing issues.
  if (!preferred_size.IsEmpty() &&
      CanFitInBounds(preferred_size, size_bounds)) {
    return preferred_size;
  }

  // Special case - if we're being asked for a zero-size layout we'll return the
  // minimum size of the layout. This is because we're being probed for how
  // small we can get, not being asked for an actual size.
  if (GetMainAxis(animating_layout->orientation(), size_bounds) <= 0) {
    return animating_layout->GetMinimumSize(view);
  }

  // We know our current size does not fit into the bounds being given to us.
  // This is going to force a snap to a new size, which will be the ideal size
  // of the target layout in the provided space.
  const LayoutManagerBase* const target_layout =
      animating_layout->target_layout_manager();

  // Easiest case is that the target layout's preferred size *does* fit, in
  // which case we can use that.
  const gfx::Size target_preferred = target_layout->GetPreferredSize(view);
  if (CanFitInBounds(target_preferred, size_bounds)) {
    return target_preferred;
  }

  // We know that at least one of the width and height are constrained, so we
  // need to ask the target layout how large it wants to be in the space
  // provided.
  gfx::Size size;
  if (size_bounds.width().is_bounded() && size_bounds.height().is_bounded()) {
    // Both width and height are specified.  Constraining the width may change
    // the desired height, so we can't just blindly return the minimum in both
    // dimensions.  Instead, query the target layout in the constrained space
    // and return its size.
    size = gfx::Size(size_bounds.width().value(), size_bounds.height().value());
  } else if (size_bounds.width().is_bounded()) {
    // The width is specified and too small.  Use the height-for-width
    // calculation.
    // TODO(dfried): This should be rare, but it is also inefficient. See if we
    // can't add an alternative to GetPreferredHeightForWidth() that actually
    // calculates the layout in this space so we don't have to do it twice.
    const int width = size_bounds.width().value();
    size = gfx::Size(width,
                     target_layout->GetPreferredHeightForWidth(view, width));
  } else {
    DCHECK(size_bounds.height().is_bounded());
    // The height is specified and too small.  Fortunately the height of a
    // layout can't (shouldn't?) affect its width.
    size = gfx::Size(target_preferred.width(), size_bounds.height().value());
  }

  return target_layout->GetProposedLayout(size).host_size;
}

}  // namespace views