chromium/content/browser/navigation_transitions/physics_model.h

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

#ifndef CONTENT_BROWSER_NAVIGATION_TRANSITIONS_PHYSICS_MODEL_H_
#define CONTENT_BROWSER_NAVIGATION_TRANSITIONS_PHYSICS_MODEL_H_

#include <deque>
#include <memory>

#include "base/time/time.h"
#include "content/common/content_export.h"

namespace content {

// The spring models. Internal to `PhysicsModel`.
class Spring;

// The animation model that drives the animations for session history
// navigations. See `animation_driver_` for what this model is composed of.
class CONTENT_EXPORT PhysicsModel {
 public:
  // The live page of the current content will stop at 85% of the screen width
  // while wait for the navigation to the new page to commit.
  static constexpr float kTargetCommitPendingRatio = 0.85f;

  // Initially the screenshot is placed at (-0.25W, 0) with respect to the
  // viewport.
  static constexpr float kScreenshotInitialPositionRatio = -0.25f;

  // The calculated layer offsets by this physics model.
  struct Result {
    // The calculated offsets for the foreground and background layers. They are
    // physical pixel values.
    float foreground_offset_physical;
    float background_offset_physical;

    // Indicating if the animation has finished. Set to true when the invoke
    // animation or cancel animation has finished playing.
    bool done;
  };

  PhysicsModel(int screen_width_physical, float device_scale_factor);
  PhysicsModel(const PhysicsModel&) = delete;
  PhysicsModel& operator=(const PhysicsModel&) = delete;
  ~PhysicsModel();

  // Called when the user swipes the finger across the screen. Uses
  // `FingerDragCurve()` to calculate the layers' positions. `movement_physical`
  // is the delta pixels since the last user gesture, meaning it can be positive
  // or negative.
  Result OnGestureProgressed(float movement_physical,
                             base::TimeTicks timestamp);

  // Called when a frame is requested at `request_animation_frame`. Uses the
  // corresponding spring models to calculate the layers' positions. It is
  // called at each vsync, except when the UI thread is busy.
  Result OnAnimate(base::TimeTicks request_animation_frame);

  enum SwitchSpringReason {
    // Switch to `kSpringCancel` because the user lifts the finger and signals
    // to not start the navigation.
    kGestureCancelled = 0,
    // Switch to `kSpringCommitPending` because the user lifts the finger and
    // signals to start the navigation.
    kGestureInvoked,
    // Switch to `kSpringCancel` because the browser has dispatched the
    // BeforeUnload message to the renderer.
    kBeforeUnloadDispatched,
    // Switch to `kSpringCommitPending` because the renderer has acked to
    // proceed the navigation, in response to the BeforeUnload message.
    kBeforeUnloadAckProceed,
  };
  // Switch to a different spring model for various reasons.
  void SwitchSpringForReason(SwitchSpringReason reason);

  // Called when the navigation is finished (destruction of the navigation
  // request). The caller is responsible for reacting to the targeted navigation
  // request (when there are multiple navigation requests).
  void OnNavigationFinished(bool navigation_committed);

 private:
  // The "state" of the physics model. The animations can be driven by four
  // models:
  // - A drag curve when the user swipes across the screen and before the user
  //   lifts the finger.
  // - A spring model that bounces around the commit-pending point. It drives
  //   the commit-pending animation: to bounce the live page around the
  //   commit-pending point while waiting for the history navigation to commit.
  //   Equilibrium is the commit-pending position.
  // - A spring model that plays the invoke animation: to bring the page from
  //   the commit-pending point to completely out of the view port. Equilibrium
  //   is at the right edge for a back navigation.
  // - A spring model that plays the cancel animation: to bring the old live
  //   content back to the center of the viewport. Equilibrium is at the left
  //   edge for a back navigation.
  //
  // A big difference between a drag curve and a spring model is the drag curve
  // user-gesture driven while the spring models are vsync driven. The
  // implication is that for the drag curve the caller will need to provide a
  // timestamp associated with the finger movement, whereas for spring models we
  // get the timestamp from the wallclock.
  enum class Driver {
    kDragCurve = 0,
    kSpringCommitPending,
    kSpringInvoke,
    kSpringCancel,
  };

