chromium/content/browser/navigation_transitions/back_forward_transition_animation_manager_android_browsertest.cc

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

#include "content/browser/navigation_transitions/back_forward_transition_animation_manager_android.h"

#include <sstream>
#include <string_view>

#include "base/android/scoped_java_ref.h"
#include "base/numerics/ranges.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "cc/slim/layer.h"
#include "cc/slim/layer_tree.h"
#include "cc/slim/layer_tree_impl.h"
#include "cc/slim/solid_color_layer.h"
#include "cc/slim/surface_layer.h"
#include "cc/slim/ui_resource_layer.h"
#include "cc/test/pixel_test_utils.h"
#include "content/browser/accessibility/browser_accessibility_manager_android.h"
#include "content/browser/browser_context_impl.h"
#include "content/browser/navigation_transitions/back_forward_transition_animator.h"
#include "content/browser/navigation_transitions/physics_model.h"
#include "content/browser/navigation_transitions/progress_bar.h"
#include "content/browser/renderer_host/compositor_impl_android.h"
#include "content/browser/renderer_host/navigation_transitions/navigation_entry_screenshot.h"
#include "content/browser/renderer_host/navigation_transitions/navigation_entry_screenshot_cache.h"
#include "content/browser/renderer_host/navigation_transitions/navigation_entry_screenshot_manager.h"
#include "content/browser/renderer_host/navigation_transitions/navigation_transition_utils.h"
#include "content/browser/web_contents/web_contents_android.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/browser/web_contents/web_contents_view.h"
#include "content/browser/web_contents/web_contents_view_android.h"
#include "content/common/content_navigation_policy.h"
#include "content/public/browser/back_forward_transition_animation_manager.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/result_codes.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/commit_message_delayer.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/navigation_transition_test_utils.h"
#include "content/public/test/test_frame_navigation_observer.h"
#include "content/public/test/update_user_activation_state_interceptor.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "content/test/did_commit_navigation_interceptor.h"
#include "content/test/render_document_feature.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/default_handlers.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "third_party/blink/public/common/input/web_input_event.h"
#include "third_party/blink/public/mojom/frame/frame.mojom-test-utils.h"
#include "ui/android/progress_bar_config.h"
#include "ui/android/ui_android_features.h"
#include "ui/android/window_android.h"
#include "ui/android/window_android_compositor.h"
#include "ui/base/l10n/l10n_util_android.h"
#include "ui/events/back_gesture_event.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gfx/geometry/test/geometry_util.h"
#include "ui/gfx/switches.h"
#include "ui/snapshot/snapshot.h"

namespace content {

namespace {

using SwipeEdge = ui::BackGestureEventSwipeEdge;
using NavType = BackForwardTransitionAnimationManager::NavigationDirection;
using base::test::TestFuture;
using testing::AssertionFailure;
using testing::AssertionResult;
using testing::AssertionSuccess;
using testing::Combine;
using testing::TestParamInfo;
using testing::Values;
using testing::WithParamInterface;

using AnimatorState = BackForwardTransitionAnimator::State;
using enum BackForwardTransitionAnimator::State;

// The tolerance for two float to be considered equal.
static constexpr float kFloatTolerance = 0.001f;

#define EXPECT_X_TRANSLATION(expected, actual)                    \
  EXPECT_TRANSFORM_NEAR(ViewportTranslationX(expected), (actual), \
                        kFloatTolerance)

#define EXPECT_STATE_EQ(expected, actual)                                  \
  EXPECT_EQ(expected, actual)                                              \
      << "Expected: " << BackForwardTransitionAnimator::ToString(expected) \
      << " but got " << BackForwardTransitionAnimator::ToString(actual);

// TODO(liuwilliam): 99 seconds seems arbitrary. Pick a meaningful constant
// instead.
// If the duration is long enough, the spring will return the final (rest /
// equilibrium) position right away. This means each spring model will just
// produce one frame: the frame for the final position.
constexpr base::TimeDelta kLongDurationBetweenFrames = base::Seconds(99);

// Test parameter to run tests either with default device-scale-factor==1 or a
// fractional (1.333) device scale factor.
enum class DSFMode { kOne, kFractional };

// Test parameter to run tests with UI laid out both left to right and right
// to left, the latter forces the back-edge to be flipped (i.e. right edge
// uses an animated gesture).
enum class UILayoutDirection { kLTR, kRTL };

static constexpr gfx::Transform kIdentityTransform;

AssertionResult ColorsNear(const SkColor4f& e, const SkColor4f& a) {
  if (base::IsApproximatelyEqual(e.fA, a.fA, kFloatTolerance) &&
      base::IsApproximatelyEqual(e.fB, a.fB, kFloatTolerance) &&
      base::IsApproximatelyEqual(e.fG, a.fG, kFloatTolerance) &&
      base::IsApproximatelyEqual(e.fR, a.fR, kFloatTolerance)) {
    return AssertionSuccess();
  }

  return AssertionFailure()
         << "Expected color [" << e.fR << "," << e.fG << "," << e.fB << ","
         << e.fA << "] but got actual [" << a.fR << "," << a.fG << "," << a.fB
         << "," << a.fA << "].";
}

// For light mode.
static constexpr SkColor4f kScrimColorAtStart = {0, 0, 0, 0.1f};
static constexpr SkColor4f kScrimColorAt30 = {0, 0, 0, 0.0745f};
static constexpr SkColor4f kScrimColorAt60 = {0, 0, 0, 0.049f};
static constexpr SkColor4f kScrimColorAt90 = {0, 0, 0, 0.0235f};

int64_t GetItemSequenceNumberForNavigation(
    NavigationHandle* navigation_handle) {
  auto* request = static_cast<NavigationRequest*>(navigation_handle);
  EXPECT_TRUE(request->GetNavigationEntry());
  EXPECT_TRUE(request->GetRenderFrameHost());
  return static_cast<NavigationEntryImpl*>(request->GetNavigationEntry())
      ->GetFrameEntry(request->GetRenderFrameHost()->frame_tree_node())
      ->item_sequence_number();
}

class AnimatorForTesting : public BackForwardTransitionAnimator {
 public:
  explicit AnimatorForTesting(
      WebContentsViewAndroid* web_contents_view_android,
      NavigationControllerImpl* controller,
      const ui::BackGestureEvent& gesture,
      BackForwardTransitionAnimationManager::NavigationDirection nav_type,
      ui::BackGestureEventSwipeEdge initiating_edge,
      NavigationEntryImpl* destination_entry,
      const SkBitmap& embedder_bitmap,
      BackForwardTransitionAnimationManagerAndroid* animation_manager)
      : BackForwardTransitionAnimator(web_contents_view_android,
                                      controller,
                                      gesture,
                                      nav_type,
                                      initiating_edge,
                                      destination_entry,
                                      embedder_bitmap,
                                      animation_manager),
        wcva_(web_contents_view_android) {}

  ~AnimatorForTesting() override {
    if (on_impl_destroyed_) {
      std::move(on_impl_destroyed_).Run(state());
    }
  }

  // `BackForwardTransitionAnimator`:
  void OnRenderFrameMetadataChangedAfterActivation(
      base::TimeTicks activation_time) override {
    if (intercept_render_frame_metadata_changed_) {
      return;
    }
    if (state() == State::kWaitingForNewRendererToDraw &&
        waited_for_renderer_new_frame_) {
      std::move(waited_for_renderer_new_frame_).Run();
    }

    BackForwardTransitionAnimator::OnRenderFrameMetadataChangedAfterActivation(
        activation_time);
  }
  // TODO(bokan): Caution: this override ignores the passed in frame_begin_time
  // and instead sets the time by incrementing by `duration_between_frames_`.
  // It would be clearer if tests simulated an animation frame from the test
  // body with a supplied frame time. See DirectlyCallOnAnimate below this one.
  void OnAnimate(base::TimeTicks frame_begin_time) override {
    if (pause_on_animate_at_state_.has_value() &&
        pause_on_animate_at_state_.value() == state()) {
      return;
    }
    if (next_on_animate_callback_) {
      std::move(next_on_animate_callback_).Run();
    }
    static base::TimeTicks tick = base::TimeTicks();
    tick += duration_between_frames_;
    BackForwardTransitionAnimator::OnAnimate(tick);
  }
  void DirectlyCallOnAnimate(base::TimeTicks frame_time) {
    BackForwardTransitionAnimator::OnAnimate(frame_time);
  }
  void OnCancelAnimationDisplayed() override {
    if (on_cancel_animation_displayed_) {
      std::move(on_cancel_animation_displayed_).Run();
    }
    BackForwardTransitionAnimator::OnCancelAnimationDisplayed();
  }
  void OnInvokeAnimationDisplayed() override {
    if (on_invoke_animation_displayed_) {
      std::move(on_invoke_animation_displayed_).Run();
    }
    BackForwardTransitionAnimator::OnInvokeAnimationDisplayed();
  }
  void OnCrossFadeAnimationDisplayed() override {
    if (on_cross_fade_animation_displayed_) {
      std::move(on_cross_fade_animation_displayed_).Run();
    }

    BackForwardTransitionAnimator::OnCrossFadeAnimationDisplayed();
  }
  void DidStartNavigation(NavigationHandle* navigation_handle) override {
    last_navigation_request_ =
        NavigationRequest::From(navigation_handle)->GetWeakPtr();
    BackForwardTransitionAnimator::DidStartNavigation(navigation_handle);
  }
  void ReadyToCommitNavigation(NavigationHandle* navigation_handle) override {
    BackForwardTransitionAnimator::ReadyToCommitNavigation(navigation_handle);
    if (post_ready_to_commit_callback_) {
      std::move(post_ready_to_commit_callback_).Run();
    }
  }
  void DidFinishNavigation(NavigationHandle* navigation_handle) override {
    if (did_finish_navigation_callback_) {
      auto* request = static_cast<NavigationRequest*>(navigation_handle);
      std::move(did_finish_navigation_callback_)
          .Run(request->GetRenderFrameHost()
                   ->GetView()
                   ->host()
                   ->IsContentRenderingTimeoutRunning());
    }
    BackForwardTransitionAnimator::DidFinishNavigation(navigation_handle);
  }

  NavigationRequest* LastNavigationRequest() {
    CHECK(last_navigation_request_);
    return last_navigation_request_.get();
  }
  void PauseAnimationAtDisplayingCancelAnimation() {
    ASSERT_FALSE(pause_on_animate_at_state_.has_value()) << "Already paused.";
    pause_on_animate_at_state_ = State::kDisplayingCancelAnimation;
  }

  void PauseAnimationAtDisplayingInvokeAnimation() {
    ASSERT_FALSE(pause_on_animate_at_state_.has_value()) << "Already paused.";
    pause_on_animate_at_state_ = State::kDisplayingInvokeAnimation;
  }

  void PauseAnimationAtDisplayingCrossFadeAnimation() {
    ASSERT_FALSE(pause_on_animate_at_state_.has_value()) << "Already paused.";
    pause_on_animate_at_state_ = State::kDisplayingCrossFadeAnimation;
  }

  void UnpauseAnimation() {
    pause_on_animate_at_state_ = std::nullopt;
    OnAnimate(base::TimeTicks{});
  }

  void set_intercept_render_frame_metadata_changed(bool intercept) {
    intercept_render_frame_metadata_changed_ = intercept;
  }
  void set_on_cancel_animation_displayed(base::OnceClosure callback) {
    CHECK(!on_cancel_animation_displayed_);
    on_cancel_animation_displayed_ = std::move(callback);
  }
  void set_on_invoke_animation_displayed(base::OnceClosure callback) {
    CHECK(!on_invoke_animation_displayed_);
    on_invoke_animation_displayed_ = std::move(callback);
  }
  void set_on_cross_fade_animation_displayed(base::OnceClosure callback) {
    CHECK(!on_cross_fade_animation_displayed_);
    on_cross_fade_animation_displayed_ = std::move(callback);
  }
  void set_waited_for_renderer_new_frame(base::OnceClosure callback) {
    CHECK(!waited_for_renderer_new_frame_);
    waited_for_renderer_new_frame_ = std::move(callback);
  }
  void set_next_on_animate_callback(base::OnceClosure callback) {
    CHECK(!next_on_animate_callback_);
    next_on_animate_callback_ = std::move(callback);
  }
  void set_post_ready_to_commit_callback(base::OnceClosure callback) {
    CHECK(!post_ready_to_commit_callback_);
    post_ready_to_commit_callback_ = std::move(callback);
  }
  void set_did_finish_navigation_callback(
      base::OnceCallback<void(bool)> callback) {
    CHECK(!did_finish_navigation_callback_);
    did_finish_navigation_callback_ = std::move(callback);
  }
  void set_on_impl_destroyed(base::OnceCallback<void(State)> callback) {
    CHECK(!on_impl_destroyed_);
    on_impl_destroyed_ = std::move(callback);
  }
  void set_duration_between_frames(base::TimeDelta duration) {
    duration_between_frames_ = duration;
  }
  void set_subframe_navigation(bool subframe_navigation) {
    subframe_navigation_ = subframe_navigation;
  }

  State state() const { return state_; }

  int64_t primary_main_frame_navigation_entry_item_sequence_number() const {
    return primary_main_frame_navigation_entry_item_sequence_number_;
  }

 private:
  const raw_ptr<WebContentsViewAndroid> wcva_;

  base::TimeDelta duration_between_frames_ = kLongDurationBetweenFrames;

  bool intercept_render_frame_metadata_changed_ = false;

  bool subframe_navigation_ = false;

  std::optional<State> pause_on_animate_at_state_;

  base::WeakPtr<NavigationRequest> last_navigation_request_;

  base::OnceClosure on_cancel_animation_displayed_;
  base::OnceClosure on_invoke_animation_displayed_;
  base::OnceClosure on_cross_fade_animation_displayed_;
  base::OnceClosure waited_for_renderer_new_frame_;
  base::OnceClosure next_on_animate_callback_;
  base::OnceClosure post_ready_to_commit_callback_;
  // The return value indicates if the paint-holding timer is running on the new
  // RenderWidgetHost when the animated history navigation commits.
  base::OnceCallback<void(bool)> did_finish_navigation_callback_;
  base::OnceCallback<void(State)> on_impl_destroyed_;
};

class FactoryForTesting : public BackForwardTransitionAnimator::Factory {
 public:
  explicit FactoryForTesting(const SkBitmap& override_bitmap)
      : override_bitmap_(override_bitmap) {}
  ~FactoryForTesting() override = default;

  std::unique_ptr<BackForwardTransitionAnimator> Create(
      WebContentsViewAndroid* web_contents_view_android,
      NavigationControllerImpl* controller,
      const ui::BackGestureEvent& gesture,
      BackForwardTransitionAnimationManager::NavigationDirection nav_type,
      ui::BackGestureEventSwipeEdge initiating_edge,
      NavigationEntryImpl* destination_entry,
      SkBitmap embedder_content,
      BackForwardTransitionAnimationManagerAndroid* animation_manager)
      override {
    return std::make_unique<AnimatorForTesting>(
        web_contents_view_android, controller, gesture, nav_type,
        initiating_edge, destination_entry,
        override_bitmap_.empty() ? embedder_content : override_bitmap_,
        animation_manager);
  }

