author Kartikaya Gupta <>
Tue, 21 Jan 2014 16:24:18 -0500
changeset 175994 f8e37f7c4034536518d7c76f9f60c52702de11e3
parent 175988 bfdb71aa8953d6776487921fb86dd03e4f146ed2
child 175995 bb7ca58bda30e57661b737bf85b8c5033ee0213c
permissions -rw-r--r--
Bug 947337 - Small refactoring to reduce an unnecessary codepath. r=botond a=1.3+

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at */

#ifndef mozilla_layers_AsyncPanZoomController_h
#define mozilla_layers_AsyncPanZoomController_h

#include "GeckoContentController.h"
#include "mozilla/Attributes.h"
#include "mozilla/EventForwards.h"
#include "mozilla/Monitor.h"
#include "mozilla/ReentrantMonitor.h"
#include "mozilla/RefPtr.h"
#include "mozilla/Atomics.h"
#include "InputData.h"
#include "Axis.h"
#include "TaskThrottler.h"
#include "gfx3DMatrix.h"

#include "base/message_loop.h"

namespace mozilla {
namespace layers {

struct ScrollableLayerGuid;
class CompositorParent;
class GestureEventListener;
class ContainerLayer;
class ViewTransform;
class APZCTreeManager;
class AsyncPanZoomAnimation;

 * Controller for all panning and zooming logic. Any time a user input is
 * detected and it must be processed in some way to affect what the user sees,
 * it goes through here. Listens for any input event from InputData and can
 * optionally handle WidgetGUIEvent-derived touch events, but this must be done
 * on the main thread. Note that this class completely cross-platform.
 * Input events originate on the UI thread of the platform that this runs on,
 * and are then sent to this class. This class processes the event in some way;
 * for example, a touch move will usually lead to a panning of content (though
 * of course there are exceptions, such as if content preventDefaults the event,
 * or if the target frame is not scrollable). The compositor interacts with this
 * class by locking it and querying it for the current transform matrix based on
 * the panning and zooming logic that was invoked on the UI thread.
 * Currently, each outer DOM window (i.e. a website in a tab, but not any
 * subframes) has its own AsyncPanZoomController. In the future, to support
 * asynchronously scrolled subframes, we want to have one AsyncPanZoomController
 * per frame.
class AsyncPanZoomController {

  typedef mozilla::MonitorAutoLock MonitorAutoLock;

  enum GestureBehavior {
    // The platform code is responsible for forwarding gesture events here. We
    // will not attempt to generate gesture events from MultiTouchInputs.
    // An instance of GestureEventListener is used to detect gestures. This is
    // handled completely internally within this class.

   * Constant describing the tolerance in distance we use, multiplied by the
   * device DPI, before we start panning the screen. This is to prevent us from
   * accidentally processing taps as touch moves, and from very short/accidental
   * touches moving the screen.
  static float GetTouchStartTolerance();

  AsyncPanZoomController(uint64_t aLayersId,
                         APZCTreeManager* aTreeManager,
                         GeckoContentController* aController,
                         GestureBehavior aGestures = DEFAULT_GESTURES);

  // --------------------------------------------------------------------------
  // These methods must only be called on the gecko thread.

   * Read the various prefs and do any global initialization for all APZC instances.
   * This must be run on the gecko thread before any APZC instances are actually
   * used for anything meaningful.
  static void InitializeGlobalState();

  // --------------------------------------------------------------------------
  // These methods must only be called on the controller/UI thread.

   * General handler for incoming input events. Manipulates the frame metrics
   * based on what type of input it is. For example, a PinchGestureEvent will
   * cause scaling. This should only be called externally to this class.
   * HandleInputEvent() should be used internally.
  nsEventStatus ReceiveInputEvent(const InputData& aEvent);

   * Kicks an animation to zoom to a rect. This may be either a zoom out or zoom
   * in. The actual animation is done on the compositor thread after being set
   * up.
  void ZoomToRect(CSSRect aRect);

   * If we have touch listeners, this should always be called when we know
   * definitively whether or not content has preventDefaulted any touch events
   * that have come in. If |aPreventDefault| is true, any touch events in the
   * queue will be discarded.
  void ContentReceivedTouch(bool aPreventDefault);

