Back out fd6ad8e567c9 (Bug 639179 Part 5) due to mobile unittest orange.
authorL. David Baron <dbaron@dbaron.org>
Sat, 09 Apr 2011 18:22:06 -0700
changeset 67750 91b90242f8822434681fe37ab20b53a1320618f8
parent 67749 18db426b1879162867bbb83579a145e90cdde042
child 67751 f91f9993012233b3d8a1a1a013c2219a783bfec9
push id19423
push userdbaron@mozilla.com
push dateSun, 10 Apr 2011 01:26:15 +0000
treeherdermozilla-central@fe3f7889918b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs639179
milestone2.2a1pre
backs outfd6ad8e567c9139c15d8323f16ddef26fe9a471a
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Back out fd6ad8e567c9 (Bug 639179 Part 5) due to mobile unittest orange.
mobile/app/mobile.js
mobile/chrome/content/input.js
--- a/mobile/app/mobile.js
+++ b/mobile/app/mobile.js
@@ -390,19 +390,18 @@ pref("javascript.options.methodjit.chrom
 
 pref("dom.max_chrome_script_run_time", 0); // disable slow script dialog for chrome
 pref("dom.max_script_run_time", 20);
 
 // JS error console
 pref("devtools.errorconsole.enabled", false);
 
 // kinetic tweakables
-pref("browser.ui.kinetic.updateInterval", 16);
-pref("browser.ui.kinetic.exponentialC", 1400);
-pref("browser.ui.kinetic.polynomialC", 100);
+pref("browser.ui.kinetic.updateInterval", 30);
+pref("browser.ui.kinetic.decelerationRate", 20);
 pref("browser.ui.kinetic.swipeLength", 160);
 
 // zooming
 pref("browser.ui.zoom.pageFitGranularity", 9); // don't zoom to fit by less than 1/9 (11%)
 pref("browser.ui.zoom.animationDuration", 200); // ms duration of double-tap zoom animation
 pref("browser.ui.zoom.reflow", false); // Change text wrapping on double-tap
 pref("browser.ui.zoom.reflow.fontSize", 720);
 
--- a/mobile/chrome/content/input.js
+++ b/mobile/chrome/content/input.js
@@ -829,19 +829,18 @@ function KineticController(aPanBy, aEndC
   this._velocity = new Point(0, 0);
   this._acceleration = new Point(0, 0);
   this._time = 0;
   this._timeStart = 0;
 
   // How often do we change the position of the scroll pane?  Too often and panning may jerk near
   // the end. Too little and panning will be choppy. In milliseconds.
   this._updateInterval = Services.prefs.getIntPref("browser.ui.kinetic.updateInterval");
-  // Constants that affect the "friction" of the scroll pane.
-  this._exponentialC = Services.prefs.getIntPref("browser.ui.kinetic.exponentialC");
-  this._polynomialC = Services.prefs.getIntPref("browser.ui.kinetic.polynomialC") / 1000000;
+  // "Friction" of the scroll pane. The lower, the less friction and the further distance traveled.
+  this._decelerationRate = Services.prefs.getIntPref("browser.ui.kinetic.decelerationRate") / 10000;
   // Number of milliseconds that can contain a swipe. Movements earlier than this are disregarded.
   this._swipeLength = Services.prefs.getIntPref("browser.ui.kinetic.swipeLength");
 
   this._reset();
 }
 
 KineticController.prototype = {
   _reset: function _reset() {
@@ -851,82 +850,90 @@ KineticController.prototype = {
     this._velocity.set(0, 0);
   },
 
   isActive: function isActive() {
     return this._active;
   },
 
   _startTimer: function _startTimer() {
-    let self = this;
+    // Use closed form of a parabola to calculate each position for panning.
+    // x(t) = v0*t + .5*t^2*a
+    // where: v0 is initial velocity
+    //        a is acceleration
+    //        t is time elapsed
+    //
+    // x(t)
+    //  ^
+    //  |                |
+    //  |
+    //  |                |
+    //  |           ....^^^^....
+    //  |      ...^^     |      ^^...
+    //  |  ...^                      ^...
+    //  |..              |               ..
+    //   -----------------------------------> t
+    //  t0             tf=-v0/a
+    //
+    // Using this formula, distance moved is independent of the time between each frame, unlike time
+    // step approaches. Once the time is up, set the position to x(tf) and stop the timer.
 
-    let lastp = this._position;  // track last position vector because pan takes deltas
+    let lastx = this._position;  // track last position vector because pan takes differences
     let v0 = this._velocity;  // initial velocity
     let a = this._acceleration;  // acceleration
-    let c = this._exponentialC;
-    let p = new Point(0, 0);
-    let dx, dy, t, realt;
 
-    function calcP(v0, a, t) {
-      // Important traits for this function:
-      //   p(t=0) is 0
-      //   p'(t=0) is v0
-      //
-      // We use exponential to get a smoother stop, but by itself exponential
-      // is too smooth at the end. Adding a polynomial with the appropriate
-      // weight helps to balance
-      return v0 * Math.exp(-t / c) * -c + a * t * t + v0 * c;
-    }
-
-    this._calcV = function(v0, a, t) {
-      return v0 * Math.exp(-t / c) + 2 * a * t;
-    }
+    // Temporary "bins" so that we don't create new objects during pan.
+    let aBin = new Point(0, 0);
+    let v0Bin = new Point(0, 0);
+    let self = this;
 
     let callback = {
       onBeforePaint: function kineticHandleEvent(timeStamp) {
         // Someone called end() on us between timer intervals
         // or we are paused.
         if (!self.isActive() || self._paused)
           return;
 
         // To make animation end fast enough but to keep smoothness, average the ideal
         // time frame (smooth animation) with the actual time lapse (end fast enough).
         // Animation will never take longer than 2 times the ideal length of time.
-        realt = timeStamp - self._initialTime;
+        let realt = timeStamp - self._initialTime;
         self._time += self._updateInterval;
-        t = (self._time + realt) / 2;
+        let t = (self._time + realt) / 2;
 
-        // Calculate new position.
-        p.x = calcP(v0.x, a.x, t);
-        p.y = calcP(v0.y, a.y, t);
-        dx = Math.round(p.x - lastp.x);
-        dy = Math.round(p.y - lastp.y);
+        // Calculate new position using x(t) formula.
+        let x = v0Bin.set(v0).scale(t).add(aBin.set(a).scale(0.5 * t * t));
+        let dx = Math.round(x.x - lastx.x);
+        let dy = Math.round(x.y - lastx.y);
 
-        // Test to see if movement is finished for each component.
-        if (dx * a.x > 0) {
-          dx = 0;
-          lastp.x = 0;
+        // Test to see if movement is finished for each component. As seen in graph, we want the
+        // final position to be at tf.
+        if (t >= -v0.x / a.x) {
+          // Plug in t=-v0/a into x(t) to get final position.
+          dx = Math.round(-v0.x * v0.x / 2 / a.x - lastx.x);
+          // Reset components. Next frame: a's component will be 0 and t >= NaN will be false.
+          lastx.x = 0;
           v0.x = 0;
           a.x = 0;
         }
         // Symmetric to above case.
-        if (dy * a.y > 0) {
-          dy = 0;
-          lastp.y = 0;
+        if (t >= -v0.y / a.y) {
+          dy = Math.round(-v0.y * v0.y / 2 / a.y - lastx.y);
+          lastx.y = 0;
           v0.y = 0;
           a.y = 0;
         }
 
         if (v0.x == 0 && v0.y == 0) {
           self.end();
         } else {
           let panStop = false;
           if (dx != 0 || dy != 0) {
             try { panStop = !self._panBy(-dx, -dy, true); } catch (e) {}
-            lastp.add(dx, dy);
+            lastx.add(dx, dy);
           }
 
           if (panStop)
             self.end();
           else
             mozRequestAnimationFrame(this);
         }
       }
@@ -937,22 +944,16 @@ KineticController.prototype = {
     mozRequestAnimationFrame(callback);
   },
 
   start: function start() {
     function sign(x) {
       return x ? ((x > 0) ? 1 : -1) : 0;
     }
 
-    function clampFromZero(x, closerToZero, furtherFromZero) {
-      if (x >= 0)
-        return Math.max(closerToZero, Math.min(furtherFromZero, x));
-      return Math.min(-closerToZero, Math.max(-furtherFromZero, x));
-    }
-
     let mb = this.momentumBuffer;
     let mblen = this.momentumBuffer.length;
 
     let lastTime = mb[mblen - 1].t;
     let distanceX = 0;
     let distanceY = 0;
     let swipeLength = this._swipeLength;
 
@@ -965,33 +966,32 @@ KineticController.prototype = {
         distanceY += me.dy;
       }
     }
 
     let currentVelocityX = 0;
     let currentVelocityY = 0;
 
     if (this.isActive()) {
-      // If active, then we expect this._calcV to be defined.
       let currentTime = Date.now() - this._initialTime;
-      currentVelocityX = Util.clamp(this._calcV(this._velocity.x, this._acceleration.x, currentTime), -kMaxVelocity, kMaxVelocity);
-      currentVelocityY = Util.clamp(this._calcV(this._velocity.y, this._acceleration.y, currentTime), -kMaxVelocity, kMaxVelocity);
+      currentVelocityX = Util.clamp(this._velocity.x + this._acceleration.x * currentTime, -kMaxVelocity, kMaxVelocity);
+      currentVelocityY = Util.clamp(this._velocity.y + this._acceleration.y * currentTime, -kMaxVelocity, kMaxVelocity);
     }
 
     if (currentVelocityX * this._velocity.x <= 0)
       currentVelocityX = 0;
     if (currentVelocityY * this._velocity.y <= 0)
       currentVelocityY = 0;
 
     let swipeTime = Math.min(swipeLength, lastTime - mb[0].t);
     this._velocity.x = clampFromZero((distanceX / swipeTime) + currentVelocityX, Math.abs(currentVelocityX), 6);
     this._velocity.y = clampFromZero((distanceY / swipeTime) + currentVelocityY, Math.abs(currentVelocityY), 6);
 
     // Set acceleration vector to opposite signs of velocity
-    this._acceleration.set(this._velocity.clone().map(sign).scale(-this._polynomialC));
+    this._acceleration.set(this._velocity.clone().map(sign).scale(-this._decelerationRate));
 
     this._position.set(0, 0);
     this._initialTime = mozAnimationStartTime;
     this._time = 0;
     this.momentumBuffer = [];
 
     if (!this.isActive() || this._paused)
       this._startTimer();