Bug 960146. r=kats, r=wesj, a=lsblakk
authorKartikaya Gupta <kgupta@mozilla.com>
Thu, 20 Feb 2014 09:36:48 -0500
changeset 182957 7167e7f77827d47d80a09fd7587fe913d92a99f5
parent 182956 0a3fdcf8b7ef8523eacfbdf7392fec1f2cd73268
child 182958 647c4831e7d16adebbf183016200c4655a1b01b1
push id3343
push userffxbld
push dateMon, 17 Mar 2014 21:55:32 +0000
treeherdermozilla-beta@2f7d3415f79f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats, wesj, lsblakk
bugs960146
milestone29.0a2
Bug 960146. r=kats, r=wesj, a=lsblakk
mobile/android/base/gfx/GeckoLayerClient.java
mobile/android/base/gfx/JavaPanZoomController.java
mobile/android/base/gfx/PanZoomTarget.java
mobile/android/base/gfx/TouchEventHandler.java
--- a/mobile/android/base/gfx/GeckoLayerClient.java
+++ b/mobile/android/base/gfx/GeckoLayerClient.java
@@ -882,17 +882,17 @@ public class GeckoLayerClient implements
     /** Implementation of PanZoomTarget
      * Notification that a subdocument has been scrolled by a certain amount.
      * This is used here to make sure that the margins are still accessible
      * during subdocument scrolling.
      *
      * You must hold the monitor while calling this.
      */
     @Override
-    public void onSubdocumentScrollBy(float dx, float dy) {
+    public void scrollMarginsBy(float dx, float dy) {
         ImmutableViewportMetrics newMarginsMetrics =
             mMarginsAnimator.scrollBy(mViewportMetrics, dx, dy);
         mViewportMetrics = mViewportMetrics.setMarginsFrom(newMarginsMetrics);
         viewportMetricsChanged(true);
     }
 
     /** Implementation of PanZoomTarget */
     @Override
--- a/mobile/android/base/gfx/JavaPanZoomController.java
+++ b/mobile/android/base/gfx/JavaPanZoomController.java
@@ -123,16 +123,18 @@ class JavaPanZoomController
     /* The per-frame zoom delta for the currently-running AUTONAV animation. */
     private float mAutonavZoomDelta;
     /* The user selected panning mode */
     private AxisLockMode mMode;
     /* A medium-length tap/press is happening */
     private boolean mMediumPress;
     /* Used to change the scrollY direction */
     private boolean mNegateWheelScrollY;
+    /* Whether the current event has been default-prevented. */
+    private boolean mDefaultPrevented;
 
     // Handler to be notified when overscroll occurs
     private Overscroll mOverscroll;
 
     public JavaPanZoomController(PanZoomTarget target, View view, EventDispatcher eventDispatcher) {
         mTarget = target;
         mSubscroller = new SubdocumentScrollHelper(eventDispatcher);
         mX = new AxisX(mSubscroller);
@@ -338,17 +340,19 @@ class JavaPanZoomController
     }
 
     /** This function MUST be called on the UI thread */
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         return mTouchEventHandler.handleEvent(event);
     }
 