 private:
  SkBitmap override_bitmap_;
};
}  // namespace

// TODO(https://crbug.com/325329998): Enable the pixel comparison so the tests
// are truly end-to-end.
class BackForwardTransitionAnimationManagerBrowserTest
    : public ContentBrowserTest {
 public:
  BackForwardTransitionAnimationManagerBrowserTest() {
    std::vector<base::test::FeatureRefAndParams> enabled_features = {
        {blink::features::kBackForwardTransitions, {}},
        {blink::features::kIncrementLocalSurfaceIdForMainframeSameDocNavigation,
         {}}};
    scoped_feature_list_.InitWithFeaturesAndParameters(
        enabled_features,
        /*disabled_features=*/{});
  }
  ~BackForwardTransitionAnimationManagerBrowserTest() override = default;

  void SetUp() override {
    if (base::SysInfo::GetAndroidHardwareEGL() == "emulation") {
      // crbug.com/337886037 and crrev.com/c/5504854/comment/b81b8fb6_95fb1381/:
      // The CopyOutputRequests crash the GPU process. ANGLE is exporting the
      // native fence support on Android emulators but it doesn't work properly.
      GTEST_SKIP();
    }
    EnablePixelOutput();
    ContentBrowserTest::SetUp();
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    ContentBrowserTest::SetUpCommandLine(command_line);
    command_line->AppendSwitch(switches::kForcePrefersNoReducedMotion);
  }

  void SetUpOnMainThread() override {
    ContentBrowserTest::SetUpOnMainThread();

    ASSERT_TRUE(
        base::FeatureList::IsEnabled(blink::features::kBackForwardTransitions));

    host_resolver()->AddRule("*", "127.0.0.1");
    embedded_test_server()->ServeFilesFromSourceDirectory(
        GetTestDataFilePath());
    net::test_server::RegisterDefaultHandlers(embedded_test_server());

    ASSERT_TRUE(embedded_test_server()->Start());

    // Manually load a "red" document because we are still at the initial
    // entry.
    ASSERT_TRUE(NavigateToURL(web_contents(), RedURL()));
    WaitForCopyableViewInWebContents(web_contents());

    auto* manager =
        BrowserContextImpl::From(web_contents()->GetBrowserContext())
            ->GetNavigationEntryScreenshotManager();
    ASSERT_TRUE(manager);
    ASSERT_EQ(manager->GetCurrentCacheSize(), 0U);
    ASSERT_TRUE(web_contents()->GetRenderWidgetHostView());
    // 10 Screenshots, with 4 bytes per screenshot.
    manager->SetMemoryBudgetForTesting(4 * GetViewportSize().Area64() * 10);

    // Set up for a backward navigation: [red&, green*].
    ScopedScreenshotCapturedObserverForTesting observer(
        web_contents()->GetController().GetLastCommittedEntryIndex());
    ASSERT_TRUE(NavigateToURL(web_contents(), GreenURL()));
    observer.Wait();
    WaitForCopyableViewInWebContents(web_contents());

    GetAnimationManager()->set_animator_factory_for_testing(
        std::make_unique<FactoryForTesting>(EmbedderBitmap()));
  }

  virtual SkBitmap EmbedderBitmap() { return SkBitmap(); }

  gfx::Size GetViewportSize() {
    return web_contents()->GetNativeView()->GetPhysicalBackingSize();
  }

  WebContentsImpl* web_contents() {
    return static_cast<WebContentsImpl*>(shell()->web_contents());
  }

  BackForwardTransitionAnimationManagerAndroid* GetAnimationManager() {
    return static_cast<BackForwardTransitionAnimationManagerAndroid*>(
        web_contents()->GetBackForwardTransitionAnimationManager());
  }

  GURL RedURL() const { return embedded_test_server()->GetURL("/red.html"); }

  GURL GreenURL() const {
    return embedded_test_server()->GetURL("/green.html");
  }

  GURL BlueURL() const { return embedded_test_server()->GetURL("/blue.html"); }

  gfx::Transform ViewportTranslationX(float translation_x) {
    return gfx::Transform::MakeTranslation(
        translation_x * GetViewportSize().width(), 0.f);
  }

  cc::slim::Layer* GetViewLayer() {
    return static_cast<WebContentsViewAndroid*>(web_contents()->GetView())
        ->GetNativeView()
        ->GetLayer();
  }

  cc::slim::Layer* GetScreenshotLayer() {
    if (!GetAnimator()) {
      return nullptr;
    }
    return GetAnimator()->screenshot_layer_for_testing();
  }

  cc::slim::Layer* GetScrimLayer() {
    if (!GetAnimator()) {
      return nullptr;
    }
    return GetAnimator()->scrim_layer_for_testing();
  }

  cc::slim::Layer* GetCloneLayer() {
    if (!GetAnimator()) {
      return nullptr;
    }
    return GetAnimator()->clone_layer_for_testing();
  }

  cc::slim::Layer* GetEmbedderLayer() {
    if (!GetAnimator()) {
      return nullptr;
    }
    return GetAnimator()->embedder_live_content_clone_for_testing();
  }

  cc::slim::Layer* GetLivePageLayer() {
    return GetAnimationManager()
        ->web_contents_view_android()
        ->parent_for_web_page_widgets();
  }

  const cc::slim::Layer* GetProgressBarLayer() {
    if (!GetAnimator() || !GetAnimator()->progress_bar_for_testing()) {
      return nullptr;
    }
    return GetAnimator()->progress_bar_for_testing()->GetLayer().get();
  }

  const cc::slim::Layer* GetRRectLayer() {
    if (!GetAnimator()) {
      return nullptr;
    }
    return GetAnimator()->rrect_layer_for_testing();
  }

  const cc::slim::Layer* GetFaviconLayer() {
    if (!GetRRectLayer()) {
      return nullptr;
    }
    EXPECT_EQ(GetRRectLayer()->children().size(), 1u);
    return GetRRectLayer()->children().at(0).get();
  }

  // Prints known children of the given layer, in increasing z-order.
  std::string ChildrenInOrder(const cc::slim::Layer& layer) {
    std::stringstream output;
    output << "[";

    bool list_non_empty = false;

    for (const auto& child : layer.children()) {
      std::string layer_name;
      if (child.get() == GetLivePageLayer()) {
        layer_name = "LivePage";
      } else if (child.get() == GetScreenshotLayer()) {
        layer_name = "Screenshot";
      } else if (child.get() == GetScrimLayer()) {
        layer_name = "Scrim";
      } else if (child.get() == GetCloneLayer()) {
        layer_name = "OldSurfaceClone";
      } else if (child.get() == GetProgressBarLayer()) {
        layer_name = "ProgressBar";
      } else if (child.get() == GetEmbedderLayer()) {
        layer_name = "EmbedderContentLayer";
      } else if (child.get() == GetRRectLayer()) {
        layer_name = "RRect";
      } else if (child.get() == GetFaviconLayer()) {
        layer_name = "Favicon";
      }

      if (!layer_name.empty()) {
        if (list_non_empty) {
          output << ",";
        }
        list_non_empty = true;

        output << layer_name;
        if (child.get() == GetScreenshotLayer() ||
            child.get() == GetRRectLayer()) {
          output << ChildrenInOrder(*child.get());
        }
      }
    }

    output << "]";
    return output.str();
  }

  // This will wait (or return immediately) for the first frame to be submitted
  // after navigating the primary main frame such that the cross fade can begin.
  // Must be called after the navigation has been committed and a the new
  // RenderFrameHost is swapped in.
  [[nodiscard]] AssertionResult WaitForFirstFrameInPrimaryMainFrame() {
    CHECK(GetAnimator());

    int64_t sequence_num =
        GetAnimator()
            ->primary_main_frame_navigation_entry_item_sequence_number();
    if (sequence_num == cc::RenderFrameMetadata::kInvalidItemSequenceNumber) {
      return AssertionFailure() << "Animator not waiting for a new frame.";
    }

    RenderFrameHostImpl* rfh = web_contents()->GetPrimaryMainFrame();
    RenderWidgetHostImpl* new_widget_host = rfh->GetRenderWidgetHost();
    while (new_widget_host->render_frame_metadata_provider()
               ->LastRenderFrameMetadata()
               .primary_main_frame_item_sequence_number != sequence_num) {
      RenderFrameSubmissionObserver frame_observer(rfh);
      frame_observer.WaitForAnyFrameSubmission();
    }

    return AssertionSuccess();
  }

  AnimatorForTesting* GetAnimator() {
    return static_cast<AnimatorForTesting*>(
        GetAnimationManager()->animator_.get());
  }

 protected:
  base::test::ScopedFeatureList scoped_feature_list_;
};

// Basic tests which will be run both with a swipe from the left edge as well as
// a swipe from the right edge with an RTL UI direction. Tests from the right
// edge also force the UI to use an RTL direction.
class BackForwardTransitionAnimationManagerBothEdgeBrowserTest
    : public BackForwardTransitionAnimationManagerBrowserTest,
      public WithParamInterface<std::tuple<UILayoutDirection, DSFMode>> {
 public:
  BackForwardTransitionAnimationManagerBothEdgeBrowserTest() {
    scoped_feature_list_.Reset();
    std::vector<base::test::FeatureRefAndParams> enabled_features = {
        {blink::features::kBackForwardTransitions, {}},
        {ui::kMirrorBackForwardGesturesInRTL, {}}};
    scoped_feature_list_.InitWithFeaturesAndParameters(
        enabled_features,
        /*disabled_features=*/{});
  }
  ~BackForwardTransitionAnimationManagerBothEdgeBrowserTest() override =
      default;

  void SetUp() override {
    if (GetUILayoutDirection() == UILayoutDirection::kRTL) {
      l10n_util::SetRtlForTesting(true);
    }

    BackForwardTransitionAnimationManagerBrowserTest::SetUp();

    if (GetDSFMode() == DSFMode::kFractional) {
      EnablePixelOutput(/*force_device_scale_factor=*/1.333f);
    }
  }

  UILayoutDirection GetUILayoutDirection() const {
    return std::get<0>(GetParam());
  }
  DSFMode GetDSFMode() const { return std::get<1>(GetParam()); }

  SwipeEdge BackEdge() const {
    if (GetUILayoutDirection() == UILayoutDirection::kRTL) {
      return SwipeEdge::RIGHT;
    }

    return SwipeEdge::LEFT;
  }

  SwipeEdge ForwardEdge() const {
    if (GetUILayoutDirection() == UILayoutDirection::kRTL) {
      return SwipeEdge::LEFT;
    }

    return SwipeEdge::RIGHT;
  }
};

// Simulates the gesture sequence: start, 30%, 60%, 90%, 60%, 30%, 60%, 90% and
// finally invoke.
IN_PROC_BROWSER_TEST_P(BackForwardTransitionAnimationManagerBothEdgeBrowserTest,
                       BasicInvokeBack) {
  ASSERT_FALSE(GetAnimator());
  ASSERT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0), BackEdge(),
                                          NavType::kBackward);
  ASSERT_TRUE(GetAnimator());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.3));
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.9));

  TestFrameNavigationObserver back_to_red(web_contents());
  {
    TestFuture<void> did_cross_fade;
    TestFuture<void> did_invoke;
    TestFuture<AnimatorState> destroyed;

    GetAnimator()->set_on_cross_fade_animation_displayed(
        did_cross_fade.GetCallback());
    GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());
    GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());

    GetAnimationManager()->OnGestureInvoked();

    ASSERT_TRUE(destroyed.Wait());
    EXPECT_STATE_EQ(kAnimationFinished, destroyed.Get());
    EXPECT_TRUE(did_invoke.IsReady());
    EXPECT_TRUE(did_cross_fade.IsReady());
  }
  back_to_red.Wait();

  ASSERT_EQ(back_to_red.last_committed_url(), RedURL());
  ASSERT_FALSE(web_contents()->GetController().GetActiveEntry()->GetUserData(
      NavigationEntryScreenshot::kUserDataKey));
}

// Simulates the gesture sequence: start, 30%, 60%, 90%, 60%, 30% and finally
// cancels.
IN_PROC_BROWSER_TEST_P(BackForwardTransitionAnimationManagerBothEdgeBrowserTest,
                       BasicCancelBack) {
  ASSERT_FALSE(GetAnimator());
  ASSERT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0), BackEdge(),
                                          NavType::kBackward);
  ASSERT_TRUE(GetAnimator());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.9));

  {
    TestFuture<AnimatorState> destroyed;
    TestFuture<void> did_cancel;
    GetAnimator()->set_on_cancel_animation_displayed(did_cancel.GetCallback());
    GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());

    GetAnimationManager()->OnGestureCancelled();

    ASSERT_TRUE(destroyed.Wait());
    EXPECT_STATE_EQ(kAnimationFinished, destroyed.Get());
    EXPECT_TRUE(did_cancel.IsReady());
  }

  ASSERT_EQ(web_contents()->GetController().GetActiveEntry()->GetURL(),
            GreenURL());
  ASSERT_EQ(web_contents()->GetController().GetLastCommittedEntryIndex(), 1);
  ASSERT_EQ(web_contents()->GetController().GetEntryAtIndex(0)->GetURL(),
            RedURL());
  ASSERT_TRUE(web_contents()->GetController().GetEntryAtIndex(0)->GetUserData(
      NavigationEntryScreenshot::kUserDataKey));
}

// Tests the translation applied to the screenshot and fade applied to the scrim
// as the gesture is progressed in both directions in a back navigation.
IN_PROC_BROWSER_TEST_P(BackForwardTransitionAnimationManagerBothEdgeBrowserTest,
                       TestScreenshotTransformAndScrimColorBackNavigation) {
  ASSERT_FALSE(GetAnimator());
  ASSERT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0), BackEdge(),
                                          NavType::kBackward);

  // The gesture should have created and attached a screenshot layer with a
  // child scrim layer, under the live page.
  ASSERT_EQ("[Screenshot[Scrim],LivePage]", ChildrenInOrder(*GetViewLayer()));

  // In a back navigation, the screenshot starts off-screen in the direction the
  // swipe is coming from and moves to the viewport origin. Therefore we expect
  // it to be at `(1-progress) * initial_position` at all times.
  float initial_position = BackEdge() == SwipeEdge::LEFT
                               ? PhysicsModel::kScreenshotInitialPositionRatio
                               : -PhysicsModel::kScreenshotInitialPositionRatio;

  EXPECT_TRUE(
      ColorsNear(kScrimColorAtStart, GetScrimLayer()->background_color()));
  EXPECT_X_TRANSLATION(initial_position, GetScreenshotLayer()->transform());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.3));
  EXPECT_TRUE(ColorsNear(kScrimColorAt30, GetScrimLayer()->background_color()));
  EXPECT_X_TRANSLATION(initial_position * 0.7,
                       GetScreenshotLayer()->transform());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));
  EXPECT_TRUE(ColorsNear(kScrimColorAt60, GetScrimLayer()->background_color()));
  EXPECT_X_TRANSLATION(initial_position * 0.4,
                       GetScreenshotLayer()->transform());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.9));
  EXPECT_TRUE(ColorsNear(kScrimColorAt90, GetScrimLayer()->background_color()));
  EXPECT_X_TRANSLATION(initial_position * 0.1,
                       GetScreenshotLayer()->transform());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));
  EXPECT_TRUE(ColorsNear(kScrimColorAt60, GetScrimLayer()->background_color()));
  EXPECT_X_TRANSLATION(initial_position * 0.4,
                       GetScreenshotLayer()->transform());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.3));
  EXPECT_TRUE(ColorsNear(kScrimColorAt30, GetScrimLayer()->background_color()));
  EXPECT_X_TRANSLATION(initial_position * 0.7,
                       GetScreenshotLayer()->transform());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));
  EXPECT_TRUE(ColorsNear(kScrimColorAt60, GetScrimLayer()->background_color()));
  EXPECT_X_TRANSLATION(initial_position * 0.4,
                       GetScreenshotLayer()->transform());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.9));
  EXPECT_TRUE(ColorsNear(kScrimColorAt90, GetScrimLayer()->background_color()));
  EXPECT_X_TRANSLATION(initial_position * 0.1,
                       GetScreenshotLayer()->transform());
}

// Tests the translation of the screenshot and the fade of the scrim as the
// gesture is progressed in both directions in a forward navigation.
IN_PROC_BROWSER_TEST_P(BackForwardTransitionAnimationManagerBothEdgeBrowserTest,
                       TestScreenshotTransformScrimColorForwardNavigation) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  // The test starts off with the session history: [red&, green*]. Add an entry
  // and navigate back so we have entries in both directions:
  // [red&, green*, blue&].
  {
    ASSERT_TRUE(NavigateToURL(web_contents(), BlueURL()));
    WaitForCopyableViewInWebContents(web_contents());

    ScopedScreenshotCapturedObserverForTesting observer(
        web_contents()->GetController().GetLastCommittedEntryIndex());
    ASSERT_TRUE(HistoryGoBack(web_contents()));
    observer.Wait();
    WaitForCopyableViewInWebContents(web_contents());
  }

  ASSERT_FALSE(GetAnimator());
  ASSERT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          ForwardEdge(), NavType::kForward);

  // In a forward navigation, the screenshot starts off-screen at the viewport
  // edge where the gesture is initiated. It in the direction of the swipe over
  // a distance defined by the commit pending ratio.
  float start_position = ForwardEdge() == SwipeEdge::RIGHT ? 1 : -1;
  float total_distance = ForwardEdge() == SwipeEdge::RIGHT
                             ? -PhysicsModel::kTargetCommitPendingRatio
                             : PhysicsModel::kTargetCommitPendingRatio;

  EXPECT_X_TRANSLATION(0, GetLivePageLayer()->transform());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.3));
  EXPECT_TRUE(ColorsNear(kScrimColorAt30, GetScrimLayer()->background_color()));
  EXPECT_X_TRANSLATION(start_position + total_distance * 0.3,
                       GetScreenshotLayer()->transform());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));
  EXPECT_TRUE(ColorsNear(kScrimColorAt60, GetScrimLayer()->background_color()));
  EXPECT_X_TRANSLATION(start_position + total_distance * 0.6,
                       GetScreenshotLayer()->transform());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.9));
  EXPECT_TRUE(ColorsNear(kScrimColorAt90, GetScrimLayer()->background_color()));
  EXPECT_X_TRANSLATION(start_position + total_distance * 0.9,
                       GetScreenshotLayer()->transform());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));
  EXPECT_TRUE(ColorsNear(kScrimColorAt60, GetScrimLayer()->background_color()));
  EXPECT_X_TRANSLATION(start_position + total_distance * 0.6,
                       GetScreenshotLayer()->transform());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.3));
  EXPECT_TRUE(ColorsNear(kScrimColorAt30, GetScrimLayer()->background_color()));
  EXPECT_X_TRANSLATION(start_position + total_distance * 0.3,
                       GetScreenshotLayer()->transform());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));
  EXPECT_TRUE(ColorsNear(kScrimColorAt60, GetScrimLayer()->background_color()));
  EXPECT_X_TRANSLATION(start_position + total_distance * 0.6,
                       GetScreenshotLayer()->transform());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.9));
  EXPECT_TRUE(ColorsNear(kScrimColorAt90, GetScrimLayer()->background_color()));
  EXPECT_X_TRANSLATION(start_position + total_distance * 0.9,
                       GetScreenshotLayer()->transform());
}

IN_PROC_BROWSER_TEST_P(BackForwardTransitionAnimationManagerBothEdgeBrowserTest,
                       DarkModeScrim) {
  ASSERT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));

  blink::web_pref::WebPreferences prefs =
      web_contents()->GetOrCreateWebPreferences();
  prefs.preferred_color_scheme = blink::mojom::PreferredColorScheme::kDark;
  web_contents()->SetWebPreferences(prefs);

  // Dark mode has twice the scrim from the light mode.
  const SkColor4f kDMScrimColorAtStart = {0, 0, 0, kScrimColorAtStart.fA * 2};
  const SkColor4f kDMScrimColorAt30 = {0, 0, 0, kScrimColorAt30.fA * 2};
  const SkColor4f kDMScrimColorAt60 = {0, 0, 0, kScrimColorAt60.fA * 2};
  const SkColor4f kDMScrimColorAt90 = {0, 0, 0, kScrimColorAt90.fA * 2};

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0), BackEdge(),
                                          NavType::kBackward);
  ASSERT_TRUE(GetScrimLayer());
  EXPECT_TRUE(
      ColorsNear(kDMScrimColorAtStart, GetScrimLayer()->background_color()));

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.3));
  EXPECT_TRUE(
      ColorsNear(kDMScrimColorAt30, GetScrimLayer()->background_color()));

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));
  EXPECT_TRUE(
      ColorsNear(kDMScrimColorAt60, GetScrimLayer()->background_color()));

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.9));
  EXPECT_TRUE(
      ColorsNear(kDMScrimColorAt90, GetScrimLayer()->background_color()));

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.3));
  EXPECT_TRUE(
      ColorsNear(kDMScrimColorAt30, GetScrimLayer()->background_color()));

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));
  EXPECT_TRUE(
      ColorsNear(kDMScrimColorAt60, GetScrimLayer()->background_color()));
}

// Tests the translation of the live page as the gesture is progressed in both
// directions in a back navigation.
IN_PROC_BROWSER_TEST_P(BackForwardTransitionAnimationManagerBothEdgeBrowserTest,
                       TestLivePageTransformBackNavigation) {
  ASSERT_FALSE(GetAnimator());
  ASSERT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0), BackEdge(),
                                          NavType::kBackward);

  // In a back navigation, the live page starts off at the viewport origin and
  // moves in the direction of the swipe to a maximum defined by the "commit
  // pending ratio" of the viewport width.
  float final_position = BackEdge() == SwipeEdge::LEFT
                             ? PhysicsModel::kTargetCommitPendingRatio
                             : -PhysicsModel::kTargetCommitPendingRatio;

  EXPECT_X_TRANSLATION(0, GetLivePageLayer()->transform());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.3));
  EXPECT_X_TRANSLATION(final_position * 0.3, GetLivePageLayer()->transform());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));
  EXPECT_X_TRANSLATION(final_position * 0.6, GetLivePageLayer()->transform());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.9));
  EXPECT_X_TRANSLATION(final_position * 0.9, GetLivePageLayer()->transform());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));
  EXPECT_X_TRANSLATION(final_position * 0.6, GetLivePageLayer()->transform());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.3));
  EXPECT_X_TRANSLATION(final_position * 0.3, GetLivePageLayer()->transform());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));
  EXPECT_X_TRANSLATION(final_position * 0.6, GetLivePageLayer()->transform());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.9));
  EXPECT_X_TRANSLATION(final_position * 0.9, GetLivePageLayer()->transform());
}

// Tests the translation of the live page as the gesture is progressed in both
// directions in a back navigation.
IN_PROC_BROWSER_TEST_P(BackForwardTransitionAnimationManagerBothEdgeBrowserTest,
                       TestLivePageTransformForwardNavigation) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  // The test starts off with the session history: [red&, green*]. Add an entry
  // and navigate back so we have entries in both directions:
  // [red&, green*, blue&].
  {
    ASSERT_TRUE(NavigateToURL(web_contents(), BlueURL()));
    WaitForCopyableViewInWebContents(web_contents());

    ScopedScreenshotCapturedObserverForTesting observer(
        web_contents()->GetController().GetLastCommittedEntryIndex());
    ASSERT_TRUE(HistoryGoBack(web_contents()));
    observer.Wait();
    WaitForCopyableViewInWebContents(web_contents());
  }

  ASSERT_FALSE(GetAnimator());
  ASSERT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          ForwardEdge(), NavType::kForward);

  // In a forward navigation, the live page starts off at the viewport origin
  // and moves in the direction of the swipe the same amount as the screenshot
  // in a back navigation.
  float final_position = ForwardEdge() == SwipeEdge::RIGHT
                             ? PhysicsModel::kScreenshotInitialPositionRatio
                             : -PhysicsModel::kScreenshotInitialPositionRatio;

  EXPECT_X_TRANSLATION(0, GetLivePageLayer()->transform());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.3));
  EXPECT_X_TRANSLATION(final_position * 0.3, GetLivePageLayer()->transform());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));
  EXPECT_X_TRANSLATION(final_position * 0.6, GetLivePageLayer()->transform());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.9));
  EXPECT_X_TRANSLATION(final_position * 0.9, GetLivePageLayer()->transform());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));
  EXPECT_X_TRANSLATION(final_position * 0.6, GetLivePageLayer()->transform());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.3));
  EXPECT_X_TRANSLATION(final_position * 0.3, GetLivePageLayer()->transform());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));
  EXPECT_X_TRANSLATION(final_position * 0.6, GetLivePageLayer()->transform());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.9));
  EXPECT_X_TRANSLATION(final_position * 0.9, GetLivePageLayer()->transform());
}

// Tests a forward navigation creates the expected layers and puts them in the
// correct z-order.
IN_PROC_BROWSER_TEST_P(BackForwardTransitionAnimationManagerBothEdgeBrowserTest,
                       TestLayerOrderForwardNavigation) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  // The test starts off with the session history: [red&, green*]. Add an entry
  // and navigate back so we have entries in both directions:
  // [red&, green*, blue&].
  {
    ASSERT_TRUE(NavigateToURL(web_contents(), BlueURL()));
    WaitForCopyableViewInWebContents(web_contents());

    ScopedScreenshotCapturedObserverForTesting observer(
        web_contents()->GetController().GetLastCommittedEntryIndex());
    ASSERT_TRUE(HistoryGoBack(web_contents()));
    observer.Wait();
    WaitForCopyableViewInWebContents(web_contents());
  }

  ASSERT_FALSE(GetAnimator());
  ASSERT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          ForwardEdge(), NavType::kForward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.9));

  // A screenshot layer with a scrim should have been added. In a forward
  // navigation, the screenshot must appear on top of the live page.
  ASSERT_EQ("[LivePage,Screenshot[Scrim]]", ChildrenInOrder(*GetViewLayer()));

  // Prevent completing the invoke animation but trigger and finish the
  // navigation. This simulates the case where the new renderer commits while
  // the invoke animation is playing.
  {
    GetAnimator()->PauseAnimationAtDisplayingInvokeAnimation();
    TestNavigationManager forward_to_blue(web_contents(), BlueURL());
    GetAnimationManager()->OnGestureInvoked();
    ASSERT_TRUE(forward_to_blue.WaitForNavigationFinished());
    EXPECT_STATE_EQ(kDisplayingInvokeAnimation, GetAnimator()->state());

    // A clone of the old page should be inserted under the screenshot but
    // above the current live page.
    ASSERT_EQ("[LivePage,OldSurfaceClone,Screenshot[Scrim]]",
              ChildrenInOrder(*GetViewLayer()));
  }

  // Make sure the new renderer has submitted a new frame and let the invoke
  // animation finish.
  {
    TestFuture<void> did_invoke;
    GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());
    GetAnimator()->UnpauseAnimation();
    ASSERT_TRUE(did_invoke.Wait());

    // A clone of the old page should be removed. The screenshot should remain
    // on top of the live page.
    ASSERT_EQ("[LivePage,Screenshot[Scrim]]", ChildrenInOrder(*GetViewLayer()));
  }

  // Let the transition play to completion. The screenshot layers should all be
  // removed and the animator complete successfully.
  {
    TestFuture<AnimatorState> destroyed;
    GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
    ASSERT_TRUE(destroyed.Wait());
    EXPECT_STATE_EQ(kAnimationFinished, destroyed.Get());

    ASSERT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));
  }
}