  // Record the starting point of the next animation driver. Called every time
  // `driver_` changes.
  void StartAnimating(base::TimeTicks time);

  // Calculates the background layer's viewport offset based on the foreground.
  float ForegroundToBackGroundOffset(float foreground_offset_viewport);

  // Calculate the foreground layer's viewport offset based on the finger's
  // movement.
  float FingerDragCurve(float movement_viewport);

  // Interpolates the velocity based off `touch_points_history_`. Used to set
  // the initial velocity of the spring model when the physics model switches
  // from drag cruve to any of the spring models.
  float CalculateVelocity();

  // Record `commit_pending_acceleration_start_`, if needed.
  void RecordCommitPendingAccelerationStartIfNeeded(
      base::TimeTicks request_animation_frame);

  // Advance the physics model to the next animation driver at
  // `request_animation_frame`. Updates `animation_driver_` and sets its initial
  // velocity. No-op for the terminal drivers (the invoke and cancel springs).
  void AdvanceToNextAnimationDriver(base::TimeTicks request_animation_frame);

  // Normalizes `request_animation_frame` with respect to the start of the
  // animation (i.e., when we first switched to the current animation driver).
  base::TimeDelta CalculateRequestAnimationFrameSinceStart(
      base::TimeTicks request_animation_frame);

  const float viewport_width_;

  // Used to convert the physical sizes into CSS/viewport sizes.
  const float device_scale_factor_;

  // Tracks the current state of the navigation.
  enum class NavigationState {
    kNotStarted = 0,
    // The navigation never starts. This is a terminal state for
    // `OnGestureCancelled()`.
    kNeverStarted,
    // The navigation has started, WITHOUT a BeforeUnload handler.
    kStarted,
    // The browser has sent the BeforeUnload message to the renderer.
    kBeforeUnloadDispatched,
    // The renderer has acked the BeforeUnload message and to start the
    // navigation.
    kBeforeUnloadAckedProceed,

    // No state when BeforeUnload ack'ed to not proceed.

    // The navigation has committed in the browser. This is one of the two
    // terminal states for `OnNavigationFinished()`.
    kCommitted,
    // The navigation is cancelled. This is another terminal state for
    // `OnNavigationFinished()`.
    kCancelled,
  };
  NavigationState navigation_state_ = NavigationState::kNotStarted;

  // The spring models correspond to
  // `Driver::{kSpringCommitPending|kSpringInvoke|kSpringCancel}`. See the
  // comments on `Driver` that describe the springs behavior. Always non-null.
  std::unique_ptr<Spring> spring_commit_pending_;
  std::unique_ptr<Spring> spring_invoke_;
  std::unique_ptr<Spring> spring_cancel_;

  // Wallclock.
  base::TimeTicks last_request_animation_frame_;

  // The physics model always starts with the drag curve.
  Driver animation_driver_ = Driver::kDragCurve;

  // Used to "speed up" the animation on `spring_commit_pending_` when the
  // invoke animation is ready to play. Set in
  // `RecordCommitPendingAccelerationStartIfNeeded()` and applied in
  // `CalculateRequestAnimationFrameSinceStart()`. Wallclock.
  base::TimeTicks commit_pending_acceleration_start_;

  // Wallclock.
  base::TimeTicks animation_start_time_;
  float animation_start_offset_viewport_ = 0.f;

  // Measured with respect to the left edge of the device.
  float foreground_offset_viewport_ = 0.f;
  bool foreground_has_reached_target_commit_pending_ = false;

  struct TouchEvent {
    float position_viewport;
    base::TimeTicks timestamp;
  };
  // Records the last few touch events. Used to interpolate the velocity. It has
  // a max size defined in the .cc file.
  std::deque<TouchEvent> touch_points_history_;
};

}  // namespace content

#endif  // CONTENT_BROWSER_NAVIGATION_TRANSITIONS_PHYSICS_MODEL_H_