chromium/content/browser/navigation_transitions/back_forward_transition_animation_manager_android.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 "content/browser/navigation_transitions/back_forward_transition_animator.h"
#include "content/browser/renderer_host/navigation_controller_impl.h"
#include "content/browser/renderer_host/navigation_transitions/navigation_entry_screenshot.h"
#include "content/browser/renderer_host/navigation_transitions/navigation_transition_config.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/browser/web_contents/web_contents_view_android.h"
#include "content/public/browser/back_forward_transition_animation_manager.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/web_contents_delegate.h"

namespace content {

namespace {

// TODO(crbug/353766658): Move these shorthands to a proper header file.
using NavigationDirection =
    BackForwardTransitionAnimationManager::NavigationDirection;

using AnimationStage = BackForwardTransitionAnimationManager::AnimationStage;
using SwipeEdge = ui::BackGestureEventSwipeEdge;

}  // namespace

BackForwardTransitionAnimationManagerAndroid::
    BackForwardTransitionAnimationManagerAndroid(
        WebContentsViewAndroid* web_contents_view_android,
        NavigationControllerImpl* navigation_controller)
    : web_contents_view_android_(web_contents_view_android),
      navigation_controller_(navigation_controller),
      animator_factory_(
          std::make_unique<BackForwardTransitionAnimator::Factory>()) {}

BackForwardTransitionAnimationManagerAndroid::
    ~BackForwardTransitionAnimationManagerAndroid() = default;

void BackForwardTransitionAnimationManagerAndroid::OnGestureStarted(
    const ui::BackGestureEvent& gesture,
    SwipeEdge edge,
    NavigationDirection navigation_direction) {
  std::optional<int> index =
      navigation_direction == NavigationDirection::kForward
          ? navigation_controller_->GetIndexForGoForward()
          : navigation_controller_->GetIndexForGoBack();
  CHECK(index.has_value());
  auto* destination_entry = navigation_controller_->GetEntryAtIndex(*index);

  CHECK(destination_entry)
      << "The embedder should only delegate the history navigation task "
         "to this manager if there is a destination entry.";

  // Each previous gesture should finished with `OnGestureCancelled()` or
  // `OnGestureInvoked()`. In both cases we reset `destination_entry_index_` to
  // -1.
  CHECK_EQ(destination_entry_index_, -1);
  destination_entry_index_ = *index;

  if (animator_) {
    // It's possible for a user to start a second gesture when the first gesture
    // animation is still on-going (aka "chained back"). For now, abort the
    // previous animation (impl's dtor will reset the layer's position and
    // reclaim all the resources).
    //
    // TODO(crbug.com/40261105): We need a proper UX to support this.
    animator_->AbortAnimation();
    DestroyAnimator();
  }

  // Handle the case where the screenshot's dimension does not match the
  // physical viewport:
  // - TODO(https://crbug.com/346979589): Screenshot is captured in a landscape
  // / portrait mode but used for transition in the different mode.
  if (!ShouldAnimateNavigationTransition(navigation_direction, edge)) {
    return;
  }

  CHECK(animator_factory_);
  animator_ = animator_factory_->Create(
      web_contents_view_android_.get(), navigation_controller_.get(), gesture,
      navigation_direction, edge, destination_entry,
      MaybeCopyContentAreaAsBitmapSync(), this);

  // Become a WCO as soon as this class is created, because we want to
  // observe all navigations while this class is controlling the UI. This
  // allows us to ensure the visuals displayed align with the active page
  // and URL in the URL bar.
  WebContentsObserver::Observe(
      this->web_contents_view_android()->web_contents());
  auto* window = web_contents_view_android()->GetTopLevelNativeWindow();
  CHECK(window);
  window->AddObserver(this);
  web_contents_view_android()->GetNativeView()->AddObserver(this);

  OnAnimationStageChanged();
}

void BackForwardTransitionAnimationManagerAndroid::OnGestureProgressed(
    const ui::BackGestureEvent& gesture) {
  if (animator_) {
    animator_->OnGestureProgressed(gesture);
  }
}

void BackForwardTransitionAnimationManagerAndroid::OnGestureCancelled() {
  CHECK_NE(destination_entry_index_, -1);
  if (animator_) {
    animator_->OnGestureCancelled();
    MaybeDestroyAnimator();
  }
  destination_entry_index_ = -1;
}

void BackForwardTransitionAnimationManagerAndroid::OnGestureInvoked() {
  CHECK_NE(destination_entry_index_, -1);
  if (animator_) {
    animator_->OnGestureInvoked();
    MaybeDestroyAnimator();
  } else {
    navigation_controller_->GoToIndex(destination_entry_index_);
  }
  destination_entry_index_ = -1;
}

void BackForwardTransitionAnimationManagerAndroid::
    OnContentForNavigationEntryShown() {
  if (animator_) {
    animator_->OnContentForNavigationEntryShown();
    MaybeDestroyAnimator();
  }
}

AnimationStage
BackForwardTransitionAnimationManagerAndroid::GetCurrentAnimationStage() {
  return animator_ ? animator_->GetCurrentAnimationStage()
                   : AnimationStage::kNone;
}

void BackForwardTransitionAnimationManagerAndroid::SetFavicon(
    const SkBitmap& favicon) {
  CHECK(NavigationTransitionConfig::AreBackForwardTransitionsEnabled());
  auto* entry = web_contents_view_android_->web_contents()
                    ->GetController()
                    .GetLastCommittedEntry();
  CHECK(entry);
  entry->navigation_transition_data().set_favicon(favicon);
}

void BackForwardTransitionAnimationManagerAndroid::OnDetachedFromWindow() {
  // The WebContentsViewAndroid's native view is detached from the top level
  // window. We must abort the transition.
  CHECK(animator_);
  animator_->AbortAnimation();
  DestroyAnimator();
}

void BackForwardTransitionAnimationManagerAndroid::
    OnRootWindowVisibilityChanged(bool visible) {
  CHECK(animator_);
  if (!visible) {
    animator_->AbortAnimation();
    DestroyAnimator();
  }
}

void BackForwardTransitionAnimationManagerAndroid::OnDetachCompositor() {
  CHECK(animator_);
  animator_->AbortAnimation();
  DestroyAnimator();
}

void BackForwardTransitionAnimationManagerAndroid::OnAnimate(
    base::TimeTicks frame_begin_time) {
  animator_->OnAnimate(frame_begin_time);
  MaybeDestroyAnimator();
}

void BackForwardTransitionAnimationManagerAndroid::RenderWidgetHostDestroyed(
    RenderWidgetHost* widget_host) {
  animator_->OnRenderWidgetHostDestroyed(widget_host);
  MaybeDestroyAnimator();
}

void BackForwardTransitionAnimationManagerAndroid::
    OnRenderFrameMetadataChangedAfterActivation(
        base::TimeTicks activation_time) {
  animator_->OnRenderFrameMetadataChangedAfterActivation(activation_time);
  MaybeDestroyAnimator();
}

void BackForwardTransitionAnimationManagerAndroid::DidStartNavigation(
    NavigationHandle* navigation_handle) {
  animator_->DidStartNavigation(navigation_handle);
  MaybeDestroyAnimator();
}

void BackForwardTransitionAnimationManagerAndroid::ReadyToCommitNavigation(
    NavigationHandle* navigation_handle) {
  animator_->ReadyToCommitNavigation(navigation_handle);
  MaybeDestroyAnimator();
}

void BackForwardTransitionAnimationManagerAndroid::DidFinishNavigation(
    NavigationHandle* navigation_handle) {
  animator_->DidFinishNavigation(navigation_handle);
  MaybeDestroyAnimator();
}

void BackForwardTransitionAnimationManagerAndroid::
    OnDidNavigatePrimaryMainFramePreCommit(
        NavigationRequest* navigation_request,
        RenderFrameHostImpl* old_host,
        RenderFrameHostImpl* new_host) {
  if (animator_) {
    animator_->OnDidNavigatePrimaryMainFramePreCommit(navigation_request,
                                                      old_host, new_host);
    MaybeDestroyAnimator();
  }
}

void BackForwardTransitionAnimationManagerAndroid::
    OnNavigationCancelledBeforeStart(NavigationHandle* navigation_handle) {
  if (animator_) {
    animator_->OnNavigationCancelledBeforeStart(navigation_handle);
    MaybeDestroyAnimator();
  }
}

void BackForwardTransitionAnimationManagerAndroid::OnAnimationStageChanged() {
  web_contents_view_android()
      ->web_contents()
      ->GetDelegate()
      ->DidBackForwardTransitionAnimationChange();
}

void BackForwardTransitionAnimationManagerAndroid::
    OnPostNavigationFirstFrameTimeout() {
  CHECK(animator_);
  CHECK(animator_->IsTerminalState());
  DestroyAnimator();
}

SkBitmap BackForwardTransitionAnimationManagerAndroid::
    MaybeCopyContentAreaAsBitmapSync() {
  return web_contents_view_android()
      ->web_contents()
      ->GetDelegate()
      ->MaybeCopyContentAreaAsBitmapSync();
}

// TODO(baranerf): Implement this.
void BackForwardTransitionAnimationManagerAndroid::MaybeRecordIgnoredInput(
    const blink::WebInputEvent& event) {}

void BackForwardTransitionAnimationManagerAndroid::MaybeDestroyAnimator() {
  CHECK(animator_);
  if (animator_->IsTerminalState()) {
    DestroyAnimator();
  }
}

void BackForwardTransitionAnimationManagerAndroid::DestroyAnimator() {
  CHECK(animator_);
  WebContentsObserver::Observe(nullptr);
  auto* window = web_contents_view_android()->GetTopLevelNativeWindow();
  CHECK(window);
  window->RemoveObserver(this);
  web_contents_view_android()->GetNativeView()->RemoveObserver(this);
  animator_.reset();
  OnAnimationStageChanged();
}

}  // namespace content