   * Updates any zoom constraints contained in the <meta name="viewport"> tag.
  void UpdateZoomConstraints(const ZoomConstraints& aConstraints);

   * Return the zoom constraints last set for this APZC (in the constructor
   * or in UpdateZoomConstraints()).
  ZoomConstraints GetZoomConstraints() const;

   * Schedules a runnable to run on the controller/UI thread at some time
   * in the future.
  void PostDelayedTask(Task* aTask, int aDelayMs);

  // --------------------------------------------------------------------------
  // These methods must only be called on the compositor thread.

  bool UpdateAnimation(const TimeStamp& aSampleTime);

   * The compositor calls this when it's about to draw pannable/zoomable content
   * and is setting up transforms for compositing the layer tree. This is not
   * idempotent. For example, a fling transform can be applied each time this is
   * called (though not necessarily). |aSampleTime| is the time that this is
   * sampled at; this is used for interpolating animations. Calling this sets a
   * new transform in |aNewTransform| which should be multiplied to the transform
   * in the shadow layer corresponding to this APZC.
   * Return value indicates whether or not any currently running animation
   * should continue. That is, if true, the compositor should schedule another
   * composite.
  bool SampleContentTransformForFrame(const TimeStamp& aSampleTime,
                                      ViewTransform* aNewTransform,
                                      ScreenPoint& aScrollOffset);

   * A shadow layer update has arrived. |aLayerMetrics| is the new FrameMetrics
   * for the container layer corresponding to this APZC.
   * |aIsFirstPaint| is a flag passed from the shadow
   * layers code indicating that the frame metrics being sent with this call are
   * the initial metrics and the initial paint of the frame has just happened.
  void NotifyLayersUpdated(const FrameMetrics& aLayerMetrics, bool aIsFirstPaint);

   * The platform implementation must set the compositor parent so that we can
   * request composites.
  void SetCompositorParent(CompositorParent* aCompositorParent);

  // --------------------------------------------------------------------------
  // These methods can be called from any thread.

   * Shut down the controller/UI thread state and prepare to be
   * deleted (which may happen from any thread).
  void Destroy();

   * Returns true if Destroy() has already been called on this APZC instance.
  bool IsDestroyed();

   * Returns the incremental transformation corresponding to the async pan/zoom
   * in progress. That is, when this transform is multiplied with the layer's
   * existing transform, it will make the layer appear with the desired pan/zoom
   * amount.
  ViewTransform GetCurrentAsyncTransform();

   * Returns the part of the async transform that will remain once Gecko does a
   * repaint at the desired metrics. That is, in the steady state:
   * gfx3DMatrix(GetCurrentAsyncTransform()) === GetNontransientAsyncTransform()
  gfx3DMatrix GetNontransientAsyncTransform();

   * Recalculates the displayport. Ideally, this should paint an area bigger
   * than the composite-to dimensions so that when you scroll down, you don't
   * checkerboard immediately. This includes a bunch of logic, including
   * algorithms to bias painting in the direction of the velocity.
  static const CSSRect CalculatePendingDisplayPort(
    const FrameMetrics& aFrameMetrics,
    const ScreenPoint& aVelocity,
    const gfx::Point& aAcceleration,
    double aEstimatedPaintDuration);

   * Send an mozbrowserasyncscroll event.
   * *** The monitor must be held while calling this.
  void SendAsyncScrollEvent();

   * Handler for events which should not be intercepted by the touch listener.
   * Does the work for ReceiveInputEvent().
  nsEventStatus HandleInputEvent(const InputData& aEvent);

   * Populates the provided object with the scrollable guid of this apzc.
  void GetGuid(ScrollableLayerGuid* aGuidOut);

   * Returns true if this APZC instance is for the layer identified by the guid.
  bool Matches(const ScrollableLayerGuid& aGuid);

   * Sync panning and zooming animation using a fixed frame time.
   * This will ensure that we animate the APZC correctly with other external
   * animations to the same timestamp.
  static void SetFrameTime(const TimeStamp& aMilliseconds);

