--- a/gfx/layers/Makefile.in
+++ b/gfx/layers/Makefile.in
@@ -105,35 +105,42 @@ CPPSRCS += \
ThebesLayerD3D10.cpp \
$(NULL)
endif
endif
EXPORTS_NAMESPACES = gfxipc mozilla/layers
EXPORTS_gfxipc = ShadowLayerUtils.h
EXPORTS_mozilla/layers =\
+ AsyncPanZoomController.h \
+ Axis.h \
CompositorCocoaWidgetHelper.h \
CompositorChild.h \
CompositorParent.h \
+ GeckoContentController.h \
+ GestureEventListener.h \
ImageBridgeChild.h \
ImageBridgeParent.h \
ImageContainerChild.h \
ImageContainerParent.h \
ShadowLayers.h \
ShadowLayersChild.h \
ShadowLayersParent.h \
ShadowLayersManager.h \
RenderTrace.h \
SharedImageUtils.h \
$(NULL)
CPPSRCS += \
+ AsyncPanZoomController.cpp \
+ Axis.cpp \
CompositorCocoaWidgetHelper.cpp \
CompositorChild.cpp \
CompositorParent.cpp \
+ GestureEventListener.cpp \
ImageBridgeChild.cpp \
ImageBridgeParent.cpp \
ImageContainerChild.cpp \
ImageContainerParent.cpp \
ShadowLayers.cpp \
ShadowLayerChild.cpp \
ShadowLayersChild.cpp \
ShadowLayerParent.cpp \
@@ -157,9 +164,13 @@ ifeq ($(MOZ_WIDGET_TOOLKIT),gonk)
EXPORTS_mozilla/layers += ShadowLayerUtilsGralloc.h
CPPSRCS += ShadowLayerUtilsGralloc.cpp
endif
include $(topsrcdir)/config/rules.mk
include $(topsrcdir)/ipc/chromium/chromium-config.mk
+LOCAL_INCLUDES += \
+ -I$(topsrcdir)/content/events/src \
+ $(NULL)
+
CXXFLAGS += $(MOZ_CAIRO_CFLAGS) $(MOZ_PIXMAN_CFLAGS) $(TK_CFLAGS)
new file mode 100644
--- /dev/null
+++ b/gfx/layers/ipc/AsyncPanZoomController.cpp
@@ -0,0 +1,729 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=4 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 http://mozilla.org/MPL/2.0/. */
+
+#include "CompositorParent.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/Util.h"
+#include "mozilla/XPCOM.h"
+#include "mozilla/Monitor.h"
+#include "AsyncPanZoomController.h"
+#include "GestureEventListener.h"
+#include "nsIThreadManager.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace layers {
+
+static const float EPSILON = 0.0001;
+
+/**
+ * Maximum amount of time while panning before sending a viewport change. This
+ * will asynchronously repaint the page. It is also forced when panning stops.
+ */
+static const PRInt32 PAN_REPAINT_INTERVAL = 250;
+
+/**
+ * Maximum amount of time flinging before sending a viewport change. This will
+ * asynchronously repaint the page.
+ */
+static const PRInt32 FLING_REPAINT_INTERVAL = 75;
+
+AsyncPanZoomController::AsyncPanZoomController(GeckoContentController* aGeckoContentController,
+ GestureBehavior aGestures)
+ : mGeckoContentController(aGeckoContentController),
+ mX(this),
+ mY(this),
+ mMonitor("AsyncPanZoomController"),
+ mLastSampleTime(TimeStamp::Now()),
+ mState(NOTHING),
+ mDPI(72)
+{
+ if (aGestures == USE_GESTURE_DETECTOR) {
+ mGestureEventListener = new GestureEventListener(this);
+ }
+
+ SetDPI(mDPI);
+}
+
+AsyncPanZoomController::~AsyncPanZoomController() {
+
+}
+
+static gfx::Point
+WidgetSpaceToCompensatedViewportSpace(const gfx::Point& aPoint,
+ gfxFloat aCurrentZoom)
+{
+ // Transform the input point from local widget space to the content document
+ // space that the user is seeing, from last composite.
+ gfx::Point pt(aPoint);
+ pt = pt / aCurrentZoom;
+
+ // FIXME/bug 775451: this doesn't attempt to compensate for content transforms
+ // in effect on the compositor. The problem is that it's very hard for us to
+ // know what content CSS pixel is at widget point 0,0 based on information
+ // available here. So we use this hacky implementation for now, which works
+ // in quiescent states.
+
+ return pt;
+}
+
+nsEventStatus
+AsyncPanZoomController::HandleInputEvent(const nsInputEvent& aEvent,
+ nsInputEvent* aOutEvent)
+{
+ float currentZoom;
+ gfx::Point currentScrollOffset, lastScrollOffset;
+ {
+ MonitorAutoLock monitor(mMonitor);
+ currentZoom = mFrameMetrics.mResolution.width;
+ currentScrollOffset = gfx::Point(mFrameMetrics.mViewportScrollOffset.x,
+ mFrameMetrics.mViewportScrollOffset.y);
+ lastScrollOffset = gfx::Point(mLastContentPaintMetrics.mViewportScrollOffset.x,
+ mLastContentPaintMetrics.mViewportScrollOffset.y);
+ }
+
+ nsEventStatus status;
+ switch (aEvent.eventStructType) {
+ case NS_TOUCH_EVENT: {
+ MultiTouchInput event(static_cast<const nsTouchEvent&>(aEvent));
+ status = HandleInputEvent(event);
+ break;
+ }
+ case NS_MOUSE_EVENT: {
+ MultiTouchInput event(static_cast<const nsMouseEvent&>(aEvent));
+ status = HandleInputEvent(event);
+ break;
+ }
+ default:
+ status = nsEventStatus_eIgnore;
+ break;
+ }
+
+ switch (aEvent.eventStructType) {
+ case NS_TOUCH_EVENT: {
+ nsTouchEvent* touchEvent = static_cast<nsTouchEvent*>(aOutEvent);
+ const nsTArray<nsCOMPtr<nsIDOMTouch> >& touches = touchEvent->touches;
+ for (PRUint32 i = 0; i < touches.Length(); ++i) {
+ nsIDOMTouch* touch = touches[i];
+ if (touch) {
+ gfx::Point refPoint = WidgetSpaceToCompensatedViewportSpace(
+ gfx::Point(touch->mRefPoint.x, touch->mRefPoint.y),
+ currentZoom);
+ touch->mRefPoint = nsIntPoint(refPoint.x, refPoint.y);
+ }
+ }
+ break;
+ }
+ default: {
+ gfx::Point refPoint = WidgetSpaceToCompensatedViewportSpace(
+ gfx::Point(aOutEvent->refPoint.x, aOutEvent->refPoint.y),
+ currentZoom);
+ aOutEvent->refPoint = nsIntPoint(refPoint.x, refPoint.y);
+ break;
+ }
+ }
+
+ return status;
+}
+
+nsEventStatus AsyncPanZoomController::HandleInputEvent(const InputData& aEvent) {
+ nsEventStatus rv = nsEventStatus_eIgnore;
+
+ if (mGestureEventListener) {
+ nsEventStatus rv = mGestureEventListener->HandleInputEvent(aEvent);
+ if (rv == nsEventStatus_eConsumeNoDefault)
+ return rv;
+ }
+
+ switch (aEvent.mInputType) {
+ case MULTITOUCH_INPUT: {
+ const MultiTouchInput& multiTouchInput = aEvent.AsMultiTouchInput();
+ switch (multiTouchInput.mType) {
+ case MultiTouchInput::MULTITOUCH_START: rv = OnTouchStart(multiTouchInput); break;
+ case MultiTouchInput::MULTITOUCH_MOVE: rv = OnTouchMove(multiTouchInput); break;
+ case MultiTouchInput::MULTITOUCH_END: rv = OnTouchEnd(multiTouchInput); break;
+ case MultiTouchInput::MULTITOUCH_CANCEL: rv = OnTouchCancel(multiTouchInput); break;
+ default: NS_WARNING("Unhandled multitouch"); break;
+ }
+ break;
+ }
+ case PINCHGESTURE_INPUT: {
+ const PinchGestureInput& pinchGestureInput = aEvent.AsPinchGestureInput();
+ switch (pinchGestureInput.mType) {
+ case PinchGestureInput::PINCHGESTURE_START: rv = OnScaleBegin(pinchGestureInput); break;
+ case PinchGestureInput::PINCHGESTURE_SCALE: rv = OnScale(pinchGestureInput); break;
+ case PinchGestureInput::PINCHGESTURE_END: rv = OnScaleEnd(pinchGestureInput); break;
+ default: NS_WARNING("Unhandled pinch gesture"); break;
+ }
+ break;
+ }
+ case TAPGESTURE_INPUT: {
+ const TapGestureInput& tapGestureInput = aEvent.AsTapGestureInput();
+ switch (tapGestureInput.mType) {
+ case TapGestureInput::TAPGESTURE_LONG: rv = OnLongPress(tapGestureInput); break;
+ case TapGestureInput::TAPGESTURE_UP: rv = OnSingleTapUp(tapGestureInput); break;
+ case TapGestureInput::TAPGESTURE_CONFIRMED: rv = OnSingleTapConfirmed(tapGestureInput); break;
+ case TapGestureInput::TAPGESTURE_DOUBLE: rv = OnDoubleTap(tapGestureInput); break;
+ case TapGestureInput::TAPGESTURE_CANCEL: rv = OnCancelTap(tapGestureInput); break;
+ default: NS_WARNING("Unhandled tap gesture"); break;
+ }
+ break;
+ }
+ default: NS_WARNING("Unhandled input event"); break;
+ }
+
+ mLastEventTime = aEvent.mTime;
+ return rv;
+}
+
+nsEventStatus AsyncPanZoomController::OnTouchStart(const MultiTouchInput& aEvent) {
+ SingleTouchData& touch = GetFirstSingleTouch(aEvent);
+
+ nsIntPoint point = touch.mScreenPoint;
+ PRInt32 xPos = point.x, yPos = point.y;
+
+ switch (mState) {
+ case FLING:
+ CancelAnimation();
+ // Fall through.
+ case NOTHING:
+ mX.StartTouch(xPos);
+ mY.StartTouch(yPos);
+ mState = TOUCHING;
+ break;
+ case TOUCHING:
+ case PANNING:
+ case PINCHING:
+ NS_WARNING("Received impossible touch in OnTouchStart");
+ break;
+ default:
+ NS_WARNING("Unhandled case in OnTouchStart");
+ break;
+ }
+
+ return nsEventStatus_eConsumeNoDefault;
+}
+
+nsEventStatus AsyncPanZoomController::OnTouchMove(const MultiTouchInput& aEvent) {
+ SingleTouchData& touch = GetFirstSingleTouch(aEvent);
+ nsIntPoint point = touch.mScreenPoint;
+ PRInt32 xPos = point.x, yPos = point.y;
+
+ switch (mState) {
+ case FLING:
+ case NOTHING:
+ // May happen if the user double-taps and drags without lifting after the
+ // second tap. Ignore the move if this happens.
+ return nsEventStatus_eIgnore;
+
+ case TOUCHING: {
+ float panThreshold = 1.0f/16.0f * mDPI;
+ if (PanDistance(aEvent) < panThreshold) {
+ return nsEventStatus_eIgnore;
+ }
+ mLastRepaint = aEvent.mTime;
+ mX.StartTouch(xPos);
+ mY.StartTouch(yPos);
+ mState = PANNING;
+ return nsEventStatus_eConsumeNoDefault;
+ }
+
+ case PANNING:
+ TrackTouch(aEvent);
+ return nsEventStatus_eConsumeNoDefault;
+
+ case PINCHING:
+ // The scale gesture listener should have handled this.
+ NS_WARNING("Gesture listener should have handled pinching in OnTouchMove.");
+ return nsEventStatus_eIgnore;
+ }
+
+ return nsEventStatus_eConsumeNoDefault;
+}
+
+nsEventStatus AsyncPanZoomController::OnTouchEnd(const MultiTouchInput& aEvent) {
+ switch (mState) {
+ case FLING:
+ // Should never happen.
+ NS_WARNING("Received impossible touch end in OnTouchEnd.");
+ // Fall through.
+ case NOTHING:
+ // May happen if the user double-taps and drags without lifting after the
+ // second tap. Ignore if this happens.
+ return nsEventStatus_eIgnore;
+
+ case TOUCHING:
+ mState = NOTHING;
+ return nsEventStatus_eIgnore;
+
+ case PANNING:
+ {
+ MonitorAutoLock monitor(mMonitor);
+ ScheduleComposite();
+ RequestContentRepaint();
+ }
+ mState = FLING;
+ mLastSampleTime = TimeStamp::Now();
+ return nsEventStatus_eConsumeNoDefault;
+ case PINCHING:
+ mState = NOTHING;
+ // Scale gesture listener should have handled this.
+ NS_WARNING("Gesture listener should have handled pinching in OnTouchEnd.");
+ return nsEventStatus_eIgnore;
+ }
+
+ return nsEventStatus_eConsumeNoDefault;
+}
+
+nsEventStatus AsyncPanZoomController::OnTouchCancel(const MultiTouchInput& aEvent) {
+ mState = NOTHING;
+ return nsEventStatus_eConsumeNoDefault;
+}
+
+nsEventStatus AsyncPanZoomController::OnScaleBegin(const PinchGestureInput& aEvent) {
+ mState = PINCHING;
+ mLastZoomFocus = aEvent.mFocusPoint;
+
+ return nsEventStatus_eConsumeNoDefault;
+}
+
+nsEventStatus AsyncPanZoomController::OnScale(const PinchGestureInput& aEvent) {
+ float prevSpan = aEvent.mPreviousSpan;
+ if (fabsf(prevSpan) <= EPSILON || fabsf(aEvent.mCurrentSpan) <= EPSILON) {
+ // We're still handling it; we've just decided to throw this event away.
+ return nsEventStatus_eConsumeNoDefault;
+ }
+
+ float spanRatio = aEvent.mCurrentSpan / aEvent.mPreviousSpan;
+
+ {
+ MonitorAutoLock monitor(mMonitor);
+
+ float scale = mFrameMetrics.mResolution.width;
+
+ nsIntPoint focusPoint = aEvent.mFocusPoint;
+ PRInt32 xFocusChange = (mLastZoomFocus.x - focusPoint.x) / scale, yFocusChange = (mLastZoomFocus.y - focusPoint.y) / scale;
+ // If displacing by the change in focus point will take us off page bounds,
+ // then reduce the displacement such that it doesn't.
+ if (mX.DisplacementWillOverscroll(xFocusChange) != Axis::OVERSCROLL_NONE) {
+ xFocusChange -= mX.DisplacementWillOverscrollAmount(xFocusChange);
+ }
+ if (mY.DisplacementWillOverscroll(yFocusChange) != Axis::OVERSCROLL_NONE) {
+ yFocusChange -= mY.DisplacementWillOverscrollAmount(yFocusChange);
+ }
+ ScrollBy(nsIntPoint(xFocusChange, yFocusChange));
+
+ // When we zoom in with focus, we can zoom too much towards the boundaries
+ // that we actually go over them. These are the needed displacements along
+ // either axis such that we don't overscroll the boundaries when zooming.
+ PRInt32 neededDisplacementX = 0, neededDisplacementY = 0;
+
+ // Only do the scaling if we won't go over 8x zoom in or out.
+ bool doScale = (scale < 8.0f && spanRatio > 1.0f) || (scale > 0.125f && spanRatio < 1.0f);
+
+ // If this zoom will take it over 8x zoom in either direction, but it's not
+ // already there, then normalize it.
+ if (scale * spanRatio > 8.0f) {
+ spanRatio = scale / 8.0f;
+ } else if (scale * spanRatio < 0.125f) {
+ spanRatio = scale / 0.125f;
+ }
+
+ if (doScale) {
+ switch (mX.ScaleWillOverscroll(spanRatio, focusPoint.x))
+ {
+ case Axis::OVERSCROLL_NONE:
+ break;
+ case Axis::OVERSCROLL_MINUS:
+ case Axis::OVERSCROLL_PLUS:
+ neededDisplacementX = -mX.ScaleWillOverscrollAmount(spanRatio, focusPoint.x);
+ break;
+ case Axis::OVERSCROLL_BOTH:
+ // If scaling this way will make us overscroll in both directions, then
+ // we must already be at the maximum zoomed out amount. In this case, we
+ // don't want to allow this scaling to go through and instead clamp it
+ // here.
+ doScale = false;
+ break;
+ }
+ }
+
+ if (doScale) {
+ switch (mY.ScaleWillOverscroll(spanRatio, focusPoint.y))
+ {
+ case Axis::OVERSCROLL_NONE:
+ break;
+ case Axis::OVERSCROLL_MINUS:
+ case Axis::OVERSCROLL_PLUS:
+ neededDisplacementY = -mY.ScaleWillOverscrollAmount(spanRatio, focusPoint.y);
+ break;
+ case Axis::OVERSCROLL_BOTH:
+ doScale = false;
+ break;
+ }
+ }
+
+ if (doScale) {
+ ScaleWithFocus(scale * spanRatio,
+ focusPoint);
+
+ if (neededDisplacementX != 0 || neededDisplacementY != 0) {
+ ScrollBy(nsIntPoint(neededDisplacementX, neededDisplacementY));
+ }
+
+ ScheduleComposite();
+ // We don't want to redraw on every scale, so don't use
+ // RequestContentRepaint()
+ }
+
+ mLastZoomFocus = focusPoint;
+ }
+
+ return nsEventStatus_eConsumeNoDefault;
+}
+
+nsEventStatus AsyncPanZoomController::OnScaleEnd(const PinchGestureInput& aEvent) {
+ mState = PANNING;
+ mX.StartTouch(aEvent.mFocusPoint.x);
+ mY.StartTouch(aEvent.mFocusPoint.y);
+ {
+ MonitorAutoLock monitor(mMonitor);
+ ScheduleComposite();
+ RequestContentRepaint();
+ }
+
+ return nsEventStatus_eConsumeNoDefault;
+}
+
+nsEventStatus AsyncPanZoomController::OnLongPress(const TapGestureInput& aEvent) {
+ // XXX: Implement this.
+ return nsEventStatus_eIgnore;
+}
+
+nsEventStatus AsyncPanZoomController::OnSingleTapUp(const TapGestureInput& aEvent) {
+ // XXX: Implement this.
+ return nsEventStatus_eIgnore;
+}
+
+nsEventStatus AsyncPanZoomController::OnSingleTapConfirmed(const TapGestureInput& aEvent) {
+ // XXX: Implement this.
+ return nsEventStatus_eIgnore;
+}
+
+nsEventStatus AsyncPanZoomController::OnDoubleTap(const TapGestureInput& aEvent) {
+ // XXX: Implement this.
+ return nsEventStatus_eIgnore;
+}
+
+nsEventStatus AsyncPanZoomController::OnCancelTap(const TapGestureInput& aEvent) {
+ // XXX: Implement this.
+ return nsEventStatus_eIgnore;
+}
+
+float AsyncPanZoomController::PanDistance(const MultiTouchInput& aEvent) {
+ SingleTouchData& touch = GetFirstSingleTouch(aEvent);
+ nsIntPoint point = touch.mScreenPoint;
+ PRInt32 xPos = point.x, yPos = point.y;
+ mX.UpdateWithTouchAtDevicePoint(xPos, 0);
+ mY.UpdateWithTouchAtDevicePoint(yPos, 0);
+ return NS_hypot(mX.PanDistance(), mY.PanDistance()) * mFrameMetrics.mResolution.width;
+}
+
+const nsPoint AsyncPanZoomController::GetVelocityVector() {
+ return nsPoint(
+ mX.GetVelocity(),
+ mY.GetVelocity()
+ );
+}
+
+void AsyncPanZoomController::TrackTouch(const MultiTouchInput& aEvent) {
+ SingleTouchData& touch = GetFirstSingleTouch(aEvent);
+ nsIntPoint point = touch.mScreenPoint;
+ PRInt32 xPos = point.x, yPos = point.y, timeDelta = aEvent.mTime - mLastEventTime;
+
+ // Probably a duplicate event, just throw it away.
+ if (!timeDelta) {
+ return;
+ }
+
+ {
+ MonitorAutoLock monitor(mMonitor);
+ mX.UpdateWithTouchAtDevicePoint(xPos, timeDelta);
+ mY.UpdateWithTouchAtDevicePoint(yPos, timeDelta);
+
+ // We want to inversely scale it because when you're zoomed further in, a
+ // larger swipe should move you a shorter distance.
+ float inverseScale = 1 / mFrameMetrics.mResolution.width;
+
+ PRInt32 xDisplacement = mX.UpdateAndGetDisplacement(inverseScale);
+ PRInt32 yDisplacement = mY.UpdateAndGetDisplacement(inverseScale);
+ if (!xDisplacement && !yDisplacement) {
+ return;
+ }
+
+ ScrollBy(nsIntPoint(xDisplacement, yDisplacement));
+ ScheduleComposite();
+
+ if (aEvent.mTime - mLastRepaint >= PAN_REPAINT_INTERVAL) {
+ RequestContentRepaint();
+ mLastRepaint = aEvent.mTime;
+ }
+ }
+}
+
+SingleTouchData& AsyncPanZoomController::GetFirstSingleTouch(const MultiTouchInput& aEvent) {
+ return (SingleTouchData&)aEvent.mTouches[0];
+}
+
+bool AsyncPanZoomController::DoFling(const TimeDuration& aDelta) {
+ if (mState != FLING) {
+ return false;
+ }
+
+ if (!mX.FlingApplyFrictionOrCancel(aDelta) && !mY.FlingApplyFrictionOrCancel(aDelta)) {
+ RequestContentRepaint();
+ mState = NOTHING;
+ return false;
+ }
+
+ // We want to inversely scale it because when you're zoomed further in, a
+ // larger swipe should move you a shorter distance.
+ float inverseScale = 1 / mFrameMetrics.mResolution.width;
+
+ ScrollBy(nsIntPoint(
+ mX.UpdateAndGetDisplacement(inverseScale),
+ mY.UpdateAndGetDisplacement(inverseScale)
+ ));
+ RequestContentRepaint();
+
+ return true;
+}
+
+void AsyncPanZoomController::CancelAnimation() {
+ mState = NOTHING;
+}
+
+void AsyncPanZoomController::SetCompositorParent(CompositorParent* aCompositorParent) {
+ mCompositorParent = aCompositorParent;
+}
+
+void AsyncPanZoomController::ScrollBy(const nsIntPoint& aOffset) {
+ nsIntPoint newOffset(mFrameMetrics.mViewportScrollOffset.x + aOffset.x,
+ mFrameMetrics.mViewportScrollOffset.y + aOffset.y);
+ FrameMetrics metrics(mFrameMetrics);
+ metrics.mViewportScrollOffset = newOffset;
+ mFrameMetrics = metrics;
+}
+
+void AsyncPanZoomController::SetPageRect(const gfx::Rect& aCSSPageRect) {
+ FrameMetrics metrics = mFrameMetrics;
+ gfx::Rect pageSize = aCSSPageRect;
+ float scale = mFrameMetrics.mResolution.width;
+
+ // The page rect is the css page rect scaled by the current zoom.
+ pageSize.ScaleRoundOut(scale);
+
+ // Round the page rect so we don't get any truncation, then get the nsIntRect
+ // from this.
+ metrics.mContentRect = nsIntRect(pageSize.x, pageSize.y, pageSize.width, pageSize.height);
+ metrics.mCSSContentRect = aCSSPageRect;
+
+ mFrameMetrics = metrics;
+}
+
+void AsyncPanZoomController::ScaleWithFocus(float aScale, const nsIntPoint& aFocus) {
+ FrameMetrics metrics(mFrameMetrics);
+
+ // Don't set the scale to the inputted value, but rather multiply it in.
+ float scaleFactor = aScale / metrics.mResolution.width;
+
+ metrics.mResolution.width = metrics.mResolution.height = aScale;
+
+ // Force a recalculation of the page rect based on the new zoom and the
+ // current CSS page rect (which is unchanged since it's not affected by zoom).
+ SetPageRect(mFrameMetrics.mCSSContentRect);
+
+ nsIntPoint scrollOffset = metrics.mViewportScrollOffset;
+
+ scrollOffset.x += aFocus.x * (scaleFactor - 1.0f);
+ scrollOffset.y += aFocus.y * (scaleFactor - 1.0f);
+
+ metrics.mViewportScrollOffset = scrollOffset;
+
+ mFrameMetrics = metrics;
+}
+
+const nsIntRect AsyncPanZoomController::CalculatePendingDisplayPort() {
+ float scale = mFrameMetrics.mResolution.width;
+ nsIntRect viewport = mFrameMetrics.mViewport;
+ viewport.ScaleRoundIn(1 / scale);
+
+ const float SIZE_MULTIPLIER = 2.0f;
+ nsIntPoint scrollOffset = mFrameMetrics.mViewportScrollOffset;
+ gfx::Rect contentRect = mFrameMetrics.mCSSContentRect;
+
+ // Paint a larger portion of the screen than just what we can see. This makes
+ // it less likely that we'll checkerboard when panning around and Gecko hasn't
+ // repainted yet.
+ float desiredWidth = viewport.width * SIZE_MULTIPLIER,
+ desiredHeight = viewport.height * SIZE_MULTIPLIER;
+
+ // The displayport is relative to the current scroll offset. Here's a little
+ // diagram to make it easier to see:
+ //
+ // - - - -
+ // | |
+ // *************
+ // * | | *
+ // - -*- @------ -*- -
+ // | * |=====| * |
+ // * |=====| *
+ // | * |=====| * |
+ // - -*- ------- -*- -
+ // * | | *
+ // *************
+ // | |
+ // - - - -
+ //
+ // The full --- area with === inside it is the actual viewport rect, the *** area
+ // is the displayport, and the - - - area is an imaginary additional page on all 4
+ // borders of the actual page. Notice that the displayport intersects half-way with
+ // each of the imaginary extra pages. The @ symbol at the top left of the
+ // viewport marks the current scroll offset. From the @ symbol to the far left
+ // and far top, it is clear that this distance is 1/4 of the displayport's
+ // height/width dimension.
+ gfx::Rect displayPort(-desiredWidth / 4, -desiredHeight / 4, desiredWidth, desiredHeight);
+
+ // Check if the desired boundaries go over the CSS page rect along the top or
+ // left. If they do, shift them to the right or down.
+ float oldDisplayPortX = displayPort.x, oldDisplayPortY = displayPort.y;
+ if (displayPort.X() + scrollOffset.x < contentRect.X()) {
+ displayPort.x = contentRect.X() - scrollOffset.x;
+ }
+ if (displayPort.Y() + scrollOffset.y < contentRect.Y()) {
+ displayPort.y = contentRect.Y() - scrollOffset.y;
+ }
+
+ // We don't need to paint the extra area that was going to overlap with the
+ // content rect. Subtract out this extra width or height.
+ displayPort.width -= displayPort.x - oldDisplayPortX;
+ displayPort.height -= displayPort.y - oldDisplayPortY;
+
+ // Check if the desired boundaries go over the CSS page rect along the right
+ // or bottom. If they do, subtract out some height or width such that they
+ // perfectly align with the end of the CSS page rect.
+ if (displayPort.XMost() + scrollOffset.x > contentRect.XMost()) {
+ displayPort.width = NS_MAX(0.0f, contentRect.XMost() - (displayPort.X() + scrollOffset.x));
+ }
+ if (displayPort.YMost() + scrollOffset.y > contentRect.YMost()) {
+ displayPort.height = NS_MAX(0.0f, contentRect.YMost() - (displayPort.Y() + scrollOffset.y));
+ }
+
+ // Round the displayport so we don't get any truncation, then get the nsIntRect
+ // from this.
+ displayPort.Round();
+ return nsIntRect(displayPort.x, displayPort.y, displayPort.width, displayPort.height);
+}
+
+void AsyncPanZoomController::SetDPI(int aDPI) {
+ mDPI = aDPI;
+}
+
+void AsyncPanZoomController::ScheduleComposite() {
+ if (mCompositorParent) {
+ mCompositorParent->ScheduleRenderOnCompositorThread();
+ }
+}
+
+void AsyncPanZoomController::RequestContentRepaint() {
+ mFrameMetrics.mDisplayPort = CalculatePendingDisplayPort();
+ mGeckoContentController->RequestContentRepaint(mFrameMetrics);
+}
+
+bool AsyncPanZoomController::SampleContentTransformForFrame(const TimeStamp& aSampleTime,
+ const FrameMetrics& aFrame,
+ const gfx3DMatrix& aCurrentTransform,
+ gfx3DMatrix* aNewTransform) {
+ // The eventual return value of this function. The compositor needs to know
+ // whether or not to advance by a frame as soon as it can. For example, if a
+ // fling is happening, it has to keep compositing so that the animation is
+ // smooth. If an animation frame is requested, it is the compositor's
+ // responsibility to schedule a composite.
+ bool requestAnimationFrame = false;
+
+ // Scales on the root layer, on what's currently painted.
+ float rootScaleX = aCurrentTransform.GetXScale(),
+ rootScaleY = aCurrentTransform.GetYScale();
+
+ nsIntPoint metricsScrollOffset(0, 0);
+ nsIntPoint scrollOffset;
+ float localScaleX, localScaleY;
+
+ {
+ MonitorAutoLock mon(mMonitor);
+
+ // If a fling is currently happening, apply it now. We can pull the updated
+ // metrics afterwards.
+ requestAnimationFrame = requestAnimationFrame || DoFling(aSampleTime - mLastSampleTime);
+
+ // Current local transform; this is not what's painted but rather what PZC has
+ // transformed due to touches like panning or pinching. Eventually, the root
+ // layer transform will become this during runtime, but we must wait for Gecko
+ // to repaint.
+ localScaleX = mFrameMetrics.mResolution.width;
+ localScaleY = mFrameMetrics.mResolution.height;
+
+ if (aFrame.IsScrollable()) {
+ metricsScrollOffset = aFrame.mViewportScrollOffset;
+ }
+
+ scrollOffset = mFrameMetrics.mViewportScrollOffset;
+ }
+
+ nsIntPoint scrollCompensation(
+ (scrollOffset.x / rootScaleX - metricsScrollOffset.x) * localScaleX,
+ (scrollOffset.y / rootScaleY - metricsScrollOffset.y) * localScaleY);
+
+ ViewTransform treeTransform(-scrollCompensation, localScaleX, localScaleY);
+ *aNewTransform = gfx3DMatrix(treeTransform) * aCurrentTransform;
+
+ mLastSampleTime = aSampleTime;
+
+ return requestAnimationFrame;
+}
+
+void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aViewportFrame, bool aIsFirstPaint) {
+ MonitorAutoLock monitor(mMonitor);
+
+ mLastContentPaintMetrics = aViewportFrame;
+
+ if (aIsFirstPaint || mFrameMetrics.IsDefault()) {
+ mX.StopTouch();
+ mY.StopTouch();
+ mFrameMetrics = aViewportFrame;
+ mFrameMetrics.mResolution.width = 1 / mFrameMetrics.mResolution.width;
+ mFrameMetrics.mResolution.height = 1 / mFrameMetrics.mResolution.height;
+ SetPageRect(mFrameMetrics.mCSSContentRect);
+ } else if (!mFrameMetrics.mContentRect.IsEqualEdges(aViewportFrame.mContentRect)) {
+ mFrameMetrics.mCSSContentRect = aViewportFrame.mCSSContentRect;
+ SetPageRect(mFrameMetrics.mCSSContentRect);
+ }
+}
+
+const FrameMetrics& AsyncPanZoomController::GetFrameMetrics() {
+ mMonitor.AssertCurrentThreadOwns();
+ return mFrameMetrics;
+}
+
+void AsyncPanZoomController::UpdateViewportSize(int aWidth, int aHeight) {
+ MonitorAutoLock mon(mMonitor);
+ FrameMetrics metrics = GetFrameMetrics();
+ metrics.mViewport = nsIntRect(0, 0, aWidth, aHeight);
+ mFrameMetrics = metrics;
+}
+
+}
+}
new file mode 100644
--- /dev/null
+++ b/gfx/layers/ipc/AsyncPanZoomController.h
@@ -0,0 +1,360 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=4 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_AsyncPanZoomController_h
+#define mozilla_layers_AsyncPanZoomController_h
+
+#include "GeckoContentController.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "InputData.h"
+#include "Axis.h"
+
+namespace mozilla {
+namespace layers {
+
+class CompositorParent;
+class GestureEventListener;
+
+/**
+ * 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 nsGUIEvent-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 {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AsyncPanZoomController)
+
+ typedef mozilla::MonitorAutoLock MonitorAutoLock;
+
+public:
+ enum GestureBehavior {
+ // The platform code is responsible for forwarding gesture events here. We
+ // will not attempt to generate gesture events from MultiTouchInputs.
+ DEFAULT_GESTURES,
+ // An instance of GestureEventListener is used to detect gestures. This is
+ // handled completely internally within this class.
+ USE_GESTURE_DETECTOR
+ };
+
+ AsyncPanZoomController(GeckoContentController* aController,
+ GestureBehavior aGestures = DEFAULT_GESTURES);
+ ~AsyncPanZoomController();
+
+ // --------------------------------------------------------------------------
+ // These methods must only be called on the controller/UI thread.
+ //
+
+ /**
+ * General handler for incoming input events. Manipulates the frame metrics
+ * basde on what type of input it is. For example, a PinchGestureEvent will
+ * cause scaling.
+ */
+
+ nsEventStatus HandleInputEvent(const InputData& aEvent);
+
+ /**
+ * Special handler for nsInputEvents. Also sets |aOutEvent| (which is assumed
+ * to be an already-existing instance of an nsInputEvent which may be an
+ * nsTouchEvent) to have its touch points in DOM space. This is so that the
+ * touches can be passed through the DOM and content can handle them.
+ *
+ * NOTE: Be careful of invoking the nsInputEvent variant. This can only be
+ * called on the main thread. See widget/InputData.h for more information on
+ * why we have InputData and nsInputEvent separated.
+ */
+ nsEventStatus HandleInputEvent(const nsInputEvent& aEvent,
+ nsInputEvent* aOutEvent);
+
+ /**
+ * Updates the viewport size, i.e. the dimensions of the frame (not
+ * necessarily the screen) content will actually be rendered onto in device
+ * pixels for example, a subframe will not take the entire screen, but we
+ * still want to know how big it is in device pixels. Ideally we want to be
+ * using CSS pixels everywhere inside here, but in this case we need to know
+ * how large of a displayport to set so we use these dimensions plus some
+ * extra.
+ *
+ * XXX: Use nsIntRect instead.
+ */
+ void UpdateViewportSize(int aWidth, int aHeight);
+
+ // --------------------------------------------------------------------------
+ // These methods must only be called on the compositor thread.
+ //
+
+ /**
+ * 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 applied directly to the
+ * shadow layer of the frame (do not multiply it in as the code already does
+ * this internally with |aCurrentTransform|.
+ *
+ * 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,
+ const FrameMetrics& aFrame,
+ const gfx3DMatrix& aCurrentTransform,
+ gfx3DMatrix* aNewTransform);
+
+ /**
+ * A shadow layer update has arrived. |aViewportFrame| is the new FrameMetrics
+ * for the top-level frame. |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& aViewportFrame, 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.
+ //
+
+ /**
+ * Sets the CSS page rect, and calculates a new page rect based on the zoom
+ * level of the current metrics and the passed in CSS page rect.
+ */
+ void SetPageRect(const gfx::Rect& aCSSPageRect);
+
+ /**
+ * Sets the DPI of the device for use within panning and zooming logic. It is
+ * a platform responsibility to set this on initialization of this class and
+ * whenever it changes.
+ */
+ void SetDPI(int aDPI);
+
+protected:
+ /**
+ * 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 method for long press gestures.
+ *
+ * XXX: Implement this.
+ */
+ nsEventStatus OnLongPress(const TapGestureInput& aEvent);
+
+ /**
+ * Helper method for single tap gestures.
+ *
+ * XXX: Implement this.
+ */
+ nsEventStatus OnSingleTapUp(const TapGestureInput& aEvent);
+
+ /**
+ * Helper method for a single tap confirmed.
+ *
+ * XXX: Implement this.
+ */
+ nsEventStatus OnSingleTapConfirmed(const TapGestureInput& aEvent);
+
+ /**
+ * Helper method for double taps.
+ *
+ * XXX: Implement this.
+ */
+ 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 nsIntPoint& 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 outward from that point.
+ *
+ * XXX: Fix focus point calculations.
+ */
+ void ScaleWithFocus(float aScale, const nsIntPoint& aFocus);
+
+ /**
+ * Schedules a composite on the compositor thread. Wrapper for
+ * CompositorParent::ScheduleRenderOnCompositorThread().
+ */
+ void ScheduleComposite();
+
+ /**
+ * 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();
+
+ /**
+ * 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(const MultiTouchInput& aEvent);
+
+ /**
+ * Gets a vector of the velocities of each axis.
+ */
+ const nsPoint GetVelocityVector();
+
+ /**
+ * Gets a reference to the first SingleTouchData from a MultiTouchInput. This
+ * gets only the first one and assumes the rest are either missing or not
+ * relevant.
+ */
+ SingleTouchData& GetFirstSingleTouch(const MultiTouchInput& aEvent);
+
+ /**
+ * Does any panning required due to a new touch event.
+ */
+ void TrackTouch(const MultiTouchInput& aEvent);
+
+ /**
+ * Recalculates the displayport. Ideally, this should paint an area bigger
+ * than the actual screen. The viewport refers to the size of the screen,
+ * while the displayport is the area actually painted by Gecko. We paint
+ * a larger area than the screen so that when you scroll down, you don't
+ * checkerboard immediately.
+ */
+ const nsIntRect CalculatePendingDisplayPort();
+
+ /**
+ * Utility function to send updated FrameMetrics to Gecko so that it can paint
+ * the displayport area. Calls into GeckoContentController to do the actual
+ * work.
+ */
+ void RequestContentRepaint();
+
+ /**
+ * 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();
+
+private:
+ 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 without axis lock */
+ PINCHING, /* nth touch-start, where n > 1. this mode allows pan and zoom */
+ };
+
+ nsRefPtr<CompositorParent> mCompositorParent;
+ nsRefPtr<GeckoContentController> mGeckoContentController;
+ nsRefPtr<GestureEventListener> mGestureEventListener;
+
+ // Both |mFrameMetrics| and |mLastContentPaintMetrics| are protected by the
+ // monitor. Do not read from or modify either of them without locking.
+ FrameMetrics mFrameMetrics;
+ // These are the metrics at last content paint, the most recent
+ // values we were notified of in NotifyLayersUpdate().
+ FrameMetrics mLastContentPaintMetrics;
+
+ AxisX mX;
+ AxisY mY;
+
+ Monitor mMonitor;
+
+ // 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.
+ PRInt32 mLastEventTime;
+ // The last time a repaint has been requested from Gecko.
+ PRInt32 mLastRepaint;
+
+ // 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).
+ nsIntPoint mLastZoomFocus;
+ PanZoomState mState;
+ int mDPI;
+
+ friend class Axis;
+};
+
+}
+}
+
+#endif // mozilla_layers_PanZoomController_h
new file mode 100644
--- /dev/null
+++ b/gfx/layers/ipc/Axis.cpp
@@ -0,0 +1,295 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=4 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 http://mozilla.org/MPL/2.0/. */
+
+#include "Axis.h"
+#include "AsyncPanZoomController.h"
+
+namespace mozilla {
+namespace layers {
+
+static const float EPSILON = 0.0001;
+
+/**
+ * Milliseconds per frame, used to judge how much displacement should have
+ * happened every frame based on the velocity calculated from touch events.
+ */
+static const float MS_PER_FRAME = 1000.0f / 60.0f;
+
+/**
+ * Maximum acceleration that can happen between two frames. Velocity is
+ * throttled if it's above this. This may happen if a time delta is very low,
+ * or we get a touch point very far away from the previous position for some
+ * reason.
+ */
+static const float MAX_EVENT_ACCELERATION = 12;
+
+/**
+ * Amount of friction applied during flings when going above
+ * VELOCITY_THRESHOLD.
+ */
+static const float FLING_FRICTION_FAST = 0.010;
+
+/**
+ * Amount of friction applied during flings when going below
+ * VELOCITY_THRESHOLD.
+ */
+static const float FLING_FRICTION_SLOW = 0.008;
+
+/**
+ * Maximum velocity before fling friction increases.
+ */
+static const float VELOCITY_THRESHOLD = 10;
+
+/**
+ * When flinging, if the velocity goes below this number, we just stop the
+ * animation completely. This is to prevent asymptotically approaching 0
+ * velocity and rerendering unnecessarily.
+ */
+static const float FLING_STOPPED_THRESHOLD = 0.1f;
+
+Axis::Axis(AsyncPanZoomController* aAsyncPanZoomController)
+ : mPos(0.0f),
+ mVelocity(0.0f),
+ mAsyncPanZoomController(aAsyncPanZoomController)
+{
+
+}
+
+void Axis::UpdateWithTouchAtDevicePoint(PRInt32 aPos, PRInt32 aTimeDelta) {
+ float newVelocity = MS_PER_FRAME * (mPos - aPos) / aTimeDelta;
+
+ bool curVelocityIsLow = fabsf(newVelocity) < 1.0f;
+ bool directionChange = (mVelocity > 0) != (newVelocity != 0);
+
+ // If a direction change has happened, or the current velocity due to this new
+ // touch is relatively low, then just apply it. If not, throttle it.
+ if (curVelocityIsLow || (directionChange && fabs(newVelocity) - EPSILON <= 0.0f)) {
+ mVelocity = newVelocity;
+ } else {
+ float maxChange = fabsf(mVelocity * aTimeDelta * MAX_EVENT_ACCELERATION);
+ mVelocity = NS_MIN(mVelocity + maxChange, NS_MAX(mVelocity - maxChange, newVelocity));
+ }
+
+ mVelocity = newVelocity;
+ mPos = aPos;
+}
+
+void Axis::StartTouch(PRInt32 aPos) {
+ mStartPos = aPos;
+ mPos = aPos;
+ mVelocity = 0.0f;
+}
+
+PRInt32 Axis::UpdateAndGetDisplacement(float aScale) {
+ PRInt32 displacement = NS_lround(mVelocity * aScale);
+ // If this displacement will cause an overscroll, throttle it. Can potentially
+ // bring it to 0 even if the velocity is high.
+ if (DisplacementWillOverscroll(displacement) != OVERSCROLL_NONE) {
+ displacement -= DisplacementWillOverscrollAmount(displacement);
+ }
+ return displacement;
+}
+
+float Axis::PanDistance() {
+ return fabsf(mPos - mStartPos);
+}
+
+void Axis::StopTouch() {
+ mVelocity = 0.0f;
+}
+
+bool Axis::FlingApplyFrictionOrCancel(const TimeDuration& aDelta) {
+ if (fabsf(mVelocity) <= FLING_STOPPED_THRESHOLD) {
+ // If the velocity is very low, just set it to 0 and stop the fling,
+ // otherwise we'll just asymptotically approach 0 and the user won't
+ // actually see any changes.
+ mVelocity = 0.0f;
+ return false;
+ } else if (fabsf(mVelocity) >= VELOCITY_THRESHOLD) {
+ mVelocity *= 1.0f - FLING_FRICTION_FAST * aDelta.ToMilliseconds();
+ } else {
+ mVelocity *= 1.0f - FLING_FRICTION_SLOW * aDelta.ToMilliseconds();
+ }
+ return true;
+}
+
+Axis::Overscroll Axis::GetOverscroll() {
+ // If the current pan takes the viewport to the left of or above the current
+ // page rect.
+ bool minus = GetOrigin() < GetPageStart();
+ // If the current pan takes the viewport to the right of or below the current
+ // page rect.
+ bool plus = GetViewportEnd() > GetPageEnd();
+ if (minus && plus) {
+ return OVERSCROLL_BOTH;
+ }
+ if (minus) {
+ return OVERSCROLL_MINUS;
+ }
+ if (plus) {
+ return OVERSCROLL_PLUS;
+ }
+ return OVERSCROLL_NONE;
+}
+
+PRInt32 Axis::GetExcess() {
+ switch (GetOverscroll()) {
+ case OVERSCROLL_MINUS: return GetOrigin() - GetPageStart();
+ case OVERSCROLL_PLUS: return GetViewportEnd() - GetPageEnd();
+ case OVERSCROLL_BOTH: return (GetViewportEnd() - GetPageEnd()) + (GetPageStart() - GetOrigin());
+ default: return 0;
+ }
+}
+
+Axis::Overscroll Axis::DisplacementWillOverscroll(PRInt32 aDisplacement) {
+ // If the current pan plus a displacement takes the viewport to the left of or
+ // above the current page rect.
+ bool minus = GetOrigin() + aDisplacement < GetPageStart();
+ // If the current pan plus a displacement takes the viewport to the right of or
+ // below the current page rect.
+ bool plus = GetViewportEnd() + aDisplacement > GetPageEnd();
+ if (minus && plus) {
+ return OVERSCROLL_BOTH;
+ }
+ if (minus) {
+ return OVERSCROLL_MINUS;
+ }
+ if (plus) {
+ return OVERSCROLL_PLUS;
+ }
+ return OVERSCROLL_NONE;
+}
+
+PRInt32 Axis::DisplacementWillOverscrollAmount(PRInt32 aDisplacement) {
+ switch (DisplacementWillOverscroll(aDisplacement)) {
+ case OVERSCROLL_MINUS: return (GetOrigin() + aDisplacement) - GetPageStart();
+ case OVERSCROLL_PLUS: return (GetViewportEnd() + aDisplacement) - GetPageEnd();
+ // Don't handle overscrolled in both directions; a displacement can't cause
+ // this, it must have already been zoomed out too far.
+ default: return 0;
+ }
+}
+
+Axis::Overscroll Axis::ScaleWillOverscroll(float aScale, PRInt32 aFocus) {
+ PRInt32 originAfterScale = NS_lround((GetOrigin() + aFocus) * aScale - aFocus);
+
+ bool both = ScaleWillOverscrollBothSides(aScale);
+ bool minus = originAfterScale < NS_lround(GetPageStart() * aScale);
+ bool plus = (originAfterScale + GetViewportLength()) > NS_lround(GetPageEnd() * aScale);
+
+ if ((minus && plus) || both) {
+ return OVERSCROLL_BOTH;
+ }
+ if (minus) {
+ return OVERSCROLL_MINUS;
+ }
+ if (plus) {
+ return OVERSCROLL_PLUS;
+ }
+ return OVERSCROLL_NONE;
+}
+
+PRInt32 Axis::ScaleWillOverscrollAmount(float aScale, PRInt32 aFocus) {
+ PRInt32 originAfterScale = NS_lround((GetOrigin() + aFocus) * aScale - aFocus);
+ switch (ScaleWillOverscroll(aScale, aFocus)) {
+ case OVERSCROLL_MINUS: return originAfterScale - NS_lround(GetPageStart() * aScale);
+ case OVERSCROLL_PLUS: return (originAfterScale + GetViewportLength()) - NS_lround(GetPageEnd() * aScale);
+ // Don't handle OVERSCROLL_BOTH. Client code is expected to deal with it.
+ default: return 0;
+ }
+}
+
+float Axis::GetVelocity() {
+ return mVelocity;
+}
+
+PRInt32 Axis::GetViewportEnd() {
+ return GetOrigin() + GetViewportLength();
+}
+
+PRInt32 Axis::GetPageEnd() {
+ return GetPageStart() + GetPageLength();
+}
+
+PRInt32 Axis::GetOrigin() {
+ nsIntPoint origin = mAsyncPanZoomController->GetFrameMetrics().mViewportScrollOffset;
+ return GetPointOffset(origin);
+}
+
+PRInt32 Axis::GetViewportLength() {
+ nsIntRect viewport = mAsyncPanZoomController->GetFrameMetrics().mViewport;
+ return GetRectLength(viewport);
+}
+
+PRInt32 Axis::GetPageStart() {
+ nsIntRect pageRect = mAsyncPanZoomController->GetFrameMetrics().mContentRect;
+ return GetRectOffset(pageRect);
+}
+
+PRInt32 Axis::GetPageLength() {
+ nsIntRect pageRect = mAsyncPanZoomController->GetFrameMetrics().mContentRect;
+ return GetRectLength(pageRect);
+}
+
+bool Axis::ScaleWillOverscrollBothSides(float aScale) {
+ const FrameMetrics& metrics = mAsyncPanZoomController->GetFrameMetrics();
+
+ float currentScale = metrics.mResolution.width;
+ gfx::Rect cssContentRect = metrics.mCSSContentRect;
+ cssContentRect.ScaleRoundIn(currentScale * aScale);
+
+ nsIntRect contentRect = nsIntRect(cssContentRect.x,
+ cssContentRect.y,
+ cssContentRect.width,
+ cssContentRect.height);
+
+ return GetRectLength(contentRect) < GetRectLength(metrics.mViewport);
+}
+
+AxisX::AxisX(AsyncPanZoomController* aAsyncPanZoomController)
+ : Axis(aAsyncPanZoomController)
+{
+
+}
+
+PRInt32 AxisX::GetPointOffset(const nsIntPoint& aPoint)
+{
+ return aPoint.x;
+}
+
+PRInt32 AxisX::GetRectLength(const nsIntRect& aRect)
+{
+ return aRect.width;
+}
+
+PRInt32 AxisX::GetRectOffset(const nsIntRect& aRect)
+{
+ return aRect.x;
+}
+
+AxisY::AxisY(AsyncPanZoomController* aAsyncPanZoomController)
+ : Axis(aAsyncPanZoomController)
+{
+
+}
+
+PRInt32 AxisY::GetPointOffset(const nsIntPoint& aPoint)
+{
+ return aPoint.y;
+}
+
+PRInt32 AxisY::GetRectLength(const nsIntRect& aRect)
+{
+ return aRect.height;
+}
+
+PRInt32 AxisY::GetRectOffset(const nsIntRect& aRect)
+{
+ return aRect.y;
+}
+
+}
+}
new file mode 100644
--- /dev/null
+++ b/gfx/layers/ipc/Axis.h
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=4 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_Axis_h
+#define mozilla_layers_Axis_h
+
+#include "nsGUIEvent.h"
+#include "mozilla/TimeStamp.h"
+
+namespace mozilla {
+namespace layers {
+
+class AsyncPanZoomController;
+
+/**
+ * Helper class to maintain each axis of movement (X,Y) for panning and zooming.
+ * Note that everything here is specific to one axis; that is, the X axis knows
+ * nothing about the Y axis and vice versa.
+ */
+class Axis {
+public:
+ Axis(AsyncPanZoomController* aAsyncPanZoomController);
+
+ enum Overscroll {
+ // Overscroll is not happening at all.
+ OVERSCROLL_NONE = 0,
+ // Overscroll is happening in the negative direction. This means either to
+ // the left or to the top depending on the axis.
+ OVERSCROLL_MINUS,
+ // Overscroll is happening in the positive direction. This means either to
+ // the right or to the bottom depending on the axis.
+ OVERSCROLL_PLUS,
+ // Overscroll is happening both ways. This only means something when the
+ // page is scaled out to a smaller size than the viewport.
+ OVERSCROLL_BOTH
+ };
+
+ /**
+ * Notify this Axis that a new touch has been received, including a time delta
+ * indicating how long it has been since the previous one. This triggers a
+ * recalculation of velocity.
+ */
+ void UpdateWithTouchAtDevicePoint(PRInt32 aPos, PRInt32 aTimeDelta);
+
+ /**
+ * Notify this Axis that a touch has begun, i.e. the user has put their finger
+ * on the screen but has not yet tried to pan.
+ */
+ void StartTouch(PRInt32 aPos);
+
+ /**
+ * Notify this Axis that a touch has ended. Useful for stopping flings when a
+ * user puts their finger down in the middle of one (i.e. to stop a previous
+ * touch including its fling so that a new one can take its place).
+ */
+ void StopTouch();
+
+ /**
+ * Gets displacement that should have happened since the previous touch.
+ * Note: Does not reset the displacement. It gets recalculated on the next
+ * updateWithTouchAtDevicePoint(), however it is not safe to assume this will
+ * be the same on every call. This also checks for page boundaries and will
+ * return an adjusted displacement to prevent the viewport from overscrolling
+ * the page rect. An example of where this might matter is when you call it,
+ * apply a displacement that takes you to the boundary of the page, then call
+ * it again. The result will be different in this case.
+ */
+ PRInt32 UpdateAndGetDisplacement(float aScale);
+
+ /**
+ * Gets the distance between the starting position of the touch supplied in
+ * startTouch() and the current touch from the last
+ * updateWithTouchAtDevicePoint().
+ */
+ float PanDistance();
+
+ /**
+ * Applies friction during a fling, or cancels the fling if the velocity is
+ * too low. Returns true if the fling should continue to another frame, or
+ * false if it should end. |aDelta| is the amount of time that has passed
+ * since the last time friction was applied.
+ */
+ bool FlingApplyFrictionOrCancel(const TimeDuration& aDelta);
+
+ /**
+ * Gets the overscroll state of the axis in its current position.
+ */
+ Overscroll GetOverscroll();
+
+ /**
+ * If there is overscroll, returns the amount. Sign depends on in what
+ * direction it is overscrolling. Positive excess means that it is
+ * overscrolling in the positive direction, whereas negative excess means
+ * that it is overscrolling in the negative direction. If there is overscroll
+ * in both directions, this returns 0; it assumes that you check
+ * GetOverscroll() first.
+ */
+ PRInt32 GetExcess();
+
+ /**
+ * Gets the raw velocity of this axis at this moment.
+ */
+ float GetVelocity();
+
+ /**
+ * Gets the overscroll state of the axis given an additional displacement.
+ * That is to say, if the given displacement is applied, this will tell you
+ * whether or not it will overscroll, and in what direction.
+ */
+ Overscroll DisplacementWillOverscroll(PRInt32 aDisplacement);
+
+ /**
+ * If a displacement will overscroll the axis, this returns the amount and in
+ * what direction. Similar to getExcess() but takes a displacement to apply.
+ */
+ PRInt32 DisplacementWillOverscrollAmount(PRInt32 aDisplacement);
+
+ /**
+ * Gets the overscroll state of the axis given a scaling of the page. That is
+ * to say, if the given scale is applied, this will tell you whether or not
+ * it will overscroll, and in what direction.
+ *
+ * |aFocus| is the point at which the scale is focused at. We will offset the
+ * scroll offset in such a way that it remains in the same place on the page
+ * relative.
+ */
+ Overscroll ScaleWillOverscroll(float aScale, PRInt32 aFocus);
+
+ /**
+ * If a scale will overscroll the axis, this returns the amount and in what
+ * direction. Similar to getExcess() but takes a displacement to apply.
+ *
+ * |aFocus| is the point at which the scale is focused at. We will offset the
+ * scroll offset in such a way that it remains in the same place on the page
+ * relative.
+ */
+ PRInt32 ScaleWillOverscrollAmount(float aScale, PRInt32 aFocus);
+
+ /**
+ * Checks if an axis will overscroll in both directions by computing the
+ * content rect and checking that its height/width (depending on the axis)
+ * does not overextend past the viewport.
+ *
+ * This gets called by ScaleWillOverscroll().
+ */
+ bool ScaleWillOverscrollBothSides(float aScale);
+
+ PRInt32 GetOrigin();
+ PRInt32 GetViewportLength();
+ PRInt32 GetPageStart();
+ PRInt32 GetPageLength();
+ PRInt32 GetViewportEnd();
+ PRInt32 GetPageEnd();
+
+ virtual PRInt32 GetPointOffset(const nsIntPoint& aPoint) = 0;
+ virtual PRInt32 GetRectLength(const nsIntRect& aRect) = 0;
+ virtual PRInt32 GetRectOffset(const nsIntRect& aRect) = 0;
+
+protected:
+ PRInt32 mPos;
+ PRInt32 mStartPos;
+ float mVelocity;
+ nsRefPtr<AsyncPanZoomController> mAsyncPanZoomController;
+};
+
+class AxisX : public Axis {
+public:
+ AxisX(AsyncPanZoomController* mAsyncPanZoomController);
+ virtual PRInt32 GetPointOffset(const nsIntPoint& aPoint);
+ virtual PRInt32 GetRectLength(const nsIntRect& aRect);
+ virtual PRInt32 GetRectOffset(const nsIntRect& aRect);
+};
+
+class AxisY : public Axis {
+public:
+ AxisY(AsyncPanZoomController* mAsyncPanZoomController);
+ virtual PRInt32 GetPointOffset(const nsIntPoint& aPoint);
+ virtual PRInt32 GetRectLength(const nsIntRect& aRect);
+ virtual PRInt32 GetRectOffset(const nsIntRect& aRect);
+};
+
+}
+}
+
+#endif
new file mode 100644
--- /dev/null
+++ b/gfx/layers/ipc/GeckoContentController.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=4 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_GeckoContentController_h
+#define mozilla_layers_GeckoContentController_h
+
+#include "Layers.h"
+
+namespace mozilla {
+namespace layers {
+
+class GeckoContentController {
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GeckoContentController)
+
+ /**
+ * Requests a paint of the given FrameMetrics |aFrameMetrics| from Gecko.
+ * Implementations per-platform are responsible for actually handling this.
+ */
+ virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) = 0;
+};
+
+}
+}
+
+#endif // mozilla_layers_GeckoContentController_h
new file mode 100644
--- /dev/null
+++ b/gfx/layers/ipc/GestureEventListener.cpp
@@ -0,0 +1,208 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=4 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 http://mozilla.org/MPL/2.0/. */
+
+#include "GestureEventListener.h"
+#include "AsyncPanZoomController.h"
+
+namespace mozilla {
+namespace layers {
+
+GestureEventListener::GestureEventListener(AsyncPanZoomController* aAsyncPanZoomController)
+ : mAsyncPanZoomController(aAsyncPanZoomController),
+ mState(NoGesture)
+{
+}
+
+GestureEventListener::~GestureEventListener()
+{
+}
+
+nsEventStatus GestureEventListener::HandleInputEvent(const InputData& aEvent)
+{
+ if (aEvent.mInputType != MULTITOUCH_INPUT) {
+ return nsEventStatus_eIgnore;
+ }
+
+ const MultiTouchInput& event = static_cast<const MultiTouchInput&>(aEvent);
+ switch (event.mType)
+ {
+ case MultiTouchInput::MULTITOUCH_START:
+ case MultiTouchInput::MULTITOUCH_ENTER: {
+ for (size_t i = 0; i < event.mTouches.Length(); i++) {
+ bool foundAlreadyExistingTouch = false;
+ for (size_t j = 0; j < mTouches.Length(); j++) {
+ if (mTouches[j].mIdentifier == event.mTouches[i].mIdentifier) {
+ foundAlreadyExistingTouch = true;
+ }
+ }
+
+ NS_WARN_IF_FALSE(!foundAlreadyExistingTouch, "Tried to add a touch that already exists");
+
+ // If we didn't find a touch in our list that matches this, then add it.
+ // If it already existed, we don't want to add it twice because that
+ // messes with our touch move/end code.
+ if (!foundAlreadyExistingTouch) {
+ mTouches.AppendElement(event.mTouches[i]);
+ }
+ }
+
+ if (mTouches.Length() == 2) {
+ // Another finger has been added; it can't be a tap anymore.
+ HandleTapCancel(event);
+ }
+
+ break;
+ }
+ case MultiTouchInput::MULTITOUCH_MOVE: {
+ // If we move at all, just bail out of the tap. We need to change this so
+ // that there's some tolerance in the future.
+ HandleTapCancel(event);
+
+ bool foundAlreadyExistingTouch = false;
+ for (size_t i = 0; i < mTouches.Length(); i++) {
+ for (size_t j = 0; j < event.mTouches.Length(); j++) {
+ if (mTouches[i].mIdentifier == event.mTouches[j].mIdentifier) {
+ foundAlreadyExistingTouch = true;
+ mTouches[i] = event.mTouches[j];
+ }
+ }
+ }
+
+ NS_WARN_IF_FALSE(foundAlreadyExistingTouch, "Touch moved, but not in list");
+
+ break;
+ }
+ case MultiTouchInput::MULTITOUCH_END:
+ case MultiTouchInput::MULTITOUCH_LEAVE: {
+ bool foundAlreadyExistingTouch = false;
+ for (size_t i = 0; i < event.mTouches.Length() && !foundAlreadyExistingTouch; i++) {
+ for (size_t j = 0; j < mTouches.Length() && !foundAlreadyExistingTouch; j++) {
+ if (event.mTouches[i].mIdentifier == mTouches[j].mIdentifier) {
+ foundAlreadyExistingTouch = true;
+ mTouches.RemoveElementAt(j);
+ }
+ }
+ }
+
+ NS_WARN_IF_FALSE(foundAlreadyExistingTouch, "Touch ended, but not in list");
+
+ if (event.mTime - mTouchStartTime <= MAX_TAP_TIME) {
+ // XXX: Incorrect use of the tap event. In the future, we want to send this
+ // on NS_TOUCH_END, then have a short timer afterwards which sends
+ // SingleTapConfirmed. Since we don't have double taps yet, this is fine for
+ // now.
+ if (HandleSingleTapUpEvent(event) == nsEventStatus_eConsumeNoDefault) {
+ return nsEventStatus_eConsumeNoDefault;
+ }
+
+ if (HandleSingleTapConfirmedEvent(event) == nsEventStatus_eConsumeNoDefault) {
+ return nsEventStatus_eConsumeNoDefault;
+ }
+ }
+
+ break;
+ }
+ case MultiTouchInput::MULTITOUCH_CANCEL:
+ // This gets called if there's a touch that has to bail for weird reasons
+ // like pinching and then moving away from the window that the pinch was
+ // started in without letting go of the screen.
+ HandlePinchGestureEvent(event, true);
+ break;
+ }
+
+ return HandlePinchGestureEvent(event, false);
+}
+
+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::HandleSingleTapUpEvent(const MultiTouchInput& aEvent)
+{
+ TapGestureInput tapEvent(TapGestureInput::TAPGESTURE_UP, aEvent.mTime, aEvent.mTouches[0].mScreenPoint);
+ mAsyncPanZoomController->HandleInputEvent(tapEvent);
+
+ return nsEventStatus_eConsumeDoDefault;
+}
+
+nsEventStatus GestureEventListener::HandleSingleTapConfirmedEvent(const MultiTouchInput& aEvent)
+{
+ TapGestureInput tapEvent(TapGestureInput::TAPGESTURE_CONFIRMED, aEvent.mTime, aEvent.mTouches[0].mScreenPoint);
+ mAsyncPanZoomController->HandleInputEvent(tapEvent);
+
+ return nsEventStatus_eConsumeDoDefault;
+}
+
+nsEventStatus GestureEventListener::HandleTapCancel(const MultiTouchInput& aEvent)
+{
+ // XXX: In the future we will have to actually send a cancel notification to
+ // Gecko, but for now since we're doing both the "SingleUp" and
+ // "SingleConfirmed" notifications together, there's no need to cancel either
+ // one.
+ mTouchStartTime = 0;
+ return nsEventStatus_eConsumeDoDefault;
+}
+
+AsyncPanZoomController* GestureEventListener::GetAsyncPanZoomController() {
+ return mAsyncPanZoomController;
+}
+
+}
+}
new file mode 100644
--- /dev/null
+++ b/gfx/layers/ipc/GestureEventListener.h
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=4 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_GestureEventListener_h
+#define mozilla_layers_GestureEventListener_h
+
+#include "mozilla/RefPtr.h"
+#include "InputData.h"
+#include "Axis.h"
+
+namespace mozilla {
+namespace layers {
+
+/**
+ * Platform-non-specific, generalized gesture event listener. This class
+ * intercepts all touches events on their way to AsyncPanZoomController and
+ * determines whether or not they are part of a gesture.
+ *
+ * For example, seeing that two fingers are on the screen means that the user
+ * wants to do a pinch gesture, so we don't forward the touches along to
+ * AsyncPanZoomController since it will think that they are just trying to pan
+ * the screen. Instead, we generate a PinchGestureInput and send that. If the
+ * touch event is not part of a gesture, we just return nsEventStatus_eIgnore
+ * and AsyncPanZoomController is expected to handle it.
+ *
+ * Android doesn't use this class because it has its own built-in gesture event
+ * listeners that should generally be preferred.
+ */
+class GestureEventListener {
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GestureEventListener)
+
+ GestureEventListener(AsyncPanZoomController* aAsyncPanZoomController);
+ ~GestureEventListener();
+
+ // --------------------------------------------------------------------------
+ // These methods must only be called on the controller/UI thread.
+ //
+
+ /**
+ * General input handler for a touch event. If the touch event is not a part
+ * of a gesture, then we pass it along to AsyncPanZoomController. Otherwise,
+ * it gets consumed here and never forwarded along.
+ */
+ nsEventStatus HandleInputEvent(const InputData& aEvent);
+
+ /**
+ * Returns the AsyncPanZoomController stored on this class and used for
+ * callbacks.
+ */
+ AsyncPanZoomController* GetAsyncPanZoomController();
+
+protected:
+ enum GestureState {
+ NoGesture = 0,
+ InPinchGesture
+ };
+
+ /**
+ * Maximum time for a touch on the screen and corresponding lift of the finger
+ * to be considered a tap.
+ */
+ enum { MAX_TAP_TIME = 500 };
+
+ /**
+ * Attempts to handle the event as a pinch event. If it is not a pinch event,
+ * then we simply tell the next consumer to consume the event instead.
+ *
+ * |aClearTouches| marks whether or not to terminate any pinch currently
+ * happening.
+ */
+ nsEventStatus HandlePinchGestureEvent(const MultiTouchInput& aEvent, bool aClearTouches);
+
+ /**
+ * Attempts to handle the event as a single tap event, which highlights links
+ * before opening them. In general, this will not attempt to block the touch
+ * event from being passed along to AsyncPanZoomController since APZC needs to
+ * know about touches ending (and we only know if a touch was a tap once it
+ * ends).
+ */
+ nsEventStatus HandleSingleTapUpEvent(const MultiTouchInput& aEvent);
+
+ /**
+ * Attempts to handle a single tap confirmation. This is what will actually
+ * open links, etc. In general, this will not attempt to block the touch event
+ * from being passed along to AsyncPanZoomController since APZC needs to know
+ * about touches ending (and we only know if a touch was a tap once it ends).
+ */
+ nsEventStatus HandleSingleTapConfirmedEvent(const MultiTouchInput& aEvent);
+
+ /**
+ * Attempts to handle a tap event cancellation. This happens when we think
+ * something was a tap but it actually wasn't. In general, this will not
+ * attempt to block the touch event from being passed along to
+ * AsyncPanZoomController since APZC needs to know about touches ending (and
+ * we only know if a touch was a tap once it ends).
+ */
+ nsEventStatus HandleTapCancel(const MultiTouchInput& aEvent);
+
+ nsRefPtr<AsyncPanZoomController> mAsyncPanZoomController;
+
+ /**
+ * Array containing all active touches. When a touch happens it, gets added to
+ * this array, even if we choose not to handle it. When it ends, we remove it.
+ */
+ nsTArray<SingleTouchData> mTouches;
+ GestureState mState;
+
+ /**
+ * Previous span calculated for the purposes of setting inside a
+ * PinchGestureInput.
+ */
+ float mPreviousSpan;
+
+ /**
+ * Stores the time a touch started, used for detecting a tap gesture. Only
+ * valid when there's exactly one touch in mTouches.
+ */
+ PRUint64 mTouchStartTime;
+};
+
+}
+}
+
+#endif
new file mode 100644
--- /dev/null
+++ b/widget/InputData.h
@@ -0,0 +1,325 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef InputData_h__
+#define InputData_h__
+
+#include "nsGUIEvent.h"
+#include "nsDOMTouchEvent.h"
+#include "nsDebug.h"
+
+namespace mozilla {
+
+
+enum InputType
+{
+ MULTITOUCH_INPUT,
+ PINCHGESTURE_INPUT,
+ TAPGESTURE_INPUT
+};
+
+class MultiTouchInput;
+class PinchGestureInput;
+class TapGestureInput;
+
+// This looks unnecessary now, but as we add more and more classes that derive
+// from InputType (eventually probably almost as many as nsGUIEvent.h has), it
+// will be more and more clear what's going on with a macro that shortens the
+// definition of the RTTI functions.
+#define INPUTDATA_AS_CHILD_TYPE(type, enumID) \
+ const type& As##type() const \
+ { \
+ NS_ABORT_IF_FALSE(mInputType == enumID, "Invalid cast of InputData."); \
+ return (const type&) *this; \
+ }
+
+/** Base input data class. Should never be instantiated. */
+class InputData
+{
+public:
+ InputType mInputType;
+ // Time in milliseconds that this data is relevant to. This only really
+ // matters when this data is used as an event. We use PRUint32 instead of
+ // TimeStamp because it is easier to convert from nsInputEvent. The time is
+ // platform-specific but it in the case of B2G and Fennec it is since startup.
+ PRUint32 mTime;
+
+ INPUTDATA_AS_CHILD_TYPE(MultiTouchInput, MULTITOUCH_INPUT)
+ INPUTDATA_AS_CHILD_TYPE(PinchGestureInput, PINCHGESTURE_INPUT)
+ INPUTDATA_AS_CHILD_TYPE(TapGestureInput, TAPGESTURE_INPUT)
+
+protected:
+ InputData(InputType aInputType, PRUint32 aTime)
+ : mInputType(aInputType),
+ mTime(aTime)
+ {
+
+
+ }
+};
+
+/**
+ * Data container for a single touch input. Similar to nsDOMTouch, but used in
+ * off-main-thread situations. This is more for just storing touch data, whereas
+ * nsDOMTouch derives from nsIDOMTouch so it is more useful for dispatching
+ * through the DOM (which can only happen on the main thread). nsDOMTouch also
+ * bears the problem of storing pointers to nsIWidget instances which can only
+ * be used on the main thread, so if instead we used nsDOMTouch and ever set
+ * these pointers off-main-thread, Bad Things Can Happen(tm).
+ *
+ * Note that this doesn't inherit from InputData because this itself is not an
+ * event. It is only a container/struct that should have any number of instances
+ * within a MultiTouchInput.
+ *
+ * fixme/bug 775746: Make nsDOMTouch inherit from this class.
+ */
+class SingleTouchData
+{
+public:
+ SingleTouchData(PRInt32 aIdentifier,
+ nsIntPoint aScreenPoint,
+ nsIntPoint aRadius,
+ float aRotationAngle,
+ float aForce)
+ : mIdentifier(aIdentifier),
+ mScreenPoint(aScreenPoint),
+ mRadius(aRadius),
+ mRotationAngle(aRotationAngle),
+ mForce(aForce)
+ {
+
+
+ }
+
+ // A unique number assigned to each SingleTouchData within a MultiTouchInput so
+ // that they can be easily distinguished when handling a touch start/move/end.
+ PRInt32 mIdentifier;
+
+ // Point on the screen that the touch hit, in device pixels. They are
+ // coordinates on the screen.
+ nsIntPoint mScreenPoint;
+
+ // Radius that the touch covers, i.e. if you're using your thumb it will
+ // probably be larger than using your pinky, even with the same force.
+ // Radius can be different along x and y. For example, if you press down with
+ // your entire finger vertically, the y radius will be much larger than the x
+ // radius.
+ nsIntPoint mRadius;
+
+ float mRotationAngle;
+
+ // How hard the screen is being pressed.
+ float mForce;
+};
+
+/**
+ * Similar to nsTouchEvent, but for use off-main-thread. Also only stores a
+ * screen touch point instead of the many different coordinate spaces nsTouchEvent
+ * stores its touch point in. This includes a way to initialize itself from an
+ * nsTouchEvent by copying all relevant data over. Note that this copying from
+ * nsTouchEvent functionality can only be used on the main thread.
+ *
+ * Stores an array of SingleTouchData.
+ */
+class MultiTouchInput : public InputData
+{
+public:
+ enum MultiTouchType
+ {
+ MULTITOUCH_START,
+ MULTITOUCH_MOVE,
+ MULTITOUCH_END,
+ MULTITOUCH_ENTER,
+ MULTITOUCH_LEAVE,
+ MULTITOUCH_CANCEL
+ };
+
+ MultiTouchInput(MultiTouchType aType, PRUint32 aTime)
+ : InputData(MULTITOUCH_INPUT, aTime),
+ mType(aType)
+ {
+
+
+ }
+
+ MultiTouchInput(const nsTouchEvent& aTouchEvent)
+ : InputData(MULTITOUCH_INPUT, aTouchEvent.time)
+ {
+ NS_ABORT_IF_FALSE(NS_IsMainThread(),
+ "Can only copy from nsTouchEvent on main thread");
+
+ switch (aTouchEvent.message) {
+ case NS_TOUCH_START:
+ mType = MULTITOUCH_START;
+ break;
+ case NS_TOUCH_MOVE:
+ mType = MULTITOUCH_MOVE;
+ break;
+ case NS_TOUCH_END:
+ mType = MULTITOUCH_END;
+ break;
+ case NS_TOUCH_ENTER:
+ mType = MULTITOUCH_ENTER;
+ break;
+ case NS_TOUCH_LEAVE:
+ mType = MULTITOUCH_LEAVE;
+ break;
+ case NS_TOUCH_CANCEL:
+ mType = MULTITOUCH_CANCEL;
+ break;
+ default:
+ NS_WARNING("Did not assign a type to a MultiTouchInput");
+ break;
+ }
+
+ for (size_t i = 0; i < aTouchEvent.touches.Length(); i++) {
+ nsDOMTouch* domTouch = (nsDOMTouch*)(aTouchEvent.touches[i].get());
+
+ // Extract data from weird interfaces.
+ PRInt32 identifier, radiusX, radiusY;
+ float rotationAngle, force;
+ domTouch->GetIdentifier(&identifier);
+ domTouch->GetRadiusX(&radiusX);
+ domTouch->GetRadiusY(&radiusY);
+ domTouch->GetRotationAngle(&rotationAngle);
+ domTouch->GetForce(&force);
+
+ SingleTouchData data(identifier,
+ domTouch->mRefPoint,
+ nsIntPoint(radiusX, radiusY),
+ rotationAngle,
+ force);
+
+ mTouches.AppendElement(data);
+ }
+ }
+
+ // This conversion from nsMouseEvent to MultiTouchInput is needed because on
+ // the B2G emulator we can only receive mouse events, but we need to be able
+ // to pan correctly. To do this, we convert the events into a format that the
+ // panning code can handle. This code is very limited and only supports
+ // SingleTouchData. It also sends garbage for the identifier, radius, force
+ // and rotation angle.
+ MultiTouchInput(const nsMouseEvent& aMouseEvent)
+ : InputData(MULTITOUCH_INPUT, aMouseEvent.time)
+ {
+ NS_ABORT_IF_FALSE(NS_IsMainThread(),
+ "Can only copy from nsMouseEvent on main thread");
+ switch (aMouseEvent.message) {
+ case NS_MOUSE_BUTTON_DOWN:
+ mType = MULTITOUCH_START;
+ break;
+ case NS_MOUSE_MOVE:
+ mType = MULTITOUCH_MOVE;
+ break;
+ case NS_MOUSE_BUTTON_UP:
+ mType = MULTITOUCH_END;
+ break;
+ // The mouse pointer has been interrupted in an implementation-specific
+ // manner, such as a synchronous event or action cancelling the touch, or a
+ // touch point leaving the document window and going into a non-document
+ // area capable of handling user interactions.
+ case NS_MOUSE_EXIT:
+ mType = MULTITOUCH_CANCEL;
+ break;
+ default:
+ NS_WARNING("Did not assign a type to a MultiTouchInput");
+ break;
+ }
+
+ mTouches.AppendElement(SingleTouchData(0,
+ aMouseEvent.refPoint,
+ nsIntPoint(1, 1),
+ 180.0f,
+ 1.0f));
+ }
+
+ MultiTouchType mType;
+ nsTArray<SingleTouchData> mTouches;
+};
+
+/**
+ * Encapsulation class for pinch events. In general, these will be generated by
+ * a gesture listener by looking at SingleTouchData/MultiTouchInput instances and
+ * determining whether or not the user was trying to do a gesture.
+ */
+class PinchGestureInput : public InputData
+{
+public:
+ enum PinchGestureType
+ {
+ PINCHGESTURE_START,
+ PINCHGESTURE_SCALE,
+ PINCHGESTURE_END
+ };
+
+ PinchGestureInput(PinchGestureType aType,
+ PRUint32 aTime,
+ const nsIntPoint& aFocusPoint,
+ float aCurrentSpan,
+ float aPreviousSpan)
+ : InputData(PINCHGESTURE_INPUT, aTime),
+ mType(aType),
+ mFocusPoint(aFocusPoint),
+ mCurrentSpan(aCurrentSpan),
+ mPreviousSpan(aPreviousSpan)
+ {
+
+
+ }
+
+ PinchGestureType mType;
+
+ // Center point of the pinch gesture. That is, if there are two fingers on the
+ // screen, it is their midpoint. In the case of more than two fingers, the
+ // point is implementation-specific, but can for example be the midpoint
+ // between the very first and very last touch. This is in device pixels and
+ // are the coordinates on the screen of this midpoint.
+ nsIntPoint mFocusPoint;
+
+ // The distance in device pixels (though as a float for increased precision
+ // and because it is the distance along both the x and y axis) between the
+ // touches responsible for the pinch gesture.
+ float mCurrentSpan;
+
+ // The previous |mCurrentSpan| in the PinchGestureInput preceding this one.
+ // This is only really relevant during a PINCHGESTURE_SCALE because when it is
+ // of this type then there must have been a history of spans.
+ float mPreviousSpan;
+};
+
+/**
+ * Encapsulation class for tap events. In general, these will be generated by
+ * a gesture listener by looking at SingleTouchData/MultiTouchInput instances and
+ * determining whether or not the user was trying to do a gesture.
+ */
+class TapGestureInput : public InputData
+{
+public:
+ enum TapGestureType
+ {
+ TAPGESTURE_LONG,
+ TAPGESTURE_UP,
+ TAPGESTURE_CONFIRMED,
+ TAPGESTURE_DOUBLE,
+ TAPGESTURE_CANCEL
+ };
+
+ TapGestureInput(TapGestureType aType, PRUint32 aTime, const nsIntPoint& aPoint)
+ : InputData(TAPGESTURE_INPUT, aTime),
+ mType(aType),
+ mPoint(aPoint)
+ {
+
+
+ }
+
+ TapGestureType mType;
+ nsIntPoint mPoint;
+};
+
+}
+
+#endif // InputData_h__
--- a/widget/Makefile.in
+++ b/widget/Makefile.in
@@ -57,16 +57,17 @@ EXPORTS_mozilla = \
ifdef MOZ_INSTRUMENT_EVENT_LOOP
EXPORTS_mozilla += \
WidgetTraceEvent.h \
$(NULL)
endif
EXPORTS = \
+ InputData.h \
nsIWidget.h \
nsGUIEvent.h \
nsEvent.h \
nsNativeWidget.h \
nsWidgetInitData.h \
nsWidgetsCID.h \
nsIPluginWidget.h \
nsINativeKeyBindings.h \