nsEventStatus GestureEventListener::HandleInputTouchMultiStart()
{
    nsEventStatus rv = nsEventStatus_eIgnore;

    switch (mState) {
    case GESTURE_NONE:
        SetState(GESTURE_MULTI_TOUCH_DOWN);
        break;
    case GESTURE_FIRST_SINGLE_TOUCH_DOWN:
        CancelLongTapTimeoutTask();
        CancelMaxTapTimeoutTask();
        SetState(GESTURE_MULTI_TOUCH_DOWN);
        // Prevent APZC::OnTouchStart() from handling MULTITOUCH_START event
        rv = nsEventStatus_eConsumeNoDefault;
        break;
    case GESTURE_FIRST_SINGLE_TOUCH_MAX_TAP_DOWN:
        CancelLongTapTimeoutTask();
        SetState(GESTURE_MULTI_TOUCH_DOWN);
        // Prevent APZC::OnTouchStart() from handling MULTITOUCH_START event
        rv = nsEventStatus_eConsumeNoDefault;
        break;
    case GESTURE_FIRST_SINGLE_TOUCH_UP:
        // Cancel wait for double tap
        CancelMaxTapTimeoutTask();
        SetState(GESTURE_MULTI_TOUCH_DOWN);
        TriggerSingleTapConfirmedEvent();
        // Prevent APZC::OnTouchStart() from handling MULTITOUCH_START event
        rv = nsEventStatus_eConsumeNoDefault;
        break;
    case GESTURE_SECOND_SINGLE_TOUCH_DOWN:
        // Cancel wait for single tap
        CancelMaxTapTimeoutTask();
        SetState(GESTURE_MULTI_TOUCH_DOWN);
        TriggerSingleTapConfirmedEvent();
        // Prevent APZC::OnTouchStart() from handling MULTITOUCH_START event
        rv = nsEventStatus_eConsumeNoDefault;
        break;
    case GESTURE_LONG_TOUCH_DOWN:
        SetState(GESTURE_MULTI_TOUCH_DOWN);
        break;
    case GESTURE_MULTI_TOUCH_DOWN:
    case GESTURE_PINCH:
        // Prevent APZC::OnTouchStart() from handling MULTITOUCH_START event
        rv = nsEventStatus_eConsumeNoDefault;
        break;
    default:
        NS_WARNING("Unhandled state upon multitouch start");
        SetState(GESTURE_NONE);
        break;
    }

    return rv;
}
nsEventStatus GestureEventListener::HandleInputTouchCancel()
{
    SetState(GESTURE_NONE);
    CancelMaxTapTimeoutTask();
    CancelLongTapTimeoutTask();
    return nsEventStatus_eIgnore;
}
nsEventStatus GestureEventListener::HandleTapCancel(const MultiTouchInput& aEvent)
{
  mTapStartTime = 0;

  switch (mState)
  {
  case GESTURE_WAITING_SINGLE_TAP:
    CancelLongTapTimeoutTask();
    mState = GESTURE_NONE;
    break;

  case GESTURE_WAITING_DOUBLE_TAP:
    mState = GESTURE_NONE;
    break;
  default:
    break;
  }

  return nsEventStatus_eConsumeDoDefault;
}
nsEventStatus GestureEventListener::HandleInputEvent(const MultiTouchInput& aEvent)
{
  // Cache the current event since it may become the single or long tap that we
  // send.
  mLastTouchInput = aEvent;

  switch (aEvent.mType)
  {
  case MultiTouchInput::MULTITOUCH_START:
  case MultiTouchInput::MULTITOUCH_ENTER: {
    for (size_t i = 0; i < aEvent.mTouches.Length(); i++) {
      bool foundAlreadyExistingTouch = false;
      for (size_t j = 0; j < mTouches.Length(); j++) {
        if (mTouches[j].mIdentifier == aEvent.mTouches[i].mIdentifier) {
          foundAlreadyExistingTouch = true;
          break;
        }
      }

      // If we didn't find a touch in our list that matches this, then add it.
      if (!foundAlreadyExistingTouch) {
        mTouches.AppendElement(aEvent.mTouches[i]);
      }
    }

    size_t length = mTouches.Length();
    if (length == 1) {
      mTapStartTime = aEvent.mTime;
      mTouchStartPosition = aEvent.mTouches[0].mScreenPoint;
      if (mState == GESTURE_NONE) {
        mState = GESTURE_WAITING_SINGLE_TAP;

        mLongTapTimeoutTask =
          NewRunnableMethod(this, &GestureEventListener::TimeoutLongTap);

        mAsyncPanZoomController->PostDelayedTask(
          mLongTapTimeoutTask,
          Preferences::GetInt("ui.click_hold_context_menus.delay", 500));
      }
    } else if (length == 2) {
      // Another finger has been added; it can't be a tap anymore.
      HandleTapCancel(aEvent);
    }

    break;
  }
  case MultiTouchInput::MULTITOUCH_MOVE: {
    // If we move too much, bail out of the tap.
    ScreenIntPoint delta = aEvent.mTouches[0].mScreenPoint - mTouchStartPosition;
    if (mTouches.Length() == 1 &&
        NS_hypot(delta.x, delta.y) > AsyncPanZoomController::GetTouchStartTolerance())
    {
      HandleTapCancel(aEvent);
    }

    size_t eventTouchesMatched = 0;
    for (size_t i = 0; i < mTouches.Length(); i++) {
      bool isTouchRemoved = true;
      for (size_t j = 0; j < aEvent.mTouches.Length(); j++) {
        if (mTouches[i].mIdentifier == aEvent.mTouches[j].mIdentifier) {
          eventTouchesMatched++;
          isTouchRemoved = false;
          mTouches[i] = aEvent.mTouches[j];
        }
      }
      if (isTouchRemoved) {
        // this touch point was lifted, so remove it from our list
        mTouches.RemoveElementAt(i);
        i--;
      }
    }

    NS_WARN_IF_FALSE(eventTouchesMatched == aEvent.mTouches.Length(), "Touch moved, but not in list");

    break;
  }
  case MultiTouchInput::MULTITOUCH_END:
  case MultiTouchInput::MULTITOUCH_LEAVE: {
    for (size_t i = 0; i < aEvent.mTouches.Length(); i++) {
      bool foundAlreadyExistingTouch = false;
      for (size_t j = 0; j < mTouches.Length() && !foundAlreadyExistingTouch; j++) {
        if (aEvent.mTouches[i].mIdentifier == mTouches[j].mIdentifier) {
          foundAlreadyExistingTouch = true;
          mTouches.RemoveElementAt(j);
        }
      }
      NS_WARN_IF_FALSE(foundAlreadyExistingTouch, "Touch ended, but not in list");
    }

    if (mState == GESTURE_WAITING_DOUBLE_TAP) {
      CancelDoubleTapTimeoutTask();
      if (mTapStartTime - mLastTapEndTime > MAX_TAP_TIME ||
          aEvent.mTime - mTapStartTime > MAX_TAP_TIME) {
        // Either the time between taps or the last tap took too long
        // confirm previous tap and handle current tap seperately
        TimeoutDoubleTap();
        mState = GESTURE_WAITING_SINGLE_TAP;
      } else {
        // We were waiting for a double tap and it has arrived.
        HandleDoubleTap(aEvent);
        mState = GESTURE_NONE;
      }
    }

    if (mState == GESTURE_LONG_TAP_UP) {
      HandleLongTapUpEvent(aEvent);
      mState = GESTURE_NONE;
    } else if (mState == GESTURE_WAITING_SINGLE_TAP &&
        aEvent.mTime - mTapStartTime > MAX_TAP_TIME) {
      // Extended taps are immediately dispatched as single taps
      CancelLongTapTimeoutTask();
      HandleSingleTapConfirmedEvent(aEvent);
      mState = GESTURE_NONE;
    } else if (mState == GESTURE_WAITING_SINGLE_TAP) {
      CancelLongTapTimeoutTask();
      nsEventStatus tapupEvent = HandleSingleTapUpEvent(aEvent);

      if (tapupEvent == nsEventStatus_eIgnore) {
        // We were not waiting for anything but a single tap has happened that
        // may turn into a double tap. Wait a while and if it doesn't turn into
        // a double tap, send a single tap instead.
        mState = GESTURE_WAITING_DOUBLE_TAP;

        mDoubleTapTimeoutTask =
          NewRunnableMethod(this, &GestureEventListener::TimeoutDoubleTap);

        mAsyncPanZoomController->PostDelayedTask(
          mDoubleTapTimeoutTask,
          MAX_TAP_TIME);

      } else if (tapupEvent == nsEventStatus_eConsumeNoDefault) {
        // We sent the tapup into content without waiting for a double tap
        mState = GESTURE_NONE;
      }
    }

    mLastTapEndTime = aEvent.mTime;

    if (!mTouches.Length()) {
      mSpanChange = 0.0f;
    }

    break;
  }
  case MultiTouchInput::MULTITOUCH_CANCEL:
    // FIXME: we should probably clear a bunch of gesture state here
    break;
  }

  return HandlePinchGestureEvent(aEvent);
}
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;
}