-    boolean handleEvent(MotionEvent event) {
+    boolean handleEvent(MotionEvent event, boolean defaultPrevented) {
+        mDefaultPrevented = defaultPrevented;
+
         switch (event.getAction() & MotionEvent.ACTION_MASK) {
         case MotionEvent.ACTION_DOWN:   return handleTouchStart(event);
         case MotionEvent.ACTION_MOVE:   return handleTouchMove(event);
         case MotionEvent.ACTION_UP:     return handleTouchEnd(event);
         case MotionEvent.ACTION_CANCEL: return handleTouchCancel(event);
         }
         return false;
     }
@@ -396,27 +400,16 @@ class JavaPanZoomController
         if (waitingForTouchListeners && (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
             // this is the first touch point going down, so we enter the pending state
             // seting the state will kill any animations in progress, possibly leaving
             // the page in overscroll
             setState(PanZoomState.WAITING_LISTENERS);
         }
     }
 
-    /** This function must be called on the UI thread. */
-    public void preventedTouchFinished() {
-        checkMainThread();
-        if (mState == PanZoomState.WAITING_LISTENERS) {
-            // if we enter here, we just finished a block of events whose default actions
-            // were prevented by touch listeners. Now there are no touch points left, so
-            // we need to reset our state and re-bounce because we might be in overscroll
-            bounce();
-        }
-    }
-
     /** This must be called on the UI thread. */
     @Override
     public void pageRectUpdated() {
         if (mState == PanZoomState.NOTHING) {
             synchronized (mTarget.getLock()) {
                 ImmutableViewportMetrics validated = getValidViewportMetrics();
                 if (!getMetrics().fuzzyEquals(validated)) {
                     // page size changed such that we are now in overscroll. snap to the
@@ -519,26 +512,28 @@ class JavaPanZoomController
     }
 
     private boolean handleTouchEnd(MotionEvent event) {
 
         switch (mState) {
         case FLING:
         case AUTONAV:
         case BOUNCE:
-        case WAITING_LISTENERS:
-            // should never happen
-            Log.e(LOGTAG, "Received impossible touch end while in " + mState);
-            // fall through
         case ANIMATED_ZOOM:
         case NOTHING:
             // may happen if user double-taps and drags without lifting after the
             // second tap. ignore if this happens.
             return false;
 
+        case WAITING_LISTENERS:
+            if (!mDefaultPrevented) {
+              // should never happen
+              Log.e(LOGTAG, "Received impossible touch end while in " + mState);
+            }
+            // fall through
         case TOUCHING:
             // the switch into TOUCHING might have happened while the page was
             // snapping back after overscroll. we need to finish the snap if that
             // was the case
             bounce();
             return false;
 
         case PANNING:
@@ -557,26 +552,16 @@ class JavaPanZoomController
         }
         Log.e(LOGTAG, "Unhandled case " + mState + " in handleTouchEnd");
         return false;
     }
 
     private boolean handleTouchCancel(MotionEvent event) {
         cancelTouch();
 
-        if (mState == PanZoomState.WAITING_LISTENERS) {
-            // we might get a cancel event from the TouchEventHandler while in the
-            // WAITING_LISTENERS state if the touch listeners prevent-default the
-            // block of events. at this point being in WAITING_LISTENERS is equivalent
-            // to being in NOTHING with the exception of possibly being in overscroll.
-            // so here we don't want to do anything right now; the overscroll will be
-            // corrected in preventedTouchFinished().
-            return false;
-        }
-
         // ensure we snap back if we're overscrolled
         bounce();
         return false;
     }
 
     private boolean handlePointerScroll(MotionEvent event) {
         if (mState == PanZoomState.NOTHING || mState == PanZoomState.FLING) {
             float scrollX = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
@@ -822,19 +807,19 @@ class JavaPanZoomController
 
     private void updatePosition() {
         mX.displace();
         mY.displace();
         PointF displacement = resetDisplacement();
         if (FloatUtils.fuzzyEquals(displacement.x, 0.0f) && FloatUtils.fuzzyEquals(displacement.y, 0.0f)) {
             return;
         }
-        if (mSubscroller.scrollBy(displacement)) {
+        if (mDefaultPrevented || mSubscroller.scrollBy(displacement)) {
             synchronized (mTarget.getLock()) {
-                mTarget.onSubdocumentScrollBy(displacement.x, displacement.y);
+                mTarget.scrollMarginsBy(displacement.x, displacement.y);
             }
         } else {
             synchronized (mTarget.getLock()) {
                 scrollBy(displacement.x, displacement.y);
             }
         }
     }
 
--- a/mobile/android/base/gfx/PanZoomTarget.java
+++ b/mobile/android/base/gfx/PanZoomTarget.java
@@ -14,17 +14,17 @@ public interface PanZoomTarget {
     public ImmutableViewportMetrics getViewportMetrics();
     public ZoomConstraints getZoomConstraints();
     public boolean isFullScreen();
     public RectF getMaxMargins();
 
     public void setAnimationTarget(ImmutableViewportMetrics viewport);
     public void setViewportMetrics(ImmutableViewportMetrics viewport);
     public void scrollBy(float dx, float dy);
-    public void onSubdocumentScrollBy(float dx, float dy);
+    public void scrollMarginsBy(float dx, float dy);
     public void panZoomStopped();
     /** This triggers an (asynchronous) viewport update/redraw. */
     public void forceRedraw(DisplayPortMetrics displayPort);
 
     public boolean post(Runnable action);
     public boolean postDelayed(Runnable action, long delayMillis);
     public void postRenderTask(RenderTask task);
     public void removeRenderTask(RenderTask task);
--- a/mobile/android/base/gfx/TouchEventHandler.java
+++ b/mobile/android/base/gfx/TouchEventHandler.java
@@ -69,21 +69,21 @@ final class TouchEventHandler implements
     // per-tab and is updated when we switch tabs).
     private boolean mWaitForTouchListeners;
 
     // true if we should hold incoming events in our queue. this is re-set for every
     // block of events, this is cleared once we find out if the block has been
     // default-prevented or not (or we time out waiting for that).
     private boolean mHoldInQueue;
 
-    // true if we should dispatch incoming events to the gesture detector and the pan/zoom
-    // controller. if this is false, then the current block of events has been
-    // default-prevented, and we should not dispatch these events (although we'll still send
-    // them to gecko listeners).
-    private boolean mDispatchEvents;
+    // false if the current event block has been default-prevented. In this case,
+    // we still pass the event to both Gecko and the pan/zoom controller, but the
+    // latter will not use it to scroll content. It may still use the events for
+    // other things, such as making the dynamic toolbar visible.
+    private boolean mAllowDefaultAction;
 
     // this next variable requires some explanation. strap yourself in.
     //
     // for each block of events, we do two things: (1) send the events to gecko and expect
     // exactly one default-prevented notification in return, and (2) kick off a delayed
     // ListenerTimeoutProcessor that triggers in case we don't hear from the listener in
     // a timely fashion.
     // since events are constantly coming in, we need to be able to handle more than one
@@ -123,37 +123,37 @@ final class TouchEventHandler implements
     TouchEventHandler(Context context, View view, JavaPanZoomController panZoomController) {
         mView = view;
 
         mEventQueue = new LinkedList<MotionEvent>();
         mPanZoomController = panZoomController;
         mGestureDetector = new GestureDetector(context, mPanZoomController);
         mScaleGestureDetector = new SimpleScaleGestureDetector(mPanZoomController);
         mListenerTimeoutProcessor = new ListenerTimeoutProcessor();
-        mDispatchEvents = true;
+        mAllowDefaultAction = true;
 
         mGestureDetector.setOnDoubleTapListener(mPanZoomController);
 
         Tabs.registerOnTabsChangedListener(this);
     }
 
     public void destroy() {
         Tabs.unregisterOnTabsChangedListener(this);
     }
 
     /* This function MUST be called on the UI thread */
     public boolean handleEvent(MotionEvent event) {
         if (isDownEvent(event)) {
             // this is the start of a new block of events! whee!
             mHoldInQueue = mWaitForTouchListeners;
 
-            // Set mDispatchEvents to true so that we are guaranteed to either queue these
-            // events or dispatch them. The only time we should not do either is once we've
-            // heard back from content to preventDefault this block.
-            mDispatchEvents = true;
+            // Set mAllowDefaultAction to true so that in the event we dispatch events, the
+            // PanZoomController doesn't treat them as if they've been prevent-defaulted
+            // when they haven't.
+            mAllowDefaultAction = true;
             if (mHoldInQueue) {
                 // if the new block we are starting is the current block (i.e. there are no
                 // other blocks waiting in the queue, then we should let the pan/zoom controller
                 // know we are waiting for the touch listeners to run
                 if (mEventQueue.isEmpty()) {
                     mPanZoomController.startingNewEventBlock(event, true);
                 }
             } else {
@@ -165,27 +165,22 @@ final class TouchEventHandler implements
                 mPanZoomController.startingNewEventBlock(event, false);
             }
 
             // set the timeout so that we dispatch these events and update mProcessingBalance
             // if we don't get a default-prevented notification
             mView.postDelayed(mListenerTimeoutProcessor, EVENT_LISTENER_TIMEOUT);
         }
 
-        // if we need to hold the events, add it to the queue. if we need to dispatch
-        // it directly, do that. it is possible that both mHoldInQueue and mDispatchEvents
-        // are false, in which case we are processing a block of events that we know
-        // has been default-prevented. in that case we don't keep the events as we don't
-        // need them (but we still pass them to the gecko listener).
+        // if we need to hold the events, add it to the queue, otherwise dispatch
+        // it directly.
         if (mHoldInQueue) {
             mEventQueue.add(MotionEvent.obtain(event));
-        } else if (mDispatchEvents) {
-            dispatchEvent(event);
-        } else if (touchFinished(event)) {
-            mPanZoomController.preventedTouchFinished();
+        } else {
+            dispatchEvent(event, mAllowDefaultAction);
         }
 
         return false;
     }
 
     /**
      * This function is how gecko sends us a default-prevented notification. It is called
      * once gecko knows definitively whether the block of events has had preventDefault
@@ -219,71 +214,58 @@ final class TouchEventHandler implements
     private boolean touchFinished(MotionEvent event) {
         int action = (event.getAction() & MotionEvent.ACTION_MASK);
         return (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL);
     }
 
     /**
      * Dispatch the event to the gesture detectors and the pan/zoom controller.
      */
-    private void dispatchEvent(MotionEvent event) {
+    private void dispatchEvent(MotionEvent event, boolean allowDefaultAction) {
         if (mGestureDetector.onTouchEvent(event)) {
             return;
         }
         mScaleGestureDetector.onTouchEvent(event);
         if (mScaleGestureDetector.isInProgress()) {
             return;
         }
-        mPanZoomController.handleEvent(event);
+        mPanZoomController.handleEvent(event, !allowDefaultAction);
     }
 
     /**
      * Process the block of events at the head of the queue now that we know
      * whether it has been default-prevented or not.
      */
     private void processEventBlock(boolean allowDefaultAction) {
-        if (!allowDefaultAction) {
-            // if the block has been default-prevented, cancel whatever stuff we had in
-            // progress in the gesture detector and pan zoom controller
-            long now = SystemClock.uptimeMillis();
-            dispatchEvent(MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0));
-        }
-
         if (mEventQueue.isEmpty()) {
             Log.e(LOGTAG, "Unexpected empty event queue in processEventBlock!", new Exception());
             return;
         }
 
         // the odd loop condition is because the first event in the queue will
         // always be a DOWN or POINTER_DOWN event, and we want to process all
         // the events in the queue starting at that one, up to but not including
         // the next DOWN or POINTER_DOWN event.
 
         MotionEvent event = mEventQueue.poll();
         while (true) {
             // event being null here is valid and represents a block of events
             // that has already been dispatched.
 
             if (event != null) {
-                // for each event we process, only dispatch it if the block hasn't been
-                // default-prevented.
-                if (allowDefaultAction) {
-                    dispatchEvent(event);
-                } else if (touchFinished(event)) {
-                    mPanZoomController.preventedTouchFinished();
-                }
+                dispatchEvent(event, allowDefaultAction);
             }
             if (mEventQueue.isEmpty()) {
                 // we have processed the backlog of events, and are all caught up.
                 // now we can set clear the hold flag and set the dispatch flag so
                 // that the handleEvent() function can do the right thing for all
                 // remaining events in this block (which is still ongoing) without
                 // having to put them in the queue.
                 mHoldInQueue = false;
-                mDispatchEvents = allowDefaultAction;
+                mAllowDefaultAction = allowDefaultAction;
                 break;
             }
             event = mEventQueue.peek();
             if (event == null || isDownEvent(event)) {
                 // we have finished processing the block we were interested in.
                 // now we wait for the next call to processEventBlock
                 if (event != null) {
                     mPanZoomController.startingNewEventBlock(event, true);