| Index: content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
 | 
| diff --git a/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java b/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
 | 
| index be4e5f912ee2553c410dd10690b4449cace477c3..0d2ee7791ef1932e05788ca496ddf32fd34ec9ce 100644
 | 
| --- a/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
 | 
| +++ b/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
 | 
| @@ -53,6 +53,8 @@ import com.google.common.annotations.VisibleForTesting;
 | 
|  import org.chromium.base.CalledByNative;
 | 
|  import org.chromium.base.CommandLine;
 | 
|  import org.chromium.base.JNINamespace;
 | 
| +import org.chromium.base.ObserverList;
 | 
| +import org.chromium.base.ObserverList.RewindableIterator;
 | 
|  import org.chromium.base.TraceEvent;
 | 
|  import org.chromium.base.WeakContext;
 | 
|  import org.chromium.content.R;
 | 
| @@ -69,6 +71,7 @@ import org.chromium.content.browser.input.SelectPopupDialog;
 | 
|  import org.chromium.content.browser.input.SelectPopupItem;
 | 
|  import org.chromium.content.browser.input.SelectionHandleController;
 | 
|  import org.chromium.content.common.ContentSwitches;
 | 
| +import org.chromium.content_public.browser.GestureStateListener;
 | 
|  import org.chromium.content_public.browser.WebContents;
 | 
|  import org.chromium.ui.base.ViewAndroid;
 | 
|  import org.chromium.ui.base.ViewAndroidDelegate;
 | 
| @@ -182,45 +185,6 @@ public class ContentViewCore
 | 
|      }
 | 
|  
 | 
|      /**
 | 
| -     * An interface that allows the embedder to be notified of events and state changes related to
 | 
| -     * gesture processing.
 | 
| -     */
 | 
| -    public interface GestureStateListener {
 | 
| -        /**
 | 
| -         * Called when the pinch gesture starts.
 | 
| -         */
 | 
| -        void onPinchGestureStart();
 | 
| -
 | 
| -        /**
 | 
| -         * Called when the pinch gesture ends.
 | 
| -         */
 | 
| -        void onPinchGestureEnd();
 | 
| -
 | 
| -        /**
 | 
| -         * Called when the fling gesture is sent.
 | 
| -         */
 | 
| -        void onFlingStartGesture(int vx, int vy);
 | 
| -
 | 
| -        /**
 | 
| -         * Called when the fling cancel gesture is sent.
 | 
| -         */
 | 
| -        void onFlingCancelGesture();
 | 
| -
 | 
| -        /**
 | 
| -         * Called when a fling event was not handled by the renderer.
 | 
| -         */
 | 
| -        void onUnhandledFlingStartEvent();
 | 
| -
 | 
| -        /**
 | 
| -         * Called to indicate that a scroll update gesture had been consumed by the page.
 | 
| -         * This callback is called whenever any layer is scrolled (like a frame or div). It is
 | 
| -         * not called when a JS touch handler consumes the event (preventDefault), it is not called
 | 
| -         * for JS-initiated scrolling.
 | 
| -         */
 | 
| -        void onScrollUpdateGestureConsumed();
 | 
| -    }
 | 
| -
 | 
| -    /**
 | 
|       * An interface for controlling visibility and state of embedder-provided zoom controls.
 | 
|       */
 | 
|      public interface ZoomControlsDelegate {
 | 
| @@ -371,7 +335,8 @@ public class ContentViewCore
 | 
|      private boolean mInForeground = false;
 | 
|  
 | 
|      private ContentViewGestureHandler mContentViewGestureHandler;
 | 
| -    private GestureStateListener mGestureStateListener;
 | 
| +    private final ObserverList<GestureStateListener> mGestureStateListeners;
 | 
| +    private final RewindableIterator<GestureStateListener> mGestureStateListenersIterator;
 | 
|      private ZoomManager mZoomManager;
 | 
|      private ZoomControlsDelegate mZoomControlsDelegate;
 | 
|  
 | 
| @@ -500,6 +465,8 @@ public class ContentViewCore
 | 
|          mInsertionHandlePoint = mRenderCoordinates.createNormalizedPoint();
 | 
|          mAccessibilityManager = (AccessibilityManager)
 | 
|                  getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
 | 
| +        mGestureStateListeners = new ObserverList<GestureStateListener>();
 | 
| +        mGestureStateListenersIterator = mGestureStateListeners.rewindableIterator();
 | 
|      }
 | 
|  
 | 