  void StartAnimation(AsyncPanZoomAnimation* aAnimation);

   * Cancels any currently running animation. Note that all this does is set the
   * state of the AsyncPanZoomController back to NOTHING, but it is the
   * animation's responsibility to check this before advancing.
  void CancelAnimation();

   * Attempt to scroll in response to a touch-move from |aStartPoint| to
   * |aEndPoint|, which are in our (transformed) screen coordinates.
   * Due to overscroll handling, there may not actually have been a touch-move
   * at these points, but this function will scroll as if there had been.
   * If this attempt causes overscroll (i.e. the layer cannot be scrolled
   * by the entire amount requested), the overscroll is passed back to the
   * tree manager via APZCTreeManager::DispatchScroll().
   * |aOverscrollHandoffChainIndex| is used by the tree manager to keep track
   * of which APZC to hand off the overscroll to; this function increments it
   * and passes it on to APZCTreeManager::DispatchScroll() in the event of
   * overscroll.
  void AttemptScroll(const ScreenPoint& aStartPoint, const ScreenPoint& aEndPoint,
                     uint32_t aOverscrollHandoffChainIndex = 0);

   * A helper function for calling APZCTreeManager::DispatchScroll().
   * Guards against the case where the APZC is being concurrently destroyed
   * (and thus mTreeManager is being nulled out).
  void CallDispatchScroll(const ScreenPoint& aStartPoint, const ScreenPoint& aEndPoint,
                          uint32_t aOverscrollHandoffChainIndex);

   * Returns whether this APZC is for an element marked with the 'scrollgrab'
   * attribute.
  bool HasScrollgrab() const { return mFrameMetrics.mHasScrollgrab; }

   * Helper method for touches beginning. Sets everything up for panning and any
   * multitouch gestures.
  nsEventStatus OnTouchStart(const MultiTouchInput& aEvent);

   * Helper method for touches moving. Does any transforms needed when panning.
  nsEventStatus OnTouchMove(const MultiTouchInput& aEvent);

   * Helper method for touches ending. Redraws the screen if necessary and does
   * any cleanup after a touch has ended.
  nsEventStatus OnTouchEnd(const MultiTouchInput& aEvent);

   * Helper method for touches being cancelled. Treated roughly the same as a
   * touch ending (OnTouchEnd()).
  nsEventStatus OnTouchCancel(const MultiTouchInput& aEvent);

   * Helper method for scales beginning. Distinct from the OnTouch* handlers in
   * that this implies some outside implementation has determined that the user
   * is pinching.
  nsEventStatus OnScaleBegin(const PinchGestureInput& aEvent);

   * Helper method for scaling. As the user moves their fingers when pinching,
   * this changes the scale of the page.
  nsEventStatus OnScale(const PinchGestureInput& aEvent);

   * Helper method for scales ending. Redraws the screen if necessary and does
   * any cleanup after a scale has ended.
  nsEventStatus OnScaleEnd(const PinchGestureInput& aEvent);

   * Helper methods for long press gestures.
  nsEventStatus OnLongPress(const TapGestureInput& aEvent);
  nsEventStatus OnLongPressUp(const TapGestureInput& aEvent);

   * Helper method for single tap gestures.
  nsEventStatus OnSingleTapUp(const TapGestureInput& aEvent);

   * Helper method for a single tap confirmed.
  nsEventStatus OnSingleTapConfirmed(const TapGestureInput& aEvent);

   * Helper method for double taps.
  nsEventStatus OnDoubleTap(const TapGestureInput& aEvent);