// Verify transforms of screenshot and live layers at the end of a cancel
// animation.
IN_PROC_BROWSER_TEST_P(BackForwardTransitionAnimationManagerBothEdgeBrowserTest,
                       TestLayerTransformsAfterCancelBack) {
  ASSERT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0), BackEdge(),
                                          NavType::kBackward);

  // Only a screenshot layer with a scrim should have been added, under the live
  // page.
  ASSERT_EQ("[Screenshot[Scrim],LivePage]", ChildrenInOrder(*GetViewLayer()));
  ASSERT_EQ(GetScrimLayer()->background_color().fA, 0.1f);

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.9));

  TestFuture<void> did_cancel;
  TestFuture<AnimatorState> destroyed;

  // Extract the state during the cancel animation finished callback since the
  // layers will be removed synchronously right after that's called.
  gfx::Transform actual_screenshot_transform;
  gfx::Transform actual_live_transform;
  std::string actual_child_layers;
  GetAnimator()->set_on_cancel_animation_displayed(
      base::BindLambdaForTesting([&]() {
        actual_screenshot_transform = GetScreenshotLayer()->transform();
        actual_live_transform = GetLivePageLayer()->transform();
        actual_child_layers = ChildrenInOrder(*GetViewLayer());
        std::move(did_cancel.GetCallback()).Run();
      }));
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());

  GetAnimationManager()->OnGestureCancelled();
  ASSERT_TRUE(did_cancel.Wait());

  float expected_screenshot_ratio =
      BackEdge() == SwipeEdge::LEFT
          ? PhysicsModel::kScreenshotInitialPositionRatio
          : -PhysicsModel::kScreenshotInitialPositionRatio;
  EXPECT_X_TRANSLATION(expected_screenshot_ratio, actual_screenshot_transform);
  EXPECT_X_TRANSLATION(0, actual_live_transform);
  EXPECT_EQ("[Screenshot[Scrim],LivePage]", actual_child_layers);

  // When the cancel animation finishes it synchronously destroys the animator.
  EXPECT_STATE_EQ(kAnimationFinished, destroyed.Get());

  EXPECT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));
}

// Verify transforms of screenshot and live layers at the end of the invoke
// animation.
IN_PROC_BROWSER_TEST_P(BackForwardTransitionAnimationManagerBothEdgeBrowserTest,
                       TestLayerTransformsAfterInvokeAnimation) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  ASSERT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0), BackEdge(),
                                          NavType::kBackward);

  // A screenshot layer should have been added, with the live page on top.
  EXPECT_EQ("[Screenshot[Scrim],LivePage]", ChildrenInOrder(*GetViewLayer()));

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.9));

  TestFuture<void> did_invoke;
  TestFuture<AnimatorState> destroyed;
  GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());

  GetAnimationManager()->OnGestureInvoked();
  ASSERT_TRUE(did_invoke.Wait());

  EXPECT_EQ("[Screenshot[Scrim],LivePage]", ChildrenInOrder(*GetViewLayer()));

  // The live page should be fully offscreen in the direction of the swipe. The
  // screenshot should be at the origin.
  float live_page_offset = BackEdge() == SwipeEdge::LEFT ? 1.f : -1.f;
  EXPECT_X_TRANSLATION(live_page_offset, GetLivePageLayer()->transform());
  EXPECT_X_TRANSLATION(0, GetScreenshotLayer()->transform());

  // Scrim should be at zero when the invoke animation is finished.
  EXPECT_EQ(GetScrimLayer()->background_color().fA, 0.0f);
}

// Verify transforms of screenshot and live layers during the crossfade
// animation.
IN_PROC_BROWSER_TEST_P(BackForwardTransitionAnimationManagerBothEdgeBrowserTest,
                       TestLayerTransformsDuringCrossFade) {
  // TODO(bokan): Should this just be added to the harness?
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  ASSERT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0), BackEdge(),
                                          NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.9));

  TestFuture<void> did_cross_fade;
  TestFuture<void> did_invoke;
  TestFuture<AnimatorState> destroyed;
  GetAnimator()->set_on_cross_fade_animation_displayed(
      did_cross_fade.GetCallback());
  GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());

  TestNavigationManager back_nav_to_red(web_contents(), RedURL());

  // Avoid processing any frames from the new renderer after invoking the
  // navigation to simulate the invoke animation finishing before the first
  // frame from the new page.
  GetAnimator()->set_intercept_render_frame_metadata_changed(true);
  GetAnimationManager()->OnGestureInvoked();
  ASSERT_TRUE(back_nav_to_red.WaitForNavigationFinished());
  ASSERT_TRUE(did_invoke.Wait());

  // Only a screenshot layer should have been added.
  EXPECT_EQ("[Screenshot[Scrim],LivePage]", ChildrenInOrder(*GetViewLayer()));

  EXPECT_STATE_EQ(kWaitingForNewRendererToDraw, GetAnimator()->state());

  // Make sure the new renderer has submitted a new frame and manually call
  // OnRenderFrameMetadataChangedAfterActivation to move into cross fading.
  ASSERT_TRUE(WaitForFirstFrameInPrimaryMainFrame());
  GetAnimator()->set_intercept_render_frame_metadata_changed(false);
  base::TimeTicks now = base::TimeTicks();
  GetAnimator()->OnRenderFrameMetadataChangedAfterActivation(now);

  EXPECT_STATE_EQ(kDisplayingCrossFadeAnimation, GetAnimator()->state());

  // The screenshot should be drawn on top of the live page now.
  EXPECT_EQ("[LivePage,Screenshot[Scrim]]", ChildrenInOrder(*GetViewLayer()));

  // Screenshot should still have the scrim and it should be at the end of its
  // timeline and fully opaque to start the cross fade.
  EXPECT_EQ(GetScrimLayer()->background_color().fA, 0.f);
  EXPECT_X_TRANSLATION(0, GetLivePageLayer()->transform());
  EXPECT_X_TRANSLATION(0, GetScreenshotLayer()->transform());
  EXPECT_EQ(GetLivePageLayer()->opacity(), 1.f);
  EXPECT_EQ(GetScreenshotLayer()->opacity(), 1.f);

  // First tick should setup the animation.
  now += base::Milliseconds(16);
  GetAnimator()->DirectlyCallOnAnimate(now);
  EXPECT_STATE_EQ(kDisplayingCrossFadeAnimation, GetAnimator()->state());
  EXPECT_EQ(GetScrimLayer()->background_color().fA, 0.f);
  EXPECT_X_TRANSLATION(0, GetLivePageLayer()->transform());
  EXPECT_X_TRANSLATION(0, GetScreenshotLayer()->transform());
  EXPECT_EQ(GetLivePageLayer()->opacity(), 1.f);
  EXPECT_EQ(GetScreenshotLayer()->opacity(), 1.f);

  // Next tick should animate screenshot opacity.
  now += base::Milliseconds(16);
  GetAnimator()->DirectlyCallOnAnimate(now);
  EXPECT_STATE_EQ(kDisplayingCrossFadeAnimation, GetAnimator()->state());
  EXPECT_EQ(GetLivePageLayer()->opacity(), 1.f);
  EXPECT_LT(GetScreenshotLayer()->opacity(), 1.f);

  // Animate to finish.
  now += base::Seconds(99);
  GetAnimator()->DirectlyCallOnAnimate(now);

  ASSERT_TRUE(did_cross_fade.IsReady());
  EXPECT_EQ("[LivePage,Screenshot[Scrim]]", ChildrenInOrder(*GetViewLayer()));
  EXPECT_X_TRANSLATION(0, GetLivePageLayer()->transform());
  EXPECT_X_TRANSLATION(0, GetScreenshotLayer()->transform());

  // The cross fade should have completed by now.
  EXPECT_EQ(GetLivePageLayer()->opacity(), 1.f);
  EXPECT_EQ(GetScreenshotLayer()->opacity(), 0.f);

  // The scrim should remain completely transparent.
  EXPECT_EQ(GetScrimLayer()->background_color().fA, 0.f);

  ASSERT_TRUE(destroyed.Wait());
  EXPECT_STATE_EQ(kAnimationFinished, destroyed.Get());
}

INSTANTIATE_TEST_SUITE_P(
    All,
    BackForwardTransitionAnimationManagerBothEdgeBrowserTest,
    Combine(Values(UILayoutDirection::kLTR, UILayoutDirection::kRTL),
            Values(DSFMode::kOne, DSFMode::kFractional)),
    [](const TestParamInfo<
        BackForwardTransitionAnimationManagerBothEdgeBrowserTest::ParamType>&
           info) {
      return base::StrCat(
          {std::get<0>(info.param) == UILayoutDirection::kLTR ? "LTR" : "RTL",
           std::get<1>(info.param) == DSFMode::kOne ? "" : "FractionalDSF"});
    });

// Runs a transition in a ViewTransition enabled page. Ensures view transition
// does not run.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       DefaultTransitionSupersedesViewTransition) {
  GURL first_url(
      embedded_test_server()->GetURL("/view_transitions/basic-vt-opt-in.html"));
  ASSERT_TRUE(NavigateToURL(web_contents(), first_url));
  WaitForCopyableViewInWebContents(web_contents());

  GURL second_url(embedded_test_server()->GetURL(
      "/view_transitions/basic-vt-opt-in.html?next"));
  ASSERT_TRUE(NavigateToURL(web_contents(), second_url));
  WaitForCopyableViewInWebContents(web_contents());

  // Back nav from the green page to the red page. The live page (green) is on
  // top and slides towards right. The red page (screenshot) is on the bottom
  // and appears on the left of screen.
  ASSERT_FALSE(GetAnimator());
  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  ASSERT_TRUE(GetAnimator());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.9));

  TestFrameNavigationObserver back_navigation(web_contents());

  // Invoke the back navigation and wait for it to complete.
  {
    TestFuture<AnimatorState> destroyed;
    GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
    GetAnimationManager()->OnGestureInvoked();
    ASSERT_TRUE(destroyed.Wait());
    back_navigation.Wait();
  }

  // Ensure the new Document has produced a frame, otherwise `pagereveal` which
  // sets had_incoming_transition might not have been fired yet.
  WaitForCopyableViewInWebContents(web_contents());

  ASSERT_EQ(back_navigation.last_committed_url(), first_url);
  EXPECT_EQ(false, EvalJs(web_contents(), "had_incoming_transition"));
}

// If the destination has no screenshot, we will compose a fallback screenshot
// for transition. The destination page has no favicon so we don't draw
// the rounded rectangle.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       DestinationHasNoScreenshot_NoFavicon) {
  std::optional<int> index =
      web_contents()->GetController().GetIndexForGoBack();
  ASSERT_TRUE(index);
  NavigationEntryImpl* red_entry =
      web_contents()->GetController().GetEntryAtIndex(*index);
  ASSERT_TRUE(web_contents()
                  ->GetController()
                  .GetNavigationEntryScreenshotCache()
                  ->RemoveScreenshot(red_entry));

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);

  // live page layer with screenshot underneath. No rrect.
  ASSERT_EQ("[Screenshot[Scrim],LivePage]", ChildrenInOrder(*GetViewLayer()));

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.2));
  auto expected_bg_color = web_contents()
                               ->GetDelegate()
                               ->GetBackForwardTransitionFallbackUXConfig()
                               .background_color;
  EXPECT_EQ(GetScreenshotLayer()->background_color(), expected_bg_color);
  EXPECT_EQ("[Screenshot[Scrim],LivePage]", ChildrenInOrder(*GetViewLayer()));

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.5));
  EXPECT_EQ(GetScreenshotLayer()->background_color(), expected_bg_color);
  EXPECT_EQ("[Screenshot[Scrim],LivePage]", ChildrenInOrder(*GetViewLayer()));

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.8));
  EXPECT_EQ(GetScreenshotLayer()->background_color(), expected_bg_color);
  EXPECT_EQ("[Screenshot[Scrim],LivePage]", ChildrenInOrder(*GetViewLayer()));

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.3));
  EXPECT_EQ(GetScreenshotLayer()->background_color(), expected_bg_color);
  EXPECT_EQ("[Screenshot[Scrim],LivePage]", ChildrenInOrder(*GetViewLayer()));

  TestFrameNavigationObserver back_navigation(web_contents());

  // Trigger and complete the back navigation.
  TestFuture<AnimatorState> destroyed;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimationManager()->OnGestureInvoked();
  ASSERT_TRUE(destroyed.Wait());
  back_navigation.Wait();

  ASSERT_EQ(back_navigation.last_committed_url(), RedURL());
  ASSERT_FALSE(web_contents()->GetController().GetActiveEntry()->GetUserData(
      NavigationEntryScreenshot::kUserDataKey));
}

// If the destination has no screenshot, we will compose a fallback screenshot
// for transition. The destination page has a favicon so we  draw
// the rounded rectangle, and the rrect embeds the favicon.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       DestinationHasNoScreenshot_HasFavicon) {
  SkBitmap stub_favicon;
  stub_favicon.allocN32Pixels(20, 20);
  stub_favicon.eraseColor(SkColors::kMagenta);
  stub_favicon.setImmutable();
  std::optional<int> index =
      web_contents()->GetController().GetIndexForGoBack();
  ASSERT_TRUE(index);
  NavigationEntryImpl* red_entry =
      web_contents()->GetController().GetEntryAtIndex(*index);
  ASSERT_TRUE(web_contents()
                  ->GetController()
                  .GetNavigationEntryScreenshotCache()
                  ->RemoveScreenshot(red_entry));
  red_entry->navigation_transition_data().set_favicon(stub_favicon);

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);

  // live page layer with screenshot underneath, and the rounded rectangle is
  // above the scrim.
  ASSERT_EQ("[Screenshot[Scrim,RRect[Favicon]],LivePage]",
            ChildrenInOrder(*GetViewLayer()));

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.2));
  auto expected_bg_color = web_contents()
                               ->GetDelegate()
                               ->GetBackForwardTransitionFallbackUXConfig()
                               .background_color;
  EXPECT_EQ(GetScreenshotLayer()->background_color(), expected_bg_color);
  EXPECT_TRUE(base::IsApproximatelyEqual(GetRRectLayer()->opacity(), 0.f,
                                         kFloatTolerance));
  // Opacity value isn't propagated into the subtree.
  EXPECT_TRUE(base::IsApproximatelyEqual(GetFaviconLayer()->opacity(), 1.f,
                                         kFloatTolerance));

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.5));
  EXPECT_EQ(GetScreenshotLayer()->background_color(), expected_bg_color);
  EXPECT_TRUE(base::IsApproximatelyEqual(GetRRectLayer()->opacity(), 0.7f,
                                         kFloatTolerance));
  EXPECT_TRUE(base::IsApproximatelyEqual(GetFaviconLayer()->opacity(), 1.f,
                                         kFloatTolerance));

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.8));
  EXPECT_EQ(GetScreenshotLayer()->background_color(), expected_bg_color);
  EXPECT_TRUE(base::IsApproximatelyEqual(GetRRectLayer()->opacity(), 1.f,
                                         kFloatTolerance));
  EXPECT_TRUE(base::IsApproximatelyEqual(GetFaviconLayer()->opacity(), 1.f,
                                         kFloatTolerance));

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.3));
  EXPECT_EQ(GetScreenshotLayer()->background_color(), expected_bg_color);
  EXPECT_TRUE(base::IsApproximatelyEqual(GetRRectLayer()->opacity(), 0.02f,
                                         kFloatTolerance));
  EXPECT_TRUE(base::IsApproximatelyEqual(GetFaviconLayer()->opacity(), 1.f,
                                         kFloatTolerance));

  TestFrameNavigationObserver back_navigation(web_contents());

  // Trigger and complete the back navigation.
  TestFuture<AnimatorState> destroyed;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimationManager()->OnGestureInvoked();
  ASSERT_TRUE(destroyed.Wait());
  back_navigation.Wait();

  ASSERT_EQ(back_navigation.last_committed_url(), RedURL());
  ASSERT_FALSE(web_contents()->GetController().GetActiveEntry()->GetUserData(
      NavigationEntryScreenshot::kUserDataKey));
}

IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       ChainedBackGesture) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  // Navigate to a third page to enable two consecutive back navigations.
  ASSERT_TRUE(NavigateToURL(web_contents(), BlueURL()));
  WaitForCopyableViewInWebContents(web_contents());

  TestFuture<AnimatorState> destroyed_first;

  // First back gesture - start and progress partially
  {
    GetAnimationManager()->OnGestureStarted(
        ui::BackGestureEvent(0), SwipeEdge::LEFT, NavType::kBackward);

    TestFuture<void> did_invoke_first;
    GetAnimator()->set_on_invoke_animation_displayed(
        did_invoke_first.GetCallback());
    GetAnimator()->set_on_impl_destroyed(destroyed_first.GetCallback());

    GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.3));
    GetAnimationManager()->OnGestureInvoked();
    EXPECT_STATE_EQ(kDisplayingCancelAnimation, GetAnimator()->state());
    ASSERT_TRUE(did_invoke_first.Wait());
  }

  // Second back gesture - start before the first one is completed.
  // The second gesture should immediately take over and progress.
  {
    GetAnimationManager()->OnGestureStarted(
        ui::BackGestureEvent(0), SwipeEdge::LEFT, NavType::kBackward);
    ASSERT_TRUE(GetAnimator());
    ASSERT_TRUE(destroyed_first.IsReady());
    EXPECT_STATE_EQ(kAnimationAborted, destroyed_first.Get());
    EXPECT_STATE_EQ(kStarted, GetAnimator()->state());

    // The navigation should go back to the red page (two back navigations).
    TestFrameNavigationObserver back_to_red(web_contents());

    TestFuture<void> did_cross_fade;
    TestFuture<AnimatorState> destroyed;
    GetAnimator()->set_on_cross_fade_animation_displayed(
        did_cross_fade.GetCallback());
    GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());

    GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));
    GetAnimationManager()->OnGestureInvoked();
    ASSERT_TRUE(did_cross_fade.Wait());

    ASSERT_TRUE(destroyed.Wait());
    EXPECT_STATE_EQ(kAnimationFinished, destroyed.Get());

    back_to_red.Wait();
    ASSERT_EQ(back_to_red.last_committed_url(), RedURL());
  }

  ASSERT_FALSE(web_contents()->GetController().GetActiveEntry()->GetUserData(
      NavigationEntryScreenshot::kUserDataKey));
}

// Assert that if the user does not start the navigation, we don't put the
// fallback screenshot back.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       Cancel_DestinationNoScreenshot) {
  std::optional<int> index =
      web_contents()->GetController().GetIndexForGoBack();
  ASSERT_TRUE(index);
  auto* red_entry = web_contents()->GetController().GetEntryAtIndex(*index);
  ASSERT_TRUE(web_contents()
                  ->GetController()
                  .GetNavigationEntryScreenshotCache()
                  ->RemoveScreenshot(red_entry));

  {
    TestFuture<void> did_cancel;

    GetAnimationManager()->OnGestureStarted(
        ui::BackGestureEvent(0), SwipeEdge::LEFT, NavType::kBackward);
    GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));
    GetAnimator()->set_on_cancel_animation_displayed(did_cancel.GetCallback());
    GetAnimationManager()->OnGestureCancelled();

    ASSERT_TRUE(did_cancel.Wait());
  }

  ASSERT_EQ(web_contents()->GetController().GetActiveEntry()->GetURL(),
            GreenURL());
  ASSERT_EQ(web_contents()->GetController().GetLastCommittedEntryIndex(), 1);
  ASSERT_EQ(web_contents()->GetController().GetEntryAtIndex(0)->GetURL(),
            RedURL());
  ASSERT_FALSE(web_contents()->GetController().GetActiveEntry()->GetUserData(
      NavigationEntryScreenshot::kUserDataKey));
}

