Bug 716673 - Split out a SubdocumentScrollHelper from the PanZoomController. r=pcwalton
authorKartikaya Gupta <kgupta@mozilla.com>
Tue, 10 Jan 2012 10:06:05 -0500
changeset 84221 66766a64792184a9ae4466faf131d0c48a236ffa
parent 84220 260031a8af9acc752a00f86dbcacc37164b6c196
child 84222 c9e0ff7b92eb51cd1b6e130ab804dd320368fa1e
push id21832
push userbmo@edmorley.co.uk
push dateWed, 11 Jan 2012 17:04:15 +0000
treeherdermozilla-central@40c9f9ff9fd5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspcwalton
bugs716673
milestone12.0a1
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
Bug 716673 - Split out a SubdocumentScrollHelper from the PanZoomController. r=pcwalton
mobile/android/base/Makefile.in
mobile/android/base/ui/PanZoomController.java
mobile/android/base/ui/SubdocumentScrollHelper.java
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -117,16 +117,17 @@ FENNEC_JAVA_FILES = \
   gfx/ScrollbarLayer.java \
   gfx/SingleTileLayer.java \
   gfx/TextLayer.java \
   gfx/TextureReaper.java \
   gfx/TileLayer.java \
   gfx/ViewportMetrics.java \
   gfx/WidgetTileLayer.java \
   ui/PanZoomController.java \
+  ui/SubdocumentScrollHelper.java \
   $(NULL)
 
 FENNEC_PP_JAVA_FILES = \
   App.java \
   LauncherShortcuts.java \
   NotificationHandler.java \
   Restarter.java \
   db/BrowserContract.java \
--- a/mobile/android/base/ui/PanZoomController.java
+++ b/mobile/android/base/ui/PanZoomController.java
@@ -130,64 +130,47 @@ public class PanZoomController
         PANNING_HOLD,   /* in panning, but not moving.
                          * similar to TOUCHING but after starting a pan */
         PANNING_HOLD_LOCKED, /* like PANNING_HOLD, but axis lock still in effect */
         PINCHING,       /* nth touch-start, where n > 1. this mode allows pan and zoom */
         ANIMATED_ZOOM   /* animated zoom to a new rect */
     }
 
     private final LayerController mController;
+    private final SubdocumentScrollHelper mSubscroller;
     private final Axis mX;
     private final Axis mY;
 
     /* The timer that handles flings or bounces. */
     private Timer mAnimationTimer;
     /* The runnable being scheduled by the animation timer. */
     private AnimationRunnable mAnimationRunnable;
     /* The zoom focus at the first zoom event (in page coordinates). */
     private PointF mLastZoomFocus;
     /* The time the last motion event took place. */
     private long mLastEventTime;
     /* Current state the pan/zoom UI is in. */
     private PanZoomState mState;
 