   * Helper method to cancel any gesture currently going to Gecko. Used
   * primarily when a user taps the screen over some clickable content but then
   * pans down instead of letting go (i.e. to cancel a previous touch so that a
   * new one can properly take effect.
  nsEventStatus OnCancelTap(const TapGestureInput& aEvent);

   * Scrolls the viewport by an X,Y offset.
  void ScrollBy(const CSSPoint& aOffset);

   * Scales the viewport by an amount (note that it multiplies this scale in to
   * the current scale, it doesn't set it to |aScale|). Also considers a focus
   * point so that the page zooms inward/outward from that point.
  void ScaleWithFocus(float aScale,
                      const CSSPoint& aFocus);

   * Schedules a composite on the compositor thread. Wrapper for
   * CompositorParent::ScheduleRenderOnCompositorThread().
  void ScheduleComposite();

   * Gets the displacement of the current touch since it began. That is, it is
   * the distance between the current position and the initial position of the
   * current touch (this only makes sense if a touch is currently happening and
   * OnTouchMove() is being invoked).
  float PanDistance();

   * Gets a vector of the velocities of each axis.
  const ScreenPoint GetVelocityVector();

   * Gets a vector of the acceleration factors of each axis.
  const gfx::Point GetAccelerationVector();

   * Gets a reference to the first touch point from a MultiTouchInput.  This
   * gets only the first one and assumes the rest are either missing or not
   * relevant.
  ScreenIntPoint& GetFirstTouchScreenPoint(const MultiTouchInput& aEvent);

   * Sets up anything needed for panning. This takes us out of the "TOUCHING"
   * state and starts actually panning us.
  nsEventStatus StartPanning(const MultiTouchInput& aStartPoint);

   * Wrapper for Axis::UpdateWithTouchAtDevicePoint(). Calls this function for
   * both axes and factors in the time delta from the last update.
  void UpdateWithTouchAtDevicePoint(const MultiTouchInput& aEvent);

   * Does any panning required due to a new touch event.
  void TrackTouch(const MultiTouchInput& aEvent);

   * Utility function to send updated FrameMetrics to Gecko so that it can paint
   * the displayport area. Calls into GeckoContentController to do the actual
   * work. Note that only one paint request can be active at a time. If a paint
   * request is made while a paint is currently happening, it gets queued up. If
   * a new paint request arrives before a paint is completed, the old request
   * gets discarded.
  void RequestContentRepaint();

   * Tell the paint throttler to request a content repaint with the given
   * metrics.  (Helper function used by RequestContentRepaint.)
  void RequestContentRepaint(FrameMetrics& aFrameMetrics);

   * Advances a fling by an interpolated amount based on the passed in |aDelta|.
   * This should be called whenever sampling the content transform for this
   * frame. Returns true if the fling animation should be advanced by one frame,
   * or false if there is no fling or the fling has ended.
  bool DoFling(const TimeDuration& aDelta);

   * Gets the current frame metrics. This is *not* the Gecko copy stored in the
   * layers code.
  const FrameMetrics& GetFrameMetrics();

   * Timeout function for touch listeners. This should be called on a timer
   * after we get our first touch event in a batch, under the condition that we
   * have touch listeners. If a notification comes indicating whether or not
   * content preventDefaulted a series of touch events before the timeout, the
   * timeout should be cancelled.
  void TimeoutTouchListeners();

   * Timeout function for mozbrowserasyncscroll event. Because we throttle
   * mozbrowserasyncscroll events in some conditions, this function ensures
   * that the last mozbrowserasyncscroll event will be fired after a period of
   * time.
  void FireAsyncScrollOnTimeout();

  enum PanZoomState {
    NOTHING,        /* no touch-start events received */
    FLING,          /* all touches removed, but we're still scrolling page */
    TOUCHING,       /* one touch-start event received */

    PANNING,           /* panning the frame */
    PANNING_LOCKED_X,  /* touch-start followed by move (i.e. panning with axis lock) X axis */
    PANNING_LOCKED_Y,  /* as above for Y axis */

    CROSS_SLIDING_X,   /* Panning disabled while user does a horizontal gesture
                          on a vertically-scrollable view. This used for the
                          Windows Metro "cross-slide" gesture. */
    CROSS_SLIDING_Y,   /* as above for Y axis */

    PINCHING,       /* nth touch-start, where n > 1. this mode allows pan and zoom */
    ANIMATING_ZOOM, /* animated zoom to a new rect */
    WAITING_LISTENERS, /* a state halfway between NOTHING and TOUCHING - the user has
                    put a finger down, but we don't yet know if a touch listener has
                    prevented the default actions yet. we still need to abort animations. */

   * Helper to set the current state. Holds the monitor before actually setting
   * it and fires content controller events based on state changes. Always set
   * the state using this call, do not set it directly.
  void SetState(PanZoomState aState);

   * Convert ScreenPoint relative to this APZC to CSSIntPoint relative
   * to the parent document. This excludes the transient compositor transform.
   * NOTE: This must be converted to CSSIntPoint relative to the child
   * document before sending over IPC.
  bool ConvertToGecko(const ScreenPoint& aPoint, CSSIntPoint* aOut);

   * Internal helpers for checking general state of this apzc.
  bool IsTransformingState(PanZoomState aState);
  bool IsPanningState(PanZoomState mState);

  bool AllowZoom();

  enum AxisLockMode {
    FREE,     /* No locking at all */
    STANDARD, /* Default axis locking mode that remains locked until pan ends*/
    STICKY,   /* Allow lock to be broken, with hysteresis */

  static AxisLockMode GetAxisLockMode();

  uint64_t mLayersId;
  nsRefPtr<CompositorParent> mCompositorParent;
  TaskThrottler mPaintThrottler;

  /* Access to the following two fields is protected by the mRefPtrMonitor,
     since they are accessed on the UI thread but can be cleared on the
     compositor thread. */
  nsRefPtr<GeckoContentController> mGeckoContentController;
  nsRefPtr<GestureEventListener> mGestureEventListener;
  Monitor mRefPtrMonitor;

  /* Utility functions that return a addrefed pointer to the corresponding fields. */
  already_AddRefed<GeckoContentController> GetGeckoContentController();
  already_AddRefed<GestureEventListener> GetGestureEventListener();

  // Both |mFrameMetrics| and |mLastContentPaintMetrics| are protected by the
  // monitor. Do not read from or modify either of them without locking.
  FrameMetrics mFrameMetrics;

  // Protects |mFrameMetrics|, |mLastContentPaintMetrics|, and |mState|.
  // Before manipulating |mFrameMetrics| or |mLastContentPaintMetrics|, the
  // monitor should be held. When setting |mState|, either the SetState()
  // function can be used, or the monitor can be held and then |mState| updated.
  ReentrantMonitor mMonitor;

  // Metrics of the container layer corresponding to this APZC. This is
  // stored here so that it is accessible from the UI/controller thread.
  // These are the metrics at last content paint, the most recent
  // values we were notified of in NotifyLayersUpdate(). Since it represents
  // the Gecko state, it should be used as a basis for untransformation when
  // sending messages back to Gecko.
  FrameMetrics mLastContentPaintMetrics;
  // The last metrics that we requested a paint for. These are used to make sure
  // that we're not requesting a paint of the same thing that's already drawn.
  // If we don't do this check, we don't get a ShadowLayersUpdated back.
  FrameMetrics mLastPaintRequestMetrics;

  nsTArray<MultiTouchInput> mTouchQueue;

  CancelableTask* mTouchListenerTimeoutTask;

  AxisX mX;
  AxisY mY;

  // Most up-to-date constraints on zooming. These should always be reasonable
  // values; for example, allowing a min zoom of 0.0 can cause very bad things
  // to happen.
  ZoomConstraints mZoomConstraints;

  // The last time the compositor has sampled the content transform for this
  // frame.
  TimeStamp mLastSampleTime;
  // The last time a touch event came through on the UI thread.
  uint32_t mLastEventTime;

  // Stores the previous focus point if there is a pinch gesture happening. Used
  // to allow panning by moving multiple fingers (thus moving the focus point).
  ScreenPoint mLastZoomFocus;

  // Stores the state of panning and zooming this frame. This is protected by
  // |mMonitor|; that is, it should be held whenever this is updated.
  PanZoomState mState;

  // The last time and offset we fire the mozbrowserasyncscroll event when
  // compositor has sampled the content transform for this frame.
  TimeStamp mLastAsyncScrollTime;
  CSSPoint mLastAsyncScrollOffset;

  // The current offset drawn on the screen, it may not be sent since we have
  // throttling policy for mozbrowserasyncscroll event.
  CSSPoint mCurrentAsyncScrollOffset;

  // The delay task triggered by the throttling mozbrowserasyncscroll event
  // ensures the last mozbrowserasyncscroll event is always been fired.
  CancelableTask* mAsyncScrollTimeoutTask;

  // Flag used to determine whether or not we should try to enter the
  // WAITING_LISTENERS state. This is used in the case that we are processing a
  // queued up event block. If set, this means that we are handling this queue
  // and we don't want to queue the events back up again.
  bool mHandlingTouchQueue;

  RefPtr<AsyncPanZoomAnimation> mAnimation;

  friend class Axis;

  /* The functions and members in this section are used to build a tree
   * structure out of APZC instances. This tree can only be walked or
   * manipulated while holding the lock in the associated APZCTreeManager
   * instance.
  void SetLastChild(AsyncPanZoomController* child) {
    mLastChild = child;
    if (child) {
      child->mParent = this;

  void SetPrevSibling(AsyncPanZoomController* sibling) {
    mPrevSibling = sibling;
    if (sibling) {
      sibling->mParent = mParent;

  AsyncPanZoomController* GetLastChild() const { return mLastChild; }
  AsyncPanZoomController* GetPrevSibling() const { return mPrevSibling; }
  AsyncPanZoomController* GetParent() const { return mParent; }

  /* Returns true if there is no APZC higher in the tree with the same
   * layers id.
  bool IsRootForLayersId() const {
    return !mParent || (mParent->mLayersId != mLayersId);

  bool IsRootForLayersId(const uint64_t& aLayersId) const {
    return (mLayersId == aLayersId) && IsRootForLayersId();

  // This is a raw pointer to avoid introducing a reference cycle between
  // AsyncPanZoomController and APZCTreeManager. Since these objects don't
  // live on the main thread, we can't use the cycle collector with them.
  // The APZCTreeManager owns the lifetime of the APZCs, so nulling this
  // pointer out in Destroy() will prevent accessing deleted memory.
  Atomic<APZCTreeManager*> mTreeManager;

  nsRefPtr<AsyncPanZoomController> mLastChild;
  nsRefPtr<AsyncPanZoomController> mPrevSibling;
  nsRefPtr<AsyncPanZoomController> mParent;

  /* The functions and members in this section are used to maintain the
   * area that this APZC instance is responsible for. This is used when
   * hit-testing to see which APZC instance should handle touch events.
  void SetLayerHitTestData(const ScreenRect& aRect, const gfx3DMatrix& aTransformToLayer,
                           const gfx3DMatrix& aTransformForLayer) {
    mVisibleRect = aRect;
    mAncestorTransform = aTransformToLayer;
    mCSSTransform = aTransformForLayer;

  gfx3DMatrix GetAncestorTransform() const {
    return mAncestorTransform;

  gfx3DMatrix GetCSSTransform() const {
    return mCSSTransform;

  bool VisibleRegionContains(const ScreenPoint& aPoint) const {
    return mVisibleRect.Contains(aPoint);

  /* This is the visible region of the layer that this APZC corresponds to, in
   * that layer's screen pixels (the same coordinate system in which this APZC
   * receives events in ReceiveInputEvent()). */
  ScreenRect mVisibleRect;
  /* This is the cumulative CSS transform for all the layers between the parent
   * APZC and this one (not inclusive) */
  gfx3DMatrix mAncestorTransform;
  /* This is the CSS transform for this APZC's layer. */
  gfx3DMatrix mCSSTransform;

class AsyncPanZoomAnimation {

  AsyncPanZoomAnimation(const TimeDuration& aRepaintInterval =
    : mRepaintInterval(aRepaintInterval)
  { }

  virtual ~AsyncPanZoomAnimation()
  { }

  virtual bool Sample(FrameMetrics& aFrameMetrics,
                      const TimeDuration& aDelta) = 0;

   * Specifies how frequently (at most) we want to do repaints during the
   * animation sequence. TimeDuration::Forever() will cause it to only repaint
   * at the end of the animation.
  TimeDuration mRepaintInterval;


#endif // mozilla_layers_PanZoomController_h