[ServiceWorker] Implement WebServiceWorkerContextClient::openWindow().
[chromium-blink-merge.git] / content / renderer / input / input_scroll_elasticity_controller.cc
blob0ab8cb0f411ed1e9559658b18391fa6623232228
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/renderer/input/input_scroll_elasticity_controller.h"
7 #include <math.h>
9 #include "base/bind.h"
10 #include "cc/input/input_handler.h"
11 #include "ui/gfx/geometry/vector2d_conversions.h"
13 // InputScrollElasticityController is based on
14 // WebKit/Source/platform/mac/InputScrollElasticityController.mm
16 * Copyright (C) 2011 Apple Inc. All rights reserved.
18 * Redistribution and use in source and binary forms, with or without
19 * modification, are permitted provided that the following conditions
20 * are met:
21 * 1. Redistributions of source code must retain the above copyright
22 * notice, this list of conditions and the following disclaimer.
23 * 2. Redistributions in binary form must reproduce the above copyright
24 * notice, this list of conditions and the following disclaimer in the
25 * documentation and/or other materials provided with the distribution.
27 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
28 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
29 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
30 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
31 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
37 * THE POSSIBILITY OF SUCH DAMAGE.
40 namespace content {
42 namespace {
44 const float kScrollVelocityZeroingTimeout = 0.10f;
45 const float kRubberbandMinimumRequiredDeltaBeforeStretch = 10;
47 const float kRubberbandStiffness = 20;
48 const float kRubberbandAmplitude = 0.31f;
49 const float kRubberbandPeriod = 1.6f;
51 // For these functions which compute the stretch amount, always return a
52 // rounded value, instead of a floating-point value. The reason for this is
53 // that Blink's scrolling can become erratic with fractional scroll amounts (in
54 // particular, if you have a scroll offset of 0.5, Blink will never actually
55 // bring that value back to 0, which breaks the logic used to determine if a
56 // layer is pinned in a direction).
58 gfx::Vector2d StretchAmountForTimeDelta(const gfx::Vector2dF& initial_position,
59 const gfx::Vector2dF& initial_velocity,
60 float elapsed_time) {
61 // Compute the stretch amount at a given time after some initial conditions.
62 // Do this by first computing an intermediary position given the initial
63 // position, initial velocity, time elapsed, and no external forces. Then
64 // take the intermediary position and damp it towards zero by multiplying
65 // against a negative exponential.
66 float amplitude = kRubberbandAmplitude;
67 float period = kRubberbandPeriod;
68 float critical_dampening_factor =
69 expf((-elapsed_time * kRubberbandStiffness) / period);
71 return gfx::ToRoundedVector2d(gfx::ScaleVector2d(
72 initial_position +
73 gfx::ScaleVector2d(initial_velocity, elapsed_time * amplitude),
74 critical_dampening_factor));
77 gfx::Vector2d StretchAmountForReboundDelta(const gfx::Vector2dF& delta) {
78 float stiffness = std::max(kRubberbandStiffness, 1.0f);
79 return gfx::ToRoundedVector2d(gfx::ScaleVector2d(delta, 1.0f / stiffness));
82 gfx::Vector2d StretchScrollForceForStretchAmount(const gfx::Vector2dF& delta) {
83 return gfx::ToRoundedVector2d(
84 gfx::ScaleVector2d(delta, kRubberbandStiffness));
87 } // namespace
89 InputScrollElasticityController::InputScrollElasticityController(
90 cc::ScrollElasticityHelper* helper)
91 : helper_(helper),
92 state_(kStateInactive),
93 momentum_animation_reset_at_next_frame_(false),
94 weak_factory_(this) {
97 InputScrollElasticityController::~InputScrollElasticityController() {
100 base::WeakPtr<InputScrollElasticityController>
101 InputScrollElasticityController::GetWeakPtr() {
102 if (helper_)
103 return weak_factory_.GetWeakPtr();
104 return base::WeakPtr<InputScrollElasticityController>();
107 void InputScrollElasticityController::ObserveWheelEventAndResult(
108 const blink::WebMouseWheelEvent& wheel_event,
109 const cc::InputHandlerScrollResult& scroll_result) {
110 // We should only get PhaseMayBegin or PhaseBegan events while in the
111 // Inactive or MomentumAnimated states, but in case we get bad input (e.g,
112 // abbreviated by tab-switch), always re-set the state to ActiveScrolling
113 // when those events are received.
114 if (wheel_event.phase == blink::WebMouseWheelEvent::PhaseMayBegin ||
115 wheel_event.phase == blink::WebMouseWheelEvent::PhaseBegan) {
116 scroll_velocity = gfx::Vector2dF();
117 last_scroll_event_timestamp_ = base::TimeTicks();
118 state_ = kStateActiveScroll;
119 pending_overscroll_delta_ = gfx::Vector2dF();
120 return;
123 gfx::Vector2dF event_delta(-wheel_event.deltaX, -wheel_event.deltaY);
124 base::TimeTicks event_timestamp =
125 base::TimeTicks() +
126 base::TimeDelta::FromSecondsD(wheel_event.timeStampSeconds);
127 switch (state_) {
128 case kStateInactive: {
129 // The PhaseMayBegin and PhaseBegan cases are handled at the top of the
130 // function.
131 if (wheel_event.momentumPhase == blink::WebMouseWheelEvent::PhaseBegan)
132 state_ = kStateMomentumScroll;
133 break;
135 case kStateActiveScroll:
136 if (wheel_event.phase == blink::WebMouseWheelEvent::PhaseChanged) {
137 UpdateVelocity(event_delta, event_timestamp);
138 Overscroll(event_delta, scroll_result.unused_scroll_delta);
139 } else if (wheel_event.phase == blink::WebMouseWheelEvent::PhaseEnded ||
140 wheel_event.phase ==
141 blink::WebMouseWheelEvent::PhaseCancelled) {
142 if (helper_->StretchAmount().IsZero()) {
143 EnterStateInactive();
144 } else {
145 EnterStateMomentumAnimated(event_timestamp);
148 break;
149 case kStateMomentumScroll:
150 if (wheel_event.momentumPhase ==
151 blink::WebMouseWheelEvent::PhaseChanged) {
152 UpdateVelocity(event_delta, event_timestamp);
153 Overscroll(event_delta, scroll_result.unused_scroll_delta);
154 if (!helper_->StretchAmount().IsZero()) {
155 EnterStateMomentumAnimated(event_timestamp);
157 } else if (wheel_event.momentumPhase ==
158 blink::WebMouseWheelEvent::PhaseEnded) {
159 EnterStateInactive();
161 case kStateMomentumAnimated:
162 // The PhaseMayBegin and PhaseBegan cases are handled at the top of the
163 // function.
164 break;
168 void InputScrollElasticityController::UpdateVelocity(
169 const gfx::Vector2dF& event_delta,
170 const base::TimeTicks& event_timestamp) {
171 float time_delta =
172 (event_timestamp - last_scroll_event_timestamp_).InSecondsF();
173 if (time_delta < kScrollVelocityZeroingTimeout && time_delta > 0) {
174 scroll_velocity = gfx::Vector2dF(event_delta.x() / time_delta,
175 event_delta.y() / time_delta);
176 } else {
177 scroll_velocity = gfx::Vector2dF();
179 last_scroll_event_timestamp_ = event_timestamp;
182 void InputScrollElasticityController::Overscroll(
183 const gfx::Vector2dF& input_delta,
184 const gfx::Vector2dF& overscroll_delta) {
185 gfx::Vector2dF adjusted_overscroll_delta =
186 pending_overscroll_delta_ + overscroll_delta;
187 pending_overscroll_delta_ = gfx::Vector2dF();
189 // Only allow one direction to overscroll at a time, and slightly prefer
190 // scrolling vertically by applying the equal case to delta_y.
191 if (fabsf(input_delta.y()) >= fabsf(input_delta.x()))
192 adjusted_overscroll_delta.set_x(0);
193 else
194 adjusted_overscroll_delta.set_y(0);
196 // Don't allow overscrolling in a direction where scrolling is possible.
197 if (!PinnedHorizontally(adjusted_overscroll_delta.x()))
198 adjusted_overscroll_delta.set_x(0);
199 if (!PinnedVertically(adjusted_overscroll_delta.y())) {
200 adjusted_overscroll_delta.set_y(0);
203 // Require a minimum of 10 units of overscroll before starting the rubber-band
204 // stretch effect, so that small stray motions don't trigger it. If that
205 // minimum isn't met, save what remains in |pending_overscroll_delta_| for
206 // the next event.
207 gfx::Vector2dF old_stretch_amount = helper_->StretchAmount();
208 gfx::Vector2dF stretch_scroll_force_delta;
209 if (old_stretch_amount.x() != 0 ||
210 fabsf(adjusted_overscroll_delta.x()) >=
211 kRubberbandMinimumRequiredDeltaBeforeStretch) {
212 stretch_scroll_force_delta.set_x(adjusted_overscroll_delta.x());
213 } else {
214 pending_overscroll_delta_.set_x(adjusted_overscroll_delta.x());
216 if (old_stretch_amount.y() != 0 ||
217 fabsf(adjusted_overscroll_delta.y()) >=
218 kRubberbandMinimumRequiredDeltaBeforeStretch) {
219 stretch_scroll_force_delta.set_y(adjusted_overscroll_delta.y());
220 } else {
221 pending_overscroll_delta_.set_y(adjusted_overscroll_delta.y());
224 // Update the stretch amount according to the spring equations.
225 if (stretch_scroll_force_delta.IsZero())
226 return;
227 stretch_scroll_force_ += stretch_scroll_force_delta;
228 gfx::Vector2dF new_stretch_amount =
229 StretchAmountForReboundDelta(stretch_scroll_force_);
230 helper_->SetStretchAmount(new_stretch_amount);
233 void InputScrollElasticityController::EnterStateInactive() {
234 DCHECK_NE(kStateInactive, state_);
235 DCHECK(helper_->StretchAmount().IsZero());
236 state_ = kStateInactive;
237 stretch_scroll_force_ = gfx::Vector2dF();
240 void InputScrollElasticityController::EnterStateMomentumAnimated(
241 const base::TimeTicks& triggering_event_timestamp) {
242 DCHECK_NE(kStateMomentumAnimated, state_);
243 state_ = kStateMomentumAnimated;
245 momentum_animation_start_time_ = triggering_event_timestamp;
246 momentum_animation_initial_stretch_ = helper_->StretchAmount();
247 momentum_animation_initial_velocity_ = scroll_velocity;
248 momentum_animation_reset_at_next_frame_ = false;
250 // Similarly to the logic in Overscroll, prefer vertical scrolling to
251 // horizontal scrolling.
252 if (fabsf(momentum_animation_initial_velocity_.y()) >=
253 fabsf(momentum_animation_initial_velocity_.x()))
254 momentum_animation_initial_velocity_.set_x(0);
256 if (!CanScrollHorizontally())
257 momentum_animation_initial_velocity_.set_x(0);
259 if (!CanScrollVertically())
260 momentum_animation_initial_velocity_.set_y(0);
262 helper_->RequestAnimate();
265 void InputScrollElasticityController::Animate(base::TimeTicks time) {
266 if (state_ != kStateMomentumAnimated)
267 return;
269 if (momentum_animation_reset_at_next_frame_) {
270 momentum_animation_start_time_ = time;
271 momentum_animation_initial_stretch_ = helper_->StretchAmount();
272 momentum_animation_initial_velocity_ = gfx::Vector2dF();
273 momentum_animation_reset_at_next_frame_ = false;
276 float time_delta =
277 std::max((time - momentum_animation_start_time_).InSecondsF(), 0.0);
279 gfx::Vector2dF old_stretch_amount = helper_->StretchAmount();
280 gfx::Vector2dF new_stretch_amount = StretchAmountForTimeDelta(
281 momentum_animation_initial_stretch_, momentum_animation_initial_velocity_,
282 time_delta);
283 gfx::Vector2dF stretch_delta = new_stretch_amount - old_stretch_amount;
285 // If the new stretch amount is near zero, set it directly to zero and enter
286 // the inactive state.
287 if (fabs(new_stretch_amount.x()) < 1 && fabs(new_stretch_amount.y()) < 1) {
288 helper_->SetStretchAmount(gfx::Vector2dF());
289 EnterStateInactive();
290 return;
293 // If we are not pinned in the direction of the delta, then the delta is only
294 // allowed to decrease the existing stretch -- it cannot increase a stretch
295 // until it is pinned.
296 if (!PinnedHorizontally(stretch_delta.x())) {
297 if (stretch_delta.x() > 0 && old_stretch_amount.x() < 0)
298 stretch_delta.set_x(std::min(stretch_delta.x(), -old_stretch_amount.x()));
299 else if (stretch_delta.x() < 0 && old_stretch_amount.x() > 0)
300 stretch_delta.set_x(std::max(stretch_delta.x(), -old_stretch_amount.x()));
301 else
302 stretch_delta.set_x(0);
304 if (!PinnedVertically(stretch_delta.y())) {
305 if (stretch_delta.y() > 0 && old_stretch_amount.y() < 0)
306 stretch_delta.set_y(std::min(stretch_delta.y(), -old_stretch_amount.y()));
307 else if (stretch_delta.y() < 0 && old_stretch_amount.y() > 0)
308 stretch_delta.set_y(std::max(stretch_delta.y(), -old_stretch_amount.y()));
309 else
310 stretch_delta.set_y(0);
312 new_stretch_amount = old_stretch_amount + stretch_delta;
314 stretch_scroll_force_ =
315 StretchScrollForceForStretchAmount(new_stretch_amount);
316 helper_->SetStretchAmount(new_stretch_amount);
317 helper_->RequestAnimate();
320 bool InputScrollElasticityController::PinnedHorizontally(
321 float direction) const {
322 gfx::ScrollOffset scroll_offset = helper_->ScrollOffset();
323 gfx::ScrollOffset max_scroll_offset = helper_->MaxScrollOffset();
324 if (direction < 0)
325 return scroll_offset.x() <= 0;
326 if (direction > 0)
327 return scroll_offset.x() >= max_scroll_offset.x();
328 return false;
331 bool InputScrollElasticityController::PinnedVertically(float direction) const {
332 gfx::ScrollOffset scroll_offset = helper_->ScrollOffset();
333 gfx::ScrollOffset max_scroll_offset = helper_->MaxScrollOffset();
334 if (direction < 0)
335 return scroll_offset.y() <= 0;
336 if (direction > 0)
337 return scroll_offset.y() >= max_scroll_offset.y();
338 return false;
341 bool InputScrollElasticityController::CanScrollHorizontally() const {
342 return helper_->MaxScrollOffset().x() > 0;
345 bool InputScrollElasticityController::CanScrollVertically() const {
346 return helper_->MaxScrollOffset().y() > 0;
349 void InputScrollElasticityController::ReconcileStretchAndScroll() {
350 gfx::Vector2dF stretch = helper_->StretchAmount();
351 if (stretch.IsZero())
352 return;
354 gfx::ScrollOffset scroll_offset = helper_->ScrollOffset();
355 gfx::ScrollOffset max_scroll_offset = helper_->MaxScrollOffset();
357 // Compute stretch_adjustment which will be added to |stretch| and subtracted
358 // from the |scroll_offset|.
359 gfx::Vector2dF stretch_adjustment;
360 if (stretch.x() < 0 && scroll_offset.x() > 0) {
361 stretch_adjustment.set_x(
362 std::min(-stretch.x(), static_cast<float>(scroll_offset.x())));
364 if (stretch.x() > 0 && scroll_offset.x() < max_scroll_offset.x()) {
365 stretch_adjustment.set_x(std::max(
366 -stretch.x(),
367 static_cast<float>(scroll_offset.x() - max_scroll_offset.x())));
369 if (stretch.y() < 0 && scroll_offset.y() > 0) {
370 stretch_adjustment.set_y(
371 std::min(-stretch.y(), static_cast<float>(scroll_offset.y())));
373 if (stretch.y() > 0 && scroll_offset.y() < max_scroll_offset.y()) {
374 stretch_adjustment.set_y(std::max(
375 -stretch.y(),
376 static_cast<float>(scroll_offset.y() - max_scroll_offset.y())));
379 if (stretch_adjustment.IsZero())
380 return;
382 gfx::Vector2dF new_stretch_amount = stretch + stretch_adjustment;
383 helper_->ScrollBy(-stretch_adjustment);
384 helper_->SetStretchAmount(new_stretch_amount);
386 // Update the internal state for the active scroll or animation to avoid
387 // discontinuities.
388 switch (state_) {
389 case kStateActiveScroll:
390 stretch_scroll_force_ =
391 StretchScrollForceForStretchAmount(new_stretch_amount);
392 break;
393 case kStateMomentumAnimated:
394 momentum_animation_reset_at_next_frame_ = true;
395 break;
396 default:
397 // These cases should not be hit because the stretch must be zero in the
398 // Inactive and MomentumScroll states.
399 NOTREACHED();
400 break;
404 } // namespace content