|      /**
 | 
| @@ -869,6 +836,7 @@ public class ContentViewCore
 | 
|          mJavaScriptInterfaces.clear();
 | 
|          mRetainedJavaScriptObjects.clear();
 | 
|          unregisterAccessibilityContentObserver();
 | 
| +        mGestureStateListeners.clear();
 | 
|      }
 | 
|  
 | 
|      private void unregisterAccessibilityContentObserver() {
 | 
| @@ -1293,35 +1261,38 @@ public class ContentViewCore
 | 
|      @SuppressWarnings("unused")
 | 
|      @CalledByNative
 | 
|      private void onFlingStartEventAck(int ackResult) {
 | 
| -        if (ackResult == ContentViewGestureHandler.INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS
 | 
| -                && mGestureStateListener != null) {
 | 
| -            mGestureStateListener.onUnhandledFlingStartEvent();
 | 
| +        if (ackResult == ContentViewGestureHandler.INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS) {
 | 
| +            for (mGestureStateListenersIterator.rewind();
 | 
| +                    mGestureStateListenersIterator.hasNext();) {
 | 
| +                mGestureStateListenersIterator.next().onUnhandledFlingStartEvent();
 | 
| +            }
 | 
|          }
 | 
|          if (ackResult != ContentViewGestureHandler.INPUT_EVENT_ACK_STATE_CONSUMED) {
 | 
|              // No fling happened for the fling start event.
 | 
|              // Cancel the fling status set when sending GestureFlingStart.
 | 
| -            getContentViewClient().onFlingStopped();
 | 
| +            updateGestureStateListener(ContentViewGestureHandler.GESTURE_FLING_END, null);
 | 
|          }
 | 
|      }
 | 
|  
 | 
|      @SuppressWarnings("unused")
 | 
|      @CalledByNative
 | 
|      private void onScrollBeginEventAck() {
 | 
| -        getContentViewClient().onScrollBeginEvent();
 | 
| +        updateGestureStateListener(ContentViewGestureHandler.GESTURE_SCROLL_START, null);
 | 
|      }
 | 
|  
 | 
|      @SuppressWarnings("unused")
 | 
|      @CalledByNative
 | 
|      private void onScrollUpdateGestureConsumed() {
 | 
| -        if (mGestureStateListener != null) {
 | 
| -            mGestureStateListener.onScrollUpdateGestureConsumed();
 | 
| +        for (mGestureStateListenersIterator.rewind();
 | 
| +                mGestureStateListenersIterator.hasNext();) {
 | 
| +            mGestureStateListenersIterator.next().onScrollUpdateGestureConsumed();
 | 
|          }
 | 
|      }
 | 
|  
 | 
|      @SuppressWarnings("unused")
 | 
|      @CalledByNative
 | 
|      private void onScrollEndEventAck() {
 | 
| -        getContentViewClient().onScrollEndEvent();
 | 
| +        updateGestureStateListener(ContentViewGestureHandler.GESTURE_SCROLL_END, null);
 | 
|      }
 | 
|  
 | 
|      private void reportActionAfterDoubleTapUMA(int type) {
 | 
| @@ -1379,7 +1350,6 @@ public class ContentViewCore
 | 
|                  nativeScrollEnd(mNativeContentViewCore, timeMs);
 | 
|                  return true;
 | 
|              case ContentViewGestureHandler.GESTURE_FLING_START:
 | 
| -                mContentViewClient.onFlingStarted();
 | 
|                  nativeFlingStart(mNativeContentViewCore, timeMs, x, y,
 | 
|                          b.getInt(ContentViewGestureHandler.VELOCITY_X, 0),
 | 
|                          b.getInt(ContentViewGestureHandler.VELOCITY_Y, 0));
 | 
| @@ -1427,30 +1397,61 @@ public class ContentViewCore
 | 
|                  UMAActionAfterDoubleTap.COUNT);
 | 
|      }
 | 
|  
 | 
| -    public void setGestureStateListener(GestureStateListener pinchGestureStateListener) {
 | 
| -        mGestureStateListener = pinchGestureStateListener;
 | 
| +    /**
 | 
| +     * Add a listener that gets alerted on gesture state changes.
 | 
| +     * @param listener Listener to add.
 | 
| +     */
 | 
| +    public void addGestureStateListener(GestureStateListener listener) {
 | 
| +        mGestureStateListeners.addObserver(listener);
 | 
|      }
 | 
|  
 | 
