1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207
  208

content / browser / navigation_transitions / physics_model.h [blame]

// 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_