michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set sw=2 ts=8 et tw=80 : */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include // for fabsf, fabs, atan2 michael@0: #include // for uint32_t, uint64_t michael@0: #include // for int32_t michael@0: #include // for max, min michael@0: #include "AnimationCommon.h" // for ComputedTimingFunction michael@0: #include "AsyncPanZoomController.h" // for AsyncPanZoomController, etc michael@0: #include "CompositorParent.h" // for CompositorParent michael@0: #include "FrameMetrics.h" // for FrameMetrics, etc michael@0: #include "GestureEventListener.h" // for GestureEventListener michael@0: #include "InputData.h" // for MultiTouchInput, etc michael@0: #include "Units.h" // for CSSRect, CSSPoint, etc michael@0: #include "UnitTransforms.h" // for TransformTo michael@0: #include "base/message_loop.h" // for MessageLoop michael@0: #include "base/task.h" // for NewRunnableMethod, etc michael@0: #include "base/tracked.h" // for FROM_HERE michael@0: #include "gfxPrefs.h" // for gfxPrefs michael@0: #include "gfxTypes.h" // for gfxFloat michael@0: #include "mozilla/Assertions.h" // for MOZ_ASSERT, etc michael@0: #include "mozilla/BasicEvents.h" // for Modifiers, MODIFIER_* michael@0: #include "mozilla/ClearOnShutdown.h" // for ClearOnShutdown michael@0: #include "mozilla/Constants.h" // for M_PI michael@0: #include "mozilla/EventForwards.h" // for nsEventStatus_* michael@0: #include "mozilla/Preferences.h" // for Preferences michael@0: #include "mozilla/ReentrantMonitor.h" // for ReentrantMonitorAutoEnter, etc michael@0: #include "mozilla/StaticPtr.h" // for StaticAutoPtr michael@0: #include "mozilla/TimeStamp.h" // for TimeDuration, TimeStamp michael@0: #include "mozilla/dom/Touch.h" // for Touch michael@0: #include "mozilla/gfx/BasePoint.h" // for BasePoint michael@0: #include "mozilla/gfx/BaseRect.h" // for BaseRect michael@0: #include "mozilla/gfx/Point.h" // for Point, RoundedToInt, etc michael@0: #include "mozilla/gfx/Rect.h" // for RoundedIn michael@0: #include "mozilla/gfx/ScaleFactor.h" // for ScaleFactor michael@0: #include "mozilla/layers/APZCTreeManager.h" // for ScrollableLayerGuid michael@0: #include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform michael@0: #include "mozilla/layers/Axis.h" // for AxisX, AxisY, Axis, etc michael@0: #include "mozilla/layers/LayerTransactionParent.h" // for LayerTransactionParent michael@0: #include "mozilla/layers/PCompositorParent.h" // for PCompositorParent michael@0: #include "mozilla/layers/TaskThrottler.h" // for TaskThrottler michael@0: #include "mozilla/mozalloc.h" // for operator new, etc michael@0: #include "mozilla/unused.h" // for unused michael@0: #include "mozilla/FloatingPoint.h" // for FuzzyEqualsMultiplicative michael@0: #include "nsAlgorithm.h" // for clamped michael@0: #include "nsAutoPtr.h" // for nsRefPtr michael@0: #include "nsCOMPtr.h" // for already_AddRefed michael@0: #include "nsDebug.h" // for NS_WARNING michael@0: #include "nsIDOMWindowUtils.h" // for nsIDOMWindowUtils michael@0: #include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc michael@0: #include "nsMathUtils.h" // for NS_hypot michael@0: #include "nsPoint.h" // for nsIntPoint michael@0: #include "nsStyleConsts.h" michael@0: #include "nsStyleStruct.h" // for nsTimingFunction michael@0: #include "nsTArray.h" // for nsTArray, nsTArray_Impl, etc michael@0: #include "nsThreadUtils.h" // for NS_IsMainThread michael@0: #include "SharedMemoryBasic.h" // for SharedMemoryBasic michael@0: michael@0: // #define APZC_ENABLE_RENDERTRACE michael@0: michael@0: #define APZC_LOG(...) michael@0: // #define APZC_LOG(...) printf_stderr("APZC: " __VA_ARGS__) michael@0: #define APZC_LOG_FM(fm, prefix, ...) \ michael@0: APZC_LOG(prefix ":" \ michael@0: " i=(%ld %lld) cb=(%d %d %d %d) rcs=(%.3f %.3f) dp=(%.3f %.3f %.3f %.3f) dpm=(%.3f %.3f %.3f %.3f) um=%d " \ michael@0: "v=(%.3f %.3f %.3f %.3f) s=(%.3f %.3f) sr=(%.3f %.3f %.3f %.3f) z=(%.3f %.3f %.3f %.3f) u=(%d %lu)\n", \ michael@0: __VA_ARGS__, \ michael@0: fm.mPresShellId, fm.GetScrollId(), \ michael@0: fm.mCompositionBounds.x, fm.mCompositionBounds.y, fm.mCompositionBounds.width, fm.mCompositionBounds.height, \ michael@0: fm.GetRootCompositionSize().width, fm.GetRootCompositionSize().height, \ michael@0: fm.mDisplayPort.x, fm.mDisplayPort.y, fm.mDisplayPort.width, fm.mDisplayPort.height, \ michael@0: fm.GetDisplayPortMargins().top, fm.GetDisplayPortMargins().right, fm.GetDisplayPortMargins().bottom, fm.GetDisplayPortMargins().left, \ michael@0: fm.GetUseDisplayPortMargins() ? 1 : 0, \ michael@0: fm.mViewport.x, fm.mViewport.y, fm.mViewport.width, fm.mViewport.height, \ michael@0: fm.GetScrollOffset().x, fm.GetScrollOffset().y, \ michael@0: fm.mScrollableRect.x, fm.mScrollableRect.y, fm.mScrollableRect.width, fm.mScrollableRect.height, \ michael@0: fm.mDevPixelsPerCSSPixel.scale, fm.mResolution.scale, fm.mCumulativeResolution.scale, fm.GetZoom().scale, \ michael@0: fm.GetScrollOffsetUpdated(), fm.GetScrollGeneration()); \ michael@0: michael@0: // Static helper functions michael@0: namespace { michael@0: michael@0: int32_t michael@0: WidgetModifiersToDOMModifiers(mozilla::Modifiers aModifiers) michael@0: { michael@0: int32_t result = 0; michael@0: if (aModifiers & mozilla::MODIFIER_SHIFT) { michael@0: result |= nsIDOMWindowUtils::MODIFIER_SHIFT; michael@0: } michael@0: if (aModifiers & mozilla::MODIFIER_CONTROL) { michael@0: result |= nsIDOMWindowUtils::MODIFIER_CONTROL; michael@0: } michael@0: if (aModifiers & mozilla::MODIFIER_ALT) { michael@0: result |= nsIDOMWindowUtils::MODIFIER_ALT; michael@0: } michael@0: if (aModifiers & mozilla::MODIFIER_META) { michael@0: result |= nsIDOMWindowUtils::MODIFIER_META; michael@0: } michael@0: if (aModifiers & mozilla::MODIFIER_ALTGRAPH) { michael@0: result |= nsIDOMWindowUtils::MODIFIER_ALTGRAPH; michael@0: } michael@0: if (aModifiers & mozilla::MODIFIER_CAPSLOCK) { michael@0: result |= nsIDOMWindowUtils::MODIFIER_CAPSLOCK; michael@0: } michael@0: if (aModifiers & mozilla::MODIFIER_FN) { michael@0: result |= nsIDOMWindowUtils::MODIFIER_FN; michael@0: } michael@0: if (aModifiers & mozilla::MODIFIER_NUMLOCK) { michael@0: result |= nsIDOMWindowUtils::MODIFIER_NUMLOCK; michael@0: } michael@0: if (aModifiers & mozilla::MODIFIER_SCROLLLOCK) { michael@0: result |= nsIDOMWindowUtils::MODIFIER_SCROLLLOCK; michael@0: } michael@0: if (aModifiers & mozilla::MODIFIER_SYMBOLLOCK) { michael@0: result |= nsIDOMWindowUtils::MODIFIER_SYMBOLLOCK; michael@0: } michael@0: if (aModifiers & mozilla::MODIFIER_OS) { michael@0: result |= nsIDOMWindowUtils::MODIFIER_OS; michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: } michael@0: michael@0: using namespace mozilla::css; michael@0: michael@0: namespace mozilla { michael@0: namespace layers { michael@0: michael@0: typedef mozilla::layers::AllowedTouchBehavior AllowedTouchBehavior; michael@0: typedef GeckoContentController::APZStateChange APZStateChange; michael@0: michael@0: /* michael@0: * The following prefs are used to control the behaviour of the APZC. michael@0: * The default values are provided in gfxPrefs.h. michael@0: * michael@0: * "apz.allow-checkerboarding" michael@0: * Pref that allows or disallows checkerboarding michael@0: * michael@0: * "apz.asyncscroll.throttle" michael@0: * The time period in ms that throttles mozbrowserasyncscroll event. michael@0: * michael@0: * "apz.asyncscroll.timeout" michael@0: * The timeout in ms for mAsyncScrollTimeoutTask delay task. michael@0: * michael@0: * "apz.axis_lock_mode" michael@0: * The preferred axis locking style. See AxisLockMode for possible values. michael@0: * michael@0: * "apz.content_response_timeout" michael@0: * Amount of time before we timeout response from content. For example, if michael@0: * content is being unruly/slow and we don't get a response back within this michael@0: * time, we will just pretend that content did not preventDefault any touch michael@0: * events we dispatched to it. michael@0: * michael@0: * "apz.cross_slide_enabled" michael@0: * Pref that enables integration with the Metro "cross-slide" gesture. michael@0: * michael@0: * "apz.enlarge_displayport_when_clipped" michael@0: * Pref that enables enlarging of the displayport along one axis when the michael@0: * generated displayport's size is beyond that of the scrollable rect on the michael@0: * opposite axis. michael@0: * michael@0: * "apz.fling_friction" michael@0: * Amount of friction applied during flings. michael@0: * michael@0: * "apz.fling_repaint_interval" michael@0: * Maximum amount of time flinging before sending a viewport change. This will michael@0: * asynchronously repaint the page. michael@0: * michael@0: * "apz.fling_stopped_threshold" michael@0: * When flinging, if the velocity goes below this number, we just stop the michael@0: * animation completely. This is to prevent asymptotically approaching 0 michael@0: * velocity and rerendering unnecessarily. michael@0: * michael@0: * "apz.max_velocity_inches_per_ms" michael@0: * Maximum velocity in inches per millisecond. Velocity will be capped at this michael@0: * value if a faster fling occurs. Negative values indicate unlimited velocity. michael@0: * michael@0: * "apz.max_velocity_queue_size" michael@0: * Maximum size of velocity queue. The queue contains last N velocity records. michael@0: * On touch end we calculate the average velocity in order to compensate michael@0: * touch/mouse drivers misbehaviour. michael@0: * michael@0: * "apz.min_skate_speed" michael@0: * Minimum amount of speed along an axis before we switch to "skate" multipliers michael@0: * rather than using the "stationary" multipliers. michael@0: * michael@0: * "apz.num_paint_duration_samples" michael@0: * Number of samples to store of how long it took to paint after the previous michael@0: * requests. michael@0: * michael@0: * "apz.pan_repaint_interval" michael@0: * Maximum amount of time while panning before sending a viewport change. This michael@0: * will asynchronously repaint the page. It is also forced when panning stops. michael@0: * michael@0: * "apz.touch_start_tolerance" michael@0: * Constant describing the tolerance in distance we use, multiplied by the michael@0: * device DPI, before we start panning the screen. This is to prevent us from michael@0: * accidentally processing taps as touch moves, and from very short/accidental michael@0: * touches moving the screen. michael@0: * michael@0: * "apz.use_paint_duration" michael@0: * Whether or not to use the estimated paint duration as a factor when projecting michael@0: * the displayport in the direction of scrolling. If this value is set to false, michael@0: * a constant 50ms paint time is used; the projection can be scaled as desired michael@0: * using the apz.velocity_bias pref below. michael@0: * michael@0: * "apz.velocity_bias" michael@0: * How much to adjust the displayport in the direction of scrolling. This value michael@0: * is multiplied by the velocity and added to the displayport offset. michael@0: * michael@0: * "apz.x_skate_size_multiplier", "apz.y_skate_size_multiplier" michael@0: * The multiplier we apply to the displayport size if it is skating (current michael@0: * velocity is above apz.min_skate_speed). We prefer to increase the size of the michael@0: * Y axis because it is more natural in the case that a user is reading a page michael@0: * that scrolls up/down. Note that one, both or neither of these may be used michael@0: * at any instant. michael@0: * In general we want apz.[xy]_skate_size_multiplier to be smaller than the corresponding michael@0: * stationary size multiplier because when panning fast we would like to paint michael@0: * less and get faster, more predictable paint times. When panning slowly we michael@0: * can afford to paint more even though it's slower. michael@0: * michael@0: * "apz.x_stationary_size_multiplier", "apz.y_stationary_size_multiplier" michael@0: * The multiplier we apply to the displayport size if it is not skating (see michael@0: * documentation for the skate size multipliers above). michael@0: */ michael@0: michael@0: /** michael@0: * Default touch behavior (is used when not touch behavior is set). michael@0: */ michael@0: static const uint32_t DefaultTouchBehavior = AllowedTouchBehavior::VERTICAL_PAN | michael@0: AllowedTouchBehavior::HORIZONTAL_PAN | michael@0: AllowedTouchBehavior::PINCH_ZOOM | michael@0: AllowedTouchBehavior::DOUBLE_TAP_ZOOM; michael@0: michael@0: /** michael@0: * Angle from axis within which we stay axis-locked michael@0: */ michael@0: static const double AXIS_LOCK_ANGLE = M_PI / 6.0; // 30 degrees michael@0: michael@0: /** michael@0: * The distance in inches the user must pan before axis lock can be broken michael@0: */ michael@0: static const float AXIS_BREAKOUT_THRESHOLD = 1.0f/32.0f; michael@0: michael@0: /** michael@0: * The angle at which axis lock can be broken michael@0: */ michael@0: static const double AXIS_BREAKOUT_ANGLE = M_PI / 8.0; // 22.5 degrees michael@0: michael@0: /** michael@0: * Angle from axis to the line drawn by pan move. michael@0: * If angle is less than this value we can assume that panning michael@0: * can be done in allowed direction (horizontal or vertical). michael@0: * Currently used only for touch-action css property stuff and was michael@0: * added to keep behavior consistent with IE. michael@0: */ michael@0: static const double ALLOWED_DIRECT_PAN_ANGLE = M_PI / 3.0; // 60 degrees michael@0: michael@0: /** michael@0: * Duration of a zoom to animation. michael@0: */ michael@0: static const TimeDuration ZOOM_TO_DURATION = TimeDuration::FromSeconds(0.25); michael@0: michael@0: /** michael@0: * Computed time function used for sampling frames of a zoom to animation. michael@0: */ michael@0: StaticAutoPtr gComputedTimingFunction; michael@0: michael@0: /** michael@0: * Maximum zoom amount, always used, even if a page asks for higher. michael@0: */ michael@0: static const CSSToScreenScale MAX_ZOOM(8.0f); michael@0: michael@0: /** michael@0: * Minimum zoom amount, always used, even if a page asks for lower. michael@0: */ michael@0: static const CSSToScreenScale MIN_ZOOM(0.125f); michael@0: michael@0: /** michael@0: * Is aAngle within the given threshold of the horizontal axis? michael@0: * @param aAngle an angle in radians in the range [0, pi] michael@0: * @param aThreshold an angle in radians in the range [0, pi/2] michael@0: */ michael@0: static bool IsCloseToHorizontal(float aAngle, float aThreshold) michael@0: { michael@0: return (aAngle < aThreshold || aAngle > (M_PI - aThreshold)); michael@0: } michael@0: michael@0: // As above, but for the vertical axis. michael@0: static bool IsCloseToVertical(float aAngle, float aThreshold) michael@0: { michael@0: return (fabs(aAngle - (M_PI / 2)) < aThreshold); michael@0: } michael@0: michael@0: template michael@0: static bool IsZero(const gfx::PointTyped& aPoint) michael@0: { michael@0: return FuzzyEqualsMultiplicative(aPoint.x, 0.0f) michael@0: && FuzzyEqualsMultiplicative(aPoint.y, 0.0f); michael@0: } michael@0: michael@0: static inline void LogRendertraceRect(const ScrollableLayerGuid& aGuid, const char* aDesc, const char* aColor, const CSSRect& aRect) michael@0: { michael@0: #ifdef APZC_ENABLE_RENDERTRACE michael@0: static const TimeStamp sRenderStart = TimeStamp::Now(); michael@0: TimeDuration delta = TimeStamp::Now() - sRenderStart; michael@0: printf_stderr("(%llu,%lu,%llu)%s RENDERTRACE %f rect %s %f %f %f %f\n", michael@0: aGuid.mLayersId, aGuid.mPresShellId, aGuid.GetScrollId(), michael@0: aDesc, delta.ToMilliseconds(), aColor, michael@0: aRect.x, aRect.y, aRect.width, aRect.height); michael@0: #endif michael@0: } michael@0: michael@0: static TimeStamp sFrameTime; michael@0: michael@0: // Counter used to give each APZC a unique id michael@0: static uint32_t sAsyncPanZoomControllerCount = 0; michael@0: michael@0: static TimeStamp michael@0: GetFrameTime() { michael@0: if (sFrameTime.IsNull()) { michael@0: return TimeStamp::Now(); michael@0: } michael@0: return sFrameTime; michael@0: } michael@0: michael@0: class FlingAnimation: public AsyncPanZoomAnimation { michael@0: public: michael@0: FlingAnimation(AsyncPanZoomController& aApzc) michael@0: : AsyncPanZoomAnimation(TimeDuration::FromMilliseconds(gfxPrefs::APZFlingRepaintInterval())) michael@0: , mApzc(aApzc) michael@0: {} michael@0: /** michael@0: * Advances a fling by an interpolated amount based on the passed in |aDelta|. michael@0: * This should be called whenever sampling the content transform for this michael@0: * frame. Returns true if the fling animation should be advanced by one frame, michael@0: * or false if there is no fling or the fling has ended. michael@0: */ michael@0: virtual bool Sample(FrameMetrics& aFrameMetrics, michael@0: const TimeDuration& aDelta); michael@0: michael@0: private: michael@0: AsyncPanZoomController& mApzc; michael@0: }; michael@0: michael@0: class ZoomAnimation: public AsyncPanZoomAnimation { michael@0: public: michael@0: ZoomAnimation(CSSPoint aStartOffset, CSSToScreenScale aStartZoom, michael@0: CSSPoint aEndOffset, CSSToScreenScale aEndZoom) michael@0: : mStartOffset(aStartOffset) michael@0: , mStartZoom(aStartZoom) michael@0: , mEndOffset(aEndOffset) michael@0: , mEndZoom(aEndZoom) michael@0: {} michael@0: michael@0: virtual bool Sample(FrameMetrics& aFrameMetrics, michael@0: const TimeDuration& aDelta); michael@0: michael@0: private: michael@0: TimeDuration mDuration; michael@0: michael@0: // Old metrics from before we started a zoom animation. This is only valid michael@0: // when we are in the "ANIMATED_ZOOM" state. This is used so that we can michael@0: // interpolate between the start and end frames. We only use the michael@0: // |mViewportScrollOffset| and |mResolution| fields on this. michael@0: CSSPoint mStartOffset; michael@0: CSSToScreenScale mStartZoom; michael@0: michael@0: // Target metrics for a zoom to animation. This is only valid when we are in michael@0: // the "ANIMATED_ZOOM" state. We only use the |mViewportScrollOffset| and michael@0: // |mResolution| fields on this. michael@0: CSSPoint mEndOffset; michael@0: CSSToScreenScale mEndZoom; michael@0: }; michael@0: michael@0: void michael@0: AsyncPanZoomController::SetFrameTime(const TimeStamp& aTime) { michael@0: sFrameTime = aTime; michael@0: } michael@0: michael@0: /*static*/ void michael@0: AsyncPanZoomController::InitializeGlobalState() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: static bool sInitialized = false; michael@0: if (sInitialized) michael@0: return; michael@0: sInitialized = true; michael@0: michael@0: gComputedTimingFunction = new ComputedTimingFunction(); michael@0: gComputedTimingFunction->Init( michael@0: nsTimingFunction(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE)); michael@0: ClearOnShutdown(&gComputedTimingFunction); michael@0: } michael@0: michael@0: AsyncPanZoomController::AsyncPanZoomController(uint64_t aLayersId, michael@0: APZCTreeManager* aTreeManager, michael@0: GeckoContentController* aGeckoContentController, michael@0: GestureBehavior aGestures) michael@0: : mLayersId(aLayersId), michael@0: mCrossProcessCompositorParent(nullptr), michael@0: mPaintThrottler(GetFrameTime()), michael@0: mGeckoContentController(aGeckoContentController), michael@0: mRefPtrMonitor("RefPtrMonitor"), michael@0: mMonitor("AsyncPanZoomController"), michael@0: mTouchActionPropertyEnabled(gfxPrefs::TouchActionEnabled()), michael@0: mContentResponseTimeoutTask(nullptr), michael@0: mX(MOZ_THIS_IN_INITIALIZER_LIST()), michael@0: mY(MOZ_THIS_IN_INITIALIZER_LIST()), michael@0: mPanDirRestricted(false), michael@0: mZoomConstraints(false, false, MIN_ZOOM, MAX_ZOOM), michael@0: mLastSampleTime(GetFrameTime()), michael@0: mState(NOTHING), michael@0: mLastAsyncScrollTime(GetFrameTime()), michael@0: mLastAsyncScrollOffset(0, 0), michael@0: mCurrentAsyncScrollOffset(0, 0), michael@0: mAsyncScrollTimeoutTask(nullptr), michael@0: mHandlingTouchQueue(false), michael@0: mTreeManager(aTreeManager), michael@0: mScrollParentId(FrameMetrics::NULL_SCROLL_ID), michael@0: mAPZCId(sAsyncPanZoomControllerCount++), michael@0: mSharedFrameMetricsBuffer(nullptr), michael@0: mSharedLock(nullptr) michael@0: { michael@0: MOZ_COUNT_CTOR(AsyncPanZoomController); michael@0: michael@0: if (aGestures == USE_GESTURE_DETECTOR) { michael@0: mGestureEventListener = new GestureEventListener(this); michael@0: } michael@0: } michael@0: michael@0: AsyncPanZoomController::~AsyncPanZoomController() { michael@0: michael@0: PCompositorParent* compositor = michael@0: (mCrossProcessCompositorParent ? mCrossProcessCompositorParent : mCompositorParent.get()); michael@0: michael@0: // Only send the release message if the SharedFrameMetrics has been created. michael@0: if (compositor && mSharedFrameMetricsBuffer) { michael@0: unused << compositor->SendReleaseSharedCompositorFrameMetrics(mFrameMetrics.GetScrollId(), mAPZCId); michael@0: } michael@0: michael@0: delete mSharedFrameMetricsBuffer; michael@0: delete mSharedLock; michael@0: michael@0: MOZ_COUNT_DTOR(AsyncPanZoomController); michael@0: } michael@0: michael@0: already_AddRefed michael@0: AsyncPanZoomController::GetGeckoContentController() { michael@0: MonitorAutoLock lock(mRefPtrMonitor); michael@0: nsRefPtr controller = mGeckoContentController; michael@0: return controller.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: AsyncPanZoomController::GetGestureEventListener() { michael@0: MonitorAutoLock lock(mRefPtrMonitor); michael@0: nsRefPtr listener = mGestureEventListener; michael@0: return listener.forget(); michael@0: } michael@0: michael@0: void michael@0: AsyncPanZoomController::Destroy() michael@0: { michael@0: { // scope the lock michael@0: MonitorAutoLock lock(mRefPtrMonitor); michael@0: mGeckoContentController = nullptr; michael@0: mGestureEventListener = nullptr; michael@0: } michael@0: mPrevSibling = nullptr; michael@0: mLastChild = nullptr; michael@0: mParent = nullptr; michael@0: mTreeManager = nullptr; michael@0: } michael@0: michael@0: bool michael@0: AsyncPanZoomController::IsDestroyed() michael@0: { michael@0: return mTreeManager == nullptr; michael@0: } michael@0: michael@0: /* static */float michael@0: AsyncPanZoomController::GetTouchStartTolerance() michael@0: { michael@0: return (gfxPrefs::APZTouchStartTolerance() * APZCTreeManager::GetDPI()); michael@0: } michael@0: michael@0: /* static */AsyncPanZoomController::AxisLockMode AsyncPanZoomController::GetAxisLockMode() michael@0: { michael@0: return static_cast(gfxPrefs::APZAxisLockMode()); michael@0: } michael@0: michael@0: nsEventStatus AsyncPanZoomController::ReceiveInputEvent(const InputData& aEvent) { michael@0: if (aEvent.mInputType == MULTITOUCH_INPUT && michael@0: aEvent.AsMultiTouchInput().mType == MultiTouchInput::MULTITOUCH_START) { michael@0: // Starting a new touch block, clear old touch block state. michael@0: mTouchBlockState = TouchBlockState(); michael@0: } michael@0: michael@0: // If we may have touch listeners and touch action property is enabled, we michael@0: // enable the machinery that allows touch listeners to preventDefault any touch inputs michael@0: // and also waits for the allowed touch behavior values to be received from the outside. michael@0: // This should not happen unless there are actually touch listeners and touch-action property michael@0: // enable as it introduces potentially unbounded lag because it causes a round-trip through michael@0: // content. Usually, if content is responding in a timely fashion, this only introduces a michael@0: // nearly constant few hundred ms of lag. michael@0: if (mFrameMetrics.mMayHaveTouchListeners && aEvent.mInputType == MULTITOUCH_INPUT && michael@0: (mState == NOTHING || mState == TOUCHING || IsPanningState(mState))) { michael@0: const MultiTouchInput& multiTouchInput = aEvent.AsMultiTouchInput(); michael@0: if (multiTouchInput.mType == MultiTouchInput::MULTITOUCH_START) { michael@0: SetState(WAITING_CONTENT_RESPONSE); michael@0: } michael@0: } michael@0: michael@0: if (mState == WAITING_CONTENT_RESPONSE || mHandlingTouchQueue) { michael@0: if (aEvent.mInputType == MULTITOUCH_INPUT) { michael@0: const MultiTouchInput& multiTouchInput = aEvent.AsMultiTouchInput(); michael@0: mTouchQueue.AppendElement(multiTouchInput); michael@0: michael@0: SetContentResponseTimer(); michael@0: } michael@0: return nsEventStatus_eIgnore; michael@0: } michael@0: michael@0: return HandleInputEvent(aEvent); michael@0: } michael@0: michael@0: nsEventStatus AsyncPanZoomController::HandleInputEvent(const InputData& aEvent) { michael@0: nsEventStatus rv = nsEventStatus_eIgnore; michael@0: michael@0: switch (aEvent.mInputType) { michael@0: case MULTITOUCH_INPUT: { michael@0: const MultiTouchInput& multiTouchInput = aEvent.AsMultiTouchInput(); michael@0: michael@0: nsRefPtr listener = GetGestureEventListener(); michael@0: if (listener) { michael@0: rv = listener->HandleInputEvent(multiTouchInput); michael@0: if (rv == nsEventStatus_eConsumeNoDefault) { michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: switch (multiTouchInput.mType) { michael@0: case MultiTouchInput::MULTITOUCH_START: rv = OnTouchStart(multiTouchInput); break; michael@0: case MultiTouchInput::MULTITOUCH_MOVE: rv = OnTouchMove(multiTouchInput); break; michael@0: case MultiTouchInput::MULTITOUCH_END: rv = OnTouchEnd(multiTouchInput); break; michael@0: case MultiTouchInput::MULTITOUCH_CANCEL: rv = OnTouchCancel(multiTouchInput); break; michael@0: default: NS_WARNING("Unhandled multitouch"); break; michael@0: } michael@0: break; michael@0: } michael@0: default: NS_WARNING("Unhandled input event"); break; michael@0: } michael@0: michael@0: mLastEventTime = aEvent.mTime; michael@0: return rv; michael@0: } michael@0: michael@0: nsEventStatus AsyncPanZoomController::HandleGestureEvent(const InputData& aEvent) michael@0: { michael@0: nsEventStatus rv = nsEventStatus_eIgnore; michael@0: michael@0: switch (aEvent.mInputType) { michael@0: case PINCHGESTURE_INPUT: { michael@0: const PinchGestureInput& pinchGestureInput = aEvent.AsPinchGestureInput(); michael@0: switch (pinchGestureInput.mType) { michael@0: case PinchGestureInput::PINCHGESTURE_START: rv = OnScaleBegin(pinchGestureInput); break; michael@0: case PinchGestureInput::PINCHGESTURE_SCALE: rv = OnScale(pinchGestureInput); break; michael@0: case PinchGestureInput::PINCHGESTURE_END: rv = OnScaleEnd(pinchGestureInput); break; michael@0: default: NS_WARNING("Unhandled pinch gesture"); break; michael@0: } michael@0: break; michael@0: } michael@0: case TAPGESTURE_INPUT: { michael@0: const TapGestureInput& tapGestureInput = aEvent.AsTapGestureInput(); michael@0: switch (tapGestureInput.mType) { michael@0: case TapGestureInput::TAPGESTURE_LONG: rv = OnLongPress(tapGestureInput); break; michael@0: case TapGestureInput::TAPGESTURE_LONG_UP: rv = OnLongPressUp(tapGestureInput); break; michael@0: case TapGestureInput::TAPGESTURE_UP: rv = OnSingleTapUp(tapGestureInput); break; michael@0: case TapGestureInput::TAPGESTURE_CONFIRMED: rv = OnSingleTapConfirmed(tapGestureInput); break; michael@0: case TapGestureInput::TAPGESTURE_DOUBLE: rv = OnDoubleTap(tapGestureInput); break; michael@0: case TapGestureInput::TAPGESTURE_CANCEL: rv = OnCancelTap(tapGestureInput); break; michael@0: default: NS_WARNING("Unhandled tap gesture"); break; michael@0: } michael@0: break; michael@0: } michael@0: default: NS_WARNING("Unhandled input event"); break; michael@0: } michael@0: michael@0: mLastEventTime = aEvent.mTime; michael@0: return rv; michael@0: } michael@0: michael@0: nsEventStatus AsyncPanZoomController::OnTouchStart(const MultiTouchInput& aEvent) { michael@0: APZC_LOG("%p got a touch-start in state %d\n", this, mState); michael@0: mPanDirRestricted = false; michael@0: ScreenIntPoint point = GetFirstTouchScreenPoint(aEvent); michael@0: michael@0: switch (mState) { michael@0: case ANIMATING_ZOOM: michael@0: // We just interrupted a double-tap animation, so force a redraw in case michael@0: // this touchstart is just a tap that doesn't end up triggering a redraw. michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mMonitor); michael@0: RequestContentRepaint(); michael@0: ScheduleComposite(); michael@0: UpdateSharedCompositorFrameMetrics(); michael@0: } michael@0: // Fall through. michael@0: case FLING: michael@0: CancelAnimation(); michael@0: // Fall through. michael@0: case NOTHING: { michael@0: mX.StartTouch(point.x); michael@0: mY.StartTouch(point.y); michael@0: APZCTreeManager* treeManagerLocal = mTreeManager; michael@0: nsRefPtr controller = GetGeckoContentController(); michael@0: if (treeManagerLocal && controller) { michael@0: bool touchCanBePan = treeManagerLocal->CanBePanned(this); michael@0: controller->NotifyAPZStateChange( michael@0: GetGuid(), APZStateChange::StartTouch, touchCanBePan); michael@0: } michael@0: SetState(TOUCHING); michael@0: break; michael@0: } michael@0: case TOUCHING: michael@0: case PANNING: michael@0: case PANNING_LOCKED_X: michael@0: case PANNING_LOCKED_Y: michael@0: case CROSS_SLIDING_X: michael@0: case CROSS_SLIDING_Y: michael@0: case PINCHING: michael@0: case WAITING_CONTENT_RESPONSE: michael@0: NS_WARNING("Received impossible touch in OnTouchStart"); michael@0: break; michael@0: default: michael@0: NS_WARNING("Unhandled case in OnTouchStart"); michael@0: break; michael@0: } michael@0: michael@0: return nsEventStatus_eConsumeNoDefault; michael@0: } michael@0: michael@0: nsEventStatus AsyncPanZoomController::OnTouchMove(const MultiTouchInput& aEvent) { michael@0: APZC_LOG("%p got a touch-move in state %d\n", this, mState); michael@0: switch (mState) { michael@0: case FLING: michael@0: case NOTHING: michael@0: case ANIMATING_ZOOM: michael@0: // May happen if the user double-taps and drags without lifting after the michael@0: // second tap. Ignore the move if this happens. michael@0: return nsEventStatus_eIgnore; michael@0: michael@0: case CROSS_SLIDING_X: michael@0: case CROSS_SLIDING_Y: michael@0: // While cross-sliding, we don't want to consume any touchmove events for michael@0: // panning or zooming, and let the caller handle them instead. michael@0: return nsEventStatus_eIgnore; michael@0: michael@0: case TOUCHING: { michael@0: float panThreshold = GetTouchStartTolerance(); michael@0: UpdateWithTouchAtDevicePoint(aEvent); michael@0: michael@0: if (PanDistance() < panThreshold) { michael@0: return nsEventStatus_eIgnore; michael@0: } michael@0: michael@0: if (mTouchActionPropertyEnabled && michael@0: (GetTouchBehavior(0) & AllowedTouchBehavior::VERTICAL_PAN) && michael@0: (GetTouchBehavior(0) & AllowedTouchBehavior::HORIZONTAL_PAN)) { michael@0: // User tries to trigger a touch behavior. If allowed touch behavior is vertical pan michael@0: // + horizontal pan (touch-action value is equal to AUTO) we can return ConsumeNoDefault michael@0: // status immediately to trigger cancel event further. It should happen independent of michael@0: // the parent type (whether it is scrolling or not). michael@0: StartPanning(aEvent); michael@0: return nsEventStatus_eConsumeNoDefault; michael@0: } michael@0: michael@0: return StartPanning(aEvent); michael@0: } michael@0: michael@0: case PANNING: michael@0: case PANNING_LOCKED_X: michael@0: case PANNING_LOCKED_Y: michael@0: TrackTouch(aEvent); michael@0: return nsEventStatus_eConsumeNoDefault; michael@0: michael@0: case PINCHING: michael@0: // The scale gesture listener should have handled this. michael@0: NS_WARNING("Gesture listener should have handled pinching in OnTouchMove."); michael@0: return nsEventStatus_eIgnore; michael@0: michael@0: case WAITING_CONTENT_RESPONSE: michael@0: NS_WARNING("Received impossible touch in OnTouchMove"); michael@0: break; michael@0: } michael@0: michael@0: return nsEventStatus_eConsumeNoDefault; michael@0: } michael@0: michael@0: nsEventStatus AsyncPanZoomController::OnTouchEnd(const MultiTouchInput& aEvent) { michael@0: APZC_LOG("%p got a touch-end in state %d\n", this, mState); michael@0: michael@0: OnTouchEndOrCancel(); michael@0: michael@0: // In case no touch behavior triggered previously we can avoid sending michael@0: // scroll events or requesting content repaint. This condition is added michael@0: // to make tests consistent - in case touch-action is NONE (and therefore michael@0: // no pans/zooms can be performed) we expected neither scroll or repaint michael@0: // events. michael@0: if (mState != NOTHING) { michael@0: ReentrantMonitorAutoEnter lock(mMonitor); michael@0: SendAsyncScrollEvent(); michael@0: } michael@0: michael@0: switch (mState) { michael@0: case FLING: michael@0: // Should never happen. michael@0: NS_WARNING("Received impossible touch end in OnTouchEnd."); michael@0: // Fall through. michael@0: case ANIMATING_ZOOM: michael@0: case NOTHING: michael@0: // May happen if the user double-taps and drags without lifting after the michael@0: // second tap. Ignore if this happens. michael@0: return nsEventStatus_eIgnore; michael@0: michael@0: case TOUCHING: michael@0: case CROSS_SLIDING_X: michael@0: case CROSS_SLIDING_Y: michael@0: SetState(NOTHING); michael@0: return nsEventStatus_eIgnore; michael@0: michael@0: case PANNING: michael@0: case PANNING_LOCKED_X: michael@0: case PANNING_LOCKED_Y: michael@0: { michael@0: // Make a local copy of the tree manager pointer and check if it's not michael@0: // null before calling FlushRepaintsForOverscrollHandoffChain(). michael@0: // This is necessary because Destroy(), which nulls out mTreeManager, michael@0: // could be called concurrently. michael@0: APZCTreeManager* treeManagerLocal = mTreeManager; michael@0: if (treeManagerLocal) { michael@0: if (!treeManagerLocal->FlushRepaintsForOverscrollHandoffChain()) { michael@0: NS_WARNING("Overscroll handoff chain was empty during panning! This should not be the case."); michael@0: // Graceful handling of error condition michael@0: FlushRepaintForOverscrollHandoff(); michael@0: } michael@0: } michael@0: } michael@0: mX.EndTouch(); michael@0: mY.EndTouch(); michael@0: SetState(FLING); michael@0: StartAnimation(new FlingAnimation(*this)); michael@0: return nsEventStatus_eConsumeNoDefault; michael@0: michael@0: case PINCHING: michael@0: SetState(NOTHING); michael@0: // Scale gesture listener should have handled this. michael@0: NS_WARNING("Gesture listener should have handled pinching in OnTouchEnd."); michael@0: return nsEventStatus_eIgnore; michael@0: michael@0: case WAITING_CONTENT_RESPONSE: michael@0: NS_WARNING("Received impossible touch in OnTouchEnd"); michael@0: break; michael@0: } michael@0: michael@0: return nsEventStatus_eConsumeNoDefault; michael@0: } michael@0: michael@0: nsEventStatus AsyncPanZoomController::OnTouchCancel(const MultiTouchInput& aEvent) { michael@0: APZC_LOG("%p got a touch-cancel in state %d\n", this, mState); michael@0: OnTouchEndOrCancel(); michael@0: SetState(NOTHING); michael@0: return nsEventStatus_eConsumeNoDefault; michael@0: } michael@0: michael@0: nsEventStatus AsyncPanZoomController::OnScaleBegin(const PinchGestureInput& aEvent) { michael@0: APZC_LOG("%p got a scale-begin in state %d\n", this, mState); michael@0: michael@0: if (!TouchActionAllowPinchZoom()) { michael@0: return nsEventStatus_eIgnore; michael@0: } michael@0: michael@0: if (!mZoomConstraints.mAllowZoom) { michael@0: return nsEventStatus_eConsumeNoDefault; michael@0: } michael@0: michael@0: SetState(PINCHING); michael@0: mLastZoomFocus = ToParentLayerCoords(aEvent.mFocusPoint) - mFrameMetrics.mCompositionBounds.TopLeft(); michael@0: michael@0: return nsEventStatus_eConsumeNoDefault; michael@0: } michael@0: michael@0: nsEventStatus AsyncPanZoomController::OnScale(const PinchGestureInput& aEvent) { michael@0: APZC_LOG("%p got a scale in state %d\n", this, mState); michael@0: if (mState != PINCHING) { michael@0: return nsEventStatus_eConsumeNoDefault; michael@0: } michael@0: michael@0: float prevSpan = aEvent.mPreviousSpan; michael@0: if (fabsf(prevSpan) <= EPSILON || fabsf(aEvent.mCurrentSpan) <= EPSILON) { michael@0: // We're still handling it; we've just decided to throw this event away. michael@0: return nsEventStatus_eConsumeNoDefault; michael@0: } michael@0: michael@0: float spanRatio = aEvent.mCurrentSpan / aEvent.mPreviousSpan; michael@0: michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mMonitor); michael@0: michael@0: CSSToParentLayerScale userZoom = mFrameMetrics.GetZoomToParent(); michael@0: ParentLayerPoint focusPoint = ToParentLayerCoords(aEvent.mFocusPoint) - mFrameMetrics.mCompositionBounds.TopLeft(); michael@0: CSSPoint cssFocusPoint = focusPoint / userZoom; michael@0: michael@0: CSSPoint focusChange = (mLastZoomFocus - focusPoint) / userZoom; michael@0: // If displacing by the change in focus point will take us off page bounds, michael@0: // then reduce the displacement such that it doesn't. michael@0: if (mX.DisplacementWillOverscroll(focusChange.x) != Axis::OVERSCROLL_NONE) { michael@0: focusChange.x -= mX.DisplacementWillOverscrollAmount(focusChange.x); michael@0: } michael@0: if (mY.DisplacementWillOverscroll(focusChange.y) != Axis::OVERSCROLL_NONE) { michael@0: focusChange.y -= mY.DisplacementWillOverscrollAmount(focusChange.y); michael@0: } michael@0: ScrollBy(focusChange); michael@0: michael@0: // When we zoom in with focus, we can zoom too much towards the boundaries michael@0: // that we actually go over them. These are the needed displacements along michael@0: // either axis such that we don't overscroll the boundaries when zooming. michael@0: CSSPoint neededDisplacement; michael@0: michael@0: CSSToParentLayerScale realMinZoom = mZoomConstraints.mMinZoom * mFrameMetrics.mTransformScale; michael@0: CSSToParentLayerScale realMaxZoom = mZoomConstraints.mMaxZoom * mFrameMetrics.mTransformScale; michael@0: realMinZoom.scale = std::max(realMinZoom.scale, michael@0: mFrameMetrics.mCompositionBounds.width / mFrameMetrics.mScrollableRect.width); michael@0: realMinZoom.scale = std::max(realMinZoom.scale, michael@0: mFrameMetrics.mCompositionBounds.height / mFrameMetrics.mScrollableRect.height); michael@0: if (realMaxZoom < realMinZoom) { michael@0: realMaxZoom = realMinZoom; michael@0: } michael@0: michael@0: bool doScale = (spanRatio > 1.0 && userZoom < realMaxZoom) || michael@0: (spanRatio < 1.0 && userZoom > realMinZoom); michael@0: michael@0: if (doScale) { michael@0: spanRatio = clamped(spanRatio, michael@0: realMinZoom.scale / userZoom.scale, michael@0: realMaxZoom.scale / userZoom.scale); michael@0: michael@0: // Note that the spanRatio here should never put us into OVERSCROLL_BOTH because michael@0: // up above we clamped it. michael@0: neededDisplacement.x = -mX.ScaleWillOverscrollAmount(spanRatio, cssFocusPoint.x); michael@0: neededDisplacement.y = -mY.ScaleWillOverscrollAmount(spanRatio, cssFocusPoint.y); michael@0: michael@0: ScaleWithFocus(spanRatio, cssFocusPoint); michael@0: michael@0: if (neededDisplacement != CSSPoint()) { michael@0: ScrollBy(neededDisplacement); michael@0: } michael@0: michael@0: ScheduleComposite(); michael@0: // We don't want to redraw on every scale, so don't use michael@0: // RequestContentRepaint() michael@0: UpdateSharedCompositorFrameMetrics(); michael@0: } michael@0: michael@0: mLastZoomFocus = focusPoint; michael@0: } michael@0: michael@0: return nsEventStatus_eConsumeNoDefault; michael@0: } michael@0: michael@0: nsEventStatus AsyncPanZoomController::OnScaleEnd(const PinchGestureInput& aEvent) { michael@0: APZC_LOG("%p got a scale-end in state %d\n", this, mState); michael@0: michael@0: SetState(NOTHING); michael@0: michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mMonitor); michael@0: ScheduleComposite(); michael@0: RequestContentRepaint(); michael@0: UpdateSharedCompositorFrameMetrics(); michael@0: } michael@0: michael@0: return nsEventStatus_eConsumeNoDefault; michael@0: } michael@0: michael@0: bool michael@0: AsyncPanZoomController::ConvertToGecko(const ScreenPoint& aPoint, CSSPoint* aOut) michael@0: { michael@0: APZCTreeManager* treeManagerLocal = mTreeManager; michael@0: if (treeManagerLocal) { michael@0: gfx3DMatrix transformToApzc; michael@0: gfx3DMatrix transformToGecko; michael@0: treeManagerLocal->GetInputTransforms(this, transformToApzc, transformToGecko); michael@0: gfxPoint result = transformToGecko.Transform(gfxPoint(aPoint.x, aPoint.y)); michael@0: // NOTE: This isn't *quite* LayoutDevicePoint, we just don't have a name michael@0: // for this coordinate space and it maps the closest to LayoutDevicePoint. michael@0: LayoutDevicePoint layoutPoint = LayoutDevicePoint(result.x, result.y); michael@0: { // scoped lock to access mFrameMetrics michael@0: ReentrantMonitorAutoEnter lock(mMonitor); michael@0: *aOut = layoutPoint / mFrameMetrics.mDevPixelsPerCSSPixel; michael@0: } michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: nsEventStatus AsyncPanZoomController::OnLongPress(const TapGestureInput& aEvent) { michael@0: APZC_LOG("%p got a long-press in state %d\n", this, mState); michael@0: nsRefPtr controller = GetGeckoContentController(); michael@0: if (controller) { michael@0: int32_t modifiers = WidgetModifiersToDOMModifiers(aEvent.modifiers); michael@0: CSSPoint geckoScreenPoint; michael@0: if (ConvertToGecko(aEvent.mPoint, &geckoScreenPoint)) { michael@0: SetState(WAITING_CONTENT_RESPONSE); michael@0: SetContentResponseTimer(); michael@0: controller->HandleLongTap(geckoScreenPoint, modifiers, GetGuid()); michael@0: return nsEventStatus_eConsumeNoDefault; michael@0: } michael@0: } michael@0: return nsEventStatus_eIgnore; michael@0: } michael@0: michael@0: nsEventStatus AsyncPanZoomController::OnLongPressUp(const TapGestureInput& aEvent) { michael@0: APZC_LOG("%p got a long-tap-up in state %d\n", this, mState); michael@0: nsRefPtr controller = GetGeckoContentController(); michael@0: if (controller) { michael@0: int32_t modifiers = WidgetModifiersToDOMModifiers(aEvent.modifiers); michael@0: CSSPoint geckoScreenPoint; michael@0: if (ConvertToGecko(aEvent.mPoint, &geckoScreenPoint)) { michael@0: controller->HandleLongTapUp(geckoScreenPoint, modifiers, GetGuid()); michael@0: return nsEventStatus_eConsumeNoDefault; michael@0: } michael@0: } michael@0: return nsEventStatus_eIgnore; michael@0: } michael@0: michael@0: nsEventStatus AsyncPanZoomController::GenerateSingleTap(const ScreenIntPoint& aPoint, mozilla::Modifiers aModifiers) { michael@0: nsRefPtr controller = GetGeckoContentController(); michael@0: if (controller) { michael@0: CSSPoint geckoScreenPoint; michael@0: if (ConvertToGecko(aPoint, &geckoScreenPoint)) { michael@0: int32_t modifiers = WidgetModifiersToDOMModifiers(aModifiers); michael@0: // Because this may be being running as part of APZCTreeManager::ReceiveInputEvent, michael@0: // calling controller->HandleSingleTap directly might mean that content receives michael@0: // the single tap message before the corresponding touch-up. To avoid that we michael@0: // schedule the singletap message to run on the next spin of the event loop. michael@0: // See bug 965381 for the issue this was causing. michael@0: controller->PostDelayedTask( michael@0: NewRunnableMethod(controller.get(), &GeckoContentController::HandleSingleTap, michael@0: geckoScreenPoint, modifiers, GetGuid()), michael@0: 0); michael@0: mTouchBlockState.mSingleTapOccurred = true; michael@0: return nsEventStatus_eConsumeNoDefault; michael@0: } michael@0: } michael@0: return nsEventStatus_eIgnore; michael@0: } michael@0: michael@0: void AsyncPanZoomController::OnTouchEndOrCancel() { michael@0: if (nsRefPtr controller = GetGeckoContentController()) { michael@0: controller->NotifyAPZStateChange( michael@0: GetGuid(), APZStateChange::EndTouch, mTouchBlockState.mSingleTapOccurred); michael@0: } michael@0: } michael@0: michael@0: nsEventStatus AsyncPanZoomController::OnSingleTapUp(const TapGestureInput& aEvent) { michael@0: APZC_LOG("%p got a single-tap-up in state %d\n", this, mState); michael@0: // If mZoomConstraints.mAllowDoubleTapZoom is true we wait for a call to OnSingleTapConfirmed before michael@0: // sending event to content michael@0: if (!(mZoomConstraints.mAllowDoubleTapZoom && TouchActionAllowDoubleTapZoom())) { michael@0: return GenerateSingleTap(aEvent.mPoint, aEvent.modifiers); michael@0: } michael@0: return nsEventStatus_eIgnore; michael@0: } michael@0: michael@0: nsEventStatus AsyncPanZoomController::OnSingleTapConfirmed(const TapGestureInput& aEvent) { michael@0: APZC_LOG("%p got a single-tap-confirmed in state %d\n", this, mState); michael@0: return GenerateSingleTap(aEvent.mPoint, aEvent.modifiers); michael@0: } michael@0: michael@0: nsEventStatus AsyncPanZoomController::OnDoubleTap(const TapGestureInput& aEvent) { michael@0: APZC_LOG("%p got a double-tap in state %d\n", this, mState); michael@0: nsRefPtr controller = GetGeckoContentController(); michael@0: if (controller) { michael@0: if (mZoomConstraints.mAllowDoubleTapZoom && TouchActionAllowDoubleTapZoom()) { michael@0: int32_t modifiers = WidgetModifiersToDOMModifiers(aEvent.modifiers); michael@0: CSSPoint geckoScreenPoint; michael@0: if (ConvertToGecko(aEvent.mPoint, &geckoScreenPoint)) { michael@0: controller->HandleDoubleTap(geckoScreenPoint, modifiers, GetGuid()); michael@0: } michael@0: } michael@0: return nsEventStatus_eConsumeNoDefault; michael@0: } michael@0: return nsEventStatus_eIgnore; michael@0: } michael@0: michael@0: nsEventStatus AsyncPanZoomController::OnCancelTap(const TapGestureInput& aEvent) { michael@0: APZC_LOG("%p got a cancel-tap in state %d\n", this, mState); michael@0: // XXX: Implement this. michael@0: return nsEventStatus_eIgnore; michael@0: } michael@0: michael@0: float AsyncPanZoomController::PanDistance() { michael@0: ReentrantMonitorAutoEnter lock(mMonitor); michael@0: return NS_hypot(mX.PanDistance(), mY.PanDistance()); michael@0: } michael@0: michael@0: const ScreenPoint AsyncPanZoomController::GetVelocityVector() { michael@0: return ScreenPoint(mX.GetVelocity(), mY.GetVelocity()); michael@0: } michael@0: michael@0: void AsyncPanZoomController::HandlePanningWithTouchAction(double aAngle, TouchBehaviorFlags aBehavior) { michael@0: // Handling of cross sliding will need to be added in this method after touch-action released michael@0: // enabled by default. michael@0: if ((aBehavior & AllowedTouchBehavior::VERTICAL_PAN) && (aBehavior & AllowedTouchBehavior::HORIZONTAL_PAN)) { michael@0: if (mX.Scrollable() && mY.Scrollable()) { michael@0: if (IsCloseToHorizontal(aAngle, AXIS_LOCK_ANGLE)) { michael@0: mY.SetAxisLocked(true); michael@0: SetState(PANNING_LOCKED_X); michael@0: } else if (IsCloseToVertical(aAngle, AXIS_LOCK_ANGLE)) { michael@0: mX.SetAxisLocked(true); michael@0: SetState(PANNING_LOCKED_Y); michael@0: } else { michael@0: SetState(PANNING); michael@0: } michael@0: } else if (mX.Scrollable() || mY.Scrollable()) { michael@0: SetState(PANNING); michael@0: } else { michael@0: SetState(NOTHING); michael@0: } michael@0: } else if (aBehavior & AllowedTouchBehavior::HORIZONTAL_PAN) { michael@0: // Using bigger angle for panning to keep behavior consistent michael@0: // with IE. michael@0: if (IsCloseToHorizontal(aAngle, ALLOWED_DIRECT_PAN_ANGLE)) { michael@0: mY.SetAxisLocked(true); michael@0: SetState(PANNING_LOCKED_X); michael@0: mPanDirRestricted = true; michael@0: } else { michael@0: // Don't treat these touches as pan/zoom movements since 'touch-action' value michael@0: // requires it. michael@0: SetState(NOTHING); michael@0: } michael@0: } else if (aBehavior & AllowedTouchBehavior::VERTICAL_PAN) { michael@0: if (IsCloseToVertical(aAngle, ALLOWED_DIRECT_PAN_ANGLE)) { michael@0: mX.SetAxisLocked(true); michael@0: SetState(PANNING_LOCKED_Y); michael@0: mPanDirRestricted = true; michael@0: } else { michael@0: SetState(NOTHING); michael@0: } michael@0: } else { michael@0: SetState(NOTHING); michael@0: } michael@0: } michael@0: michael@0: void AsyncPanZoomController::HandlePanning(double aAngle) { michael@0: if (!gfxPrefs::APZCrossSlideEnabled() && (!mX.Scrollable() || !mY.Scrollable())) { michael@0: SetState(PANNING); michael@0: } else if (IsCloseToHorizontal(aAngle, AXIS_LOCK_ANGLE)) { michael@0: mY.SetAxisLocked(true); michael@0: if (mX.Scrollable()) { michael@0: SetState(PANNING_LOCKED_X); michael@0: } else { michael@0: SetState(CROSS_SLIDING_X); michael@0: mX.SetAxisLocked(true); michael@0: } michael@0: } else if (IsCloseToVertical(aAngle, AXIS_LOCK_ANGLE)) { michael@0: mX.SetAxisLocked(true); michael@0: if (mY.Scrollable()) { michael@0: SetState(PANNING_LOCKED_Y); michael@0: } else { michael@0: SetState(CROSS_SLIDING_Y); michael@0: mY.SetAxisLocked(true); michael@0: } michael@0: } else { michael@0: SetState(PANNING); michael@0: } michael@0: } michael@0: michael@0: nsEventStatus AsyncPanZoomController::StartPanning(const MultiTouchInput& aEvent) { michael@0: ReentrantMonitorAutoEnter lock(mMonitor); michael@0: michael@0: ScreenIntPoint point = GetFirstTouchScreenPoint(aEvent); michael@0: float dx = mX.PanDistance(point.x); michael@0: float dy = mY.PanDistance(point.y); michael@0: michael@0: // When the touch move breaks through the pan threshold, reposition the touch down origin michael@0: // so the page won't jump when we start panning. michael@0: mX.StartTouch(point.x); michael@0: mY.StartTouch(point.y); michael@0: mLastEventTime = aEvent.mTime; michael@0: michael@0: double angle = atan2(dy, dx); // range [-pi, pi] michael@0: angle = fabs(angle); // range [0, pi] michael@0: michael@0: if (mTouchActionPropertyEnabled) { michael@0: HandlePanningWithTouchAction(angle, GetTouchBehavior(0)); michael@0: } else { michael@0: if (GetAxisLockMode() == FREE) { michael@0: SetState(PANNING); michael@0: } else { michael@0: HandlePanning(angle); michael@0: } michael@0: } michael@0: michael@0: if (IsPanningState(mState)) { michael@0: if (nsRefPtr controller = GetGeckoContentController()) { michael@0: controller->NotifyAPZStateChange(GetGuid(), APZStateChange::StartPanning); michael@0: } michael@0: return nsEventStatus_eConsumeNoDefault; michael@0: } michael@0: // Don't consume an event that didn't trigger a panning. michael@0: return nsEventStatus_eIgnore; michael@0: } michael@0: michael@0: void AsyncPanZoomController::UpdateWithTouchAtDevicePoint(const MultiTouchInput& aEvent) { michael@0: ScreenIntPoint point = GetFirstTouchScreenPoint(aEvent); michael@0: TimeDuration timeDelta = TimeDuration().FromMilliseconds(aEvent.mTime - mLastEventTime); michael@0: michael@0: // Probably a duplicate event, just throw it away. michael@0: if (timeDelta.ToMilliseconds() <= EPSILON) { michael@0: return; michael@0: } michael@0: michael@0: mX.UpdateWithTouchAtDevicePoint(point.x, timeDelta); michael@0: mY.UpdateWithTouchAtDevicePoint(point.y, timeDelta); michael@0: } michael@0: michael@0: void AsyncPanZoomController::AttemptScroll(const ScreenPoint& aStartPoint, michael@0: const ScreenPoint& aEndPoint, michael@0: uint32_t aOverscrollHandoffChainIndex) { michael@0: michael@0: // "start - end" rather than "end - start" because e.g. moving your finger michael@0: // down (*positive* direction along y axis) causes the vertical scroll offset michael@0: // to *decrease* as the page follows your finger. michael@0: ScreenPoint displacement = aStartPoint - aEndPoint; michael@0: michael@0: ScreenPoint overscroll; // will be used outside monitor block michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mMonitor); michael@0: michael@0: CSSToScreenScale zoom = mFrameMetrics.GetZoom(); michael@0: michael@0: // Inversely scale the offset by the resolution (when you're zoomed further in, michael@0: // the same swipe should move you a shorter distance). michael@0: CSSPoint cssDisplacement = displacement / zoom; michael@0: michael@0: CSSPoint cssOverscroll; michael@0: CSSPoint allowedDisplacement(mX.AdjustDisplacement(cssDisplacement.x, michael@0: cssOverscroll.x), michael@0: mY.AdjustDisplacement(cssDisplacement.y, michael@0: cssOverscroll.y)); michael@0: overscroll = cssOverscroll * zoom; michael@0: michael@0: if (!IsZero(allowedDisplacement)) { michael@0: ScrollBy(allowedDisplacement); michael@0: ScheduleComposite(); michael@0: michael@0: TimeDuration timePaintDelta = mPaintThrottler.TimeSinceLastRequest(GetFrameTime()); michael@0: if (timePaintDelta.ToMilliseconds() > gfxPrefs::APZPanRepaintInterval()) { michael@0: RequestContentRepaint(); michael@0: } michael@0: UpdateSharedCompositorFrameMetrics(); michael@0: } michael@0: } michael@0: michael@0: if (!IsZero(overscroll)) { michael@0: // "+ overscroll" rather than "- overscroll" because "overscroll" is what's michael@0: // left of "displacement", and "displacement" is "start - end". michael@0: CallDispatchScroll(aEndPoint + overscroll, aEndPoint, aOverscrollHandoffChainIndex + 1); michael@0: } michael@0: } michael@0: michael@0: void AsyncPanZoomController::TakeOverFling(ScreenPoint aVelocity) { michael@0: // We may have a pre-existing velocity for whatever reason (for example, michael@0: // a previously handed off fling). We don't want to clobber that. michael@0: mX.SetVelocity(mX.GetVelocity() + aVelocity.x); michael@0: mY.SetVelocity(mY.GetVelocity() + aVelocity.y); michael@0: SetState(FLING); michael@0: StartAnimation(new FlingAnimation(*this)); michael@0: } michael@0: michael@0: void AsyncPanZoomController::CallDispatchScroll(const ScreenPoint& aStartPoint, const ScreenPoint& aEndPoint, michael@0: uint32_t aOverscrollHandoffChainIndex) { michael@0: // Make a local copy of the tree manager pointer and check if it's not michael@0: // null before calling DispatchScroll(). This is necessary because michael@0: // Destroy(), which nulls out mTreeManager, could be called concurrently. michael@0: APZCTreeManager* treeManagerLocal = mTreeManager; michael@0: if (treeManagerLocal) { michael@0: treeManagerLocal->DispatchScroll(this, aStartPoint, aEndPoint, michael@0: aOverscrollHandoffChainIndex); michael@0: } michael@0: } michael@0: michael@0: void AsyncPanZoomController::TrackTouch(const MultiTouchInput& aEvent) { michael@0: ScreenIntPoint prevTouchPoint(mX.GetPos(), mY.GetPos()); michael@0: ScreenIntPoint touchPoint = GetFirstTouchScreenPoint(aEvent); michael@0: TimeDuration timeDelta = TimeDuration().FromMilliseconds(aEvent.mTime - mLastEventTime); michael@0: michael@0: // Probably a duplicate event, just throw it away. michael@0: if (timeDelta.ToMilliseconds() <= EPSILON) { michael@0: return; michael@0: } michael@0: michael@0: // If we're axis-locked, check if the user is trying to break the lock michael@0: if (GetAxisLockMode() == STICKY && !mPanDirRestricted) { michael@0: ScreenIntPoint point = GetFirstTouchScreenPoint(aEvent); michael@0: float dx = mX.PanDistance(point.x); michael@0: float dy = mY.PanDistance(point.y); michael@0: michael@0: double angle = atan2(dy, dx); // range [-pi, pi] michael@0: angle = fabs(angle); // range [0, pi] michael@0: michael@0: float breakThreshold = AXIS_BREAKOUT_THRESHOLD * APZCTreeManager::GetDPI(); michael@0: michael@0: if (fabs(dx) > breakThreshold || fabs(dy) > breakThreshold) { michael@0: if (mState == PANNING_LOCKED_X || mState == CROSS_SLIDING_X) { michael@0: if (!IsCloseToHorizontal(angle, AXIS_BREAKOUT_ANGLE)) { michael@0: mY.SetAxisLocked(false); michael@0: SetState(PANNING); michael@0: } michael@0: } else if (mState == PANNING_LOCKED_Y || mState == CROSS_SLIDING_Y) { michael@0: if (!IsCloseToVertical(angle, AXIS_BREAKOUT_ANGLE)) { michael@0: mX.SetAxisLocked(false); michael@0: SetState(PANNING); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: UpdateWithTouchAtDevicePoint(aEvent); michael@0: michael@0: CallDispatchScroll(prevTouchPoint, touchPoint, 0); michael@0: } michael@0: michael@0: ScreenIntPoint& AsyncPanZoomController::GetFirstTouchScreenPoint(const MultiTouchInput& aEvent) { michael@0: return ((SingleTouchData&)aEvent.mTouches[0]).mScreenPoint; michael@0: } michael@0: michael@0: bool FlingAnimation::Sample(FrameMetrics& aFrameMetrics, michael@0: const TimeDuration& aDelta) { michael@0: michael@0: // If the fling is handed off to our APZC from a child, on the first call to michael@0: // Sample() aDelta might be negative because it's computed as the sample time michael@0: // from SampleContentTransformForFrame() minus our APZC's mLastSampleTime michael@0: // which is the time the child handed off the fling from its call to michael@0: // SampleContentTransformForFrame() with the same sample time. If we allow michael@0: // the negative aDelta to be processed, it will yield a displacement in the michael@0: // direction opposite to the fling, which can cause us to overscroll and michael@0: // hand off the fling to _our_ parent, which effectively kills the fling. michael@0: if (aDelta.ToMilliseconds() <= 0) { michael@0: return true; michael@0: } michael@0: michael@0: bool shouldContinueFlingX = mApzc.mX.FlingApplyFrictionOrCancel(aDelta), michael@0: shouldContinueFlingY = mApzc.mY.FlingApplyFrictionOrCancel(aDelta); michael@0: // If we shouldn't continue the fling, let's just stop and repaint. michael@0: if (!shouldContinueFlingX && !shouldContinueFlingY) { michael@0: return false; michael@0: } michael@0: michael@0: // AdjustDisplacement() zeroes out the Axis velocity if we're in overscroll. michael@0: // Since we need to hand off the velocity to the tree manager in such a case, michael@0: // we save it here. Would be ScreenVector instead of ScreenPoint if we had michael@0: // vector classes. michael@0: ScreenPoint velocity(mApzc.mX.GetVelocity(), mApzc.mY.GetVelocity()); michael@0: michael@0: ScreenPoint offset = velocity * aDelta.ToMilliseconds(); michael@0: michael@0: // Inversely scale the offset by the resolution (when you're zoomed further in, michael@0: // the same swipe should move you a shorter distance). michael@0: CSSPoint cssOffset = offset / aFrameMetrics.GetZoom(); michael@0: CSSPoint overscroll; michael@0: aFrameMetrics.ScrollBy(CSSPoint( michael@0: mApzc.mX.AdjustDisplacement(cssOffset.x, overscroll.x), michael@0: mApzc.mY.AdjustDisplacement(cssOffset.y, overscroll.y) michael@0: )); michael@0: michael@0: // If the fling has caused us to reach the end of our scroll range, hand michael@0: // off the fling to the next APZC in the overscroll handoff chain. michael@0: if (!IsZero(overscroll)) { michael@0: // We may have reached the end of the scroll range along one axis but michael@0: // not the other. In such a case we only want to hand off the relevant michael@0: // component of the fling. michael@0: if (FuzzyEqualsMultiplicative(overscroll.x, 0.0f)) { michael@0: velocity.x = 0; michael@0: } else if (FuzzyEqualsMultiplicative(overscroll.y, 0.0f)) { michael@0: velocity.y = 0; michael@0: } michael@0: michael@0: // To hand off the fling, we call APZCTreeManager::HandleFlingOverscroll() michael@0: // which starts a new fling in the next APZC in the handoff chain with michael@0: // the same velocity. For simplicity, the actual overscroll of the current michael@0: // sample is discarded rather than being handed off. The compositor should michael@0: // sample animations sufficiently frequently that this is not noticeable. michael@0: michael@0: // Make a local copy of the tree manager pointer and check if it's not michael@0: // null before calling HandleFlingOverscroll(). This is necessary because michael@0: // Destroy(), which nulls out mTreeManager, could be called concurrently. michael@0: APZCTreeManager* treeManagerLocal = mApzc.mTreeManager; michael@0: if (treeManagerLocal) { michael@0: // APZC is holding mMonitor, so directly calling HandleFlingOverscroll() michael@0: // (which acquires the tree lock) would violate the lock ordering. Instead michael@0: // we schedule HandleFlingOverscroll() to be called after mMonitor is michael@0: // released. michael@0: mDeferredTasks.append(NewRunnableMethod(treeManagerLocal, michael@0: &APZCTreeManager::HandOffFling, michael@0: &mApzc, michael@0: velocity)); michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void AsyncPanZoomController::StartAnimation(AsyncPanZoomAnimation* aAnimation) michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mMonitor); michael@0: mAnimation = aAnimation; michael@0: mLastSampleTime = GetFrameTime(); michael@0: ScheduleComposite(); michael@0: } michael@0: michael@0: void AsyncPanZoomController::CancelAnimation() { michael@0: ReentrantMonitorAutoEnter lock(mMonitor); michael@0: SetState(NOTHING); michael@0: mAnimation = nullptr; michael@0: } michael@0: michael@0: void AsyncPanZoomController::SetCompositorParent(CompositorParent* aCompositorParent) { michael@0: mCompositorParent = aCompositorParent; michael@0: } michael@0: michael@0: void AsyncPanZoomController::SetCrossProcessCompositorParent(PCompositorParent* aCrossProcessCompositorParent) { michael@0: mCrossProcessCompositorParent = aCrossProcessCompositorParent; michael@0: } michael@0: michael@0: void AsyncPanZoomController::ScrollBy(const CSSPoint& aOffset) { michael@0: mFrameMetrics.ScrollBy(aOffset); michael@0: } michael@0: michael@0: void AsyncPanZoomController::ScaleWithFocus(float aScale, michael@0: const CSSPoint& aFocus) { michael@0: mFrameMetrics.ZoomBy(aScale); michael@0: // We want to adjust the scroll offset such that the CSS point represented by aFocus remains michael@0: // at the same position on the screen before and after the change in zoom. The below code michael@0: // accomplishes this; see https://bugzilla.mozilla.org/show_bug.cgi?id=923431#c6 for an michael@0: // in-depth explanation of how. michael@0: mFrameMetrics.SetScrollOffset((mFrameMetrics.GetScrollOffset() + aFocus) - (aFocus / aScale)); michael@0: } michael@0: michael@0: /** michael@0: * Enlarges the displayport along both axes based on the velocity. michael@0: */ michael@0: static CSSSize michael@0: CalculateDisplayPortSize(const CSSSize& aCompositionSize, michael@0: const CSSPoint& aVelocity) michael@0: { michael@0: float xMultiplier = fabsf(aVelocity.x) < gfxPrefs::APZMinSkateSpeed() michael@0: ? gfxPrefs::APZXStationarySizeMultiplier() michael@0: : gfxPrefs::APZXSkateSizeMultiplier(); michael@0: float yMultiplier = fabsf(aVelocity.y) < gfxPrefs::APZMinSkateSpeed() michael@0: ? gfxPrefs::APZYStationarySizeMultiplier() michael@0: : gfxPrefs::APZYSkateSizeMultiplier(); michael@0: return CSSSize(aCompositionSize.width * xMultiplier, michael@0: aCompositionSize.height * yMultiplier); michael@0: } michael@0: michael@0: /** michael@0: * Attempts to redistribute any area in the displayport that would get clipped michael@0: * by the scrollable rect, or be inaccessible due to disabled scrolling, to the michael@0: * other axis, while maintaining total displayport area. michael@0: */ michael@0: static void michael@0: RedistributeDisplayPortExcess(CSSSize& aDisplayPortSize, michael@0: const CSSRect& aScrollableRect) michael@0: { michael@0: float xSlack = std::max(0.0f, aDisplayPortSize.width - aScrollableRect.width); michael@0: float ySlack = std::max(0.0f, aDisplayPortSize.height - aScrollableRect.height); michael@0: michael@0: if (ySlack > 0) { michael@0: // Reassign wasted y-axis displayport to the x-axis michael@0: aDisplayPortSize.height -= ySlack; michael@0: float xExtra = ySlack * aDisplayPortSize.width / aDisplayPortSize.height; michael@0: aDisplayPortSize.width += xExtra; michael@0: } else if (xSlack > 0) { michael@0: // Reassign wasted x-axis displayport to the y-axis michael@0: aDisplayPortSize.width -= xSlack; michael@0: float yExtra = xSlack * aDisplayPortSize.height / aDisplayPortSize.width; michael@0: aDisplayPortSize.height += yExtra; michael@0: } michael@0: } michael@0: michael@0: /* static */ michael@0: const LayerMargin AsyncPanZoomController::CalculatePendingDisplayPort( michael@0: const FrameMetrics& aFrameMetrics, michael@0: const ScreenPoint& aVelocity, michael@0: double aEstimatedPaintDuration) michael@0: { michael@0: CSSSize compositionBounds = aFrameMetrics.CalculateCompositedSizeInCssPixels(); michael@0: CSSSize compositionSize = aFrameMetrics.GetRootCompositionSize(); michael@0: compositionSize = michael@0: CSSSize(std::min(compositionBounds.width, compositionSize.width), michael@0: std::min(compositionBounds.height, compositionSize.height)); michael@0: CSSPoint velocity = aVelocity / aFrameMetrics.GetZoom(); michael@0: CSSPoint scrollOffset = aFrameMetrics.GetScrollOffset(); michael@0: CSSRect scrollableRect = aFrameMetrics.GetExpandedScrollableRect(); michael@0: michael@0: // Calculate the displayport size based on how fast we're moving along each axis. michael@0: CSSSize displayPortSize = CalculateDisplayPortSize(compositionSize, velocity); michael@0: michael@0: if (gfxPrefs::APZEnlargeDisplayPortWhenClipped()) { michael@0: RedistributeDisplayPortExcess(displayPortSize, scrollableRect); michael@0: } michael@0: michael@0: // Offset the displayport, depending on how fast we're moving and the michael@0: // estimated time it takes to paint, to try to minimise checkerboarding. michael@0: float estimatedPaintDurationMillis = (float)(aEstimatedPaintDuration * 1000.0); michael@0: float paintFactor = (gfxPrefs::APZUsePaintDuration() ? estimatedPaintDurationMillis : 50.0f); michael@0: CSSRect displayPort = CSSRect(scrollOffset + (velocity * paintFactor * gfxPrefs::APZVelocityBias()), michael@0: displayPortSize); michael@0: michael@0: // Re-center the displayport based on its expansion over the composition size. michael@0: displayPort.MoveBy((compositionSize.width - displayPort.width)/2.0f, michael@0: (compositionSize.height - displayPort.height)/2.0f); michael@0: michael@0: // Make sure the displayport remains within the scrollable rect. michael@0: displayPort = displayPort.ForceInside(scrollableRect) - scrollOffset; michael@0: michael@0: APZC_LOG_FM(aFrameMetrics, michael@0: "Calculated displayport as (%f %f %f %f) from velocity (%f %f) paint time %f metrics", michael@0: displayPort.x, displayPort.y, displayPort.width, displayPort.height, michael@0: aVelocity.x, aVelocity.y, (float)estimatedPaintDurationMillis); michael@0: michael@0: CSSMargin cssMargins; michael@0: cssMargins.left = -displayPort.x; michael@0: cssMargins.top = -displayPort.y; michael@0: cssMargins.right = displayPort.width - compositionSize.width - cssMargins.left; michael@0: cssMargins.bottom = displayPort.height - compositionSize.height - cssMargins.top; michael@0: michael@0: LayerMargin layerMargins = cssMargins * aFrameMetrics.LayersPixelsPerCSSPixel(); michael@0: michael@0: return layerMargins; michael@0: } michael@0: michael@0: void AsyncPanZoomController::ScheduleComposite() { michael@0: if (mCompositorParent) { michael@0: mCompositorParent->ScheduleRenderOnCompositorThread(); michael@0: } michael@0: } michael@0: michael@0: void AsyncPanZoomController::FlushRepaintForOverscrollHandoff() { michael@0: ReentrantMonitorAutoEnter lock(mMonitor); michael@0: RequestContentRepaint(); michael@0: UpdateSharedCompositorFrameMetrics(); michael@0: } michael@0: michael@0: bool AsyncPanZoomController::IsPannable() const { michael@0: ReentrantMonitorAutoEnter lock(mMonitor); michael@0: return mX.HasRoomToPan() || mY.HasRoomToPan(); michael@0: } michael@0: michael@0: void AsyncPanZoomController::RequestContentRepaint() { michael@0: RequestContentRepaint(mFrameMetrics); michael@0: } michael@0: michael@0: void AsyncPanZoomController::RequestContentRepaint(FrameMetrics& aFrameMetrics) { michael@0: aFrameMetrics.SetDisplayPortMargins( michael@0: CalculatePendingDisplayPort(aFrameMetrics, michael@0: GetVelocityVector(), michael@0: mPaintThrottler.AverageDuration().ToSeconds())); michael@0: aFrameMetrics.SetUseDisplayPortMargins(); michael@0: michael@0: // If we're trying to paint what we already think is painted, discard this michael@0: // request since it's a pointless paint. michael@0: LayerMargin marginDelta = mLastPaintRequestMetrics.GetDisplayPortMargins() michael@0: - aFrameMetrics.GetDisplayPortMargins(); michael@0: if (fabsf(marginDelta.left) < EPSILON && michael@0: fabsf(marginDelta.top) < EPSILON && michael@0: fabsf(marginDelta.right) < EPSILON && michael@0: fabsf(marginDelta.bottom) < EPSILON && michael@0: fabsf(mLastPaintRequestMetrics.GetScrollOffset().x - michael@0: aFrameMetrics.GetScrollOffset().x) < EPSILON && michael@0: fabsf(mLastPaintRequestMetrics.GetScrollOffset().y - michael@0: aFrameMetrics.GetScrollOffset().y) < EPSILON && michael@0: aFrameMetrics.GetZoom() == mLastPaintRequestMetrics.GetZoom() && michael@0: fabsf(aFrameMetrics.mViewport.width - mLastPaintRequestMetrics.mViewport.width) < EPSILON && michael@0: fabsf(aFrameMetrics.mViewport.height - mLastPaintRequestMetrics.mViewport.height) < EPSILON) { michael@0: return; michael@0: } michael@0: michael@0: SendAsyncScrollEvent(); michael@0: mPaintThrottler.PostTask( michael@0: FROM_HERE, michael@0: NewRunnableMethod(this, michael@0: &AsyncPanZoomController::DispatchRepaintRequest, michael@0: aFrameMetrics), michael@0: GetFrameTime()); michael@0: michael@0: aFrameMetrics.mPresShellId = mLastContentPaintMetrics.mPresShellId; michael@0: mLastPaintRequestMetrics = aFrameMetrics; michael@0: } michael@0: michael@0: /*static*/ CSSRect michael@0: GetDisplayPortRect(const FrameMetrics& aFrameMetrics) michael@0: { michael@0: // This computation is based on what happens in CalculatePendingDisplayPort. If that michael@0: // changes then this might need to change too michael@0: CSSRect baseRect(aFrameMetrics.GetScrollOffset(), michael@0: CSSSize(std::min(aFrameMetrics.CalculateCompositedSizeInCssPixels().width, michael@0: aFrameMetrics.GetRootCompositionSize().width), michael@0: std::min(aFrameMetrics.CalculateCompositedSizeInCssPixels().height, michael@0: aFrameMetrics.GetRootCompositionSize().height))); michael@0: baseRect.Inflate(aFrameMetrics.GetDisplayPortMargins() / aFrameMetrics.LayersPixelsPerCSSPixel()); michael@0: return baseRect; michael@0: } michael@0: michael@0: void michael@0: AsyncPanZoomController::DispatchRepaintRequest(const FrameMetrics& aFrameMetrics) { michael@0: nsRefPtr controller = GetGeckoContentController(); michael@0: if (controller) { michael@0: APZC_LOG_FM(aFrameMetrics, "%p requesting content repaint", this); michael@0: LogRendertraceRect(GetGuid(), "requested displayport", "yellow", GetDisplayPortRect(aFrameMetrics)); michael@0: michael@0: controller->RequestContentRepaint(aFrameMetrics); michael@0: mLastDispatchedPaintMetrics = aFrameMetrics; michael@0: } michael@0: } michael@0: michael@0: void michael@0: AsyncPanZoomController::FireAsyncScrollOnTimeout() michael@0: { michael@0: if (mCurrentAsyncScrollOffset != mLastAsyncScrollOffset) { michael@0: ReentrantMonitorAutoEnter lock(mMonitor); michael@0: SendAsyncScrollEvent(); michael@0: } michael@0: mAsyncScrollTimeoutTask = nullptr; michael@0: } michael@0: michael@0: bool ZoomAnimation::Sample(FrameMetrics& aFrameMetrics, michael@0: const TimeDuration& aDelta) { michael@0: mDuration += aDelta; michael@0: double animPosition = mDuration / ZOOM_TO_DURATION; michael@0: michael@0: if (animPosition >= 1.0) { michael@0: aFrameMetrics.SetZoom(mEndZoom); michael@0: aFrameMetrics.SetScrollOffset(mEndOffset); michael@0: return false; michael@0: } michael@0: michael@0: // Sample the zoom at the current time point. The sampled zoom michael@0: // will affect the final computed resolution. michael@0: double sampledPosition = gComputedTimingFunction->GetValue(animPosition); michael@0: michael@0: // We scale the scrollOffset linearly with sampledPosition, so the zoom michael@0: // needs to scale inversely to match. michael@0: aFrameMetrics.SetZoom(CSSToScreenScale(1 / michael@0: (sampledPosition / mEndZoom.scale + michael@0: (1 - sampledPosition) / mStartZoom.scale))); michael@0: michael@0: aFrameMetrics.SetScrollOffset(CSSPoint::FromUnknownPoint(gfx::Point( michael@0: mEndOffset.x * sampledPosition + mStartOffset.x * (1 - sampledPosition), michael@0: mEndOffset.y * sampledPosition + mStartOffset.y * (1 - sampledPosition) michael@0: ))); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool AsyncPanZoomController::UpdateAnimation(const TimeStamp& aSampleTime) michael@0: { michael@0: if (mAnimation) { michael@0: if (mAnimation->Sample(mFrameMetrics, aSampleTime - mLastSampleTime)) { michael@0: if (mPaintThrottler.TimeSinceLastRequest(aSampleTime) > michael@0: mAnimation->mRepaintInterval) { michael@0: RequestContentRepaint(); michael@0: } michael@0: } else { michael@0: mAnimation = nullptr; michael@0: SetState(NOTHING); michael@0: SendAsyncScrollEvent(); michael@0: RequestContentRepaint(); michael@0: } michael@0: UpdateSharedCompositorFrameMetrics(); michael@0: mLastSampleTime = aSampleTime; michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool AsyncPanZoomController::SampleContentTransformForFrame(const TimeStamp& aSampleTime, michael@0: ViewTransform* aNewTransform, michael@0: ScreenPoint& aScrollOffset) { michael@0: // The eventual return value of this function. The compositor needs to know michael@0: // whether or not to advance by a frame as soon as it can. For example, if a michael@0: // fling is happening, it has to keep compositing so that the animation is michael@0: // smooth. If an animation frame is requested, it is the compositor's michael@0: // responsibility to schedule a composite. michael@0: bool requestAnimationFrame = false; michael@0: Vector deferredTasks; michael@0: michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mMonitor); michael@0: michael@0: requestAnimationFrame = UpdateAnimation(aSampleTime); michael@0: michael@0: aScrollOffset = mFrameMetrics.GetScrollOffset() * mFrameMetrics.GetZoom(); michael@0: *aNewTransform = GetCurrentAsyncTransform(); michael@0: michael@0: LogRendertraceRect(GetGuid(), "viewport", "red", michael@0: CSSRect(mFrameMetrics.GetScrollOffset(), michael@0: ParentLayerSize(mFrameMetrics.mCompositionBounds.Size()) / mFrameMetrics.GetZoomToParent())); michael@0: michael@0: mCurrentAsyncScrollOffset = mFrameMetrics.GetScrollOffset(); michael@0: michael@0: // Get any deferred tasks queued up by mAnimation's Sample() (called by michael@0: // UpdateAnimation()). This needs to be done here since mAnimation can michael@0: // be destroyed by another thread when we release the monitor, but michael@0: // the tasks need to be executed after we release the monitor since they michael@0: // are allowed to call APZCTreeManager methods which can grab the tree lock. michael@0: if (mAnimation) { michael@0: deferredTasks = mAnimation->TakeDeferredTasks(); michael@0: } michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < deferredTasks.length(); ++i) { michael@0: deferredTasks[i]->Run(); michael@0: delete deferredTasks[i]; michael@0: } michael@0: michael@0: // Cancel the mAsyncScrollTimeoutTask because we will fire a michael@0: // mozbrowserasyncscroll event or renew the mAsyncScrollTimeoutTask again. michael@0: if (mAsyncScrollTimeoutTask) { michael@0: mAsyncScrollTimeoutTask->Cancel(); michael@0: mAsyncScrollTimeoutTask = nullptr; michael@0: } michael@0: // Fire the mozbrowserasyncscroll event immediately if it's been michael@0: // sAsyncScrollThrottleTime ms since the last time we fired the event and the michael@0: // current scroll offset is different than the mLastAsyncScrollOffset we sent michael@0: // with the last event. michael@0: // Otherwise, start a timer to fire the event sAsyncScrollTimeout ms from now. michael@0: TimeDuration delta = aSampleTime - mLastAsyncScrollTime; michael@0: if (delta.ToMilliseconds() > gfxPrefs::APZAsyncScrollThrottleTime() && michael@0: mCurrentAsyncScrollOffset != mLastAsyncScrollOffset) { michael@0: ReentrantMonitorAutoEnter lock(mMonitor); michael@0: mLastAsyncScrollTime = aSampleTime; michael@0: mLastAsyncScrollOffset = mCurrentAsyncScrollOffset; michael@0: SendAsyncScrollEvent(); michael@0: } michael@0: else { michael@0: mAsyncScrollTimeoutTask = michael@0: NewRunnableMethod(this, &AsyncPanZoomController::FireAsyncScrollOnTimeout); michael@0: MessageLoop::current()->PostDelayedTask(FROM_HERE, michael@0: mAsyncScrollTimeoutTask, michael@0: gfxPrefs::APZAsyncScrollTimeout()); michael@0: } michael@0: michael@0: return requestAnimationFrame; michael@0: } michael@0: michael@0: ViewTransform AsyncPanZoomController::GetCurrentAsyncTransform() { michael@0: ReentrantMonitorAutoEnter lock(mMonitor); michael@0: michael@0: CSSPoint lastPaintScrollOffset; michael@0: if (mLastContentPaintMetrics.IsScrollable()) { michael@0: lastPaintScrollOffset = mLastContentPaintMetrics.GetScrollOffset(); michael@0: } michael@0: michael@0: CSSPoint currentScrollOffset = mFrameMetrics.GetScrollOffset() + michael@0: mTestAsyncScrollOffset; michael@0: michael@0: // If checkerboarding has been disallowed, clamp the scroll position to stay michael@0: // within rendered content. michael@0: if (!gfxPrefs::APZAllowCheckerboarding() && michael@0: !mLastContentPaintMetrics.mDisplayPort.IsEmpty()) { michael@0: CSSSize compositedSize = mLastContentPaintMetrics.CalculateCompositedSizeInCssPixels(); michael@0: CSSPoint maxScrollOffset = lastPaintScrollOffset + michael@0: CSSPoint(mLastContentPaintMetrics.mDisplayPort.XMost() - compositedSize.width, michael@0: mLastContentPaintMetrics.mDisplayPort.YMost() - compositedSize.height); michael@0: CSSPoint minScrollOffset = lastPaintScrollOffset + mLastContentPaintMetrics.mDisplayPort.TopLeft(); michael@0: michael@0: if (minScrollOffset.x < maxScrollOffset.x) { michael@0: currentScrollOffset.x = clamped(currentScrollOffset.x, minScrollOffset.x, maxScrollOffset.x); michael@0: } michael@0: if (minScrollOffset.y < maxScrollOffset.y) { michael@0: currentScrollOffset.y = clamped(currentScrollOffset.y, minScrollOffset.y, maxScrollOffset.y); michael@0: } michael@0: } michael@0: michael@0: LayerPoint translation = (currentScrollOffset - lastPaintScrollOffset) michael@0: * mLastContentPaintMetrics.LayersPixelsPerCSSPixel(); michael@0: michael@0: return ViewTransform(-translation, michael@0: mFrameMetrics.GetZoom() michael@0: / mLastContentPaintMetrics.mDevPixelsPerCSSPixel michael@0: / mFrameMetrics.GetParentResolution()); michael@0: } michael@0: michael@0: gfx3DMatrix AsyncPanZoomController::GetNontransientAsyncTransform() { michael@0: ReentrantMonitorAutoEnter lock(mMonitor); michael@0: return gfx3DMatrix::ScalingMatrix(mLastContentPaintMetrics.mResolution.scale, michael@0: mLastContentPaintMetrics.mResolution.scale, michael@0: 1.0f); michael@0: } michael@0: michael@0: gfx3DMatrix AsyncPanZoomController::GetTransformToLastDispatchedPaint() { michael@0: ReentrantMonitorAutoEnter lock(mMonitor); michael@0: LayerPoint scrollChange = (mLastContentPaintMetrics.GetScrollOffset() - mLastDispatchedPaintMetrics.GetScrollOffset()) michael@0: * mLastContentPaintMetrics.LayersPixelsPerCSSPixel(); michael@0: float zoomChange = mLastContentPaintMetrics.GetZoom().scale / mLastDispatchedPaintMetrics.GetZoom().scale; michael@0: return gfx3DMatrix::Translation(scrollChange.x, scrollChange.y, 0) * michael@0: gfx3DMatrix::ScalingMatrix(zoomChange, zoomChange, 1); michael@0: } michael@0: michael@0: void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aLayerMetrics, bool aIsFirstPaint) { michael@0: ReentrantMonitorAutoEnter lock(mMonitor); michael@0: michael@0: mLastContentPaintMetrics = aLayerMetrics; michael@0: UpdateTransformScale(); michael@0: michael@0: bool isDefault = mFrameMetrics.IsDefault(); michael@0: mFrameMetrics.mMayHaveTouchListeners = aLayerMetrics.mMayHaveTouchListeners; michael@0: APZC_LOG_FM(aLayerMetrics, "%p got a NotifyLayersUpdated with aIsFirstPaint=%d", this, aIsFirstPaint); michael@0: michael@0: LogRendertraceRect(GetGuid(), "page", "brown", aLayerMetrics.mScrollableRect); michael@0: LogRendertraceRect(GetGuid(), "painted displayport", "green", michael@0: aLayerMetrics.mDisplayPort + aLayerMetrics.GetScrollOffset()); michael@0: michael@0: mPaintThrottler.TaskComplete(GetFrameTime()); michael@0: bool needContentRepaint = false; michael@0: if (aLayerMetrics.mCompositionBounds.width == mFrameMetrics.mCompositionBounds.width && michael@0: aLayerMetrics.mCompositionBounds.height == mFrameMetrics.mCompositionBounds.height) { michael@0: // Remote content has sync'd up to the composition geometry michael@0: // change, so we can accept the viewport it's calculated. michael@0: if (mFrameMetrics.mViewport.width != aLayerMetrics.mViewport.width || michael@0: mFrameMetrics.mViewport.height != aLayerMetrics.mViewport.height) { michael@0: needContentRepaint = true; michael@0: } michael@0: mFrameMetrics.mViewport = aLayerMetrics.mViewport; michael@0: } michael@0: michael@0: // If the layers update was not triggered by our own repaint request, then michael@0: // we want to take the new scroll offset. Check the scroll generation as well michael@0: // to filter duplicate calls to NotifyLayersUpdated with the same scroll offset michael@0: // update message. michael@0: bool scrollOffsetUpdated = aLayerMetrics.GetScrollOffsetUpdated() michael@0: && (aLayerMetrics.GetScrollGeneration() != mFrameMetrics.GetScrollGeneration()); michael@0: michael@0: if (aIsFirstPaint || isDefault) { michael@0: // Initialize our internal state to something sane when the content michael@0: // that was just painted is something we knew nothing about previously michael@0: mPaintThrottler.ClearHistory(); michael@0: mPaintThrottler.SetMaxDurations(gfxPrefs::APZNumPaintDurationSamples()); michael@0: michael@0: mX.CancelTouch(); michael@0: mY.CancelTouch(); michael@0: SetState(NOTHING); michael@0: michael@0: mFrameMetrics = aLayerMetrics; michael@0: mLastDispatchedPaintMetrics = aLayerMetrics; michael@0: ShareCompositorFrameMetrics(); michael@0: } else { michael@0: // If we're not taking the aLayerMetrics wholesale we still need to pull michael@0: // in some things into our local mFrameMetrics because these things are michael@0: // determined by Gecko and our copy in mFrameMetrics may be stale. michael@0: michael@0: if (mFrameMetrics.mCompositionBounds.width == aLayerMetrics.mCompositionBounds.width && michael@0: mFrameMetrics.mDevPixelsPerCSSPixel == aLayerMetrics.mDevPixelsPerCSSPixel) { michael@0: float parentResolutionChange = aLayerMetrics.GetParentResolution().scale michael@0: / mFrameMetrics.GetParentResolution().scale; michael@0: mFrameMetrics.ZoomBy(parentResolutionChange); michael@0: } else { michael@0: // Take the new zoom as either device scale or composition width or both michael@0: // got changed (e.g. due to orientation change). michael@0: mFrameMetrics.SetZoom(aLayerMetrics.GetZoom()); michael@0: mFrameMetrics.mDevPixelsPerCSSPixel.scale = aLayerMetrics.mDevPixelsPerCSSPixel.scale; michael@0: } michael@0: mFrameMetrics.mScrollableRect = aLayerMetrics.mScrollableRect; michael@0: mFrameMetrics.mCompositionBounds = aLayerMetrics.mCompositionBounds; michael@0: mFrameMetrics.SetRootCompositionSize(aLayerMetrics.GetRootCompositionSize()); michael@0: mFrameMetrics.mResolution = aLayerMetrics.mResolution; michael@0: mFrameMetrics.mCumulativeResolution = aLayerMetrics.mCumulativeResolution; michael@0: mFrameMetrics.mHasScrollgrab = aLayerMetrics.mHasScrollgrab; michael@0: michael@0: if (scrollOffsetUpdated) { michael@0: APZC_LOG("%p updating scroll offset from (%f, %f) to (%f, %f)\n", this, michael@0: mFrameMetrics.GetScrollOffset().x, mFrameMetrics.GetScrollOffset().y, michael@0: aLayerMetrics.GetScrollOffset().x, aLayerMetrics.GetScrollOffset().y); michael@0: michael@0: mFrameMetrics.CopyScrollInfoFrom(aLayerMetrics); michael@0: michael@0: // Because of the scroll offset update, any inflight paint requests are michael@0: // going to be ignored by layout, and so mLastDispatchedPaintMetrics michael@0: // becomes incorrect for the purposes of calculating the LD transform. To michael@0: // correct this we need to update mLastDispatchedPaintMetrics to be the michael@0: // last thing we know was painted by Gecko. michael@0: mLastDispatchedPaintMetrics = aLayerMetrics; michael@0: } michael@0: } michael@0: michael@0: if (scrollOffsetUpdated) { michael@0: // Once layout issues a scroll offset update, it becomes impervious to michael@0: // scroll offset updates from APZ until we acknowledge the update it sent. michael@0: // This prevents APZ updates from clobbering scroll updates from other michael@0: // more "legitimate" sources like content scripts. michael@0: nsRefPtr controller = GetGeckoContentController(); michael@0: if (controller) { michael@0: APZC_LOG("%p sending scroll update acknowledgement with gen %lu\n", this, aLayerMetrics.GetScrollGeneration()); michael@0: controller->AcknowledgeScrollUpdate(aLayerMetrics.GetScrollId(), michael@0: aLayerMetrics.GetScrollGeneration()); michael@0: } michael@0: } michael@0: michael@0: if (needContentRepaint) { michael@0: RequestContentRepaint(); michael@0: } michael@0: UpdateSharedCompositorFrameMetrics(); michael@0: } michael@0: michael@0: const FrameMetrics& AsyncPanZoomController::GetFrameMetrics() { michael@0: mMonitor.AssertCurrentThreadIn(); michael@0: return mFrameMetrics; michael@0: } michael@0: michael@0: void AsyncPanZoomController::ZoomToRect(CSSRect aRect) { michael@0: if (!aRect.IsFinite()) { michael@0: NS_WARNING("ZoomToRect got called with a non-finite rect; ignoring...\n"); michael@0: return; michael@0: } michael@0: michael@0: SetState(ANIMATING_ZOOM); michael@0: michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mMonitor); michael@0: michael@0: ParentLayerIntRect compositionBounds = mFrameMetrics.mCompositionBounds; michael@0: CSSRect cssPageRect = mFrameMetrics.mScrollableRect; michael@0: CSSPoint scrollOffset = mFrameMetrics.GetScrollOffset(); michael@0: CSSToParentLayerScale currentZoom = mFrameMetrics.GetZoomToParent(); michael@0: CSSToParentLayerScale targetZoom; michael@0: michael@0: // The minimum zoom to prevent over-zoom-out. michael@0: // If the zoom factor is lower than this (i.e. we are zoomed more into the page), michael@0: // then the CSS content rect, in layers pixels, will be smaller than the michael@0: // composition bounds. If this happens, we can't fill the target composited michael@0: // area with this frame. michael@0: CSSToParentLayerScale localMinZoom(std::max((mZoomConstraints.mMinZoom * mFrameMetrics.mTransformScale).scale, michael@0: std::max(compositionBounds.width / cssPageRect.width, michael@0: compositionBounds.height / cssPageRect.height))); michael@0: CSSToParentLayerScale localMaxZoom = mZoomConstraints.mMaxZoom * mFrameMetrics.mTransformScale; michael@0: michael@0: if (!aRect.IsEmpty()) { michael@0: // Intersect the zoom-to-rect to the CSS rect to make sure it fits. michael@0: aRect = aRect.Intersect(cssPageRect); michael@0: targetZoom = CSSToParentLayerScale(std::min(compositionBounds.width / aRect.width, michael@0: compositionBounds.height / aRect.height)); michael@0: } michael@0: // 1. If the rect is empty, request received from browserElementScrolling.js michael@0: // 2. currentZoom is equal to mZoomConstraints.mMaxZoom and user still double-tapping it michael@0: // 3. currentZoom is equal to localMinZoom and user still double-tapping it michael@0: // Treat these three cases as a request to zoom out as much as possible. michael@0: if (aRect.IsEmpty() || michael@0: (currentZoom == localMaxZoom && targetZoom >= localMaxZoom) || michael@0: (currentZoom == localMinZoom && targetZoom <= localMinZoom)) { michael@0: CSSSize compositedSize = mFrameMetrics.CalculateCompositedSizeInCssPixels(); michael@0: float y = scrollOffset.y; michael@0: float newHeight = michael@0: cssPageRect.width * (compositedSize.height / compositedSize.width); michael@0: float dh = compositedSize.height - newHeight; michael@0: michael@0: aRect = CSSRect(0.0f, michael@0: y + dh/2, michael@0: cssPageRect.width, michael@0: newHeight); michael@0: aRect = aRect.Intersect(cssPageRect); michael@0: targetZoom = CSSToParentLayerScale(std::min(compositionBounds.width / aRect.width, michael@0: compositionBounds.height / aRect.height)); michael@0: } michael@0: michael@0: targetZoom.scale = clamped(targetZoom.scale, localMinZoom.scale, localMaxZoom.scale); michael@0: FrameMetrics endZoomToMetrics = mFrameMetrics; michael@0: endZoomToMetrics.SetZoom(targetZoom / mFrameMetrics.mTransformScale); michael@0: michael@0: // Adjust the zoomToRect to a sensible position to prevent overscrolling. michael@0: CSSSize sizeAfterZoom = endZoomToMetrics.CalculateCompositedSizeInCssPixels(); michael@0: michael@0: // If either of these conditions are met, the page will be michael@0: // overscrolled after zoomed michael@0: if (aRect.y + sizeAfterZoom.height > cssPageRect.height) { michael@0: aRect.y = cssPageRect.height - sizeAfterZoom.height; michael@0: aRect.y = aRect.y > 0 ? aRect.y : 0; michael@0: } michael@0: if (aRect.x + sizeAfterZoom.width > cssPageRect.width) { michael@0: aRect.x = cssPageRect.width - sizeAfterZoom.width; michael@0: aRect.x = aRect.x > 0 ? aRect.x : 0; michael@0: } michael@0: michael@0: endZoomToMetrics.SetScrollOffset(aRect.TopLeft()); michael@0: endZoomToMetrics.SetDisplayPortMargins( michael@0: CalculatePendingDisplayPort(endZoomToMetrics, michael@0: ScreenPoint(0,0), michael@0: 0)); michael@0: endZoomToMetrics.SetUseDisplayPortMargins(); michael@0: michael@0: StartAnimation(new ZoomAnimation( michael@0: mFrameMetrics.GetScrollOffset(), michael@0: mFrameMetrics.GetZoom(), michael@0: endZoomToMetrics.GetScrollOffset(), michael@0: endZoomToMetrics.GetZoom())); michael@0: michael@0: // Schedule a repaint now, so the new displayport will be painted before the michael@0: // animation finishes. michael@0: RequestContentRepaint(endZoomToMetrics); michael@0: } michael@0: } michael@0: michael@0: void AsyncPanZoomController::ContentReceivedTouch(bool aPreventDefault) { michael@0: mTouchBlockState.mPreventDefaultSet = true; michael@0: mTouchBlockState.mPreventDefault = aPreventDefault; michael@0: CheckContentResponse(); michael@0: } michael@0: michael@0: void AsyncPanZoomController::CheckContentResponse() { michael@0: bool canProceedToTouchState = true; michael@0: michael@0: if (mFrameMetrics.mMayHaveTouchListeners) { michael@0: canProceedToTouchState &= mTouchBlockState.mPreventDefaultSet; michael@0: } michael@0: michael@0: if (mTouchActionPropertyEnabled) { michael@0: canProceedToTouchState &= mTouchBlockState.mAllowedTouchBehaviorSet; michael@0: } michael@0: michael@0: if (!canProceedToTouchState) { michael@0: return; michael@0: } michael@0: michael@0: if (mContentResponseTimeoutTask) { michael@0: mContentResponseTimeoutTask->Cancel(); michael@0: mContentResponseTimeoutTask = nullptr; michael@0: } michael@0: michael@0: if (mState == WAITING_CONTENT_RESPONSE) { michael@0: if (!mTouchBlockState.mPreventDefault) { michael@0: SetState(NOTHING); michael@0: } michael@0: michael@0: mHandlingTouchQueue = true; michael@0: michael@0: while (!mTouchQueue.IsEmpty()) { michael@0: if (!mTouchBlockState.mPreventDefault) { michael@0: HandleInputEvent(mTouchQueue[0]); michael@0: } michael@0: michael@0: if (mTouchQueue[0].mType == MultiTouchInput::MULTITOUCH_END || michael@0: mTouchQueue[0].mType == MultiTouchInput::MULTITOUCH_CANCEL) { michael@0: mTouchQueue.RemoveElementAt(0); michael@0: break; michael@0: } michael@0: michael@0: mTouchQueue.RemoveElementAt(0); michael@0: } michael@0: michael@0: mHandlingTouchQueue = false; michael@0: } michael@0: } michael@0: michael@0: bool AsyncPanZoomController::TouchActionAllowPinchZoom() { michael@0: if (!mTouchActionPropertyEnabled) { michael@0: return true; michael@0: } michael@0: // Pointer events specification implies all touch points to allow zoom michael@0: // to perform it. michael@0: for (size_t i = 0; i < mTouchBlockState.mAllowedTouchBehaviors.Length(); i++) { michael@0: if (!(mTouchBlockState.mAllowedTouchBehaviors[i] & AllowedTouchBehavior::PINCH_ZOOM)) { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool AsyncPanZoomController::TouchActionAllowDoubleTapZoom() { michael@0: if (!mTouchActionPropertyEnabled) { michael@0: return true; michael@0: } michael@0: for (size_t i = 0; i < mTouchBlockState.mAllowedTouchBehaviors.Length(); i++) { michael@0: if (!(mTouchBlockState.mAllowedTouchBehaviors[i] & AllowedTouchBehavior::DOUBLE_TAP_ZOOM)) { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: AsyncPanZoomController::TouchBehaviorFlags michael@0: AsyncPanZoomController::GetTouchBehavior(uint32_t touchIndex) { michael@0: if (touchIndex < mTouchBlockState.mAllowedTouchBehaviors.Length()) { michael@0: return mTouchBlockState.mAllowedTouchBehaviors[touchIndex]; michael@0: } michael@0: return DefaultTouchBehavior; michael@0: } michael@0: michael@0: AsyncPanZoomController::TouchBehaviorFlags michael@0: AsyncPanZoomController::GetAllowedTouchBehavior(ScreenIntPoint& aPoint) { michael@0: // Here we need to perform a hit testing over the touch-action regions attached to the michael@0: // layer associated with current apzc. michael@0: // Currently they are in progress, for more info see bug 928833. michael@0: return AllowedTouchBehavior::UNKNOWN; michael@0: } michael@0: michael@0: void AsyncPanZoomController::SetAllowedTouchBehavior(const nsTArray& aBehaviors) { michael@0: mTouchBlockState.mAllowedTouchBehaviors.Clear(); michael@0: mTouchBlockState.mAllowedTouchBehaviors.AppendElements(aBehaviors); michael@0: mTouchBlockState.mAllowedTouchBehaviorSet = true; michael@0: CheckContentResponse(); michael@0: } michael@0: michael@0: void AsyncPanZoomController::SetState(PanZoomState aNewState) { michael@0: michael@0: PanZoomState oldState; michael@0: michael@0: // Intentional scoping for mutex michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mMonitor); michael@0: oldState = mState; michael@0: mState = aNewState; michael@0: } michael@0: michael@0: if (nsRefPtr controller = GetGeckoContentController()) { michael@0: if (!IsTransformingState(oldState) && IsTransformingState(aNewState)) { michael@0: controller->NotifyAPZStateChange( michael@0: GetGuid(), APZStateChange::TransformBegin); michael@0: } else if (IsTransformingState(oldState) && !IsTransformingState(aNewState)) { michael@0: controller->NotifyAPZStateChange( michael@0: GetGuid(), APZStateChange::TransformEnd); michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool AsyncPanZoomController::IsTransformingState(PanZoomState aState) { michael@0: return !(aState == NOTHING || aState == TOUCHING || aState == WAITING_CONTENT_RESPONSE); michael@0: } michael@0: michael@0: bool AsyncPanZoomController::IsPanningState(PanZoomState aState) { michael@0: return (aState == PANNING || aState == PANNING_LOCKED_X || aState == PANNING_LOCKED_Y); michael@0: } michael@0: michael@0: void AsyncPanZoomController::SetContentResponseTimer() { michael@0: if (!mContentResponseTimeoutTask) { michael@0: mContentResponseTimeoutTask = michael@0: NewRunnableMethod(this, &AsyncPanZoomController::TimeoutContentResponse); michael@0: michael@0: PostDelayedTask(mContentResponseTimeoutTask, gfxPrefs::APZContentResponseTimeout()); michael@0: } michael@0: } michael@0: michael@0: void AsyncPanZoomController::TimeoutContentResponse() { michael@0: mContentResponseTimeoutTask = nullptr; michael@0: ContentReceivedTouch(false); michael@0: } michael@0: michael@0: void AsyncPanZoomController::UpdateZoomConstraints(const ZoomConstraints& aConstraints) { michael@0: APZC_LOG("%p updating zoom constraints to %d %d %f %f\n", this, aConstraints.mAllowZoom, michael@0: aConstraints.mAllowDoubleTapZoom, aConstraints.mMinZoom.scale, aConstraints.mMaxZoom.scale); michael@0: if (IsNaN(aConstraints.mMinZoom.scale) || IsNaN(aConstraints.mMaxZoom.scale)) { michael@0: NS_WARNING("APZC received zoom constraints with NaN values; dropping...\n"); michael@0: return; michael@0: } michael@0: // inf float values and other bad cases should be sanitized by the code below. michael@0: mZoomConstraints.mAllowZoom = aConstraints.mAllowZoom; michael@0: mZoomConstraints.mAllowDoubleTapZoom = aConstraints.mAllowDoubleTapZoom; michael@0: mZoomConstraints.mMinZoom = (MIN_ZOOM > aConstraints.mMinZoom ? MIN_ZOOM : aConstraints.mMinZoom); michael@0: mZoomConstraints.mMaxZoom = (MAX_ZOOM > aConstraints.mMaxZoom ? aConstraints.mMaxZoom : MAX_ZOOM); michael@0: if (mZoomConstraints.mMaxZoom < mZoomConstraints.mMinZoom) { michael@0: mZoomConstraints.mMaxZoom = mZoomConstraints.mMinZoom; michael@0: } michael@0: } michael@0: michael@0: ZoomConstraints michael@0: AsyncPanZoomController::GetZoomConstraints() const michael@0: { michael@0: return mZoomConstraints; michael@0: } michael@0: michael@0: michael@0: void AsyncPanZoomController::PostDelayedTask(Task* aTask, int aDelayMs) { michael@0: nsRefPtr controller = GetGeckoContentController(); michael@0: if (controller) { michael@0: controller->PostDelayedTask(aTask, aDelayMs); michael@0: } michael@0: } michael@0: michael@0: void AsyncPanZoomController::SendAsyncScrollEvent() { michael@0: nsRefPtr controller = GetGeckoContentController(); michael@0: if (!controller) { michael@0: return; michael@0: } michael@0: michael@0: bool isRoot; michael@0: CSSRect contentRect; michael@0: CSSSize scrollableSize; michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mMonitor); michael@0: michael@0: isRoot = mFrameMetrics.mIsRoot; michael@0: scrollableSize = mFrameMetrics.mScrollableRect.Size(); michael@0: contentRect = mFrameMetrics.CalculateCompositedRectInCssPixels(); michael@0: contentRect.MoveTo(mCurrentAsyncScrollOffset); michael@0: } michael@0: michael@0: controller->SendAsyncScrollDOMEvent(isRoot, contentRect, scrollableSize); michael@0: } michael@0: michael@0: bool AsyncPanZoomController::Matches(const ScrollableLayerGuid& aGuid) michael@0: { michael@0: return aGuid == GetGuid(); michael@0: } michael@0: michael@0: void AsyncPanZoomController::GetGuid(ScrollableLayerGuid* aGuidOut) michael@0: { michael@0: if (aGuidOut) { michael@0: *aGuidOut = GetGuid(); michael@0: } michael@0: } michael@0: michael@0: ScrollableLayerGuid AsyncPanZoomController::GetGuid() michael@0: { michael@0: return ScrollableLayerGuid(mLayersId, mFrameMetrics); michael@0: } michael@0: michael@0: void AsyncPanZoomController::UpdateSharedCompositorFrameMetrics() michael@0: { michael@0: mMonitor.AssertCurrentThreadIn(); michael@0: michael@0: FrameMetrics* frame = mSharedFrameMetricsBuffer ? michael@0: static_cast(mSharedFrameMetricsBuffer->memory()) : nullptr; michael@0: michael@0: if (frame && mSharedLock && gfxPrefs::UseProgressiveTilePainting()) { michael@0: mSharedLock->Lock(); michael@0: *frame = mFrameMetrics; michael@0: mSharedLock->Unlock(); michael@0: } michael@0: } michael@0: michael@0: void AsyncPanZoomController::ShareCompositorFrameMetrics() { michael@0: michael@0: PCompositorParent* compositor = michael@0: (mCrossProcessCompositorParent ? mCrossProcessCompositorParent : mCompositorParent.get()); michael@0: michael@0: // Only create the shared memory buffer if it hasn't already been created, michael@0: // we are using progressive tile painting, and we have a michael@0: // compositor to pass the shared memory back to the content process/thread. michael@0: if (!mSharedFrameMetricsBuffer && compositor && gfxPrefs::UseProgressiveTilePainting()) { michael@0: michael@0: // Create shared memory and initialize it with the current FrameMetrics value michael@0: mSharedFrameMetricsBuffer = new ipc::SharedMemoryBasic; michael@0: FrameMetrics* frame = nullptr; michael@0: mSharedFrameMetricsBuffer->Create(sizeof(FrameMetrics)); michael@0: mSharedFrameMetricsBuffer->Map(sizeof(FrameMetrics)); michael@0: frame = static_cast(mSharedFrameMetricsBuffer->memory()); michael@0: michael@0: if (frame) { michael@0: michael@0: { // scope the monitor, only needed to copy the FrameMetrics. michael@0: ReentrantMonitorAutoEnter lock(mMonitor); michael@0: *frame = mFrameMetrics; michael@0: } michael@0: michael@0: // Get the process id of the content process michael@0: base::ProcessHandle processHandle = compositor->OtherProcess(); michael@0: ipc::SharedMemoryBasic::Handle mem = ipc::SharedMemoryBasic::NULLHandle(); michael@0: michael@0: // Get the shared memory handle to share with the content process michael@0: mSharedFrameMetricsBuffer->ShareToProcess(processHandle, &mem); michael@0: michael@0: // Get the cross process mutex handle to share with the content process michael@0: mSharedLock = new CrossProcessMutex("AsyncPanZoomControlLock"); michael@0: CrossProcessMutexHandle handle = mSharedLock->ShareToProcess(processHandle); michael@0: michael@0: // Send the shared memory handle and cross process handle to the content michael@0: // process by an asynchronous ipc call. Include the APZC unique ID michael@0: // so the content process know which APZC sent this shared FrameMetrics. michael@0: if (!compositor->SendSharedCompositorFrameMetrics(mem, handle, mAPZCId)) { michael@0: APZC_LOG("%p failed to share FrameMetrics with content process.", this); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: ParentLayerPoint AsyncPanZoomController::ToParentLayerCoords(const ScreenPoint& aPoint) michael@0: { michael@0: return TransformTo(GetNontransientAsyncTransform() * GetCSSTransform(), aPoint); michael@0: } michael@0: michael@0: void AsyncPanZoomController::UpdateTransformScale() michael@0: { michael@0: gfx3DMatrix nontransientTransforms = GetNontransientAsyncTransform() * GetCSSTransform(); michael@0: if (!FuzzyEqualsMultiplicative(nontransientTransforms.GetXScale(), nontransientTransforms.GetYScale())) { michael@0: NS_WARNING("The x- and y-scales of the nontransient transforms should be equal"); michael@0: } michael@0: mFrameMetrics.mTransformScale.scale = nontransientTransforms.GetXScale(); michael@0: } michael@0: michael@0: } michael@0: }