// Simulating the user click the X button to cancel the navigation while the
// animation is at commit-pending.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       NavigationAborted) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  // We haven't started the navigation at this point.
  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  // The user has lifted the finger - signaling the start of the navigation.
  TestNavigationManager back_to_red(web_contents(), RedURL());
  GetAnimationManager()->OnGestureInvoked();
  ASSERT_TRUE(back_to_red.WaitForResponse());

  TestFuture<AnimatorState> destroyed;
  TestFuture<void> did_cancel;
  GetAnimator()->set_on_cancel_animation_displayed(did_cancel.GetCallback());
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());

  // The user clicks the X (stop) button in the UI.
  web_contents()->Stop();
  ASSERT_TRUE(did_cancel.Wait());
  ASSERT_FALSE(back_to_red.was_committed());
  ASSERT_TRUE(destroyed.Wait());

  // Screenshot layer should be removed and the page should be back at the
  // origin.
  ASSERT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));
  EXPECT_TRANSFORM_NEAR(kIdentityTransform, GetLivePageLayer()->transform(),
                        kFloatTolerance);

  // [red, green*].
  ASSERT_EQ(web_contents()->GetController().GetEntryCount(), 2);
  ASSERT_EQ(web_contents()->GetController().GetLastCommittedEntryIndex(), 1);
  ASSERT_EQ(web_contents()->GetController().GetLastCommittedEntry()->GetURL(),
            GreenURL());
  ASSERT_EQ(web_contents()->GetController().GetEntryAtIndex(0)->GetURL(),
            RedURL());
  ASSERT_TRUE(web_contents()->GetController().GetEntryAtIndex(0)->GetUserData(
      NavigationEntryScreenshot::kUserDataKey));
}

// The invoke animation is displaying and the gesture navigation is <
// READY_TO_COMMIT. A secondary navigation cancels our gesture navigation as the
// gesture navigation has not told the renderer to commit. The cancel animation
// will be placed to bring the active page back.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       GestureNavigationBeingReplaced) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<AnimatorState> destroyed;
  TestFuture<void> did_cancel;
  GetAnimator()->set_on_cancel_animation_displayed(did_cancel.GetCallback());
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());

  TestNavigationManager back_nav_to_red(web_contents(), RedURL());
  {
    // The pause here prevents the manager from finishing the invoke animation.
    // When the navigation to blue starts, blue's navigation request will cancel
    // the red's navigation request, and the manager will get a
    // DidFinishNavigation to advance itself from `kDisplayingInvokeAnimation`
    // to `kDisplayingCancelAnimation`.
    GetAnimator()->PauseAnimationAtDisplayingInvokeAnimation();

    GetAnimationManager()->OnGestureInvoked();
    ASSERT_TRUE(back_nav_to_red.WaitForRequestStart());

    EXPECT_STATE_EQ(kDisplayingInvokeAnimation, GetAnimator()->state());
  }

  // We can't use NavigateToURL() here. NavigateToURL will wait for the
  // current WebContents to stop loading. We have an on-going navigation here
  // so the wait will timeout.
  {
    TestNavigationManager nav_to_blue(web_contents(), BlueURL());
    web_contents()->GetController().LoadURL(
        BlueURL(), Referrer{},
        ui::PageTransitionFromInt(
            ui::PageTransition::PAGE_TRANSITION_FROM_ADDRESS_BAR |
            ui::PageTransition::PAGE_TRANSITION_TYPED),
        std::string{});
    ASSERT_TRUE(nav_to_blue.WaitForRequestStart());
    // The start of blue will advance the manager to
    // kDisplayingCancelAnimation.
    EXPECT_STATE_EQ(kDisplayingCancelAnimation, GetAnimator()->state());
    // Force the cancel animation to finish playing, by unpausing it and
    // calling OnAnimate on it.
    GetAnimator()->UnpauseAnimation();

    ASSERT_TRUE(did_cancel.Wait());
    ASSERT_TRUE(nav_to_blue.WaitForNavigationFinished());
  }

  ASSERT_TRUE(destroyed.Wait());

  ASSERT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));
  EXPECT_TRANSFORM_NEAR(kIdentityTransform, GetLivePageLayer()->transform(),
                        kFloatTolerance);

  ASSERT_FALSE(back_nav_to_red.was_committed());
}

// The user swipes across the screen while a cross-doc navigation commits. We
// destroy the animation manager synchronously.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       NavigationWhileOnGestureProgressed) {
  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<AnimatorState> destroyed;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  ASSERT_TRUE(NavigateToURL(web_contents(), BlueURL()));
  ASSERT_TRUE(destroyed.Wait());
  EXPECT_STATE_EQ(kAnimationAborted, destroyed.Get());

  ASSERT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));
  EXPECT_TRANSFORM_NEAR(kIdentityTransform, GetLivePageLayer()->transform(),
                        kFloatTolerance);
}

// The cancel animation is displaying while a cross-doc navigation commits. We
// destroy the animation manager synchronously.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       NavigationWhileDisplayingCancelAnimation) {
  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<AnimatorState> destroyed;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->PauseAnimationAtDisplayingCancelAnimation();
  GetAnimationManager()->OnGestureCancelled();
  ASSERT_TRUE(NavigateToURL(web_contents(), BlueURL()));
  ASSERT_TRUE(destroyed.Wait());
  EXPECT_STATE_EQ(kAnimationAborted, destroyed.Get());

  ASSERT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));
  EXPECT_TRANSFORM_NEAR(kIdentityTransform, GetLivePageLayer()->transform(),
                        kFloatTolerance);
}

IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       NavigationWhileWaitingForRendererNewFrame) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<AnimatorState> destroyed;
  TestFuture<void> did_invoke;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());

  TestNavigationManager back_nav_to_red(web_contents(), RedURL());
  // The user has lifted the finger - signaling the start of the navigation.
  GetAnimationManager()->OnGestureInvoked();
  ASSERT_TRUE(back_nav_to_red.WaitForResponse());

  // Intercept all the `OnRenderFrameMetadataChangedAfterActivation()`s.
  GetAnimator()->set_intercept_render_frame_metadata_changed(true);
  ASSERT_TRUE(back_nav_to_red.WaitForNavigationFinished());
  ASSERT_TRUE(did_invoke.Wait());

  EXPECT_STATE_EQ(kWaitingForNewRendererToDraw, GetAnimator()->state());

  ASSERT_TRUE(NavigateToURL(web_contents(), BlueURL()));
  ASSERT_TRUE(destroyed.Wait());
  EXPECT_STATE_EQ(kAnimationAborted, destroyed.Get());

  ASSERT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));
  EXPECT_TRANSFORM_NEAR(kIdentityTransform, GetLivePageLayer()->transform(),
                        kFloatTolerance);
}

// Test `BackForwardTransitionAnimator::StartNavigationAndTrackRequest()`
// returns false:
// - at OnGestureStarted() there is a destination entry;
// - at OnGestureInvoked() the entry cannot be found.
// - Upon the user lifts the finger, the cancel animation should be played, and
//   no navigation committed.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       NotAbleToStartNavigationOnInvoke) {
  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  // Only have the active green entry after this call.
  // `StartNavigationAndTrackRequest()` will fail.
  ASSERT_EQ(web_contents()->GetController().GetLastCommittedEntryIndex(), 1);
  web_contents()->GetController().PruneAllButLastCommitted();
  ASSERT_EQ(web_contents()->GetController().GetLastCommittedEntryIndex(), 0);
  ASSERT_EQ(web_contents()->GetController().GetLastCommittedEntry()->GetURL(),
            GreenURL());

  TestFuture<AnimatorState> destroyed;
  TestFuture<void> did_cancel;
  GetAnimator()->set_on_cancel_animation_displayed(did_cancel.GetCallback());
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());

  GetAnimationManager()->OnGestureInvoked();

  ASSERT_TRUE(destroyed.Wait());
  EXPECT_TRUE(did_cancel.IsReady());

  ASSERT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));
  EXPECT_TRANSFORM_NEAR(kIdentityTransform, GetLivePageLayer()->transform(),
                        kFloatTolerance);

  ASSERT_EQ(web_contents()->GetController().GetLastCommittedEntryIndex(), 0);
  ASSERT_EQ(web_contents()->GetController().GetLastCommittedEntry()->GetURL(),
            GreenURL());
}

// Test that the animation manager is blocked by the renderer's impl thread
// submitting a new compostior frame.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       AnimationStaysBeforeFrameActivation) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<void> did_cross_fade;
  TestFuture<void> did_invoke;
  TestFuture<AnimatorState> destroyed;
  GetAnimator()->set_on_cross_fade_animation_displayed(
      did_cross_fade.GetCallback());
  GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());

  TestNavigationManager back_nav_to_red(web_contents(), RedURL());
  // The user has lifted the finger - signaling the start of the navigation.
  GetAnimationManager()->OnGestureInvoked();
  ASSERT_TRUE(back_nav_to_red.WaitForResponse());

  // Intercept all the `OnRenderFrameMetadataChangedAfterActivation()`s.
  GetAnimator()->set_intercept_render_frame_metadata_changed(true);
  ASSERT_TRUE(back_nav_to_red.WaitForNavigationFinished());
  ASSERT_TRUE(did_invoke.Wait());
  EXPECT_STATE_EQ(kWaitingForNewRendererToDraw, GetAnimator()->state());

  GetAnimator()->set_intercept_render_frame_metadata_changed(false);
  GetAnimator()->OnRenderFrameMetadataChangedAfterActivation(base::TimeTicks());

  ASSERT_TRUE(destroyed.Wait());
  EXPECT_TRUE(did_cross_fade.IsReady());

  ASSERT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));
  EXPECT_TRANSFORM_NEAR(kIdentityTransform, GetLivePageLayer()->transform(),
                        kFloatTolerance);
}

// Test that the animation manager is destroyed when the visibility changes for
// that tab.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       OnVisibilityChange) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<AnimatorState> destroyed;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());

  // Pause at the beginning of the invoke animation but wait for the navigation
  // to finish, so we can guarantee to have subscribed to the new
  // RenderWidgetHost.
  GetAnimator()->PauseAnimationAtDisplayingInvokeAnimation();

  {
    TestNavigationManager back_nav_to_red(web_contents(), RedURL());
    GetAnimationManager()->OnGestureInvoked();
    ASSERT_TRUE(back_nav_to_red.WaitForNavigationFinished());
  }

  ui::WindowAndroid* window = web_contents()->GetTopLevelNativeWindow();
  // The first two args don't matter in tests.
  window->OnVisibilityChanged(
      /*env=*/nullptr,
      /*obj=*/base::android::JavaParamRef<jobject>(nullptr),
      /*visible=*/false);
  ASSERT_TRUE(destroyed.Wait());
  EXPECT_STATE_EQ(kAnimationAborted, destroyed.Get());

  ASSERT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));
  EXPECT_TRANSFORM_NEAR(kIdentityTransform, GetLivePageLayer()->transform(),
                        kFloatTolerance);
}

// Test that the animation manager is destroyed when the browser compositor is
// detached.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       OnDetachCompositor) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<AnimatorState> destroyed;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());

  // Pause at the beginning of the invoke animation but wait for the navigation
  // to finish, so we can guarantee to have subscribed to the new
  // RenderWidgetHost.
  GetAnimator()->PauseAnimationAtDisplayingInvokeAnimation();

  {
    TestNavigationManager back_nav_to_red(web_contents(), RedURL());
    GetAnimationManager()->OnGestureInvoked();
    ASSERT_TRUE(back_nav_to_red.WaitForNavigationFinished());
  }

  ui::WindowAndroid* window = web_contents()->GetTopLevelNativeWindow();
  window->DetachCompositor();

  ASSERT_TRUE(destroyed.IsReady());
  EXPECT_STATE_EQ(kAnimationAborted, destroyed.Get());

  ASSERT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));
  EXPECT_TRANSFORM_NEAR(kIdentityTransform, GetLivePageLayer()->transform(),
                        kFloatTolerance);
}

// Assert that non primary main frame navigations won't cancel the ongoing
// animation.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       IgnoreNonPrimaryMainFrameNavigations) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<void> did_cross_fade;
  TestFuture<void> did_invoke;
  TestFuture<AnimatorState> destroyed;
  GetAnimator()->set_on_cross_fade_animation_displayed(
      did_cross_fade.GetCallback());
  GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());

  TestNavigationManager back_to_red(web_contents(), RedURL());
  GetAnimationManager()->OnGestureInvoked();
  ASSERT_TRUE(back_to_red.WaitForResponse());

  // Add an iframe to the green page while the gesture is in-progress. This will
  // trigger a renderer-initiated navigation in the subframe.
  constexpr char kAddIframeScript[] = R"({
    (()=>{
        return new Promise((resolve) => {
          const frame = document.createElement('iframe');
          frame.addEventListener('load', () => {resolve();});
          frame.src = $1;
          document.body.appendChild(frame);
        });
    })();
  })";
  ASSERT_TRUE(ExecJs(web_contents()->GetPrimaryMainFrame(),
                     JsReplace(kAddIframeScript, BlueURL())));

  ASSERT_TRUE(back_to_red.WaitForNavigationFinished());
  ASSERT_TRUE(destroyed.Wait());
  EXPECT_TRUE(did_invoke.IsReady());
  EXPECT_TRUE(did_cross_fade.IsReady());

  ASSERT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));
  EXPECT_TRANSFORM_NEAR(kIdentityTransform, GetLivePageLayer()->transform(),
                        kFloatTolerance);

  ASSERT_EQ(web_contents()->GetController().GetEntryCount(), 2);
  ASSERT_EQ(web_contents()->GetController().GetLastCommittedEntryIndex(), 0);
  ASSERT_EQ(web_contents()->GetController().GetLastCommittedEntry()->GetURL(),
            RedURL());
}

// Assert that during OnAnimate, if the current animation hasn't finish, we
// should expect a follow up OnAnimate call.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       OnAnimateIsCalled) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<void> did_cross_fade;
  TestFuture<void> did_invoke;
  TestFuture<AnimatorState> destroyed;
  GetAnimator()->set_on_cross_fade_animation_displayed(
      did_cross_fade.GetCallback());
  GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());

  TestNavigationManager back_nav_to_red(web_contents(), RedURL());
  GetAnimator()->set_duration_between_frames(base::Milliseconds(1));
  GetAnimationManager()->OnGestureInvoked();
  ASSERT_TRUE(back_nav_to_red.WaitForResponse());
  {
    SCOPED_TRACE("first on animate call");
    base::RunLoop first_on_animate_call;
    GetAnimator()->set_next_on_animate_callback(
        first_on_animate_call.QuitClosure());
    first_on_animate_call.Run();
    EXPECT_STATE_EQ(kDisplayingInvokeAnimation, GetAnimator()->state());
  }
  GetAnimator()->set_duration_between_frames(kLongDurationBetweenFrames);
  {
    SCOPED_TRACE("second on animate call");
    base::RunLoop second_on_animate_call;
    GetAnimator()->set_next_on_animate_callback(
        second_on_animate_call.QuitClosure());
    second_on_animate_call.Run();
  }

  ASSERT_TRUE(back_nav_to_red.WaitForNavigationFinished());
  ASSERT_TRUE(destroyed.Wait());
  EXPECT_TRUE(did_invoke.IsReady());
  EXPECT_TRUE(did_cross_fade.IsReady());

  ASSERT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));
  EXPECT_TRANSFORM_NEAR(kIdentityTransform, GetLivePageLayer()->transform(),
                        kFloatTolerance);
}

// Test that, when the browser receives the DidCommit message, Viz has already
// activated a render frame, we will also skip `kWaitingForNewRendererToDraw`.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       RenderFrameActivatedBeforeDidCommit) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<void> did_cross_fade;
  TestFuture<AnimatorState> destroyed;
  GetAnimator()->set_on_cross_fade_animation_displayed(
      did_cross_fade.GetCallback());
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());

  bool received_frame_while_waiting = false;
  GetAnimator()->set_waited_for_renderer_new_frame(base::BindLambdaForTesting(
      [&]() { received_frame_while_waiting = true; }));

  TestNavigationManager back_to_red(web_contents(), RedURL());
  GetAnimationManager()->OnGestureInvoked();
  ASSERT_TRUE(back_to_red.WaitForResponse());

  // Manually set the new frame metadata before the DidCommit message and call
  // `OnRenderFrameMetadataChangedAfterActivation()` to simulate a frame
  // activation.
  {
    RenderFrameHostImpl* red_rfh =
        GetAnimator()->LastNavigationRequest()->GetRenderFrameHost();
    auto* new_widget_host = red_rfh->GetRenderWidgetHost();
    ASSERT_TRUE(new_widget_host);
    auto* new_view = new_widget_host->GetView();
    ASSERT_TRUE(new_view);
    cc::RenderFrameMetadata metadata;
    metadata.primary_main_frame_item_sequence_number =
        GetItemSequenceNumberForNavigation(back_to_red.GetNavigationHandle());
    GetAnimator()->set_post_ready_to_commit_callback(
        base::BindLambdaForTesting([&]() {
          new_widget_host->render_frame_metadata_provider()
              ->SetLastRenderFrameMetadataForTest(std::move(metadata));
          GetAnimator()->OnRenderFrameMetadataChangedAfterActivation(
              base::TimeTicks());
        }));
  }

  ASSERT_TRUE(back_to_red.WaitForNavigationFinished());
  ASSERT_TRUE(destroyed.Wait());
  EXPECT_TRUE(did_cross_fade.IsReady());

  ASSERT_FALSE(received_frame_while_waiting);

  ASSERT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));
  EXPECT_TRANSFORM_NEAR(kIdentityTransform, GetLivePageLayer()->transform(),
                        kFloatTolerance);
}

// Test that, when the invoke animation finishes (when the active page is
// completely out of the view port), if Viz has already activated a new frame
// submitted by the new renderer, we skip `kWaitingForNewRendererToDraw`.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       RenderFrameActivatedDuringInvokeAnimation) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<AnimatorState> destroyed;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());

  bool received_frame_while_waiting = false;
  GetAnimator()->set_waited_for_renderer_new_frame(base::BindLambdaForTesting(
      [&]() { received_frame_while_waiting = true; }));

  TestNavigationManager back_to_red(web_contents(), RedURL());
  GetAnimationManager()->OnGestureInvoked();
  ASSERT_TRUE(back_to_red.WaitForResponse());

  // Manually set the new frame metadata before the DidCommit message and call
  // `OnRenderFrameMetadataChangedAfterActivation()` to simulate a frame
  // activation. Do this at the end of the "DidCommit" stack to simulate the
  // viz activates the first frame while the invoke animation is still playing.
  {
    RenderFrameHostImpl* red_rfh =
        GetAnimator()->LastNavigationRequest()->GetRenderFrameHost();
    auto* new_widget_host = red_rfh->GetRenderWidgetHost();
    ASSERT_TRUE(new_widget_host);
    auto* new_view = new_widget_host->GetView();
    ASSERT_TRUE(new_view);
    cc::RenderFrameMetadata metadata;
    metadata.primary_main_frame_item_sequence_number =
        GetItemSequenceNumberForNavigation(back_to_red.GetNavigationHandle());
    GetAnimator()->set_did_finish_navigation_callback(
        base::BindLambdaForTesting([&](bool) {
          new_widget_host->render_frame_metadata_provider()
              ->SetLastRenderFrameMetadataForTest(std::move(metadata));
          GetAnimator()->OnRenderFrameMetadataChangedAfterActivation(
              base::TimeTicks());
        }));
  }

  ASSERT_TRUE(back_to_red.WaitForNavigationFinished());
  ASSERT_TRUE(destroyed.Wait());
  ASSERT_FALSE(received_frame_while_waiting);

  ASSERT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));
  EXPECT_TRANSFORM_NEAR(kIdentityTransform, GetLivePageLayer()->transform(),
                        kFloatTolerance);
}

// E.g., google.com --back nav--> bank.com. Bank.com commits, but before the
// invoke animation has finished, bank.com's document redirects the user to
// bank.com/login.html.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       ClientRedirectWhileDisplayingInvokeAnimation) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<AnimatorState> destroyed;
  TestFuture<bool> did_finish_navigation;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->set_did_finish_navigation_callback(
      did_finish_navigation.GetCallback());

  GetAnimator()->PauseAnimationAtDisplayingInvokeAnimation();

  {
    TestNavigationManager back_to_red(web_contents(), RedURL());
    GetAnimationManager()->OnGestureInvoked();
    ASSERT_TRUE(back_to_red.WaitForNavigationFinished());
  }

  ASSERT_TRUE(did_finish_navigation.Wait());

  // Navigate to the blue page while the animator is still displaying the
  // invoke animation.
  EXPECT_STATE_EQ(kDisplayingInvokeAnimation, GetAnimator()->state());

  {
    TestNavigationManager nav_to_blue(web_contents(), BlueURL());
    // Simulate a client redirect, from red's document.
    ASSERT_TRUE(ExecJs(web_contents(), "window.location.href = 'blue.html'"));
    ASSERT_TRUE(nav_to_blue.WaitForNavigationFinished());
  }

  ASSERT_TRUE(destroyed.Wait());
  EXPECT_STATE_EQ(kAnimationAborted, destroyed.Get());

  ASSERT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));
  EXPECT_TRANSFORM_NEAR(kIdentityTransform, GetLivePageLayer()->transform(),
                        kFloatTolerance);
}

IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       ClientRedirectWhileWaitingForNewFrame) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<AnimatorState> destroyed;
  TestFuture<bool> did_finish_navigation;
  TestFuture<void> did_cross_fade;
  TestFuture<void> did_invoke;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->set_did_finish_navigation_callback(
      did_finish_navigation.GetCallback());
  GetAnimator()->set_on_cross_fade_animation_displayed(
      did_cross_fade.GetCallback());
  GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());

  {
    TestNavigationManager back_to_red(web_contents(), RedURL());
    GetAnimationManager()->OnGestureInvoked();
    ASSERT_TRUE(back_to_red.WaitForResponse());
    GetAnimator()->set_intercept_render_frame_metadata_changed(true);
    ASSERT_TRUE(back_to_red.WaitForNavigationFinished());
  }

  ASSERT_TRUE(did_finish_navigation.Wait());
  ASSERT_TRUE(did_invoke.Wait());

  EXPECT_STATE_EQ(kWaitingForNewRendererToDraw, GetAnimator()->state());

  {
    TestNavigationManager nav_to_blue(web_contents(), BlueURL());
    // Simulate a client redirect, from red's document.
    ASSERT_TRUE(ExecJs(web_contents(), "window.location.href = 'blue.html'"));
    ASSERT_TRUE(nav_to_blue.WaitForNavigationFinished());
  }

  ASSERT_TRUE(destroyed.Wait());
  EXPECT_STATE_EQ(kAnimationAborted, destroyed.Get());
  EXPECT_FALSE(did_cross_fade.IsReady());

  ASSERT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));
  EXPECT_TRANSFORM_NEAR(kIdentityTransform, GetLivePageLayer()->transform(),
                        kFloatTolerance);

  // [red, blue]. The green entry is pruned because of the client redirect.
  ASSERT_EQ(web_contents()->GetController().GetEntryCount(), 2);
  ASSERT_EQ(web_contents()->GetController().GetLastCommittedEntry()->GetURL(),
            BlueURL());
}

// Assert that navigating from a crashed page should have no impact on the
// animations.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       NavigatingFromACrashedPage) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  // Crash the green page.
  RenderFrameHostWrapper crashed(web_contents()->GetPrimaryMainFrame());
  RenderProcessHostWatcher crashed_obs(
      crashed->GetProcess(), RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
  crashed->GetProcess()->Shutdown(content::RESULT_CODE_KILLED);
  crashed_obs.Wait();
  ASSERT_TRUE(crashed.WaitUntilRenderFrameDeleted());
  // The crashed RFH is still owned by the RFHManager.
  ASSERT_FALSE(crashed.IsDestroyed());
  ASSERT_FALSE(crashed->IsRenderFrameLive());
  ASSERT_FALSE(crashed->GetView());

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  EXPECT_EQ("[Screenshot[Scrim],LivePage]", ChildrenInOrder(*GetViewLayer()));

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<AnimatorState> destroyed;
  TestFuture<void> did_cross_fade;
  TestFuture<void> did_invoke;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->set_on_cross_fade_animation_displayed(
      did_cross_fade.GetCallback());
  GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());

  TestFrameNavigationObserver back_to_red(web_contents());

  // Ignore frames from the new RenderFrameHost until we're ready.
  GetAnimator()->set_intercept_render_frame_metadata_changed(true);

  GetAnimationManager()->OnGestureInvoked();
  back_to_red.Wait();

  // Wait for the invoke animation to finish.
  {
    ASSERT_TRUE(did_invoke.Wait());
    EXPECT_STATE_EQ(kWaitingForNewRendererToDraw, GetAnimator()->state());

    // A screenshot layer should have been added, with the live page on top.
    EXPECT_EQ("[Screenshot[Scrim],LivePage]", ChildrenInOrder(*GetViewLayer()));

    // The live page should be fully offscreen in the direction of the swipe.
    // The screenshot should be at the origin.
    EXPECT_X_TRANSLATION(1.f, GetLivePageLayer()->transform());
    EXPECT_X_TRANSLATION(0.f, GetScreenshotLayer()->transform());

    // Scrim should be at zero when the invoke animation is finished.
    EXPECT_EQ(GetScrimLayer()->background_color().fA, 0.f);
  }

  base::TimeTicks now = base::TimeTicks();

  // Un-block waiting on the frame activation to start the cross fade animation.
  {
    // If the new frame hasn't yet submitted a new frame, wait for it so that
    // calling OnRenderFrameMetadataChangedAfterActivation moves the animator
    // into a cross fade.
    ASSERT_TRUE(WaitForFirstFrameInPrimaryMainFrame());
    GetAnimator()->set_intercept_render_frame_metadata_changed(false);
    GetAnimator()->OnRenderFrameMetadataChangedAfterActivation(now);
    EXPECT_STATE_EQ(kDisplayingCrossFadeAnimation, GetAnimator()->state());

    // Screenshot should still have the scrim and it should be at the end of
    // its timeline. Both screenshot and live page should be fully opaque to
    // start the cross fade.
    EXPECT_EQ(GetScrimLayer()->background_color().fA, 0.f);
    EXPECT_X_TRANSLATION(0.f, GetLivePageLayer()->transform());
    EXPECT_X_TRANSLATION(0.f, GetScreenshotLayer()->transform());
    EXPECT_EQ(GetLivePageLayer()->opacity(), 1.f);
    EXPECT_EQ(GetScreenshotLayer()->opacity(), 1.f);
  }

  // Wait for the crossfade animation to complete.
  {
    ASSERT_TRUE(did_cross_fade.Wait());
    ASSERT_TRUE(destroyed.IsReady());
    EXPECT_STATE_EQ(kAnimationFinished, destroyed.Get());

    EXPECT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));
    EXPECT_X_TRANSLATION(0.f, GetLivePageLayer()->transform());

    // The cross fade should have completed.
    EXPECT_EQ(GetLivePageLayer()->opacity(), 1.f);
  }

  ASSERT_EQ(back_to_red.last_committed_url(), RedURL());
  ASSERT_FALSE(web_contents()->GetController().GetActiveEntry()->GetUserData(
      NavigationEntryScreenshot::kUserDataKey));
}

// Regression test for https://crbug.com/326516254: If the destination page is
// skipped for a back/forward navigation due to the lack of user activation, the
// animator should also skip that entry.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       SkipPageWithNoUserActivation) {
  auto& nav_controller = web_contents()->GetController();

  // [red&, green&, blue*]
  {
    ScopedScreenshotCapturedObserverForTesting observer(
        web_contents()->GetController().GetLastCommittedEntryIndex());
    ASSERT_TRUE(NavigateToURL(web_contents(), BlueURL()));
    observer.Wait();
    WaitForCopyableViewInWebContents(web_contents());
    ASSERT_EQ(nav_controller.GetEntryCount(), 3);
    ASSERT_EQ(nav_controller.GetCurrentEntryIndex(), 2);
  }

  // Mark green as skipped.
  nav_controller.GetEntryAtIndex(1)->set_should_skip_on_back_forward_ui(true);

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.3));

  TestFuture<AnimatorState> destroyed;
  TestFuture<void> did_cross_fade;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->set_on_cross_fade_animation_displayed(
      did_cross_fade.GetCallback());

  TestFrameNavigationObserver back_to_red(web_contents());
  GetAnimationManager()->OnGestureInvoked();
  ASSERT_TRUE(destroyed.Wait());
  EXPECT_TRUE(did_cross_fade.IsReady());
  back_to_red.Wait();

  // TODO(https://crbug.com/325329998): We should also test that the transition
  // is from blue to red via pixel comparison.

  ASSERT_EQ(back_to_red.last_committed_url(), RedURL());
  ASSERT_EQ(nav_controller.GetEntryCount(), 3);
  ASSERT_EQ(nav_controller.GetCurrentEntryIndex(), 0);
}

IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       DifferentScreenAndScreenshotOrientation) {
  // Resize the screen.
  auto* native_view = web_contents()->GetNativeView();
  ASSERT_TRUE(native_view);
  native_view->OnPhysicalBackingSizeChanged(
      gfx::ScaleToCeiledSize(native_view->GetPhysicalBackingSize(), 2, 0.5f));

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  ASSERT_TRUE(GetAnimator());

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.5));
  EXPECT_EQ(GetScreenshotLayer()->background_color(),
            web_contents()
                ->GetDelegate()
                ->GetBackForwardTransitionFallbackUXConfig()
                .background_color);
  EXPECT_FALSE(GetRRectLayer());
  EXPECT_FALSE(GetFaviconLayer());

  const auto& children =
      static_cast<WebContentsViewAndroid*>(web_contents()->GetView())
          ->parent_for_web_page_widgets()
          ->parent()
          ->children();
  // `parent_for_web_page_widgets()` and the screenshot.
  ASSERT_EQ(children.size(), 2U);
  auto* fallback_screenshot = GetScreenshotLayer();
  auto expected_bg_color = web_contents()
                               ->GetDelegate()
                               ->GetBackForwardTransitionFallbackUXConfig()
                               .background_color;
  ASSERT_EQ(fallback_screenshot->background_color(), expected_bg_color);

  TestFrameNavigationObserver back_to_red(web_contents());
  base::test::TestFuture<void> cross_fade_displayed;
  GetAnimator()->set_on_cross_fade_animation_displayed(
      cross_fade_displayed.GetCallback());
  TestFuture<AnimatorState> destroyed;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimationManager()->OnGestureInvoked();
  ASSERT_TRUE(cross_fade_displayed.Wait());
  ASSERT_TRUE(destroyed.Wait());
  back_to_red.Wait();

  ASSERT_EQ(back_to_red.last_committed_url(), RedURL());
  ASSERT_FALSE(web_contents()->GetController().GetActiveEntry()->GetUserData(
      NavigationEntryScreenshot::kUserDataKey));
}

namespace {

// Wait for the main frame to receive a UpdateUserActivationState from the
// renderer with the expected new state.
class BrowserUserActivationWaiter
    : public UpdateUserActivationStateInterceptor {
 public:
  BrowserUserActivationWaiter(
      RenderFrameHost* rfh,
      blink::mojom::UserActivationNotificationType expected_type)
      : UpdateUserActivationStateInterceptor(rfh),
        expected_type_(expected_type) {}
  ~BrowserUserActivationWaiter() override = default;

  // Blocks until the renderer sends the expected user activation via
  // `UpdateUserActivationState()`.
  void Wait() { run_loop_.Run(); }

  void UpdateUserActivationState(
      blink::mojom::UserActivationUpdateType update_type,
      blink::mojom::UserActivationNotificationType notification_type) override {
    if (notification_type == expected_type_) {
      run_loop_.Quit();
    }
    UpdateUserActivationStateInterceptor::UpdateUserActivationState(
        update_type, notification_type);
  }

 private:
  const blink::mojom::UserActivationNotificationType expected_type_;
  base::RunLoop run_loop_;
};

void InjectBeforeUnload(ToRenderFrameHost adapter) {
  static constexpr std::string_view kScript = R"(
    window.onbeforeunload = (event) => {
      // Recommended
      event.preventDefault();

      // Included for legacy support, e.g. Chrome/Edge < 119
      event.returnValue = true;
    };
  )";
  ASSERT_TRUE(ExecJs(adapter, kScript, EXECUTE_SCRIPT_NO_USER_GESTURE));
}

void AddUserActivationForBeforeUnload(RenderFrameHostImpl* frame) {
  // Set the sticky user activation and let the bit propagate from renderer to
  // the browser.
  BrowserUserActivationWaiter wait_for_expected_user_activation(
      frame, blink::mojom::UserActivationNotificationType::kTest);
  frame->GetAssociatedLocalFrame()->NotifyUserActivation(
      blink::mojom::UserActivationNotificationType::kTest);
  wait_for_expected_user_activation.Wait();
  ASSERT_TRUE(frame->ShouldDispatchBeforeUnload(
      /*check_subframes_only=*/false));
  ASSERT_TRUE(frame->HasStickyUserActivation());
}

// Inject a BeforeUnload handler into `adapter`, and maybe set the stick user
// activation.
void InjectBeforeUnloadAndSetStickyUserActivation(
    ToRenderFrameHost adapter,
    bool set_sticky_user_activation = true) {
  InjectBeforeUnload(adapter);

  auto* frame = static_cast<RenderFrameHostImpl*>(adapter.render_frame_host());

  if (set_sticky_user_activation) {
    AddUserActivationForBeforeUnload(frame);
  } else {
    ASSERT_TRUE(
        frame->ShouldDispatchBeforeUnload(/*check_subframes_only=*/false));
    ASSERT_FALSE(frame->HasStickyUserActivation());
  }
}

// Intercept the BeforeUnload dialog. Used to block the execution until the
// confirmation dialog shows up, and to interact with the dialog to either
// cancel or start the navigation.
class BeforeUnloadDialogObserver
    : public blink::mojom::LocalFrameHostInterceptorForTesting {
 public:
  explicit BeforeUnloadDialogObserver(RenderFrameHostImpl* frame)
      : frame_(frame), impl_(receiver().SwapImplForTesting(this)) {}
  ~BeforeUnloadDialogObserver() override = default;

  // `blink::mojom::LocalFrameHostInterceptorForTesting`:
  LocalFrameHost* GetForwardingInterface() override { return impl_; }
  void RunBeforeUnloadConfirm(
      bool is_reload,
      RunBeforeUnloadConfirmCallback callback) override {
    CHECK(!is_reload);
    ack_ = std::move(callback);
    run_loop_.Quit();
    // Reset immediately. `frame_` and `impl_` will be destroyed once
    // `ack_` is executed with "proceed".
    std::ignore = receiver().SwapImplForTesting(impl_);
    frame_ = nullptr;
    impl_ = nullptr;
  }

  void WaitForDialog() { run_loop_.Run(); }

  void RespondToDialogue(bool proceed) { std::move(ack_).Run(proceed); }

  [[nodiscard]] bool shown() const { return !frame_; }

 private:
  mojo::AssociatedReceiver<blink::mojom::LocalFrameHost>& receiver() {
    return frame_->local_frame_host_receiver_for_testing();
  }

  raw_ptr<RenderFrameHostImpl> frame_;
  raw_ptr<blink::mojom::LocalFrameHost> impl_;
  base::RunLoop run_loop_;
  RunBeforeUnloadConfirmCallback ack_;
};

}  // namespace

// Test the case where the renderer acks the BeforeUnload message without
// showing a prompt.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       BeforeUnload_Proceed_NoPrompt) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  InjectBeforeUnloadAndSetStickyUserActivation(
      web_contents(), /*set_sticky_user_activation=*/false);

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<AnimatorState> destroyed;
  TestFuture<bool> did_finish_nav;
  TestFuture<void> did_cross_fade;
  TestFuture<void> did_cancel;
  TestFuture<void> did_invoke;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->set_did_finish_navigation_callback(
      did_finish_nav.GetCallback());
  GetAnimator()->set_on_cross_fade_animation_displayed(
      did_cross_fade.GetCallback());
  GetAnimator()->set_on_cancel_animation_displayed(did_cancel.GetCallback());
  GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());

  BeforeUnloadDialogObserver dialog_observer(
      web_contents()->GetPrimaryMainFrame());
  TestFrameNavigationObserver back_to_red(web_contents());
  GetAnimationManager()->OnGestureInvoked();

  ASSERT_TRUE(destroyed.Wait());
  EXPECT_TRUE(did_invoke.IsReady());
  EXPECT_TRUE(did_cross_fade.IsReady());
  EXPECT_TRUE(did_finish_nav.IsReady());
  back_to_red.Wait();
  ASSERT_EQ(back_to_red.last_committed_url(), RedURL());

  ASSERT_FALSE(dialog_observer.shown());
  ASSERT_FALSE(did_cancel.IsReady());
}

// Test the case where the renderer shows a prompt for the BeforeUnload message,
// and the user decides to proceed.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       BeforeUnload_Proceed_WithPrompt) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);
  InjectBeforeUnloadAndSetStickyUserActivation(web_contents());

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<AnimatorState> destroyed;
  TestFuture<bool> did_finish_nav;
  TestFuture<void> did_cancel;
  TestFuture<void> did_invoke;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->set_did_finish_navigation_callback(
      did_finish_nav.GetCallback());
  GetAnimator()->set_on_cancel_animation_displayed(did_cancel.GetCallback());
  GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());

  BeforeUnloadDialogObserver dialog_observer(
      web_contents()->GetPrimaryMainFrame());
  TestFrameNavigationObserver back_to_red(web_contents());
  GetAnimationManager()->OnGestureInvoked();

  ASSERT_TRUE(did_cancel.Wait());
  dialog_observer.WaitForDialog();
  EXPECT_STATE_EQ(kWaitingForBeforeUnloadResponse, GetAnimator()->state());
  dialog_observer.RespondToDialogue(/*proceed=*/true);

  ASSERT_TRUE(destroyed.Wait());
  EXPECT_TRUE(did_invoke.IsReady());
  EXPECT_TRUE(did_finish_nav.IsReady());

  back_to_red.Wait();
  ASSERT_EQ(back_to_red.last_committed_url(), RedURL());

  ASSERT_TRUE(dialog_observer.shown());
}

// Test the case where the user cancels the navigation via the prompt, after
// the cancel animation finishes.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       BeforeUnload_Cancel_AfterCancelAnimationFinishes) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  InjectBeforeUnloadAndSetStickyUserActivation(web_contents());

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<AnimatorState> destroyed;
  TestFuture<void> did_cancel;
  TestFuture<void> did_invoke;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->set_on_cancel_animation_displayed(did_cancel.GetCallback());
  GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());

  BeforeUnloadDialogObserver dialog_observer(
      web_contents()->GetPrimaryMainFrame());
  TestFrameNavigationObserver back_to_red(web_contents());
  GetAnimationManager()->OnGestureInvoked();

  ASSERT_TRUE(did_cancel.Wait());
  dialog_observer.WaitForDialog();
  EXPECT_STATE_EQ(kWaitingForBeforeUnloadResponse, GetAnimator()->state());
  dialog_observer.RespondToDialogue(/*proceed=*/false);

  ASSERT_TRUE(destroyed.Wait());
  ASSERT_FALSE(back_to_red.last_navigation_succeeded());

  ASSERT_FALSE(did_invoke.IsReady());
  ASSERT_TRUE(dialog_observer.shown());
}

// Test the case where the user cancels the navigation via the prompt, before
// the cancel animation finishes.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       BeforeUnload_Cancel_BeforeCancelAnimationFinishes) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  InjectBeforeUnloadAndSetStickyUserActivation(web_contents());

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<AnimatorState> destroyed;
  TestFuture<void> did_cancel;
  TestFuture<void> did_invoke;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->set_on_cancel_animation_displayed(did_cancel.GetCallback());
  GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());

  BeforeUnloadDialogObserver dialog_observer(
      web_contents()->GetPrimaryMainFrame());
  TestFrameNavigationObserver back_to_red(web_contents());
  GetAnimator()->PauseAnimationAtDisplayingCancelAnimation();
  GetAnimationManager()->OnGestureInvoked();

  dialog_observer.WaitForDialog();
  EXPECT_STATE_EQ(kDisplayingCancelAnimation, GetAnimator()->state());
  dialog_observer.RespondToDialogue(/*proceed=*/false);
  GetAnimator()->UnpauseAnimation();

  ASSERT_TRUE(destroyed.Wait());
  EXPECT_TRUE(did_cancel.IsReady());
  ASSERT_FALSE(back_to_red.last_navigation_succeeded());

  ASSERT_FALSE(did_invoke.IsReady());
  ASSERT_TRUE(dialog_observer.shown());
}

// Test that when the user has decided not leave the current page by interacting
// with the prompt and the cancel animation is still playing, another navigation
// commits in the main frame. We should destroy the animator when the other
// navigation commits.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       BeforeUnload_RequestCancelledBeforeStart) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  InjectBeforeUnloadAndSetStickyUserActivation(web_contents());

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<AnimatorState> destroyed;
  TestFuture<void> did_cancel;
  TestFuture<void> did_invoke;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->set_on_cancel_animation_displayed(did_cancel.GetCallback());
  GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());

  BeforeUnloadDialogObserver dialog_observer(
      web_contents()->GetPrimaryMainFrame());
  TestFrameNavigationObserver back_to_red(web_contents());
  GetAnimator()->set_duration_between_frames(base::Microseconds(1));
  GetAnimator()->PauseAnimationAtDisplayingCancelAnimation();
  GetAnimationManager()->OnGestureInvoked();

  dialog_observer.WaitForDialog();
  EXPECT_STATE_EQ(kDisplayingCancelAnimation, GetAnimator()->state());
  // Expectation the animator will be destroyed while playing the cancel
  // animation.
  dialog_observer.RespondToDialogue(/*proceed=*/false);
  GetAnimator()->UnpauseAnimation();

  ASSERT_TRUE(NavigateToURL(web_contents(), BlueURL()));
  ASSERT_TRUE(destroyed.Wait());
  EXPECT_STATE_EQ(kAnimationAborted, destroyed.Get());

  ASSERT_FALSE(did_invoke.IsReady());
  ASSERT_FALSE(did_cancel.IsReady());
  ASSERT_TRUE(dialog_observer.shown());

  ASSERT_EQ(web_contents()->GetController().GetEntryCount(), 3);
  ASSERT_EQ(web_contents()->GetController().GetLastCommittedEntryIndex(), 2);
}

IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       HasUaVisualTransitionSameDocument) {
  GURL url1 = embedded_test_server()->GetURL(
      "a.com", "/has-ua-visual-transition.html#frag1");
  GURL url2 = embedded_test_server()->GetURL(
      "a.com", "/has-ua-visual-transition.html#frag2");
  NavigationHandleCommitObserver navigation_0(web_contents(), url1);
  NavigationHandleCommitObserver navigation_1(web_contents(), url2);

  ASSERT_TRUE(NavigateToURL(web_contents(), url1));
  NavigationEntry* entry =
      web_contents()->GetController().GetLastCommittedEntry();
  ASSERT_TRUE(NavigateToURL(web_contents(), url2));
  // The NavigationEntry changes on a same-document navigation.
  EXPECT_NE(web_contents()->GetController().GetLastCommittedEntry(), entry);
  EXPECT_FALSE(
      EvalJs(web_contents(), "hasUAVisualTransitionValue").ExtractBool());

  EXPECT_TRUE(navigation_0.has_committed());
  EXPECT_TRUE(navigation_1.has_committed());
  EXPECT_FALSE(navigation_0.was_same_document());
  EXPECT_TRUE(navigation_1.was_same_document());

  TestNavigationManager manager(web_contents(), url1);
  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureInvoked();

  ASSERT_TRUE(manager.WaitForNavigationFinished());
  ASSERT_TRUE(
      EvalJs(web_contents(), "hasUAVisualTransitionValue").ExtractBool());
}

// Test the case where script commits a same-document navigation in beforeunload
// while the cancel animation is playing.
IN_PROC_BROWSER_TEST_F(
    BackForwardTransitionAnimationManagerBrowserTest,
    BeforeUnload_SameDocumentNavigation_DuringCancelAnimation) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  ASSERT_TRUE(NavigateToURL(
      web_contents(),
      embedded_test_server()->GetURL("/before_unload_same_doc_nav.html")));
  AddUserActivationForBeforeUnload(web_contents()->GetPrimaryMainFrame());

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<AnimatorState> destroyed;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->PauseAnimationAtDisplayingCancelAnimation();

  GetAnimationManager()->OnGestureInvoked();
  EXPECT_TRUE(web_contents()->HasUncommittedNavigationInPrimaryMainFrame());

  ASSERT_TRUE(destroyed.Wait());
  EXPECT_STATE_EQ(kAnimationAborted, destroyed.Get());
  EXPECT_EQ(
      web_contents()->GetController().GetLastCommittedEntry()->GetURL(),
      embedded_test_server()->GetURL("/before_unload_same_doc_nav.html#foo"));
}

namespace {
class FailBeginNavigationImpl : public ContentBrowserTestContentBrowserClient {
 public:
  FailBeginNavigationImpl() = default;
  ~FailBeginNavigationImpl() override = default;

  // `ContentBrowserTestContentBrowserClient`:
  bool ShouldOverrideUrlLoading(int frame_tree_node_id,
                                bool browser_initiated,
                                const GURL& gurl,
                                const std::string& request_method,
                                bool has_user_gesture,
                                bool is_redirect,
                                bool is_outermost_main_frame,
                                bool is_prerendering,
                                ui::PageTransition transition,
                                bool* ignore_navigation) final {
    // See `NavigationRequest::BeginNavigationImpl()`.
    *ignore_navigation = true;
    return true;
  }
};
}  // namespace

// Test that the animator is behaving correctly, even after the renderer acks
// the BeforeUnload message to proceed (begin) the navigation, but
// `BeginNavigationImpl()` hits an early out so we never each
// `DidStartNavigation()`.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       BeforeUnload_BeginNavigationImplFails) {
  FailBeginNavigationImpl fail_begin_navigation_client;

  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  InjectBeforeUnloadAndSetStickyUserActivation(web_contents());

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<AnimatorState> destroyed;
  TestFuture<void> did_cancel;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->set_on_cancel_animation_displayed(did_cancel.GetCallback());

  BeforeUnloadDialogObserver dialog_observer(
      web_contents()->GetPrimaryMainFrame());
  GetAnimationManager()->OnGestureInvoked();

  ASSERT_TRUE(did_cancel.Wait());
  dialog_observer.WaitForDialog();
  EXPECT_STATE_EQ(kWaitingForBeforeUnloadResponse, GetAnimator()->state());
  dialog_observer.RespondToDialogue(/*proceed=*/true);

  ASSERT_TRUE(destroyed.Wait());
  ASSERT_TRUE(dialog_observer.shown());

  // Still on the green page.
  ASSERT_EQ(web_contents()->GetController().GetEntryCount(), 2);
  ASSERT_EQ(web_contents()->GetController().GetLastCommittedEntryIndex(), 1);
  ASSERT_EQ(web_contents()->GetController().GetLastCommittedEntry()->GetURL(),
            GreenURL());
}

// Testing that, on the back nav from green.html to red.html, red.html redirects
// to blue.html. while the cross-fading animation is playing from the red.html's
// screenshot to the live page. We should abort the cross-fade animation when
// the redirect to blue.html commits.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       ClientRedirect_AnimatorDestroyedDuringCrossFade) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<AnimatorState> destroyed;
  TestFuture<void> did_invoke;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());

  GURL client_redirect =
      embedded_test_server()->GetURL("/red_redirect_to_blue.html#redirect");

  ASSERT_EQ(web_contents()->GetController().GetEntryCount(), 2);
  web_contents()->GetController().GetEntryAtIndex(0)->SetURL(client_redirect);

  TestNavigationManager back_nav_to_red(web_contents(), client_redirect);
  TestNavigationManager nav_to_blue(web_contents(), BlueURL());

  GetAnimator()->PauseAnimationAtDisplayingCrossFadeAnimation();

  GetAnimationManager()->OnGestureInvoked();
  ASSERT_TRUE(back_nav_to_red.WaitForResponse());

  // Force a call of `OnRenderFrameMetadataChangedAfterActivation()` when the
  // navigation back to red is committed. This makes sure that the animation
  // manager is displaying the cross-fade animation while the redirec to blue
  // is happening.
  {
    RenderFrameHostImpl* red_rfh =
        GetAnimator()->LastNavigationRequest()->GetRenderFrameHost();
    auto* new_widget_host = red_rfh->GetRenderWidgetHost();
    ASSERT_TRUE(new_widget_host);
    auto* new_view = new_widget_host->GetView();
    ASSERT_TRUE(new_view);
    cc::RenderFrameMetadata metadata;
    metadata.primary_main_frame_item_sequence_number =
        GetItemSequenceNumberForNavigation(
            back_nav_to_red.GetNavigationHandle());
    GetAnimator()->set_did_finish_navigation_callback(
        base::BindLambdaForTesting([&](bool) {
          new_widget_host->render_frame_metadata_provider()
              ->SetLastRenderFrameMetadataForTest(std::move(metadata));
          GetAnimator()->OnRenderFrameMetadataChangedAfterActivation(
              base::TimeTicks());
        }));
  }

  ASSERT_TRUE(back_nav_to_red.WaitForNavigationFinished());
  ASSERT_TRUE(back_nav_to_red.was_successful());
  ASSERT_TRUE(did_invoke.Wait());
  EXPECT_STATE_EQ(kDisplayingCrossFadeAnimation, GetAnimator()->state());

  ASSERT_TRUE(nav_to_blue.WaitForNavigationFinished());
  ASSERT_TRUE(destroyed.Wait());
  EXPECT_STATE_EQ(kAnimationAborted, destroyed.Get());

  ASSERT_EQ(web_contents()->GetController().GetEntryCount(), 2);
  ASSERT_EQ(web_contents()->GetController().GetEntryAtIndex(0)->GetURL(),
            BlueURL());
  ASSERT_EQ(web_contents()->GetController().GetEntryAtIndex(1)->GetURL(),
            GreenURL());
}

// Test that input isn't dispatched to the renderer while the transition
// animation is in progress.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       SuppressRendererInputDuringTransition) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  // Start a back transition gesture.
  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<AnimatorState> destroyed;
  TestFuture<void> did_invoke;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());

  // Once the gesture's invoked, block the response so we're waiting with the
  // transition active.
  TestNavigationManager back_nav_to_green(web_contents(), RedURL());
  GetAnimationManager()->OnGestureInvoked();
  ASSERT_TRUE(back_nav_to_green.WaitForResponse());

  // Simulate various kinds of user input, these events should not be dispatched
  // to the renderer.
  {
    InputMsgWatcher input_watcher(
        web_contents()->GetPrimaryMainFrame()->GetRenderWidgetHost(),
        blink::WebInputEvent::Type::kUndefined);
    SimulateGestureScrollSequence(web_contents(), gfx::Point(100, 100),
                                  gfx::Vector2dF(0, 50));
    {
      SCOPED_TRACE("process_scroll");
      RunUntilInputProcessed(
          web_contents()->GetPrimaryMainFrame()->GetRenderWidgetHost());
    }
    EXPECT_EQ(input_watcher.last_sent_event_type(),
              blink::WebInputEvent::Type::kUndefined);

    SimulateTapDownAt(web_contents(), gfx::Point(100, 100));
    SimulateTapAt(web_contents(), gfx::Point(100, 100));
    {
      SCOPED_TRACE("process_tap");
      RunUntilInputProcessed(
          web_contents()->GetPrimaryMainFrame()->GetRenderWidgetHost());
    }

    EXPECT_EQ(input_watcher.last_sent_event_type(),
              blink::WebInputEvent::Type::kUndefined);

    SimulateMouseClick(web_contents(),
                       blink::WebInputEvent::Modifiers::kNoModifiers,
                       blink::WebMouseEvent::Button::kLeft);
    {
      SCOPED_TRACE("process_mouse_click");
      RunUntilInputProcessed(
          web_contents()->GetPrimaryMainFrame()->GetRenderWidgetHost());
    }
    EXPECT_EQ(input_watcher.last_sent_event_type(),
              blink::WebInputEvent::Type::kUndefined);
  }

  // Unblock the navigation and wait until the transition is completed.
  ASSERT_TRUE(back_nav_to_green.WaitForNavigationFinished());
  ASSERT_TRUE(back_nav_to_green.was_successful());

  ASSERT_TRUE(destroyed.Wait());
  EXPECT_TRUE(did_invoke.IsReady());

  // Ensure input is now successfully dispatched.
  {
    InputMsgWatcher input_watcher(
        web_contents()->GetPrimaryMainFrame()->GetRenderWidgetHost(),
        blink::WebInputEvent::Type::kUndefined);
    SimulateTapDownAt(web_contents(), gfx::Point(100, 100));
    SimulateTapAt(web_contents(), gfx::Point(100, 100));

    {
      SCOPED_TRACE("process_not_suppressed_tap");
      RunUntilInputProcessed(
          web_contents()->GetPrimaryMainFrame()->GetRenderWidgetHost());
    }
    EXPECT_EQ(input_watcher.last_sent_event_type(),
              blink::WebInputEvent::Type::kGestureTap);
  }
}

// Regression test for https://crbug.com/339501357: If the animator is destroyed
// in the middle of a gesture, the history navigation should still proceed.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       AnimatorDestroyedMidGesture) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  // Start a navigation and wait until the request has been sent
  TestNavigationManager nav_to_blue(web_contents(), BlueURL());
  web_contents()->GetController().LoadURL(
      BlueURL(), Referrer{},
      ui::PageTransitionFromInt(
          ui::PageTransition::PAGE_TRANSITION_FROM_ADDRESS_BAR |
          ui::PageTransition::PAGE_TRANSITION_TYPED),
      std::string{});
  ASSERT_TRUE(nav_to_blue.WaitForRequestStart());

  // Start a swipe gesture
  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.3));

  TestFuture<AnimatorState> destroyed;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());

  // When the navigation above commits the animator should be destroyed with an
  // abort
  ASSERT_TRUE(nav_to_blue.WaitForNavigationFinished());
  ASSERT_TRUE(destroyed.Wait());
  EXPECT_STATE_EQ(kAnimationAborted, destroyed.Get());

  TestNavigationManager back_nav_to_red(web_contents(), RedURL());
  GetAnimationManager()->OnGestureInvoked();
  ASSERT_TRUE(back_nav_to_red.WaitForNavigationFinished());
  ASSERT_TRUE(back_nav_to_red.was_committed());
}

// Regression test for https://crbug.com/344761329: If the
// WebContentsViewAndroid's native view is detached from the root window, we
// should abort the transition.
IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       AnimatorDestroyedWhenViewAndroidDetachedFromWindow) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.3));

  TestFuture<AnimatorState> destroyed;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  // Pause at the beginning of the invoke animation but wait for the navigation
  // to finish, so we can guarantee to have subscribed to the new
  // RenderWidgetHost.
  GetAnimator()->PauseAnimationAtDisplayingInvokeAnimation();
  TestNavigationManager back_nav_to_red(web_contents(), RedURL());
  GetAnimationManager()->OnGestureInvoked();
  ASSERT_TRUE(back_nav_to_red.WaitForNavigationFinished());

  web_contents()->GetWebContentsAndroid()->SetTopLevelNativeWindow(
      /*env=*/nullptr,
      /*jwindow_android=*/base::android::JavaParamRef<jobject>(nullptr));
  ASSERT_TRUE(destroyed.Wait());
  EXPECT_STATE_EQ(kAnimationAborted, destroyed.Get());
}

class BackForwardTransitionAnimationManagerBrowserTestWithProgressBar
    : public BackForwardTransitionAnimationManagerBrowserTest {
 public:
  void SetUpOnMainThread() override {
    BackForwardTransitionAnimationManagerBrowserTest::SetUpOnMainThread();
    web_contents()
        ->GetNativeView()
        ->GetWindowAndroid()
        ->set_progress_bar_config_for_testing(kConfig);
  }

  void ValidateNoProgressBar() {
    const auto* screenshot_layer = GetScreenshotLayer();
    EXPECT_EQ(screenshot_layer->children().size(), 1u);
  }

 protected:
  static constexpr ui::ProgressBarConfig kConfig = {
      .background_color = SkColors::kWhite,
      .height_physical = 10,
      .color = SkColors::kBlue,
      .hairline_color = SkColors::kWhite};
};

// Tests that the progress bar is drawn at the correct position during the
// invoke phase.
IN_PROC_BROWSER_TEST_F(
    BackForwardTransitionAnimationManagerBrowserTestWithProgressBar,
    ProgressBar) {
  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.3));
  ValidateNoProgressBar();

  GetAnimationManager()->OnGestureInvoked();
  {
    // Progress bar should be displayed when invoke animation starts.
    TestFuture<void> on_animate;
    GetAnimator()->set_next_on_animate_callback(on_animate.GetCallback());
    ASSERT_TRUE(on_animate.Wait())
        << "Timed out waiting for invoke animation to start";
    EXPECT_STATE_EQ(kDisplayingInvokeAnimation, GetAnimator()->state());
    const auto* progress_layer = GetProgressBarLayer();
    const int viewport_width = GetViewportSize().width();
    EXPECT_EQ(progress_layer->bounds(),
              gfx::Size(viewport_width, kConfig.height_physical));
  }

  {
    TestFuture<void> did_invoke;
    GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());
    ASSERT_TRUE(did_invoke.Wait())
        << "Timed out waiting for invoke animation to finish";

    TestFuture<void> on_animate;
    GetAnimator()->set_next_on_animate_callback(on_animate.GetCallback());
    ASSERT_TRUE(on_animate.Wait())
        << "Timed out waiting for animation after invoke to start";

    // Progress bar should be removed.
    ValidateNoProgressBar();
  }

  TestFuture<void> on_destroyed;
  GetAnimator()->set_next_on_animate_callback(on_destroyed.GetCallback());
  ASSERT_TRUE(on_destroyed.Wait())
      << "Timed out waiting for animator to be destroyed";
}

class BackForwardTransitionAnimationManagerBrowserTestWithNavigationQueueing
    : public BackForwardTransitionAnimationManagerBrowserTest {
 public:
  BackForwardTransitionAnimationManagerBrowserTestWithNavigationQueueing() =
      default;
  ~BackForwardTransitionAnimationManagerBrowserTestWithNavigationQueueing()
      override = default;

  void SetUp() override {
    BackForwardTransitionAnimationManagerBrowserTest::SetUp();

    std::vector<base::test::FeatureRefAndParams> enabled_features = {
        {features::kQueueNavigationsWhileWaitingForCommit,
         {{"queueing_level", "full"}}}};
    scoped_feature_list_.InitWithFeaturesAndParameters(
        enabled_features,
        /*disabled_features=*/{});
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    BackForwardTransitionAnimationManagerBrowserTest::SetUpCommandLine(
        command_line);
    // Force --site-per-process because this test is testing races with
    // committing a navigation in a speculative `RenderFrameHost`.
    command_line->AppendSwitch(switches::kSitePerProcess);
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

// Assert that once the gesture navigation has sent the commit message to the
// renderer, the animation will not be cancelled.
//
// TODO(https://crbug.com/326256165): Re-enable this in a follow up.
IN_PROC_BROWSER_TEST_F(
    BackForwardTransitionAnimationManagerBrowserTestWithNavigationQueueing,
    DISABLED_QueuedNavigationNoCancel) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);
  // We haven't started the navigation at this point.
  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestNavigationManager back_nav_to_red(web_contents(), RedURL());

  // Set the interceptor, so we start the navigation to blue when the
  // DidCommit message to red has just arrived at the browser.
  CommitMessageDelayer delay_nav_to_red(
      web_contents(), RedURL(),
      base::BindOnce(
          [](WebContentsImpl* web_contents, const GURL& blue_url,
             RenderFrameHost* rfh) {
            web_contents->GetController().LoadURL(
                blue_url, Referrer{},
                ui::PageTransitionFromInt(
                    ui::PageTransition::PAGE_TRANSITION_FROM_ADDRESS_BAR |
                    ui::PageTransition::PAGE_TRANSITION_TYPED),
                std::string{});
          },
          web_contents(), BlueURL()));

  TestFuture<AnimatorState> destroyed;
  TestFuture<void> did_invoke;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());

  // The user has lifted the finger - signaling the start of the navigation.
  GetAnimationManager()->OnGestureInvoked();
  ASSERT_TRUE(back_nav_to_red.WaitForResponse());

  // Wait for the navigation to the blue page has started.
  TestNavigationManager nav_to_blue(web_contents(), BlueURL());
  back_nav_to_red.ResumeNavigation();

  // Wait for the DidCommit message to red is intercepted, and then the
  // navigation to blue has started.
  delay_nav_to_red.Wait();
  // Pause the navigation to the blue page so we can let the committing red
  // navigation and its animations to finish.
  ASSERT_TRUE(nav_to_blue.WaitForRequestStart());

  // The start of navigation to the blue page means the history nav to the red
  // page has committed. Since the history nav to the red page has committed,
  // the animation manager must have brought the red page to the center of the
  // viewport.
  ASSERT_TRUE(back_nav_to_red.was_successful());
  ASSERT_TRUE(destroyed.Wait());
  EXPECT_TRUE(did_invoke.IsReady());
  ASSERT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));
  EXPECT_TRANSFORM_NEAR(kIdentityTransform, GetLivePageLayer()->transform(),
                        kFloatTolerance);

  // Wait for the navigation to the blue have finished.
  ASSERT_TRUE(nav_to_blue.WaitForNavigationFinished());
  ASSERT_TRUE(nav_to_blue.was_successful());

  // [red, blue]. The green NavigationEntry is pruned because we performed a
  // forward navigation from red to blue.
  ASSERT_EQ(web_contents()->GetController().GetEntryCount(), 2);
  ASSERT_EQ(web_contents()->GetController().GetLastCommittedEntryIndex(), 1);
  ASSERT_EQ(web_contents()->GetController().GetLastCommittedEntry()->GetURL(),
            BlueURL());
  ASSERT_EQ(web_contents()->GetController().GetEntryAtIndex(0)->GetURL(),
            RedURL());
}

namespace {

class BackForwardTransitionAnimationManagerWithRedirectBrowserTest
    : public BackForwardTransitionAnimationManagerBrowserTest {
 public:
  BackForwardTransitionAnimationManagerWithRedirectBrowserTest() = default;
  ~BackForwardTransitionAnimationManagerWithRedirectBrowserTest() override =
      default;

  void SetUpOnMainThread() override {
    SetupCrossSiteRedirector(embedded_test_server());
    BackForwardTransitionAnimationManagerBrowserTest::SetUpOnMainThread();
  }
};

}  // namespace

