nsEventStatus GestureEventListener::HandlePinchGestureEvent(const MultiTouchInput& aEvent, bool aClearTouches)
{
  nsEventStatus rv = nsEventStatus_eIgnore;

  if (mTouches.Length() > 1 && !aClearTouches) {
    const nsIntPoint& firstTouch = mTouches[0].mScreenPoint,
                      secondTouch = mTouches[mTouches.Length() - 1].mScreenPoint;
    nsIntPoint focusPoint =
      nsIntPoint((firstTouch.x + secondTouch.x)/2,
                 (firstTouch.y + secondTouch.y)/2);
    float currentSpan =
      float(NS_hypot(firstTouch.x - secondTouch.x,
                     firstTouch.y - secondTouch.y));

    if (mState == NoGesture) {
      PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_START,
                                   aEvent.mTime,
                                   focusPoint,
                                   currentSpan,
                                   currentSpan);

      mAsyncPanZoomController->HandleInputEvent(pinchEvent);

      mState = InPinchGesture;
    } else {
      PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_SCALE,
                                   aEvent.mTime,
                                   focusPoint,
                                   currentSpan,
                                   mPreviousSpan);

      mAsyncPanZoomController->HandleInputEvent(pinchEvent);
    }

    mPreviousSpan = currentSpan;

    rv = nsEventStatus_eConsumeNoDefault;
  } else if (mState == InPinchGesture) {
    PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_END,
                                 aEvent.mTime,
                                 mTouches[0].mScreenPoint,
                                 1.0f,
                                 1.0f);
 
    mAsyncPanZoomController->HandleInputEvent(pinchEvent);

    mState = NoGesture;

    rv = nsEventStatus_eConsumeNoDefault;
  }

  if (aClearTouches) {
    mTouches.Clear();
  }

  return rv;
}
nsEventStatus GestureEventListener::HandlePinchGestureEvent(const MultiTouchInput& aEvent)
{
  nsEventStatus rv = nsEventStatus_eIgnore;

  if (aEvent.mType == MultiTouchInput::MULTITOUCH_CANCEL) {
    mTouches.Clear();
    mState = GESTURE_NONE;
    return rv;
  }

  if (mTouches.Length() > 1) {
    const ScreenIntPoint& firstTouch = mTouches[0].mScreenPoint,
                         secondTouch = mTouches[1].mScreenPoint;
    ScreenPoint focusPoint = ScreenPoint(firstTouch + secondTouch) / 2;
    ScreenIntPoint delta = secondTouch - firstTouch;
    float currentSpan = float(NS_hypot(delta.x, delta.y));

    switch (mState) {
    case GESTURE_NONE:
      mPreviousSpan = currentSpan;
      mState = GESTURE_WAITING_PINCH;
      // Deliberately fall through. If the user pinched and took their fingers
      // off the screen such that they still had 1 left on it, we want there to
      // be no resistance. We should only reset |mSpanChange| once all fingers
      // are off the screen.
    case GESTURE_WAITING_PINCH: {
      mSpanChange += fabsf(currentSpan - mPreviousSpan);
      if (mSpanChange > PINCH_START_THRESHOLD) {
        PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_START,
                                     aEvent.mTime,
                                     focusPoint,
                                     currentSpan,
                                     currentSpan,
                                     aEvent.modifiers);

        mAsyncPanZoomController->HandleInputEvent(pinchEvent);

        mState = GESTURE_PINCH;
      }

      break;
    }
    case GESTURE_PINCH: {
      PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_SCALE,
                                   aEvent.mTime,
                                   focusPoint,
                                   currentSpan,
                                   mPreviousSpan,
                                   aEvent.modifiers);

      mAsyncPanZoomController->HandleInputEvent(pinchEvent);
      break;
    }
    default:
      // What?
      break;
    }

    mPreviousSpan = currentSpan;

    rv = nsEventStatus_eConsumeNoDefault;
  } else if (mState == GESTURE_PINCH) {
    PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_END,
                                 aEvent.mTime,
                                 ScreenPoint(),
                                 1.0f,
                                 1.0f,
                                 aEvent.modifiers);
    mAsyncPanZoomController->HandleInputEvent(pinchEvent);

    mState = GESTURE_NONE;

    // If the user left a finger on the screen, spoof a touch start event and
    // send it to APZC so that they can continue panning from that point.
    if (mTouches.Length() == 1) {
      MultiTouchInput touchEvent(MultiTouchInput::MULTITOUCH_START,
                                 aEvent.mTime,
                                 aEvent.modifiers);
      touchEvent.mTouches.AppendElement(mTouches[0]);
      mAsyncPanZoomController->HandleInputEvent(touchEvent);

      // The spoofed touch start will get back to GEL and make us enter the
      // GESTURE_WAITING_SINGLE_TAP state, but this isn't a new touch, so there
      // is no condition under which this touch should turn into any tap.
      mState = GESTURE_NONE;
    }

    rv = nsEventStatus_eConsumeNoDefault;
  } else if (mState == GESTURE_WAITING_PINCH) {
    mState = GESTURE_NONE;
  }

  return rv;
}
nsEventStatus GestureEventListener::HandleInputTouchEnd()
{
    // We intentionally do not pass apzc return statuses up since
    // it may cause apzc stay in the touching state even after
    // gestures are completed (please see Bug 1013378 for reference).

    nsEventStatus rv = nsEventStatus_eIgnore;

    switch (mState) {
    case GESTURE_NONE:
        // GEL doesn't have a dedicated state for PANNING handled in APZC thus ignore.
        break;

    case GESTURE_FIRST_SINGLE_TOUCH_DOWN: {
        CancelLongTapTimeoutTask();
        CancelMaxTapTimeoutTask();
        nsEventStatus tapupStatus = mAsyncPanZoomController->HandleGestureEvent(
                                        CreateTapEvent(mLastTouchInput, TapGestureInput::TAPGESTURE_UP));
        if (tapupStatus == nsEventStatus_eIgnore) {
            SetState(GESTURE_FIRST_SINGLE_TOUCH_UP);
            CreateMaxTapTimeoutTask();
        } else {
            // We sent the tapup into content without waiting for a double tap
            SetState(GESTURE_NONE);
        }
        break;
    }

    case GESTURE_SECOND_SINGLE_TOUCH_DOWN: {
        CancelMaxTapTimeoutTask();
        SetState(GESTURE_NONE);
        mAsyncPanZoomController->HandleGestureEvent(
            CreateTapEvent(mLastTouchInput, TapGestureInput::TAPGESTURE_DOUBLE));
        break;
    }

    case GESTURE_FIRST_SINGLE_TOUCH_MAX_TAP_DOWN:
        CancelLongTapTimeoutTask();
        SetState(GESTURE_NONE);
        TriggerSingleTapConfirmedEvent();
        break;

    case GESTURE_LONG_TOUCH_DOWN: {
        SetState(GESTURE_NONE);
        mAsyncPanZoomController->HandleGestureEvent(
            CreateTapEvent(mLastTouchInput, TapGestureInput::TAPGESTURE_LONG_UP));
        break;
    }

    case GESTURE_MULTI_TOUCH_DOWN:
        if (mTouches.Length() < 2) {
            SetState(GESTURE_NONE);
        }
        break;

    case GESTURE_PINCH:
        if (mTouches.Length() < 2) {
            SetState(GESTURE_NONE);
            PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_END,
                                         mLastTouchInput.mTime,
                                         mLastTouchInput.mTimeStamp,
                                         ScreenPoint(),
                                         1.0f,
                                         1.0f,
                                         mLastTouchInput.modifiers);
            mAsyncPanZoomController->HandleGestureEvent(pinchEvent);
        }

        rv = nsEventStatus_eConsumeNoDefault;

        break;

    default:
        NS_WARNING("Unhandled state upon touch end");
        SetState(GESTURE_NONE);
        break;
    }

    return rv;
}
nsEventStatus GestureEventListener::HandleInputTouchMove()
{
    nsEventStatus rv = nsEventStatus_eIgnore;

    switch (mState) {
    case GESTURE_NONE:
        // Ignore this input signal as the corresponding events get handled by APZC
        break;

    case GESTURE_LONG_TOUCH_DOWN:
        if (MoveDistanceIsLarge()) {
            // So that we don't fire a long-tap-up if the user moves around after a
            // long-tap
            SetState(GESTURE_NONE);
        }
        break;

    case GESTURE_FIRST_SINGLE_TOUCH_DOWN:
    case GESTURE_FIRST_SINGLE_TOUCH_MAX_TAP_DOWN:
    case GESTURE_SECOND_SINGLE_TOUCH_DOWN: {
        // If we move too much, bail out of the tap.
        if (MoveDistanceIsLarge()) {
            CancelLongTapTimeoutTask();
            CancelMaxTapTimeoutTask();
            SetState(GESTURE_NONE);
        }
        break;
    }

    case GESTURE_MULTI_TOUCH_DOWN: {
        if (mLastTouchInput.mTouches.Length() < 2) {
            NS_WARNING("Wrong input: less than 2 moving points in GESTURE_MULTI_TOUCH_DOWN state");
            break;
        }

        float currentSpan = GetCurrentSpan(mLastTouchInput);

        mSpanChange += fabsf(currentSpan - mPreviousSpan);
        if (mSpanChange > PINCH_START_THRESHOLD) {
            SetState(GESTURE_PINCH);
            PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_START,
                                         mLastTouchInput.mTime,
                                         mLastTouchInput.mTimeStamp,
                                         GetCurrentFocus(mLastTouchInput),
                                         currentSpan,
                                         currentSpan,
                                         mLastTouchInput.modifiers);

            rv = mAsyncPanZoomController->HandleGestureEvent(pinchEvent);
        } else {
            // Prevent APZC::OnTouchMove from processing a move event when two
            // touches are active
            rv = nsEventStatus_eConsumeNoDefault;
        }

        mPreviousSpan = currentSpan;
        break;
    }

    case GESTURE_PINCH: {
        if (mLastTouchInput.mTouches.Length() < 2) {
            NS_WARNING("Wrong input: less than 2 moving points in GESTURE_PINCH state");
            // Prevent APZC::OnTouchMove() from handling this wrong input
            rv = nsEventStatus_eConsumeNoDefault;
            break;
        }

        float currentSpan = GetCurrentSpan(mLastTouchInput);

        PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_SCALE,
                                     mLastTouchInput.mTime,
                                     mLastTouchInput.mTimeStamp,
                                     GetCurrentFocus(mLastTouchInput),
                                     currentSpan,
                                     mPreviousSpan,
                                     mLastTouchInput.modifiers);

        rv = mAsyncPanZoomController->HandleGestureEvent(pinchEvent);
        mPreviousSpan = currentSpan;

        break;
    }

    default:
        NS_WARNING("Unhandled state upon touch move");
        SetState(GESTURE_NONE);
        break;
    }

    return rv;
}
nsEventStatus GestureEventListener::HandleInputTouchEnd()
{
  nsEventStatus rv = nsEventStatus_eIgnore;

  switch (mState) {
  case GESTURE_NONE:
    // GEL doesn't have a dedicated state for PANNING handled in APZC thus ignore.
    break;

  case GESTURE_FIRST_SINGLE_TOUCH_DOWN: {
    CancelLongTapTimeoutTask();
    CancelMaxTapTimeoutTask();
    TapGestureInput tapEvent(TapGestureInput::TAPGESTURE_UP,
                             mLastTouchInput.mTime,
                             mLastTouchInput.mTouches[0].mScreenPoint,
                             mLastTouchInput.modifiers);
    nsEventStatus tapupStatus = mAsyncPanZoomController->HandleGestureEvent(tapEvent);
    if (tapupStatus == nsEventStatus_eIgnore) {
      SetState(GESTURE_FIRST_SINGLE_TOUCH_UP);
      CreateMaxTapTimeoutTask();
    } else {
      // We sent the tapup into content without waiting for a double tap
      SetState(GESTURE_NONE);
    }
    break;
  }

  case GESTURE_SECOND_SINGLE_TOUCH_DOWN: {
    CancelMaxTapTimeoutTask();
    SetState(GESTURE_NONE);
    TapGestureInput tapEvent(TapGestureInput::TAPGESTURE_DOUBLE,
                             mLastTouchInput.mTime,
                             mLastTouchInput.mTouches[0].mScreenPoint,
                             mLastTouchInput.modifiers);
    mAsyncPanZoomController->HandleGestureEvent(tapEvent);
    break;
  }

  case GESTURE_FIRST_SINGLE_TOUCH_MAX_TAP_DOWN:
    CancelLongTapTimeoutTask();
    SetState(GESTURE_NONE);
    TriggerSingleTapConfirmedEvent();
    break;

  case GESTURE_LONG_TOUCH_DOWN: {
    SetState(GESTURE_NONE);
    TapGestureInput tapEvent(TapGestureInput::TAPGESTURE_LONG_UP,
                             mLastTouchInput.mTime,
                             mLastTouchInput.mTouches[0].mScreenPoint,
                             mLastTouchInput.modifiers);
    mAsyncPanZoomController->HandleGestureEvent(tapEvent);
    break;
  }

  case GESTURE_MULTI_TOUCH_DOWN:
    if (mTouches.Length() < 2) {
      SetState(GESTURE_NONE);
    }
    break;

  case GESTURE_PINCH:
    if (mTouches.Length() < 2) {
      SetState(GESTURE_NONE);
      PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_END,
                                   mLastTouchInput.mTime,
                                   ScreenPoint(),
                                   1.0f,
                                   1.0f,
                                   mLastTouchInput.modifiers);
      mAsyncPanZoomController->HandleGestureEvent(pinchEvent);
    }
    rv = nsEventStatus_eConsumeNoDefault;
    break;

  default:
    NS_WARNING("Unhandled state upon touch end");
    SetState(GESTURE_NONE);
    break;
  }

  return rv;
}
nsEventStatus GestureEventListener::HandleInputTouchMove()
{
  nsEventStatus rv = nsEventStatus_eIgnore;

  switch (mState) {
  case GESTURE_NONE:
  case GESTURE_LONG_TOUCH_DOWN:
    // Ignore this input signal as the corresponding events get handled by APZC
    break;

  case GESTURE_FIRST_SINGLE_TOUCH_DOWN:
  case GESTURE_FIRST_SINGLE_TOUCH_MAX_TAP_DOWN:
  case GESTURE_SECOND_SINGLE_TOUCH_DOWN: {
    // If we move too much, bail out of the tap.
    ScreenIntPoint delta = mLastTouchInput.mTouches[0].mScreenPoint - mTouchStartPosition;
    if (NS_hypot(delta.x, delta.y) > AsyncPanZoomController::GetTouchStartTolerance()) {
      CancelLongTapTimeoutTask();
      CancelMaxTapTimeoutTask();
      SetState(GESTURE_NONE);
    }
    break;
  }

  case GESTURE_MULTI_TOUCH_DOWN: {
    if (mLastTouchInput.mTouches.Length() < 2) {
      NS_WARNING("Wrong input: less than 2 moving points in GESTURE_MULTI_TOUCH_DOWN state");
      break;
    }

    float currentSpan = GetCurrentSpan(mLastTouchInput);

    mSpanChange += fabsf(currentSpan - mPreviousSpan);
    if (mSpanChange > PINCH_START_THRESHOLD) {
      SetState(GESTURE_PINCH);
      PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_START,
                                   mLastTouchInput.mTime,
                                   GetCurrentFocus(mLastTouchInput),
                                   currentSpan,
                                   currentSpan,
                                   mLastTouchInput.modifiers);

      mAsyncPanZoomController->HandleGestureEvent(pinchEvent);
    }
    rv = nsEventStatus_eConsumeNoDefault;
    mPreviousSpan = currentSpan;
    break;
  }

  case GESTURE_PINCH: {
    if (mLastTouchInput.mTouches.Length() < 2) {
      NS_WARNING("Wrong input: less than 2 moving points in GESTURE_PINCH state");
      // Prevent APZC::OnTouchMove() from handling this wrong input
      rv = nsEventStatus_eConsumeNoDefault;
      break;
    }

    float currentSpan = GetCurrentSpan(mLastTouchInput);

    PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_SCALE,
                                 mLastTouchInput.mTime,
                                 GetCurrentFocus(mLastTouchInput),
                                 currentSpan,
                                 mPreviousSpan,
                                 mLastTouchInput.modifiers);

    mAsyncPanZoomController->HandleGestureEvent(pinchEvent);
    rv = nsEventStatus_eConsumeNoDefault;
    mPreviousSpan = currentSpan;

    break;
  }

  default:
    NS_WARNING("Unhandled state upon touch move");
    SetState(GESTURE_NONE);
    break;
  }

  return rv;
}
nsEventStatus GestureEventListener::HandlePinchGestureEvent(const MultiTouchInput& aEvent, bool aClearTouches)
{
  nsEventStatus rv = nsEventStatus_eIgnore;

  if (mTouches.Length() > 1 && !aClearTouches) {
    const nsIntPoint& firstTouch = mTouches[0].mScreenPoint,
                      secondTouch = mTouches[mTouches.Length() - 1].mScreenPoint;
    nsIntPoint focusPoint =
      nsIntPoint((firstTouch.x + secondTouch.x)/2,
                 (firstTouch.y + secondTouch.y)/2);
    float currentSpan =
      float(NS_hypot(firstTouch.x - secondTouch.x,
                     firstTouch.y - secondTouch.y));

    switch (mState) {
    case GESTURE_NONE:
      mPreviousSpan = currentSpan;
      mState = GESTURE_WAITING_PINCH;
      // Deliberately fall through. If the user pinched and took their fingers
      // off the screen such that they still had 1 left on it, we want there to
      // be no resistance. We should only reset |mSpanChange| once all fingers
      // are off the screen.
    case GESTURE_WAITING_PINCH: {
      mSpanChange += fabsf(currentSpan - mPreviousSpan);
      if (mSpanChange > PINCH_START_THRESHOLD) {
        PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_START,
                                     aEvent.mTime,
                                     focusPoint,
                                     currentSpan,
                                     currentSpan);

        mAsyncPanZoomController->ReceiveInputEvent(pinchEvent);

        mState = GESTURE_PINCH;
      }

      break;
    }
    case GESTURE_PINCH: {
      PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_SCALE,
                                   aEvent.mTime,
                                   focusPoint,
                                   currentSpan,
                                   mPreviousSpan);

      mAsyncPanZoomController->ReceiveInputEvent(pinchEvent);
      break;
    }
    default:
      // What?
      break;
    }

    mPreviousSpan = currentSpan;

    rv = nsEventStatus_eConsumeNoDefault;
  } else if (mState == GESTURE_PINCH) {
    PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_END,
                                 aEvent.mTime,
                                 mTouches[0].mScreenPoint,
                                 1.0f,
                                 1.0f);

    mAsyncPanZoomController->ReceiveInputEvent(pinchEvent);

    mState = GESTURE_NONE;

    rv = nsEventStatus_eConsumeNoDefault;
  }

  if (aClearTouches) {
    mTouches.Clear();
  }

  return rv;
}