| -    void updateGestureStateListener(int gestureType, Bundle b) {
 | 
| -        if (mGestureStateListener == null) return;
 | 
| +    /**
 | 
| +     * Removes a listener that was added to watch for gesture state changes.
 | 
| +     * @param listener Listener to remove.
 | 
| +     */
 | 
| +    public void removeGestureStateListener(GestureStateListener listener) {
 | 
| +        mGestureStateListeners.removeObserver(listener);
 | 
| +    }
 | 
|  
 | 
| -        switch (gestureType) {
 | 
| -            case ContentViewGestureHandler.GESTURE_PINCH_BEGIN:
 | 
| -                mGestureStateListener.onPinchGestureStart();
 | 
| -                break;
 | 
| -            case ContentViewGestureHandler.GESTURE_PINCH_END:
 | 
| -                mGestureStateListener.onPinchGestureEnd();
 | 
| -                break;
 | 
| -            case ContentViewGestureHandler.GESTURE_FLING_START:
 | 
| -                mGestureStateListener.onFlingStartGesture(
 | 
| -                        b.getInt(ContentViewGestureHandler.VELOCITY_X, 0),
 | 
| -                        b.getInt(ContentViewGestureHandler.VELOCITY_Y, 0));
 | 
| -                break;
 | 
| -            case ContentViewGestureHandler.GESTURE_FLING_CANCEL:
 | 
| -                mGestureStateListener.onFlingCancelGesture();
 | 
| -                break;
 | 
| -            default:
 | 
| -                break;
 | 
| +    void updateGestureStateListener(int gestureType, Bundle b) {
 | 
| +        for (mGestureStateListenersIterator.rewind();
 | 
| +                mGestureStateListenersIterator.hasNext();) {
 | 
| +            GestureStateListener listener = mGestureStateListenersIterator.next();
 | 
| +            switch (gestureType) {
 | 
| +                case ContentViewGestureHandler.GESTURE_PINCH_BEGIN:
 | 
| +                    listener.onPinchGestureStart();
 | 
| +                    break;
 | 
| +                case ContentViewGestureHandler.GESTURE_PINCH_END:
 | 
| +                    listener.onPinchGestureEnd();
 | 
| +                    break;
 | 
| +                case ContentViewGestureHandler.GESTURE_FLING_START:
 | 
| +                    listener.onFlingStartGesture(
 | 
| +                            b.getInt(ContentViewGestureHandler.VELOCITY_X, 0),
 | 
| +                            b.getInt(ContentViewGestureHandler.VELOCITY_Y, 0),
 | 
| +                            computeVerticalScrollOffset(),
 | 
| +                            computeVerticalScrollExtent());
 | 
| +                    break;
 | 
| +                case ContentViewGestureHandler.GESTURE_FLING_END:
 | 
| +                    listener.onFlingEndGesture(
 | 
| +                            computeVerticalScrollOffset(),
 | 
| +                            computeVerticalScrollExtent());
 | 
| +                    break;
 | 
| +                case ContentViewGestureHandler.GESTURE_FLING_CANCEL:
 | 
| +                    listener.onFlingCancelGesture();
 | 
| +                    break;
 | 
| +                case ContentViewGestureHandler.GESTURE_SCROLL_START:
 | 
| +                    listener.onScrollStarted(
 | 
| +                            computeVerticalScrollOffset(),
 | 
| +                            computeVerticalScrollExtent());
 | 
| +                    break;
 | 
| +                case ContentViewGestureHandler.GESTURE_SCROLL_END:
 | 
| +                    listener.onScrollEnded(
 | 
| +                            computeVerticalScrollOffset(),
 | 
| +                            computeVerticalScrollExtent());
 | 
| +                    break;
 | 
| +                default:
 | 
| +                    break;
 | 
| +            }
 | 
|          }
 | 
|      }
 | 
|  
 | 
| @@ -2444,7 +2445,12 @@ public class ContentViewCore
 | 
|          onRenderCoordinatesUpdated();
 | 
|  
 | 
|          if (scrollChanged || contentOffsetChanged) {
 | 
| -            getContentViewClient().onScrollOrViewportChanged();
 | 
| +            for (mGestureStateListenersIterator.rewind();
 | 
| +                    mGestureStateListenersIterator.hasNext();) {
 | 
| +                mGestureStateListenersIterator.next().onScrollOffsetOrExtentChanged(
 | 
| +                        computeVerticalScrollOffset(),
 | 
| +                        computeVerticalScrollExtent());
 | 
| +            }
 | 
|          }
 | 
|  
 | 
|          if (needTemporarilyHideHandles) temporarilyHideTextHandles();
 | 
| @@ -3215,7 +3221,7 @@ public class ContentViewCore
 | 
|  
 | 
|      @CalledByNative
 | 
|      private void onNativeFlingStopped() {
 | 
| -        getContentViewClient().onFlingStopped();
 | 
| +        updateGestureStateListener(ContentViewGestureHandler.GESTURE_FLING_END, null);
 | 
|      }
 | 
|  
 | 
|      private native WebContents nativeGetWebContentsAndroid(long nativeContentViewCoreImpl);
 | 
| 
 |