-    private boolean mOverridePanning;
-    private boolean mOverrideScrollAck;
-    private boolean mOverrideScrollPending;
-
     public PanZoomController(LayerController controller) {
         mController = controller;
-        mX = new AxisX();
-        mY = new AxisY();
+        mSubscroller = new SubdocumentScrollHelper(this);
+        mX = new AxisX(mSubscroller);
+        mY = new AxisY(mSubscroller);
+
         mState = PanZoomState.NOTHING;
 
         GeckoAppShell.registerGeckoEventListener("Browser:ZoomToRect", this);
         GeckoAppShell.registerGeckoEventListener("Browser:ZoomToPageWidth", this);
-        GeckoAppShell.registerGeckoEventListener("Panning:Override", this);
-        GeckoAppShell.registerGeckoEventListener("Panning:CancelOverride", this);
-        GeckoAppShell.registerGeckoEventListener("Gesture:ScrollAck", this);
     }
 
     public void handleMessage(String event, JSONObject message) {
         Log.i(LOGTAG, "Got message: " + event);
         try {
-            if ("Panning:Override".equals(event)) {
-                mOverridePanning = true;
-                mOverrideScrollAck = true;
-            } else if ("Panning:CancelOverride".equals(event)) {
-                mOverridePanning = false;
-            } else if ("Gesture:ScrollAck".equals(event)) {
-                mController.post(new Runnable() {
-                    public void run() {
-                        mOverrideScrollAck = true;
-                        if (mOverridePanning && mOverrideScrollPending)
-                            updatePosition();
-                    }
-                });
-            } else if (event.equals("Browser:ZoomToRect")) {
+            if (event.equals("Browser:ZoomToRect")) {
                 float scale = mController.getZoomFactor();
                 float x = (float)message.getDouble("x");
                 float y = (float)message.getDouble("y");
                 final RectF zoomRect = new RectF(x, y,
                                      x + (float)message.getDouble("w"),
                                      y + (float)message.getDouble("h"));
                 mController.post(new Runnable() {
                     public void run() {
@@ -257,17 +240,17 @@ public class PanZoomController
      * Panning/scrolling
      */
 
     private boolean onTouchStart(MotionEvent event) {
         Log.d(LOGTAG, "onTouchStart in state " + mState);
         // user is taking control of movement, so stop
         // any auto-movement we have going
         stopAnimationTimer();
-        mOverridePanning = false;
+        mSubscroller.cancel();
 
         switch (mState) {
         case ANIMATED_ZOOM:
             return false;
         case FLING:
         case NOTHING:
             startTouch(event.getX(0), event.getY(0), event.getEventTime());
             return false;
@@ -432,29 +415,27 @@ public class PanZoomController
                 // should never happen, but handle anyway for robustness
                 Log.e(LOGTAG, "Impossible case " + mState + " when stopped in track");
                 mState = PanZoomState.PANNING_HOLD_LOCKED;
             }
         }
 
         mX.startPan();
         mY.startPan();
-        mX.displace(mOverridePanning); mY.displace(mOverridePanning);
         updatePosition();
     }
 
     private void fling() {
-        mX.displace(mOverridePanning); mY.displace(mOverridePanning);
         updatePosition();
 
         stopAnimationTimer();
 
         boolean stopped = stopped();
-        mX.startFling(stopped, mOverridePanning);
-        mY.startFling(stopped, mOverridePanning);
+        mX.startFling(stopped);
+        mY.startFling(stopped);
 
         startAnimationTimer(new FlingRunnable());
     }
 
     /* Performs a bounce-back animation to the given viewport metrics. */
     private void bounce(ViewportMetrics metrics) {
         stopAnimationTimer();
 
@@ -507,43 +488,29 @@ public class PanZoomController
         float yvel = mY.getRealVelocity();
         return FloatMath.sqrt(xvel * xvel + yvel * yvel);
     }
 
     private boolean stopped() {
         return getVelocity() < STOPPED_THRESHOLD;
     }
 
-    private void updatePosition() {
-        if (mOverridePanning) {
-            if (!mOverrideScrollAck) {
-                mOverrideScrollPending = true;
-                return;
-            }
-
-            mOverrideScrollPending = false;
-            JSONObject json = new JSONObject();
+    PointF getDisplacement() {
+        return new PointF(mX.resetDisplacement(), mY.resetDisplacement());
+    }
 
-            try {
-                json.put("x", mX.displacement);
-                json.put("y", mY.displacement);
-            } catch (JSONException e) {
-                Log.e(LOGTAG, "Error forming Gesture:Scroll message: " + e);
-            }
-
-            GeckoEvent e = new GeckoEvent("Gesture:Scroll", json.toString());
-            GeckoAppShell.sendEventToGecko(e);
-            mOverrideScrollAck = false;
-        } else {
+    private void updatePosition() {
+        mX.displace();
+        mY.displace();
+        PointF displacement = getDisplacement();
+        if (! mSubscroller.scrollBy(displacement)) {
             synchronized (mController) {
-                mController.scrollBy(new PointF(mX.displacement, mY.displacement));
+                mController.scrollBy(displacement);
             }
         }
-
-        mX.displacement = mY.displacement = 0;
     }
 
     private abstract class AnimationRunnable implements Runnable {
         private boolean mAnimationTerminated;
 
         /* This should always run on the UI thread */
         public final void run() {
             /*
@@ -638,21 +605,20 @@ public class PanZoomController
                 finishAnimation();
                 return;
             }
 
             /* Advance flings, if necessary. */
             boolean flingingX = mX.advanceFling();
             boolean flingingY = mY.advanceFling();
 
-            boolean overscrolled = ((mX.overscrolled() || mY.overscrolled()) && !mOverridePanning);
+            boolean overscrolled = ((mX.overscrolled() || mY.overscrolled()) && !mSubscroller.scrolling());
 
             /* If we're still flinging in any direction, update the origin. */
             if (flingingX || flingingY) {
-                mX.displace(mOverridePanning); mY.displace(mOverridePanning);
                 updatePosition();
 
                 /*
                  * Check to see if we're still flinging with an appreciable velocity. The threshold is
                  * higher in the case of overscroll, so we bounce back eagerly when overscrolling but
                  * coast smoothly to a stop when not. In other words, require a greater velocity to
                  * maintain the fling once we enter overscroll.
                  */
@@ -662,18 +628,18 @@ public class PanZoomController
                     return;
                 }
 
                 mX.stopFling();
                 mY.stopFling();
             }
 
             /*
-             * Perform a bounce-back animation if overscrolled, unless panning is being overridden
-             * (which happens e.g. when the user is panning an iframe).
+             * Perform a bounce-back animation if overscrolled, unless panning is being
+             * handled by the subwindow scroller.
              */
             if (overscrolled) {
                 bounce();
             } else {
                 finishAnimation();
                 mState = PanZoomState.NOTHING;
             }
         }
@@ -698,32 +664,36 @@ public class PanZoomController
 
         private enum Overscroll {
             NONE,
             MINUS,      // Overscrolled in the negative direction
             PLUS,       // Overscrolled in the positive direction
             BOTH,       // Overscrolled in both directions (page is zoomed to smaller than screen)
         }
 
+        private final SubdocumentScrollHelper mSubscroller;
+
         private float firstTouchPos;            /* Position of the first touch event on the current drag. */
         private float touchPos;                 /* Position of the most recent touch event on the current drag. */
         private float lastTouchPos;             /* Position of the touch event before touchPos. */
         private float velocity;                  /* Velocity in this direction. */
         private boolean locked;                  /* Whether movement on this axis is locked. */
         private boolean disableSnap;             /* Whether overscroll snapping is disabled. */
 
         private FlingStates mFlingState;        /* The fling state we're in on this axis. */
 
         public abstract float getOrigin();
         protected abstract float getViewportLength();
         protected abstract float getPageLength();
 
         public float displacement;
 
-        public Axis() {}
+        public Axis(SubdocumentScrollHelper subscroller) {
+            mSubscroller = subscroller;
+        }
 
         private float getViewportEnd() { return getOrigin() + getViewportLength(); }
 
         public void startTouch(float pos) {
             velocity = 0.0f;
             locked = false;
             firstTouchPos = touchPos = lastTouchPos = pos;
         }
@@ -807,18 +777,18 @@ public class PanZoomController
         public float getRealVelocity() {
             return locked ? 0.0f : velocity;
         }
 
         void startPan() {
             mFlingState = FlingStates.PANNING;
         }
 
-        public void startFling(boolean stopped, boolean panningOverridden) {
-            disableSnap = panningOverridden;
+        public void startFling(boolean stopped) {
+            disableSnap = mSubscroller.scrolling();
 
             if (stopped) {
                 mFlingState = FlingStates.STOPPED;
             } else {
                 mFlingState = FlingStates.FLINGING;
             }
         }
 
@@ -849,25 +819,31 @@ public class PanZoomController
         }
 
         void stopFling() {
             velocity = 0.0f;
             mFlingState = FlingStates.STOPPED;
         }
 
         // Performs displacement of the viewport position according to the current velocity.
-        public void displace(boolean panningOverridden) {
-            if (!panningOverridden && (locked || !scrollable()))
+        public void displace() {
+            if (!mSubscroller.scrolling() && (locked || !scrollable()))
                 return;
 
             if (mFlingState == FlingStates.PANNING)
                 displacement += (lastTouchPos - touchPos) * getEdgeResistance();
             else
                 displacement += velocity;
         }
+
+        float resetDisplacement() {
+            float d = displacement;
+            displacement = 0.0f;
+            return d;
+        }
     }
 
     /* Returns the nearest viewport metrics with no overscroll visible. */
     private ViewportMetrics getValidViewportMetrics() {
         ViewportMetrics viewportMetrics = new ViewportMetrics(mController.getViewportMetrics());
         Log.d(LOGTAG, "generating valid viewport using " + viewportMetrics);
 
         /* First, we adjust the zoom factor so that we can make no overscrolled area visible. */
@@ -896,25 +872,27 @@ public class PanZoomController
         /* Now we pan to the right origin. */
         viewportMetrics.setViewport(viewportMetrics.getClampedViewport());
         Log.d(LOGTAG, "generated valid viewport as " + viewportMetrics);
 
         return viewportMetrics;
     }
 
     private class AxisX extends Axis {
+        AxisX(SubdocumentScrollHelper subscroller) { super(subscroller); }
         @Override
         public float getOrigin() { return mController.getOrigin().x; }
         @Override
         protected float getViewportLength() { return mController.getViewportSize().width; }
         @Override
         protected float getPageLength() { return mController.getPageSize().width; }
     }
 
     private class AxisY extends Axis {
+        AxisY(SubdocumentScrollHelper subscroller) { super(subscroller); }
         @Override
         public float getOrigin() { return mController.getOrigin().y; }
         @Override
         protected float getViewportLength() { return mController.getViewportSize().height; }
         @Override
         protected float getPageLength() { return mController.getPageSize().height; }
     }
 
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/ui/SubdocumentScrollHelper.java
@@ -0,0 +1,133 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Kartikaya Gupta <kgupta@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.ui;
+
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.GeckoEvent;
+import org.mozilla.gecko.GeckoEventListener;
+import org.json.JSONObject;
+import org.json.JSONException;
+import android.graphics.PointF;
+import android.os.Handler;
+import android.util.Log;
+
+class SubdocumentScrollHelper implements GeckoEventListener {
+    private static final String LOGTAG = "GeckoSubdocumentScrollHelper";
+
+    private static String MESSAGE_PANNING_OVERRIDE = "Panning:Override";
+    private static String MESSAGE_CANCEL_OVERRIDE = "Panning:CancelOverride";
+    private static String MESSAGE_SCROLL = "Gesture:Scroll";
+    private static String MESSAGE_SCROLL_ACK = "Gesture:ScrollAck";
+
+    private final PanZoomController mPanZoomController;
+    private final Handler mUiHandler;
+
+    private boolean mOverridePanning;
+    private boolean mOverrideScrollAck;
+    private boolean mOverrideScrollPending;
+
+    SubdocumentScrollHelper(PanZoomController controller) {
+        mPanZoomController = controller;
+        // mUiHandler will be bound to the UI thread since that's where this constructor runs
+        mUiHandler = new Handler();
+
+        GeckoAppShell.registerGeckoEventListener(MESSAGE_PANNING_OVERRIDE, this);
+        GeckoAppShell.registerGeckoEventListener(MESSAGE_CANCEL_OVERRIDE, this);
+        GeckoAppShell.registerGeckoEventListener(MESSAGE_SCROLL_ACK, this);
+    }
+
+    boolean scrollBy(PointF displacement) {
+        if (! mOverridePanning) {
+            return false;
+        }
+
+        if (! mOverrideScrollAck) {
+            mOverrideScrollPending = true;
+            return true;
+        }
+
+        mOverrideScrollAck = false;
+        mOverrideScrollPending = false;
+
+        JSONObject json = new JSONObject();
+        try {
+            json.put("x", displacement.x);
+            json.put("y", displacement.y);
+        } catch (JSONException e) {
+            Log.e(LOGTAG, "Error forming subwindow scroll message: ", e);
+        }
+        GeckoAppShell.sendEventToGecko(new GeckoEvent(MESSAGE_SCROLL, json.toString()));
+
+        return true;
+    }
+
+    void cancel() {
+        mOverridePanning = false;
+    }
+
+    boolean scrolling() {
+        return mOverridePanning;
+    }
+
+    // GeckoEventListener implementation
+
+    public void handleMessage(final String event, final JSONObject message) {
+        // this comes in on the gecko thread; hand off the handling to the UI thread
+        mUiHandler.post(new Runnable() {
+            public void run() {
+                Log.i(LOGTAG, "Got message: " + event);
+                try {
+                    if (MESSAGE_PANNING_OVERRIDE.equals(event)) {
+                        mOverridePanning = true;
+                        mOverrideScrollAck = true;
+                        mOverrideScrollPending = false;
+                    } else if (MESSAGE_CANCEL_OVERRIDE.equals(event)) {
+                        mOverridePanning = false;
+                    } else if (MESSAGE_SCROLL_ACK.equals(event)) {
+                        mOverrideScrollAck = true;
+                        if (mOverridePanning && mOverrideScrollPending) {
+                            scrollBy(mPanZoomController.getDisplacement());
+                        }
+                    }
+                } catch (Exception e) {
+                    Log.e(LOGTAG, "Exception handling message", e);
+                }
+            }
+        });
+    }
+}