IN_PROC_BROWSER_TEST_F(
    BackForwardTransitionAnimationManagerWithRedirectBrowserTest,
    AbortedOnCrossOriginRedirect) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<AnimatorState> destroyed;
  TestFuture<void> did_invoke;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());

  std::string different_host("b.com");
  GURL redirect = embedded_test_server()->GetURL(
      "/cross-site/" + different_host + "/empty.html");
  GURL expected_url =
      embedded_test_server()->GetURL(different_host, "/empty.html");

  // [red&, green*]
  ASSERT_EQ(web_contents()->GetController().GetEntryCount(), 2);
  web_contents()->GetController().GetEntryAtIndex(0)->SetURL(redirect);

  TestNavigationManager redirect_nav(web_contents(), redirect);

  GetAnimationManager()->OnGestureInvoked();

  ASSERT_TRUE(redirect_nav.WaitForNavigationFinished());
  ASSERT_TRUE(destroyed.Wait());
  EXPECT_STATE_EQ(kAnimationAborted, destroyed.Get());
  ASSERT_FALSE(did_invoke.IsReady());

  // [empty.html*, green&]
  ASSERT_EQ(web_contents()->GetController().GetEntryCount(), 2);
  ASSERT_EQ(web_contents()->GetController().GetEntryAtIndex(0)->GetURL(),
            expected_url);
  ASSERT_EQ(web_contents()->GetController().GetEntryAtIndex(1)->GetURL(),
            GreenURL());
}

// Assert that the navigation back to a site with an opaque origin is not
// considered as redirect. Such sites can be "chrome://newtabpage", "data:" or
// "file://".
IN_PROC_BROWSER_TEST_F(
    BackForwardTransitionAnimationManagerWithRedirectBrowserTest,
    OpaqueOriginsAreNotRedirects) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  constexpr char kGreenDataURL[] = R"(
    data:text/html,<body style="background-color:green"></body>
  )";

  ASSERT_TRUE(NavigateToURL(web_contents(), GURL(kGreenDataURL)));
  WaitForCopyableViewInWebContents(web_contents());
  ASSERT_TRUE(NavigateToURL(web_contents(), BlueURL()));
  WaitForCopyableViewInWebContents(web_contents());

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<AnimatorState> destroyed;
  TestFuture<void> did_invoke;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());

  TestNavigationManager back_nav_to_data_url(web_contents(),
                                             GURL(kGreenDataURL));

  GetAnimationManager()->OnGestureInvoked();

  ASSERT_TRUE(back_nav_to_data_url.WaitForNavigationFinished());
  ASSERT_TRUE(destroyed.Wait());
  EXPECT_TRUE(did_invoke.IsReady());
}

namespace {

class BackForwardTransitionAnimationManagerBrowserTestSameDocument
    : public BackForwardTransitionAnimationManagerBrowserTest {
 public:
  BackForwardTransitionAnimationManagerBrowserTestSameDocument() = default;
  ~BackForwardTransitionAnimationManagerBrowserTestSameDocument() override =
      default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    // Disable the vertical scroll bar, otherwise they might show up on the
    // screenshot, making the test flaky.
    command_line->AppendSwitch(switches::kHideScrollbars);
    BackForwardTransitionAnimationManagerBrowserTest::SetUpCommandLine(
        command_line);
  }

  void SetUpOnMainThread() override {
    ContentBrowserTest::SetUpOnMainThread();

    host_resolver()->AddRule("*", "127.0.0.1");
    embedded_test_server()->ServeFilesFromSourceDirectory(
        GetTestDataFilePath());
    net::test_server::RegisterDefaultHandlers(embedded_test_server());

    ASSERT_TRUE(embedded_test_server()->Start());

    // Load the red portion of the page.
    ASSERT_TRUE(NavigateToURL(web_contents(), embedded_test_server()->GetURL(
                                                  "/changing_color.html")));
    WaitForCopyableViewInWebContents(web_contents());

    auto* manager =
        BrowserContextImpl::From(web_contents()->GetBrowserContext())
            ->GetNavigationEntryScreenshotManager();
    ASSERT_TRUE(manager);
    ASSERT_EQ(manager->GetCurrentCacheSize(), 0U);
    ASSERT_TRUE(web_contents()->GetRenderWidgetHostView());

    // Limit three screenshots.
    manager->SetMemoryBudgetForTesting(4 * GetViewportSize().Area64() * 3);

    auto& controller = web_contents()->GetController();
    // Navigate to the green portion of the page.
    const int num_request_before_nav =
        NavigationTransitionUtils::GetNumCopyOutputRequestIssuedForTesting();
    const int entries_count_before_nav = controller.GetEntryCount();
    {
      ScopedScreenshotCapturedObserverForTesting observer(
          controller.GetLastCommittedEntryIndex());
      ASSERT_TRUE(NavigateToURL(
          web_contents(),
          embedded_test_server()->GetURL("/changing_color.html#green")));
      observer.Wait();
    }
    ASSERT_EQ(controller.GetEntryCount(), entries_count_before_nav + 1);
    ASSERT_EQ(
        NavigationTransitionUtils::GetNumCopyOutputRequestIssuedForTesting(),
        num_request_before_nav + 1);

    GetAnimationManager()->set_animator_factory_for_testing(
        std::make_unique<FactoryForTesting>(EmbedderBitmap()));
  }
};

}  // namespace

// Basic test for the animated transition on same-doc navigations. The
// transition is from a green portion of a page to a red portion of the same
// page.
IN_PROC_BROWSER_TEST_F(
    BackForwardTransitionAnimationManagerBrowserTestSameDocument,
    SmokeTest) {
  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<AnimatorState> destroyed;
  TestFuture<void> did_cross_fade;
  TestFuture<void> did_invoke;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->set_on_cross_fade_animation_displayed(
      did_cross_fade.GetCallback());
  GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());

  TestNavigationManager back_to_red(
      web_contents(), embedded_test_server()->GetURL("/changing_color.html"));
  GetAnimationManager()->OnGestureInvoked();

  ASSERT_TRUE(back_to_red.WaitForNavigationFinished());
  ASSERT_TRUE(destroyed.Wait());
  EXPECT_TRUE(did_invoke.IsReady());
  EXPECT_TRUE(did_cross_fade.IsReady());
}

IN_PROC_BROWSER_TEST_F(BackForwardTransitionAnimationManagerBrowserTest,
                       ScreenshotCompression) {
  SkBitmap expected_pixels;
  {
    NavigationEntryScreenshot::SetDisableCompressionForTesting(true);
    ASSERT_EQ(web_contents()->GetController().GetLastCommittedEntry()->GetURL(),
              GreenURL());
    GetAnimationManager()->OnGestureStarted(
        ui::BackGestureEvent(0), SwipeEdge::LEFT, NavType::kBackward);
    GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

    TestFuture<gfx::Image> result;
    auto* window = web_contents()->GetNativeView()->GetWindowAndroid();
    ui::GrabWindowSnapshot(window, gfx::Rect(), result.GetCallback());
    expected_pixels = result.Get().AsBitmap();
    ASSERT_FALSE(expected_pixels.empty());

    for (int col = 0.6 * expected_pixels.width() + 1;
         col < expected_pixels.width(); col++) {
      for (int row = 0; row < expected_pixels.height(); row++) {
        ASSERT_EQ(expected_pixels.getColor(col, row), SK_ColorGREEN)
            << col << "," << row << " and image "
            << cc::GetPNGDataUrl(expected_pixels);
      }
    }

    TestFuture<AnimatorState> destroyed;
    GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
    ScopedScreenshotCapturedObserverForTesting observer(
        web_contents()->GetController().GetLastCommittedEntryIndex());
    GetAnimationManager()->OnGestureInvoked();
    ASSERT_TRUE(destroyed.Wait());
    ASSERT_EQ(web_contents()->GetController().GetLastCommittedEntry()->GetURL(),
              RedURL());
    observer.Wait();
  }

  SkBitmap actual_pixels;
  {
    NavigationEntryScreenshot::SetDisableCompressionForTesting(false);
    ScopedScreenshotCapturedObserverForTesting observer(
        web_contents()->GetController().GetLastCommittedEntryIndex());
    ASSERT_TRUE(NavigateToURL(web_contents(), GreenURL()));
    observer.Wait();
    WaitForCopyableViewInWebContents(web_contents());
    NavigationTransitionTestUtils::WaitForScreenshotCompressed(
        web_contents()->GetController(),
        web_contents()->GetController().GetLastCommittedEntryIndex() - 1);

    GetAnimationManager()->OnGestureStarted(
        ui::BackGestureEvent(0), SwipeEdge::LEFT, NavType::kBackward);
    GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

    TestFuture<gfx::Image> result;
    auto* window = web_contents()->GetNativeView()->GetWindowAndroid();
    ui::GrabWindowSnapshot(window, gfx::Rect(), result.GetCallback());
    actual_pixels = result.Get().AsBitmap();
    ASSERT_FALSE(actual_pixels.empty());

    TestFuture<AnimatorState> destroyed;
    GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
    GetAnimationManager()->OnGestureCancelled();
    ASSERT_TRUE(destroyed.Wait());
  }

  // Allow all pixels to be off by 1.
  auto comparator = cc::FuzzyPixelComparator()
                        .SetErrorPixelsPercentageLimit(100.0f)
                        .SetAbsErrorLimit(2);
  EXPECT_TRUE(cc::MatchesBitmap(actual_pixels, expected_pixels, comparator));
}

namespace {
class BackForwardTransitionAnimationManagerBrowserTestSubframeTransitions
    : public BackForwardTransitionAnimationManagerBrowserTest {
 public:
  BackForwardTransitionAnimationManagerBrowserTestSubframeTransitions() =
      default;
  ~BackForwardTransitionAnimationManagerBrowserTestSubframeTransitions()
      override = default;

  void SetUpOnMainThread() override {
    BackForwardTransitionAnimationManagerBrowserTest::SetUpOnMainThread();
    // Load the main frame with title1.html. In all the tests the mainframe's
    // URL should always be title1.html. No subframe navigations can change
    // that.
    ASSERT_TRUE(NavigateToURL(web_contents(), MainFrameURL()));
    web_contents()->GetController().PruneAllButLastCommitted();
    AddIFrame(RedURL());
  }

  void AddIFrame(const GURL& url) {
    constexpr char kAddIframeScript[] = R"({
      (()=>{
          return new Promise((resolve) => {
            const frame = document.createElement('iframe');
            frame.addEventListener('load', () => {resolve();});
            frame.src = $1;
            document.body.appendChild(frame);
          });
      })();
    })";
    ASSERT_TRUE(ExecJs(web_contents()->GetPrimaryMainFrame(),
                       JsReplace(kAddIframeScript, url),
                       EXECUTE_SCRIPT_NO_USER_GESTURE));
  }

  FrameTreeNode* GetIFrameFrameTreeNodeAt(size_t child_index) {
    return web_contents()->GetPrimaryMainFrame()->child_at(child_index);
  }

  GURL MainFrameURL() const {
    return embedded_test_server()->GetURL("/title1.html");
  }
};
}  // namespace

IN_PROC_BROWSER_TEST_F(
    BackForwardTransitionAnimationManagerBrowserTestSubframeTransitions,
    SmokeTest) {
  auto* iframe = GetIFrameFrameTreeNodeAt(0);
  ASSERT_EQ(iframe->current_frame_host()->GetLastCommittedURL(), RedURL());

  ASSERT_TRUE(
      NavigateToURLFromRenderer(iframe->current_frame_host(), GreenURL()));
  ASSERT_EQ(web_contents()->GetController().GetVisibleEntry()->GetURL(),
            MainFrameURL());

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  GetAnimator()->set_subframe_navigation(true);

  TestFuture<void> crossfade_displayed;
  TestFuture<AnimatorState> destroyed;
  TestFuture<void> did_invoke;
  GetAnimator()->set_on_cross_fade_animation_displayed(
      crossfade_displayed.GetCallback());
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());

  TestNavigationObserver iframe_back_to_red(web_contents());
  GetAnimationManager()->OnGestureInvoked();

  iframe_back_to_red.Wait();
  ASSERT_TRUE(iframe_back_to_red.last_navigation_succeeded());
  ASSERT_EQ(iframe_back_to_red.last_navigation_url(), RedURL());
  ASSERT_TRUE(did_invoke.Wait());
  ASSERT_TRUE(crossfade_displayed.Wait());
  ASSERT_TRUE(destroyed.Wait());

  ASSERT_EQ(iframe->current_frame_host()->GetLastCommittedURL(), RedURL());
  ASSERT_EQ(web_contents()->GetController().GetVisibleEntry()->GetURL(),
            MainFrameURL());
}

// Test the iframe's renderer shows a prompt for the BeforeUnload message, and
// the user decides to proceed.
IN_PROC_BROWSER_TEST_F(
    BackForwardTransitionAnimationManagerBrowserTestSubframeTransitions,
    BeforeUnload_Proceed_WithPrompt) {
  auto* iframe = GetIFrameFrameTreeNodeAt(0);
  ASSERT_EQ(iframe->current_frame_host()->GetLastCommittedURL(), RedURL());

  // Note:
  // - We can't use `NavigateToURLFromRendererWithoutUserGesture()`. A subframe
  // navigation without a user gesture will make the navigation entry being
  // skipped on the back forward UI.
  // - `NavigateToURLFromRenderer()` will set the the sticky user activation bit
  // on the renderer.
  ASSERT_TRUE(
      NavigateToURLFromRenderer(iframe->current_frame_host(), GreenURL()));
  ASSERT_EQ(web_contents()->GetController().GetVisibleEntry()->GetURL(),
            MainFrameURL());

  InjectBeforeUnload(iframe->current_frame_host());
  ASSERT_TRUE(iframe->current_frame_host()->HasStickyUserActivation());

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  GetAnimator()->set_subframe_navigation(true);

  TestFuture<void> cancel_displayed;
  TestFuture<void> crossfade_displayed;
  TestFuture<AnimatorState> destroyed;
  TestFuture<void> did_invoke;
  GetAnimator()->set_on_cancel_animation_displayed(
      cancel_displayed.GetCallback());
  GetAnimator()->set_on_cross_fade_animation_displayed(
      crossfade_displayed.GetCallback());
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());

  BeforeUnloadDialogObserver dialog_observer(iframe->current_frame_host());
  TestNavigationObserver iframe_back_to_red(web_contents());
  GetAnimationManager()->OnGestureInvoked();

  ASSERT_TRUE(cancel_displayed.Wait());
  dialog_observer.WaitForDialog();
  EXPECT_STATE_EQ(kWaitingForBeforeUnloadResponse, GetAnimator()->state());
  dialog_observer.RespondToDialogue(/*proceed=*/true);
  iframe_back_to_red.Wait();
  ASSERT_TRUE(iframe_back_to_red.last_navigation_succeeded());
  ASSERT_EQ(iframe_back_to_red.last_navigation_url(), RedURL());

  ASSERT_TRUE(did_invoke.Wait());
  ASSERT_TRUE(crossfade_displayed.Wait());
  ASSERT_TRUE(destroyed.Wait());
  ASSERT_TRUE(dialog_observer.shown());
}

// Test that the user cancels the navigation via the prompt, after the cancel
// animation finishes.
IN_PROC_BROWSER_TEST_F(
    BackForwardTransitionAnimationManagerBrowserTestSubframeTransitions,
    BeforeUnload_Cancel_AfterCancelAnimationFinishes) {
  auto* iframe = GetIFrameFrameTreeNodeAt(0);
  ASSERT_EQ(iframe->current_frame_host()->GetLastCommittedURL(), RedURL());

  ASSERT_TRUE(
      NavigateToURLFromRenderer(iframe->current_frame_host(), GreenURL()));
  ASSERT_EQ(web_contents()->GetController().GetVisibleEntry()->GetURL(),
            MainFrameURL());

  InjectBeforeUnload(iframe->current_frame_host());
  ASSERT_TRUE(iframe->current_frame_host()->HasStickyUserActivation());

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  GetAnimator()->set_subframe_navigation(true);

  TestFuture<void> cancel_displayed;
  TestFuture<AnimatorState> destroyed;
  TestFuture<void> did_invoke;
  GetAnimator()->set_on_cancel_animation_displayed(
      cancel_displayed.GetCallback());
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());

  BeforeUnloadDialogObserver dialog_observer(iframe->current_frame_host());
  TestNavigationObserver back_to_red(web_contents());
  GetAnimationManager()->OnGestureInvoked();

  ASSERT_TRUE(cancel_displayed.Wait());
  dialog_observer.WaitForDialog();
  EXPECT_STATE_EQ(kWaitingForBeforeUnloadResponse, GetAnimator()->state());
  dialog_observer.RespondToDialogue(/*proceed=*/false);

  ASSERT_TRUE(destroyed.Wait());
  ASSERT_FALSE(back_to_red.last_navigation_succeeded());

  ASSERT_FALSE(did_invoke.IsReady());
  ASSERT_TRUE(dialog_observer.shown());
  ASSERT_EQ(iframe->current_frame_host()->GetLastCommittedURL(), GreenURL());
}

// Test that the user cancels the navigation via the prompt, before the cancel
// animation finishes.
IN_PROC_BROWSER_TEST_F(
    BackForwardTransitionAnimationManagerBrowserTestSubframeTransitions,
    BeforeUnload_Cancel_BeforeCancelAnimationFinishes) {
  auto* iframe = GetIFrameFrameTreeNodeAt(0);
  ASSERT_EQ(iframe->current_frame_host()->GetLastCommittedURL(), RedURL());

  ASSERT_TRUE(
      NavigateToURLFromRenderer(iframe->current_frame_host(), GreenURL()));
  ASSERT_EQ(web_contents()->GetController().GetVisibleEntry()->GetURL(),
            MainFrameURL());

  InjectBeforeUnload(iframe->current_frame_host());
  ASSERT_TRUE(iframe->current_frame_host()->HasStickyUserActivation());

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  GetAnimator()->set_subframe_navigation(true);

  TestFuture<void> cancel_displayed;
  TestFuture<AnimatorState> destroyed;
  TestFuture<void> did_invoke;
  GetAnimator()->set_on_cancel_animation_displayed(
      cancel_displayed.GetCallback());
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());

  BeforeUnloadDialogObserver dialog_observer(iframe->current_frame_host());
  GetAnimator()->PauseAnimationAtDisplayingCancelAnimation();
  GetAnimationManager()->OnGestureInvoked();

  dialog_observer.WaitForDialog();
  EXPECT_STATE_EQ(kDisplayingCancelAnimation, GetAnimator()->state());
  dialog_observer.RespondToDialogue(/*proceed=*/false);
  GetAnimator()->UnpauseAnimation();

  ASSERT_TRUE(cancel_displayed.Wait());
  ASSERT_TRUE(destroyed.Wait());

  ASSERT_FALSE(did_invoke.IsReady());
  ASSERT_TRUE(dialog_observer.shown());
  ASSERT_EQ(iframe->current_frame_host()->GetLastCommittedURL(), GreenURL());
}

// Test that when the user has decided not leave the current page by interacting
// with the prompt and the cancel animation is still playing, another navigation
// commits in the main frame. We should destroy the animator when the other
// navigation commits.
IN_PROC_BROWSER_TEST_F(
    BackForwardTransitionAnimationManagerBrowserTestSubframeTransitions,
    BeforeUnload_RequestCancelledBeforeStart) {
  auto* iframe = GetIFrameFrameTreeNodeAt(0);
  ASSERT_EQ(iframe->current_frame_host()->GetLastCommittedURL(), RedURL());

  ASSERT_TRUE(
      NavigateToURLFromRenderer(iframe->current_frame_host(), GreenURL()));
  ASSERT_EQ(web_contents()->GetController().GetVisibleEntry()->GetURL(),
            MainFrameURL());

  InjectBeforeUnload(iframe->current_frame_host());
  ASSERT_TRUE(iframe->current_frame_host()->HasStickyUserActivation());

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  GetAnimator()->set_subframe_navigation(true);

  bool cancel_played = false;
  GetAnimator()->set_on_cancel_animation_displayed(
      base::BindLambdaForTesting([&]() { cancel_played = true; }));
  TestFuture<AnimatorState> destroyed;
  TestFuture<void> did_invoke;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());

  BeforeUnloadDialogObserver dialog_observer(iframe->current_frame_host());
  TestNavigationObserver back_to_red(web_contents());
  GetAnimator()->set_duration_between_frames(base::Microseconds(1));
  GetAnimator()->PauseAnimationAtDisplayingCancelAnimation();
  GetAnimationManager()->OnGestureInvoked();

  dialog_observer.WaitForDialog();
  EXPECT_STATE_EQ(kDisplayingCancelAnimation, GetAnimator()->state());
  // Expectation the animator will be destroyed while playing the cancel
  // animation.
  dialog_observer.RespondToDialogue(/*proceed=*/false);
  GetAnimator()->UnpauseAnimation();

  ASSERT_TRUE(NavigateToURL(web_contents(), BlueURL()));
  ASSERT_TRUE(destroyed.Wait());
  EXPECT_STATE_EQ(kAnimationAborted, destroyed.Get());

  ASSERT_FALSE(did_invoke.IsReady());
  ASSERT_FALSE(cancel_played);
  ASSERT_TRUE(dialog_observer.shown());
}

// Test that the animator is behaving correctly, even after the renderer acks
// the BeforeUnload message to proceed (begin) the navigation, but
// `BeginNavigationImpl()` hits an early out so we never each
// `DidStartNavigation()`.
IN_PROC_BROWSER_TEST_F(
    BackForwardTransitionAnimationManagerBrowserTestSubframeTransitions,
    BeforeUnload_BeginNavigationImplFails) {
  auto* iframe = GetIFrameFrameTreeNodeAt(0);
  ASSERT_EQ(iframe->current_frame_host()->GetLastCommittedURL(), RedURL());

  ASSERT_TRUE(
      NavigateToURLFromRenderer(iframe->current_frame_host(), GreenURL()));
  ASSERT_EQ(web_contents()->GetController().GetVisibleEntry()->GetURL(),
            MainFrameURL());

  InjectBeforeUnload(iframe->current_frame_host());
  ASSERT_TRUE(iframe->current_frame_host()->HasStickyUserActivation());

  std::vector<NavigationEntryImpl*> entries_before;
  for (int i = 0; i < web_contents()->GetController().GetEntryCount(); ++i) {
    entries_before.push_back(
        web_contents()->GetController().GetEntryAtIndex(i));
  }

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  GetAnimator()->set_subframe_navigation(true);

  // Fail the next `BeginNavigationImpl()`.
  FailBeginNavigationImpl fail_begin_navigation_client;

  TestFuture<AnimatorState> destroyed;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  TestFuture<void> cancel_displayed;
  GetAnimator()->set_on_cancel_animation_displayed(
      cancel_displayed.GetCallback());

  BeforeUnloadDialogObserver dialog_observer(iframe->current_frame_host());
  GetAnimationManager()->OnGestureInvoked();

  ASSERT_TRUE(cancel_displayed.Wait());
  dialog_observer.WaitForDialog();
  EXPECT_STATE_EQ(kWaitingForBeforeUnloadResponse, GetAnimator()->state());
  dialog_observer.RespondToDialogue(/*proceed=*/true);

  ASSERT_TRUE(destroyed.Wait());

  std::vector<NavigationEntryImpl*> entries_after;
  for (int i = 0; i < web_contents()->GetController().GetEntryCount(); ++i) {
    entries_after.push_back(web_contents()->GetController().GetEntryAtIndex(i));
  }
  ASSERT_THAT(entries_after, ::testing::ContainerEq(entries_before));
}

// If a primary main frame request is present, all subframe requests are
// ignored. Note: this is only possible when the main frame is navigating within
// the same document.
IN_PROC_BROWSER_TEST_F(
    BackForwardTransitionAnimationManagerBrowserTestSubframeTransitions,
    OneMainFrameRequest_OneSubframeRequest) {
  auto& controller = web_contents()->GetController();

  // Navigate to the red portion of the document.
  // [..., red*]
  ASSERT_TRUE(NavigateToURL(web_contents(), embedded_test_server()->GetURL(
                                                "/changing_color.html#red")));
  WaitForCopyableViewInWebContents(web_contents());

  // Reset the list of entries.
  controller.PruneAllButLastCommitted();

  // Add a blue iframe at the red portion.
  // [red(blue)*]
  AddIFrame(BlueURL());

  // Navigate the iframe to another URL.
  // [red(blue), red(green)*]
  ASSERT_TRUE(NavigateToURLFromRenderer(
      GetIFrameFrameTreeNodeAt(0)->current_frame_host(), GreenURL()));
  WaitForCopyableViewInWebContents(web_contents());

  // Navigate to the green portion of the document.
  // [red(blue), red(green), green(green)*]
  {
    ScopedScreenshotCapturedObserverForTesting observer(
        controller.GetLastCommittedEntryIndex());
    ASSERT_TRUE(NavigateToURL(
        web_contents(),
        embedded_test_server()->GetURL("/changing_color.html#green")));
    observer.Wait();
  }

  ASSERT_EQ(controller.GetEntryCount(), 3);
  // Mark the middle entry as skipped.
  controller.GetEntryAtIndex(1)->set_should_skip_on_back_forward_ui(true);

  // Perform a back navigation from green(green) to red(blue), skipping
  // red(green) completely.
  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<void> crossfade_displayed;
  TestFuture<AnimatorState> destroyed;
  TestFuture<void> did_invoke;
  GetAnimator()->set_on_cross_fade_animation_displayed(
      crossfade_displayed.GetCallback());
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());

  TestNavigationManager back_to_red(
      web_contents(),
      embedded_test_server()->GetURL("/changing_color.html#red"));
  TestNavigationManager iframe_to_blue(web_contents(), BlueURL());
  GetAnimationManager()->OnGestureInvoked();

  ASSERT_TRUE(back_to_red.WaitForNavigationFinished());
  ASSERT_TRUE(did_invoke.Wait());
  ASSERT_TRUE(crossfade_displayed.Wait());
  ASSERT_TRUE(destroyed.Wait());
  ASSERT_TRUE(iframe_to_blue.WaitForNavigationFinished());
}

// The mainframe, who embeds an iframe, is doing a cross-document navigation.
// We should animate the mainframe navigation.
IN_PROC_BROWSER_TEST_F(
    BackForwardTransitionAnimationManagerBrowserTestSubframeTransitions,
    MainFrameCrossDocNav_WithIFrame) {
  auto& controller = web_contents()->GetController();
  // [title1&, red*]
  {
    ScopedScreenshotCapturedObserverForTesting observer(
        controller.GetLastCommittedEntryIndex());
    ASSERT_TRUE(NavigateToURL(web_contents(), RedURL()));
    observer.Wait();
  }
  // [title1&, red(blue)*]
  AddIFrame(BlueURL());
  // [title1&, red(blue), red(green)*]
  ASSERT_TRUE(NavigateToURLFromRenderer(
      GetIFrameFrameTreeNodeAt(0)->current_frame_host(), GreenURL()));

  ASSERT_EQ(controller.GetEntryCount(), 3);
  controller.GetEntryAtIndex(1)->set_should_skip_on_back_forward_ui(true);

  // Perform a back navigation from green(green) to title1, skipping red(blue).
  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<void> crossfade_displayed;
  TestFuture<AnimatorState> destroyed;
  TestFuture<void> did_invoke;
  GetAnimator()->set_on_cross_fade_animation_displayed(
      crossfade_displayed.GetCallback());
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());

  TestNavigationObserver mainframe_back_to_title1(
      web_contents(), /*expected_number_of_navigations=*/1);
  GetAnimationManager()->OnGestureInvoked();

  mainframe_back_to_title1.Wait();
  ASSERT_TRUE(mainframe_back_to_title1.last_navigation_succeeded());
  ASSERT_EQ(mainframe_back_to_title1.last_navigation_url(), MainFrameURL());
  ASSERT_TRUE(did_invoke.Wait());
  ASSERT_TRUE(crossfade_displayed.Wait());
  ASSERT_TRUE(destroyed.Wait());
}

// If a back navigation has more than one subframe requests and no main frame
// requests, the animated transition will be aborted.
IN_PROC_BROWSER_TEST_F(
    BackForwardTransitionAnimationManagerBrowserTestSubframeTransitions,
    MultipleSubframeRequests) {
  auto& controller = web_contents()->GetController();
  // Reset the list of entries.
  controller.PruneAllButLastCommitted();

  // [main(red)*]
  auto* iframe1 = GetIFrameFrameTreeNodeAt(0);
  ASSERT_EQ(iframe1->current_frame_host()->GetLastCommittedURL(), RedURL());

  // [main(red, green)*]: the initial navigation of the iframe won't create an
  // entry.
  AddIFrame(GreenURL());
  auto* iframe2 = GetIFrameFrameTreeNodeAt(1);
  ASSERT_EQ(iframe2->current_frame_host()->GetLastCommittedURL(), GreenURL());

  // [main(red, green), main(green, green)*]
  ASSERT_TRUE(
      NavigateToURLFromRenderer(iframe1->current_frame_host(), GreenURL()));
  // [main(red, green), main(green, green), main(green, blue)*]
  ASSERT_TRUE(
      NavigateToURLFromRenderer(iframe2->current_frame_host(), BlueURL()));

  ASSERT_EQ(controller.GetEntryCount(), 3);
  controller.GetEntryAtIndex(1)->set_should_skip_on_back_forward_ui(true);

  // Perform a back navigation from main(green, blue) to main(red, green),
  // skipping main(green, green) completely. This navigation will create two
  // subframe requests, and the animated transition will be aborted.
  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<void> crossfade_displayed;
  TestFuture<AnimatorState> destroyed;
  TestFuture<void> did_invoke;
  GetAnimator()->set_on_cross_fade_animation_displayed(
      crossfade_displayed.GetCallback());
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());

  TestNavigationObserver back_nav(web_contents());
  GetAnimationManager()->OnGestureInvoked();

  back_nav.WaitForNavigationFinished();
  ASSERT_TRUE(destroyed.Wait());
  EXPECT_STATE_EQ(kAnimationAborted, destroyed.Get());
  ASSERT_FALSE(did_invoke.IsReady());
  ASSERT_FALSE(crossfade_displayed.IsReady());

  ASSERT_EQ(controller.GetEntryCount(), 3);
  ASSERT_EQ(controller.GetLastCommittedEntryIndex(), 0);
}

// The transition of a subframe navigation will be cancelled if there is a main
// frame navigation.
//
// TODO(crbug/356417937): Flaky on bots. Reneable before Launch.
IN_PROC_BROWSER_TEST_F(
    BackForwardTransitionAnimationManagerBrowserTestSubframeTransitions,
    DISABLED_MainframeNavCancelsSubframeTransition) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  auto* iframe = GetIFrameFrameTreeNodeAt(0);
  ASSERT_EQ(iframe->current_frame_host()->GetLastCommittedURL(), RedURL());
  ASSERT_TRUE(
      NavigateToURLFromRenderer(iframe->current_frame_host(), GreenURL()));

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  GetAnimator()->set_subframe_navigation(true);

  TestFuture<AnimatorState> destroyed;
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());

  TestNavigationManager iframe_back_to_red(web_contents(), RedURL());
  GetAnimator()->PauseAnimationAtDisplayingInvokeAnimation();
  GetAnimationManager()->OnGestureInvoked();
  ASSERT_TRUE(iframe_back_to_red.WaitForRequestStart());
  EXPECT_STATE_EQ(kDisplayingInvokeAnimation, GetAnimator()->state());

  GURL title2 = embedded_test_server()->GetURL("/title2.html");
  TestNavigationManager navigation_mainframe(web_contents(), title2);
  web_contents()->GetController().LoadURL(
      title2, Referrer{},
      ui::PageTransitionFromInt(
          ui::PageTransition::PAGE_TRANSITION_FROM_ADDRESS_BAR |
          ui::PageTransition::PAGE_TRANSITION_TYPED),
      std::string{});
  ASSERT_TRUE(navigation_mainframe.WaitForNavigationFinished());
  ASSERT_TRUE(destroyed.Wait());
  EXPECT_STATE_EQ(kAnimationAborted, destroyed.Get());
  ASSERT_FALSE(iframe_back_to_red.was_committed());
}

namespace {
class BackForwardTransitionAnimationManagerBrowserTestNoPaintHolding
    : public BackForwardTransitionAnimationManagerBrowserTest {
 public:
  BackForwardTransitionAnimationManagerBrowserTestNoPaintHolding() {
    scoped_feature_list_.Reset();
    std::vector<base::test::FeatureRefAndParams> enabled_features = {
        {blink::features::kBackForwardTransitions, {}},
        {blink::features::kIncrementLocalSurfaceIdForMainframeSameDocNavigation,
         {}},
        {features::kRenderDocument,
         {{kRenderDocumentLevelParameterName,
           GetRenderDocumentLevelName(RenderDocumentLevel::kAllFrames)}}}};
    scoped_feature_list_.InitWithFeaturesAndParameters(
        enabled_features,
        /*disabled_features=*/{});
  }
  ~BackForwardTransitionAnimationManagerBrowserTestNoPaintHolding() override =
      default;
};
}  // namespace

IN_PROC_BROWSER_TEST_F(
    BackForwardTransitionAnimationManagerBrowserTestNoPaintHolding,
    PaintHoldingDisabledOnTransition) {
  // Disable BFCache. Since RenderDocument is fully enabled (in the test
  // harness), the cross-doc navigation will have paint holding enabled.
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<bool> is_paint_holding_timer_running_when_nav_finishes;
  TestFuture<void> invoke_played;
  TestFuture<AnimatorState> destroyed;
  GetAnimator()->set_did_finish_navigation_callback(
      is_paint_holding_timer_running_when_nav_finishes.GetCallback());
  GetAnimator()->set_on_invoke_animation_displayed(invoke_played.GetCallback());
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());

  TestNavigationManager back_nav_to_red(web_contents(), RedURL());
  GetAnimationManager()->OnGestureInvoked();
  ASSERT_TRUE(back_nav_to_red.WaitForNavigationFinished());
  ASSERT_TRUE(is_paint_holding_timer_running_when_nav_finishes.Wait());
  EXPECT_FALSE(is_paint_holding_timer_running_when_nav_finishes.Get());
  ASSERT_TRUE(invoke_played.Wait());
  ASSERT_TRUE(destroyed.Wait());
  EXPECT_STATE_EQ(kAnimationFinished, destroyed.Get());
}

// Test that the timer to dismiss the screenshot is properly started, if the
// renderer never submits a new frame post-navigation.
IN_PROC_BROWSER_TEST_F(
    BackForwardTransitionAnimationManagerBrowserTestNoPaintHolding,
    ScreenshotDismissalTimer) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<void> invoke_played;
  TestFuture<AnimatorState> destroyed;
  GetAnimator()->set_on_invoke_animation_displayed(invoke_played.GetCallback());
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->set_intercept_render_frame_metadata_changed(true);

  TestFrameNavigationObserver back_nav_to_red(web_contents());
  GetAnimationManager()->OnGestureInvoked();
  back_nav_to_red.Wait();
  ASSERT_EQ(back_nav_to_red.last_committed_url(), RedURL());
  ASSERT_TRUE(back_nav_to_red.last_navigation_succeeded());
  ASSERT_TRUE(invoke_played.Wait());

  ASSERT_TRUE(
      GetAnimator()->dismiss_screenshot_timer_for_testing()->IsRunning());
  ASSERT_EQ("[Screenshot[Scrim],LivePage]", ChildrenInOrder(*GetViewLayer()));
  EXPECT_TRUE(base::IsApproximatelyEqual(
      GetAnimator()->scrim_layer_for_testing()->background_color().fA, 0.f,
      kFloatTolerance));
  GetAnimator()->dismiss_screenshot_timer_for_testing()->FireNow();
  ASSERT_TRUE(destroyed.Wait());
  EXPECT_STATE_EQ(kAnimationAborted, destroyed.Get());
}

// Test that the timer to dismiss the screenshot is stopped once the renderer
// submits a new frame.
IN_PROC_BROWSER_TEST_F(
    BackForwardTransitionAnimationManagerBrowserTestNoPaintHolding,
    ScreenshotDismissalTimerStopped) {
  DisableBackForwardCacheForTesting(
      web_contents(),
      BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));

  TestFuture<void> invoke_played;
  TestFuture<AnimatorState> destroyed;
  GetAnimator()->set_on_invoke_animation_displayed(invoke_played.GetCallback());
  GetAnimator()->set_on_impl_destroyed(destroyed.GetCallback());
  GetAnimator()->set_intercept_render_frame_metadata_changed(true);

  TestFrameNavigationObserver back_nav_to_red(web_contents());
  GetAnimationManager()->OnGestureInvoked();
  back_nav_to_red.Wait();
  ASSERT_EQ(back_nav_to_red.last_committed_url(), RedURL());
  ASSERT_TRUE(back_nav_to_red.last_navigation_succeeded());
  ASSERT_TRUE(invoke_played.Wait());

  ASSERT_TRUE(
      GetAnimator()->dismiss_screenshot_timer_for_testing()->IsRunning());
  ASSERT_EQ("[Screenshot[Scrim],LivePage]", ChildrenInOrder(*GetViewLayer()));
  EXPECT_TRUE(base::IsApproximatelyEqual(
      GetAnimator()->scrim_layer_for_testing()->background_color().fA, 0.f,
      kFloatTolerance));

  cc::RenderFrameMetadata metadata;
  metadata.primary_main_frame_item_sequence_number =
      GetAnimator()->primary_main_frame_navigation_entry_item_sequence_number();
  web_contents()
      ->GetPrimaryMainFrame()
      ->GetRenderWidgetHost()
      ->render_frame_metadata_provider()
      ->SetLastRenderFrameMetadataForTest(metadata);

  GetAnimator()->set_intercept_render_frame_metadata_changed(false);
  GetAnimator()->OnRenderFrameMetadataChangedAfterActivation(base::TimeTicks());

  EXPECT_FALSE(
      GetAnimator()->dismiss_screenshot_timer_for_testing()->IsRunning());
  ASSERT_TRUE(destroyed.Wait());
  EXPECT_STATE_EQ(kAnimationFinished, destroyed.Get());
}

namespace {

class BackForwardTransitionAnimationManagerBrowserTestEmbedderLiveContent
    : public BackForwardTransitionAnimationManagerBrowserTest {
 public:
  SkBitmap EmbedderBitmap() override {
    SkBitmap bitmap;
    bitmap.allocN32Pixels(10, 10, true);
    bitmap.eraseColor(SkColors::kRed);
    bitmap.setImmutable();
    return bitmap;
  }
};

}  // namespace

IN_PROC_BROWSER_TEST_F(
    BackForwardTransitionAnimationManagerBrowserTestEmbedderLiveContent,
    Cancel_EmbedderScreenshot) {
  TestFuture<void> did_cancel;

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  EXPECT_EQ("[Screenshot[Scrim],LivePage,EmbedderContentLayer]",
            ChildrenInOrder(*GetViewLayer()));

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));
  EXPECT_EQ("[Screenshot[Scrim],LivePage,EmbedderContentLayer]",
            ChildrenInOrder(*GetViewLayer()));

  GetAnimator()->set_on_cancel_animation_displayed(did_cancel.GetCallback());
  GetAnimationManager()->OnGestureCancelled();
  EXPECT_EQ("[Screenshot[Scrim],LivePage,EmbedderContentLayer]",
            ChildrenInOrder(*GetViewLayer()));

  ASSERT_TRUE(did_cancel.Wait());
  EXPECT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));
}

IN_PROC_BROWSER_TEST_F(
    BackForwardTransitionAnimationManagerBrowserTestEmbedderLiveContent,
    Invoke_EmbedderScreenshot) {
  TestFuture<void> did_invoke;

  GetAnimationManager()->OnGestureStarted(ui::BackGestureEvent(0),
                                          SwipeEdge::LEFT, NavType::kBackward);
  EXPECT_EQ("[Screenshot[Scrim],LivePage,EmbedderContentLayer]",
            ChildrenInOrder(*GetViewLayer()));

  GetAnimationManager()->OnGestureProgressed(ui::BackGestureEvent(0.6));
  EXPECT_EQ("[Screenshot[Scrim],LivePage,EmbedderContentLayer]",
            ChildrenInOrder(*GetViewLayer()));

  GetAnimator()->set_intercept_render_frame_metadata_changed(true);
  GetAnimator()->set_on_invoke_animation_displayed(did_invoke.GetCallback());
  GetAnimationManager()->OnGestureInvoked();
  EXPECT_EQ("[Screenshot[Scrim],LivePage,EmbedderContentLayer]",
            ChildrenInOrder(*GetViewLayer()));

  ASSERT_TRUE(did_invoke.Wait());
  EXPECT_EQ("[Screenshot[Scrim],LivePage]", ChildrenInOrder(*GetViewLayer()));

  GetAnimator()->set_intercept_render_frame_metadata_changed(false);
  base::TimeTicks now = base::TimeTicks();
  GetAnimator()->OnRenderFrameMetadataChangedAfterActivation(now);

  TestFuture<AnimatorForTesting::State> did_finish;
  GetAnimator()->set_on_impl_destroyed(did_finish.GetCallback());
  EXPECT_EQ(did_finish.Get(), AnimatorForTesting::State::kAnimationFinished);
  EXPECT_EQ("[LivePage]", ChildrenInOrder(*GetViewLayer()));
}

}  // namespace content