1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,2209 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set sw=2 ts=8 et tw=80 : */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include <math.h> // for fabsf, fabs, atan2 1.11 +#include <stdint.h> // for uint32_t, uint64_t 1.12 +#include <sys/types.h> // for int32_t 1.13 +#include <algorithm> // for max, min 1.14 +#include "AnimationCommon.h" // for ComputedTimingFunction 1.15 +#include "AsyncPanZoomController.h" // for AsyncPanZoomController, etc 1.16 +#include "CompositorParent.h" // for CompositorParent 1.17 +#include "FrameMetrics.h" // for FrameMetrics, etc 1.18 +#include "GestureEventListener.h" // for GestureEventListener 1.19 +#include "InputData.h" // for MultiTouchInput, etc 1.20 +#include "Units.h" // for CSSRect, CSSPoint, etc 1.21 +#include "UnitTransforms.h" // for TransformTo 1.22 +#include "base/message_loop.h" // for MessageLoop 1.23 +#include "base/task.h" // for NewRunnableMethod, etc 1.24 +#include "base/tracked.h" // for FROM_HERE 1.25 +#include "gfxPrefs.h" // for gfxPrefs 1.26 +#include "gfxTypes.h" // for gfxFloat 1.27 +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc 1.28 +#include "mozilla/BasicEvents.h" // for Modifiers, MODIFIER_* 1.29 +#include "mozilla/ClearOnShutdown.h" // for ClearOnShutdown 1.30 +#include "mozilla/Constants.h" // for M_PI 1.31 +#include "mozilla/EventForwards.h" // for nsEventStatus_* 1.32 +#include "mozilla/Preferences.h" // for Preferences 1.33 +#include "mozilla/ReentrantMonitor.h" // for ReentrantMonitorAutoEnter, etc 1.34 +#include "mozilla/StaticPtr.h" // for StaticAutoPtr 1.35 +#include "mozilla/TimeStamp.h" // for TimeDuration, TimeStamp 1.36 +#include "mozilla/dom/Touch.h" // for Touch 1.37 +#include "mozilla/gfx/BasePoint.h" // for BasePoint 1.38 +#include "mozilla/gfx/BaseRect.h" // for BaseRect 1.39 +#include "mozilla/gfx/Point.h" // for Point, RoundedToInt, etc 1.40 +#include "mozilla/gfx/Rect.h" // for RoundedIn 1.41 +#include "mozilla/gfx/ScaleFactor.h" // for ScaleFactor 1.42 +#include "mozilla/layers/APZCTreeManager.h" // for ScrollableLayerGuid 1.43 +#include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform 1.44 +#include "mozilla/layers/Axis.h" // for AxisX, AxisY, Axis, etc 1.45 +#include "mozilla/layers/LayerTransactionParent.h" // for LayerTransactionParent 1.46 +#include "mozilla/layers/PCompositorParent.h" // for PCompositorParent 1.47 +#include "mozilla/layers/TaskThrottler.h" // for TaskThrottler 1.48 +#include "mozilla/mozalloc.h" // for operator new, etc 1.49 +#include "mozilla/unused.h" // for unused 1.50 +#include "mozilla/FloatingPoint.h" // for FuzzyEqualsMultiplicative 1.51 +#include "nsAlgorithm.h" // for clamped 1.52 +#include "nsAutoPtr.h" // for nsRefPtr 1.53 +#include "nsCOMPtr.h" // for already_AddRefed 1.54 +#include "nsDebug.h" // for NS_WARNING 1.55 +#include "nsIDOMWindowUtils.h" // for nsIDOMWindowUtils 1.56 +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc 1.57 +#include "nsMathUtils.h" // for NS_hypot 1.58 +#include "nsPoint.h" // for nsIntPoint 1.59 +#include "nsStyleConsts.h" 1.60 +#include "nsStyleStruct.h" // for nsTimingFunction 1.61 +#include "nsTArray.h" // for nsTArray, nsTArray_Impl, etc 1.62 +#include "nsThreadUtils.h" // for NS_IsMainThread 1.63 +#include "SharedMemoryBasic.h" // for SharedMemoryBasic 1.64 + 1.65 +// #define APZC_ENABLE_RENDERTRACE 1.66 + 1.67 +#define APZC_LOG(...) 1.68 +// #define APZC_LOG(...) printf_stderr("APZC: " __VA_ARGS__) 1.69 +#define APZC_LOG_FM(fm, prefix, ...) \ 1.70 + APZC_LOG(prefix ":" \ 1.71 + " i=(%ld %lld) cb=(%d %d %d %d) rcs=(%.3f %.3f) dp=(%.3f %.3f %.3f %.3f) dpm=(%.3f %.3f %.3f %.3f) um=%d " \ 1.72 + "v=(%.3f %.3f %.3f %.3f) s=(%.3f %.3f) sr=(%.3f %.3f %.3f %.3f) z=(%.3f %.3f %.3f %.3f) u=(%d %lu)\n", \ 1.73 + __VA_ARGS__, \ 1.74 + fm.mPresShellId, fm.GetScrollId(), \ 1.75 + fm.mCompositionBounds.x, fm.mCompositionBounds.y, fm.mCompositionBounds.width, fm.mCompositionBounds.height, \ 1.76 + fm.GetRootCompositionSize().width, fm.GetRootCompositionSize().height, \ 1.77 + fm.mDisplayPort.x, fm.mDisplayPort.y, fm.mDisplayPort.width, fm.mDisplayPort.height, \ 1.78 + fm.GetDisplayPortMargins().top, fm.GetDisplayPortMargins().right, fm.GetDisplayPortMargins().bottom, fm.GetDisplayPortMargins().left, \ 1.79 + fm.GetUseDisplayPortMargins() ? 1 : 0, \ 1.80 + fm.mViewport.x, fm.mViewport.y, fm.mViewport.width, fm.mViewport.height, \ 1.81 + fm.GetScrollOffset().x, fm.GetScrollOffset().y, \ 1.82 + fm.mScrollableRect.x, fm.mScrollableRect.y, fm.mScrollableRect.width, fm.mScrollableRect.height, \ 1.83 + fm.mDevPixelsPerCSSPixel.scale, fm.mResolution.scale, fm.mCumulativeResolution.scale, fm.GetZoom().scale, \ 1.84 + fm.GetScrollOffsetUpdated(), fm.GetScrollGeneration()); \ 1.85 + 1.86 +// Static helper functions 1.87 +namespace { 1.88 + 1.89 +int32_t 1.90 +WidgetModifiersToDOMModifiers(mozilla::Modifiers aModifiers) 1.91 +{ 1.92 + int32_t result = 0; 1.93 + if (aModifiers & mozilla::MODIFIER_SHIFT) { 1.94 + result |= nsIDOMWindowUtils::MODIFIER_SHIFT; 1.95 + } 1.96 + if (aModifiers & mozilla::MODIFIER_CONTROL) { 1.97 + result |= nsIDOMWindowUtils::MODIFIER_CONTROL; 1.98 + } 1.99 + if (aModifiers & mozilla::MODIFIER_ALT) { 1.100 + result |= nsIDOMWindowUtils::MODIFIER_ALT; 1.101 + } 1.102 + if (aModifiers & mozilla::MODIFIER_META) { 1.103 + result |= nsIDOMWindowUtils::MODIFIER_META; 1.104 + } 1.105 + if (aModifiers & mozilla::MODIFIER_ALTGRAPH) { 1.106 + result |= nsIDOMWindowUtils::MODIFIER_ALTGRAPH; 1.107 + } 1.108 + if (aModifiers & mozilla::MODIFIER_CAPSLOCK) { 1.109 + result |= nsIDOMWindowUtils::MODIFIER_CAPSLOCK; 1.110 + } 1.111 + if (aModifiers & mozilla::MODIFIER_FN) { 1.112 + result |= nsIDOMWindowUtils::MODIFIER_FN; 1.113 + } 1.114 + if (aModifiers & mozilla::MODIFIER_NUMLOCK) { 1.115 + result |= nsIDOMWindowUtils::MODIFIER_NUMLOCK; 1.116 + } 1.117 + if (aModifiers & mozilla::MODIFIER_SCROLLLOCK) { 1.118 + result |= nsIDOMWindowUtils::MODIFIER_SCROLLLOCK; 1.119 + } 1.120 + if (aModifiers & mozilla::MODIFIER_SYMBOLLOCK) { 1.121 + result |= nsIDOMWindowUtils::MODIFIER_SYMBOLLOCK; 1.122 + } 1.123 + if (aModifiers & mozilla::MODIFIER_OS) { 1.124 + result |= nsIDOMWindowUtils::MODIFIER_OS; 1.125 + } 1.126 + return result; 1.127 +} 1.128 + 1.129 +} 1.130 + 1.131 +using namespace mozilla::css; 1.132 + 1.133 +namespace mozilla { 1.134 +namespace layers { 1.135 + 1.136 +typedef mozilla::layers::AllowedTouchBehavior AllowedTouchBehavior; 1.137 +typedef GeckoContentController::APZStateChange APZStateChange; 1.138 + 1.139 +/* 1.140 + * The following prefs are used to control the behaviour of the APZC. 1.141 + * The default values are provided in gfxPrefs.h. 1.142 + * 1.143 + * "apz.allow-checkerboarding" 1.144 + * Pref that allows or disallows checkerboarding 1.145 + * 1.146 + * "apz.asyncscroll.throttle" 1.147 + * The time period in ms that throttles mozbrowserasyncscroll event. 1.148 + * 1.149 + * "apz.asyncscroll.timeout" 1.150 + * The timeout in ms for mAsyncScrollTimeoutTask delay task. 1.151 + * 1.152 + * "apz.axis_lock_mode" 1.153 + * The preferred axis locking style. See AxisLockMode for possible values. 1.154 + * 1.155 + * "apz.content_response_timeout" 1.156 + * Amount of time before we timeout response from content. For example, if 1.157 + * content is being unruly/slow and we don't get a response back within this 1.158 + * time, we will just pretend that content did not preventDefault any touch 1.159 + * events we dispatched to it. 1.160 + * 1.161 + * "apz.cross_slide_enabled" 1.162 + * Pref that enables integration with the Metro "cross-slide" gesture. 1.163 + * 1.164 + * "apz.enlarge_displayport_when_clipped" 1.165 + * Pref that enables enlarging of the displayport along one axis when the 1.166 + * generated displayport's size is beyond that of the scrollable rect on the 1.167 + * opposite axis. 1.168 + * 1.169 + * "apz.fling_friction" 1.170 + * Amount of friction applied during flings. 1.171 + * 1.172 + * "apz.fling_repaint_interval" 1.173 + * Maximum amount of time flinging before sending a viewport change. This will 1.174 + * asynchronously repaint the page. 1.175 + * 1.176 + * "apz.fling_stopped_threshold" 1.177 + * When flinging, if the velocity goes below this number, we just stop the 1.178 + * animation completely. This is to prevent asymptotically approaching 0 1.179 + * velocity and rerendering unnecessarily. 1.180 + * 1.181 + * "apz.max_velocity_inches_per_ms" 1.182 + * Maximum velocity in inches per millisecond. Velocity will be capped at this 1.183 + * value if a faster fling occurs. Negative values indicate unlimited velocity. 1.184 + * 1.185 + * "apz.max_velocity_queue_size" 1.186 + * Maximum size of velocity queue. The queue contains last N velocity records. 1.187 + * On touch end we calculate the average velocity in order to compensate 1.188 + * touch/mouse drivers misbehaviour. 1.189 + * 1.190 + * "apz.min_skate_speed" 1.191 + * Minimum amount of speed along an axis before we switch to "skate" multipliers 1.192 + * rather than using the "stationary" multipliers. 1.193 + * 1.194 + * "apz.num_paint_duration_samples" 1.195 + * Number of samples to store of how long it took to paint after the previous 1.196 + * requests. 1.197 + * 1.198 + * "apz.pan_repaint_interval" 1.199 + * Maximum amount of time while panning before sending a viewport change. This 1.200 + * will asynchronously repaint the page. It is also forced when panning stops. 1.201 + * 1.202 + * "apz.touch_start_tolerance" 1.203 + * Constant describing the tolerance in distance we use, multiplied by the 1.204 + * device DPI, before we start panning the screen. This is to prevent us from 1.205 + * accidentally processing taps as touch moves, and from very short/accidental 1.206 + * touches moving the screen. 1.207 + * 1.208 + * "apz.use_paint_duration" 1.209 + * Whether or not to use the estimated paint duration as a factor when projecting 1.210 + * the displayport in the direction of scrolling. If this value is set to false, 1.211 + * a constant 50ms paint time is used; the projection can be scaled as desired 1.212 + * using the apz.velocity_bias pref below. 1.213 + * 1.214 + * "apz.velocity_bias" 1.215 + * How much to adjust the displayport in the direction of scrolling. This value 1.216 + * is multiplied by the velocity and added to the displayport offset. 1.217 + * 1.218 + * "apz.x_skate_size_multiplier", "apz.y_skate_size_multiplier" 1.219 + * The multiplier we apply to the displayport size if it is skating (current 1.220 + * velocity is above apz.min_skate_speed). We prefer to increase the size of the 1.221 + * Y axis because it is more natural in the case that a user is reading a page 1.222 + * that scrolls up/down. Note that one, both or neither of these may be used 1.223 + * at any instant. 1.224 + * In general we want apz.[xy]_skate_size_multiplier to be smaller than the corresponding 1.225 + * stationary size multiplier because when panning fast we would like to paint 1.226 + * less and get faster, more predictable paint times. When panning slowly we 1.227 + * can afford to paint more even though it's slower. 1.228 + * 1.229 + * "apz.x_stationary_size_multiplier", "apz.y_stationary_size_multiplier" 1.230 + * The multiplier we apply to the displayport size if it is not skating (see 1.231 + * documentation for the skate size multipliers above). 1.232 + */ 1.233 + 1.234 +/** 1.235 + * Default touch behavior (is used when not touch behavior is set). 1.236 + */ 1.237 +static const uint32_t DefaultTouchBehavior = AllowedTouchBehavior::VERTICAL_PAN | 1.238 + AllowedTouchBehavior::HORIZONTAL_PAN | 1.239 + AllowedTouchBehavior::PINCH_ZOOM | 1.240 + AllowedTouchBehavior::DOUBLE_TAP_ZOOM; 1.241 + 1.242 +/** 1.243 + * Angle from axis within which we stay axis-locked 1.244 + */ 1.245 +static const double AXIS_LOCK_ANGLE = M_PI / 6.0; // 30 degrees 1.246 + 1.247 +/** 1.248 + * The distance in inches the user must pan before axis lock can be broken 1.249 + */ 1.250 +static const float AXIS_BREAKOUT_THRESHOLD = 1.0f/32.0f; 1.251 + 1.252 +/** 1.253 + * The angle at which axis lock can be broken 1.254 + */ 1.255 +static const double AXIS_BREAKOUT_ANGLE = M_PI / 8.0; // 22.5 degrees 1.256 + 1.257 +/** 1.258 + * Angle from axis to the line drawn by pan move. 1.259 + * If angle is less than this value we can assume that panning 1.260 + * can be done in allowed direction (horizontal or vertical). 1.261 + * Currently used only for touch-action css property stuff and was 1.262 + * added to keep behavior consistent with IE. 1.263 + */ 1.264 +static const double ALLOWED_DIRECT_PAN_ANGLE = M_PI / 3.0; // 60 degrees 1.265 + 1.266 +/** 1.267 + * Duration of a zoom to animation. 1.268 + */ 1.269 +static const TimeDuration ZOOM_TO_DURATION = TimeDuration::FromSeconds(0.25); 1.270 + 1.271 +/** 1.272 + * Computed time function used for sampling frames of a zoom to animation. 1.273 + */ 1.274 +StaticAutoPtr<ComputedTimingFunction> gComputedTimingFunction; 1.275 + 1.276 +/** 1.277 + * Maximum zoom amount, always used, even if a page asks for higher. 1.278 + */ 1.279 +static const CSSToScreenScale MAX_ZOOM(8.0f); 1.280 + 1.281 +/** 1.282 + * Minimum zoom amount, always used, even if a page asks for lower. 1.283 + */ 1.284 +static const CSSToScreenScale MIN_ZOOM(0.125f); 1.285 + 1.286 +/** 1.287 + * Is aAngle within the given threshold of the horizontal axis? 1.288 + * @param aAngle an angle in radians in the range [0, pi] 1.289 + * @param aThreshold an angle in radians in the range [0, pi/2] 1.290 + */ 1.291 +static bool IsCloseToHorizontal(float aAngle, float aThreshold) 1.292 +{ 1.293 + return (aAngle < aThreshold || aAngle > (M_PI - aThreshold)); 1.294 +} 1.295 + 1.296 +// As above, but for the vertical axis. 1.297 +static bool IsCloseToVertical(float aAngle, float aThreshold) 1.298 +{ 1.299 + return (fabs(aAngle - (M_PI / 2)) < aThreshold); 1.300 +} 1.301 + 1.302 +template <typename Units> 1.303 +static bool IsZero(const gfx::PointTyped<Units>& aPoint) 1.304 +{ 1.305 + return FuzzyEqualsMultiplicative(aPoint.x, 0.0f) 1.306 + && FuzzyEqualsMultiplicative(aPoint.y, 0.0f); 1.307 +} 1.308 + 1.309 +static inline void LogRendertraceRect(const ScrollableLayerGuid& aGuid, const char* aDesc, const char* aColor, const CSSRect& aRect) 1.310 +{ 1.311 +#ifdef APZC_ENABLE_RENDERTRACE 1.312 + static const TimeStamp sRenderStart = TimeStamp::Now(); 1.313 + TimeDuration delta = TimeStamp::Now() - sRenderStart; 1.314 + printf_stderr("(%llu,%lu,%llu)%s RENDERTRACE %f rect %s %f %f %f %f\n", 1.315 + aGuid.mLayersId, aGuid.mPresShellId, aGuid.GetScrollId(), 1.316 + aDesc, delta.ToMilliseconds(), aColor, 1.317 + aRect.x, aRect.y, aRect.width, aRect.height); 1.318 +#endif 1.319 +} 1.320 + 1.321 +static TimeStamp sFrameTime; 1.322 + 1.323 +// Counter used to give each APZC a unique id 1.324 +static uint32_t sAsyncPanZoomControllerCount = 0; 1.325 + 1.326 +static TimeStamp 1.327 +GetFrameTime() { 1.328 + if (sFrameTime.IsNull()) { 1.329 + return TimeStamp::Now(); 1.330 + } 1.331 + return sFrameTime; 1.332 +} 1.333 + 1.334 +class FlingAnimation: public AsyncPanZoomAnimation { 1.335 +public: 1.336 + FlingAnimation(AsyncPanZoomController& aApzc) 1.337 + : AsyncPanZoomAnimation(TimeDuration::FromMilliseconds(gfxPrefs::APZFlingRepaintInterval())) 1.338 + , mApzc(aApzc) 1.339 + {} 1.340 + /** 1.341 + * Advances a fling by an interpolated amount based on the passed in |aDelta|. 1.342 + * This should be called whenever sampling the content transform for this 1.343 + * frame. Returns true if the fling animation should be advanced by one frame, 1.344 + * or false if there is no fling or the fling has ended. 1.345 + */ 1.346 + virtual bool Sample(FrameMetrics& aFrameMetrics, 1.347 + const TimeDuration& aDelta); 1.348 + 1.349 +private: 1.350 + AsyncPanZoomController& mApzc; 1.351 +}; 1.352 + 1.353 +class ZoomAnimation: public AsyncPanZoomAnimation { 1.354 +public: 1.355 + ZoomAnimation(CSSPoint aStartOffset, CSSToScreenScale aStartZoom, 1.356 + CSSPoint aEndOffset, CSSToScreenScale aEndZoom) 1.357 + : mStartOffset(aStartOffset) 1.358 + , mStartZoom(aStartZoom) 1.359 + , mEndOffset(aEndOffset) 1.360 + , mEndZoom(aEndZoom) 1.361 + {} 1.362 + 1.363 + virtual bool Sample(FrameMetrics& aFrameMetrics, 1.364 + const TimeDuration& aDelta); 1.365 + 1.366 +private: 1.367 + TimeDuration mDuration; 1.368 + 1.369 + // Old metrics from before we started a zoom animation. This is only valid 1.370 + // when we are in the "ANIMATED_ZOOM" state. This is used so that we can 1.371 + // interpolate between the start and end frames. We only use the 1.372 + // |mViewportScrollOffset| and |mResolution| fields on this. 1.373 + CSSPoint mStartOffset; 1.374 + CSSToScreenScale mStartZoom; 1.375 + 1.376 + // Target metrics for a zoom to animation. This is only valid when we are in 1.377 + // the "ANIMATED_ZOOM" state. We only use the |mViewportScrollOffset| and 1.378 + // |mResolution| fields on this. 1.379 + CSSPoint mEndOffset; 1.380 + CSSToScreenScale mEndZoom; 1.381 +}; 1.382 + 1.383 +void 1.384 +AsyncPanZoomController::SetFrameTime(const TimeStamp& aTime) { 1.385 + sFrameTime = aTime; 1.386 +} 1.387 + 1.388 +/*static*/ void 1.389 +AsyncPanZoomController::InitializeGlobalState() 1.390 +{ 1.391 + MOZ_ASSERT(NS_IsMainThread()); 1.392 + 1.393 + static bool sInitialized = false; 1.394 + if (sInitialized) 1.395 + return; 1.396 + sInitialized = true; 1.397 + 1.398 + gComputedTimingFunction = new ComputedTimingFunction(); 1.399 + gComputedTimingFunction->Init( 1.400 + nsTimingFunction(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE)); 1.401 + ClearOnShutdown(&gComputedTimingFunction); 1.402 +} 1.403 + 1.404 +AsyncPanZoomController::AsyncPanZoomController(uint64_t aLayersId, 1.405 + APZCTreeManager* aTreeManager, 1.406 + GeckoContentController* aGeckoContentController, 1.407 + GestureBehavior aGestures) 1.408 + : mLayersId(aLayersId), 1.409 + mCrossProcessCompositorParent(nullptr), 1.410 + mPaintThrottler(GetFrameTime()), 1.411 + mGeckoContentController(aGeckoContentController), 1.412 + mRefPtrMonitor("RefPtrMonitor"), 1.413 + mMonitor("AsyncPanZoomController"), 1.414 + mTouchActionPropertyEnabled(gfxPrefs::TouchActionEnabled()), 1.415 + mContentResponseTimeoutTask(nullptr), 1.416 + mX(MOZ_THIS_IN_INITIALIZER_LIST()), 1.417 + mY(MOZ_THIS_IN_INITIALIZER_LIST()), 1.418 + mPanDirRestricted(false), 1.419 + mZoomConstraints(false, false, MIN_ZOOM, MAX_ZOOM), 1.420 + mLastSampleTime(GetFrameTime()), 1.421 + mState(NOTHING), 1.422 + mLastAsyncScrollTime(GetFrameTime()), 1.423 + mLastAsyncScrollOffset(0, 0), 1.424 + mCurrentAsyncScrollOffset(0, 0), 1.425 + mAsyncScrollTimeoutTask(nullptr), 1.426 + mHandlingTouchQueue(false), 1.427 + mTreeManager(aTreeManager), 1.428 + mScrollParentId(FrameMetrics::NULL_SCROLL_ID), 1.429 + mAPZCId(sAsyncPanZoomControllerCount++), 1.430 + mSharedFrameMetricsBuffer(nullptr), 1.431 + mSharedLock(nullptr) 1.432 +{ 1.433 + MOZ_COUNT_CTOR(AsyncPanZoomController); 1.434 + 1.435 + if (aGestures == USE_GESTURE_DETECTOR) { 1.436 + mGestureEventListener = new GestureEventListener(this); 1.437 + } 1.438 +} 1.439 + 1.440 +AsyncPanZoomController::~AsyncPanZoomController() { 1.441 + 1.442 + PCompositorParent* compositor = 1.443 + (mCrossProcessCompositorParent ? mCrossProcessCompositorParent : mCompositorParent.get()); 1.444 + 1.445 + // Only send the release message if the SharedFrameMetrics has been created. 1.446 + if (compositor && mSharedFrameMetricsBuffer) { 1.447 + unused << compositor->SendReleaseSharedCompositorFrameMetrics(mFrameMetrics.GetScrollId(), mAPZCId); 1.448 + } 1.449 + 1.450 + delete mSharedFrameMetricsBuffer; 1.451 + delete mSharedLock; 1.452 + 1.453 + MOZ_COUNT_DTOR(AsyncPanZoomController); 1.454 +} 1.455 + 1.456 +already_AddRefed<GeckoContentController> 1.457 +AsyncPanZoomController::GetGeckoContentController() { 1.458 + MonitorAutoLock lock(mRefPtrMonitor); 1.459 + nsRefPtr<GeckoContentController> controller = mGeckoContentController; 1.460 + return controller.forget(); 1.461 +} 1.462 + 1.463 +already_AddRefed<GestureEventListener> 1.464 +AsyncPanZoomController::GetGestureEventListener() { 1.465 + MonitorAutoLock lock(mRefPtrMonitor); 1.466 + nsRefPtr<GestureEventListener> listener = mGestureEventListener; 1.467 + return listener.forget(); 1.468 +} 1.469 + 1.470 +void 1.471 +AsyncPanZoomController::Destroy() 1.472 +{ 1.473 + { // scope the lock 1.474 + MonitorAutoLock lock(mRefPtrMonitor); 1.475 + mGeckoContentController = nullptr; 1.476 + mGestureEventListener = nullptr; 1.477 + } 1.478 + mPrevSibling = nullptr; 1.479 + mLastChild = nullptr; 1.480 + mParent = nullptr; 1.481 + mTreeManager = nullptr; 1.482 +} 1.483 + 1.484 +bool 1.485 +AsyncPanZoomController::IsDestroyed() 1.486 +{ 1.487 + return mTreeManager == nullptr; 1.488 +} 1.489 + 1.490 +/* static */float 1.491 +AsyncPanZoomController::GetTouchStartTolerance() 1.492 +{ 1.493 + return (gfxPrefs::APZTouchStartTolerance() * APZCTreeManager::GetDPI()); 1.494 +} 1.495 + 1.496 +/* static */AsyncPanZoomController::AxisLockMode AsyncPanZoomController::GetAxisLockMode() 1.497 +{ 1.498 + return static_cast<AxisLockMode>(gfxPrefs::APZAxisLockMode()); 1.499 +} 1.500 + 1.501 +nsEventStatus AsyncPanZoomController::ReceiveInputEvent(const InputData& aEvent) { 1.502 + if (aEvent.mInputType == MULTITOUCH_INPUT && 1.503 + aEvent.AsMultiTouchInput().mType == MultiTouchInput::MULTITOUCH_START) { 1.504 + // Starting a new touch block, clear old touch block state. 1.505 + mTouchBlockState = TouchBlockState(); 1.506 + } 1.507 + 1.508 + // If we may have touch listeners and touch action property is enabled, we 1.509 + // enable the machinery that allows touch listeners to preventDefault any touch inputs 1.510 + // and also waits for the allowed touch behavior values to be received from the outside. 1.511 + // This should not happen unless there are actually touch listeners and touch-action property 1.512 + // enable as it introduces potentially unbounded lag because it causes a round-trip through 1.513 + // content. Usually, if content is responding in a timely fashion, this only introduces a 1.514 + // nearly constant few hundred ms of lag. 1.515 + if (mFrameMetrics.mMayHaveTouchListeners && aEvent.mInputType == MULTITOUCH_INPUT && 1.516 + (mState == NOTHING || mState == TOUCHING || IsPanningState(mState))) { 1.517 + const MultiTouchInput& multiTouchInput = aEvent.AsMultiTouchInput(); 1.518 + if (multiTouchInput.mType == MultiTouchInput::MULTITOUCH_START) { 1.519 + SetState(WAITING_CONTENT_RESPONSE); 1.520 + } 1.521 + } 1.522 + 1.523 + if (mState == WAITING_CONTENT_RESPONSE || mHandlingTouchQueue) { 1.524 + if (aEvent.mInputType == MULTITOUCH_INPUT) { 1.525 + const MultiTouchInput& multiTouchInput = aEvent.AsMultiTouchInput(); 1.526 + mTouchQueue.AppendElement(multiTouchInput); 1.527 + 1.528 + SetContentResponseTimer(); 1.529 + } 1.530 + return nsEventStatus_eIgnore; 1.531 + } 1.532 + 1.533 + return HandleInputEvent(aEvent); 1.534 +} 1.535 + 1.536 +nsEventStatus AsyncPanZoomController::HandleInputEvent(const InputData& aEvent) { 1.537 + nsEventStatus rv = nsEventStatus_eIgnore; 1.538 + 1.539 + switch (aEvent.mInputType) { 1.540 + case MULTITOUCH_INPUT: { 1.541 + const MultiTouchInput& multiTouchInput = aEvent.AsMultiTouchInput(); 1.542 + 1.543 + nsRefPtr<GestureEventListener> listener = GetGestureEventListener(); 1.544 + if (listener) { 1.545 + rv = listener->HandleInputEvent(multiTouchInput); 1.546 + if (rv == nsEventStatus_eConsumeNoDefault) { 1.547 + return rv; 1.548 + } 1.549 + } 1.550 + 1.551 + switch (multiTouchInput.mType) { 1.552 + case MultiTouchInput::MULTITOUCH_START: rv = OnTouchStart(multiTouchInput); break; 1.553 + case MultiTouchInput::MULTITOUCH_MOVE: rv = OnTouchMove(multiTouchInput); break; 1.554 + case MultiTouchInput::MULTITOUCH_END: rv = OnTouchEnd(multiTouchInput); break; 1.555 + case MultiTouchInput::MULTITOUCH_CANCEL: rv = OnTouchCancel(multiTouchInput); break; 1.556 + default: NS_WARNING("Unhandled multitouch"); break; 1.557 + } 1.558 + break; 1.559 + } 1.560 + default: NS_WARNING("Unhandled input event"); break; 1.561 + } 1.562 + 1.563 + mLastEventTime = aEvent.mTime; 1.564 + return rv; 1.565 +} 1.566 + 1.567 +nsEventStatus AsyncPanZoomController::HandleGestureEvent(const InputData& aEvent) 1.568 +{ 1.569 + nsEventStatus rv = nsEventStatus_eIgnore; 1.570 + 1.571 + switch (aEvent.mInputType) { 1.572 + case PINCHGESTURE_INPUT: { 1.573 + const PinchGestureInput& pinchGestureInput = aEvent.AsPinchGestureInput(); 1.574 + switch (pinchGestureInput.mType) { 1.575 + case PinchGestureInput::PINCHGESTURE_START: rv = OnScaleBegin(pinchGestureInput); break; 1.576 + case PinchGestureInput::PINCHGESTURE_SCALE: rv = OnScale(pinchGestureInput); break; 1.577 + case PinchGestureInput::PINCHGESTURE_END: rv = OnScaleEnd(pinchGestureInput); break; 1.578 + default: NS_WARNING("Unhandled pinch gesture"); break; 1.579 + } 1.580 + break; 1.581 + } 1.582 + case TAPGESTURE_INPUT: { 1.583 + const TapGestureInput& tapGestureInput = aEvent.AsTapGestureInput(); 1.584 + switch (tapGestureInput.mType) { 1.585 + case TapGestureInput::TAPGESTURE_LONG: rv = OnLongPress(tapGestureInput); break; 1.586 + case TapGestureInput::TAPGESTURE_LONG_UP: rv = OnLongPressUp(tapGestureInput); break; 1.587 + case TapGestureInput::TAPGESTURE_UP: rv = OnSingleTapUp(tapGestureInput); break; 1.588 + case TapGestureInput::TAPGESTURE_CONFIRMED: rv = OnSingleTapConfirmed(tapGestureInput); break; 1.589 + case TapGestureInput::TAPGESTURE_DOUBLE: rv = OnDoubleTap(tapGestureInput); break; 1.590 + case TapGestureInput::TAPGESTURE_CANCEL: rv = OnCancelTap(tapGestureInput); break; 1.591 + default: NS_WARNING("Unhandled tap gesture"); break; 1.592 + } 1.593 + break; 1.594 + } 1.595 + default: NS_WARNING("Unhandled input event"); break; 1.596 + } 1.597 + 1.598 + mLastEventTime = aEvent.mTime; 1.599 + return rv; 1.600 +} 1.601 + 1.602 +nsEventStatus AsyncPanZoomController::OnTouchStart(const MultiTouchInput& aEvent) { 1.603 + APZC_LOG("%p got a touch-start in state %d\n", this, mState); 1.604 + mPanDirRestricted = false; 1.605 + ScreenIntPoint point = GetFirstTouchScreenPoint(aEvent); 1.606 + 1.607 + switch (mState) { 1.608 + case ANIMATING_ZOOM: 1.609 + // We just interrupted a double-tap animation, so force a redraw in case 1.610 + // this touchstart is just a tap that doesn't end up triggering a redraw. 1.611 + { 1.612 + ReentrantMonitorAutoEnter lock(mMonitor); 1.613 + RequestContentRepaint(); 1.614 + ScheduleComposite(); 1.615 + UpdateSharedCompositorFrameMetrics(); 1.616 + } 1.617 + // Fall through. 1.618 + case FLING: 1.619 + CancelAnimation(); 1.620 + // Fall through. 1.621 + case NOTHING: { 1.622 + mX.StartTouch(point.x); 1.623 + mY.StartTouch(point.y); 1.624 + APZCTreeManager* treeManagerLocal = mTreeManager; 1.625 + nsRefPtr<GeckoContentController> controller = GetGeckoContentController(); 1.626 + if (treeManagerLocal && controller) { 1.627 + bool touchCanBePan = treeManagerLocal->CanBePanned(this); 1.628 + controller->NotifyAPZStateChange( 1.629 + GetGuid(), APZStateChange::StartTouch, touchCanBePan); 1.630 + } 1.631 + SetState(TOUCHING); 1.632 + break; 1.633 + } 1.634 + case TOUCHING: 1.635 + case PANNING: 1.636 + case PANNING_LOCKED_X: 1.637 + case PANNING_LOCKED_Y: 1.638 + case CROSS_SLIDING_X: 1.639 + case CROSS_SLIDING_Y: 1.640 + case PINCHING: 1.641 + case WAITING_CONTENT_RESPONSE: 1.642 + NS_WARNING("Received impossible touch in OnTouchStart"); 1.643 + break; 1.644 + default: 1.645 + NS_WARNING("Unhandled case in OnTouchStart"); 1.646 + break; 1.647 + } 1.648 + 1.649 + return nsEventStatus_eConsumeNoDefault; 1.650 +} 1.651 + 1.652 +nsEventStatus AsyncPanZoomController::OnTouchMove(const MultiTouchInput& aEvent) { 1.653 + APZC_LOG("%p got a touch-move in state %d\n", this, mState); 1.654 + switch (mState) { 1.655 + case FLING: 1.656 + case NOTHING: 1.657 + case ANIMATING_ZOOM: 1.658 + // May happen if the user double-taps and drags without lifting after the 1.659 + // second tap. Ignore the move if this happens. 1.660 + return nsEventStatus_eIgnore; 1.661 + 1.662 + case CROSS_SLIDING_X: 1.663 + case CROSS_SLIDING_Y: 1.664 + // While cross-sliding, we don't want to consume any touchmove events for 1.665 + // panning or zooming, and let the caller handle them instead. 1.666 + return nsEventStatus_eIgnore; 1.667 + 1.668 + case TOUCHING: { 1.669 + float panThreshold = GetTouchStartTolerance(); 1.670 + UpdateWithTouchAtDevicePoint(aEvent); 1.671 + 1.672 + if (PanDistance() < panThreshold) { 1.673 + return nsEventStatus_eIgnore; 1.674 + } 1.675 + 1.676 + if (mTouchActionPropertyEnabled && 1.677 + (GetTouchBehavior(0) & AllowedTouchBehavior::VERTICAL_PAN) && 1.678 + (GetTouchBehavior(0) & AllowedTouchBehavior::HORIZONTAL_PAN)) { 1.679 + // User tries to trigger a touch behavior. If allowed touch behavior is vertical pan 1.680 + // + horizontal pan (touch-action value is equal to AUTO) we can return ConsumeNoDefault 1.681 + // status immediately to trigger cancel event further. It should happen independent of 1.682 + // the parent type (whether it is scrolling or not). 1.683 + StartPanning(aEvent); 1.684 + return nsEventStatus_eConsumeNoDefault; 1.685 + } 1.686 + 1.687 + return StartPanning(aEvent); 1.688 + } 1.689 + 1.690 + case PANNING: 1.691 + case PANNING_LOCKED_X: 1.692 + case PANNING_LOCKED_Y: 1.693 + TrackTouch(aEvent); 1.694 + return nsEventStatus_eConsumeNoDefault; 1.695 + 1.696 + case PINCHING: 1.697 + // The scale gesture listener should have handled this. 1.698 + NS_WARNING("Gesture listener should have handled pinching in OnTouchMove."); 1.699 + return nsEventStatus_eIgnore; 1.700 + 1.701 + case WAITING_CONTENT_RESPONSE: 1.702 + NS_WARNING("Received impossible touch in OnTouchMove"); 1.703 + break; 1.704 + } 1.705 + 1.706 + return nsEventStatus_eConsumeNoDefault; 1.707 +} 1.708 + 1.709 +nsEventStatus AsyncPanZoomController::OnTouchEnd(const MultiTouchInput& aEvent) { 1.710 + APZC_LOG("%p got a touch-end in state %d\n", this, mState); 1.711 + 1.712 + OnTouchEndOrCancel(); 1.713 + 1.714 + // In case no touch behavior triggered previously we can avoid sending 1.715 + // scroll events or requesting content repaint. This condition is added 1.716 + // to make tests consistent - in case touch-action is NONE (and therefore 1.717 + // no pans/zooms can be performed) we expected neither scroll or repaint 1.718 + // events. 1.719 + if (mState != NOTHING) { 1.720 + ReentrantMonitorAutoEnter lock(mMonitor); 1.721 + SendAsyncScrollEvent(); 1.722 + } 1.723 + 1.724 + switch (mState) { 1.725 + case FLING: 1.726 + // Should never happen. 1.727 + NS_WARNING("Received impossible touch end in OnTouchEnd."); 1.728 + // Fall through. 1.729 + case ANIMATING_ZOOM: 1.730 + case NOTHING: 1.731 + // May happen if the user double-taps and drags without lifting after the 1.732 + // second tap. Ignore if this happens. 1.733 + return nsEventStatus_eIgnore; 1.734 + 1.735 + case TOUCHING: 1.736 + case CROSS_SLIDING_X: 1.737 + case CROSS_SLIDING_Y: 1.738 + SetState(NOTHING); 1.739 + return nsEventStatus_eIgnore; 1.740 + 1.741 + case PANNING: 1.742 + case PANNING_LOCKED_X: 1.743 + case PANNING_LOCKED_Y: 1.744 + { 1.745 + // Make a local copy of the tree manager pointer and check if it's not 1.746 + // null before calling FlushRepaintsForOverscrollHandoffChain(). 1.747 + // This is necessary because Destroy(), which nulls out mTreeManager, 1.748 + // could be called concurrently. 1.749 + APZCTreeManager* treeManagerLocal = mTreeManager; 1.750 + if (treeManagerLocal) { 1.751 + if (!treeManagerLocal->FlushRepaintsForOverscrollHandoffChain()) { 1.752 + NS_WARNING("Overscroll handoff chain was empty during panning! This should not be the case."); 1.753 + // Graceful handling of error condition 1.754 + FlushRepaintForOverscrollHandoff(); 1.755 + } 1.756 + } 1.757 + } 1.758 + mX.EndTouch(); 1.759 + mY.EndTouch(); 1.760 + SetState(FLING); 1.761 + StartAnimation(new FlingAnimation(*this)); 1.762 + return nsEventStatus_eConsumeNoDefault; 1.763 + 1.764 + case PINCHING: 1.765 + SetState(NOTHING); 1.766 + // Scale gesture listener should have handled this. 1.767 + NS_WARNING("Gesture listener should have handled pinching in OnTouchEnd."); 1.768 + return nsEventStatus_eIgnore; 1.769 + 1.770 + case WAITING_CONTENT_RESPONSE: 1.771 + NS_WARNING("Received impossible touch in OnTouchEnd"); 1.772 + break; 1.773 + } 1.774 + 1.775 + return nsEventStatus_eConsumeNoDefault; 1.776 +} 1.777 + 1.778 +nsEventStatus AsyncPanZoomController::OnTouchCancel(const MultiTouchInput& aEvent) { 1.779 + APZC_LOG("%p got a touch-cancel in state %d\n", this, mState); 1.780 + OnTouchEndOrCancel(); 1.781 + SetState(NOTHING); 1.782 + return nsEventStatus_eConsumeNoDefault; 1.783 +} 1.784 + 1.785 +nsEventStatus AsyncPanZoomController::OnScaleBegin(const PinchGestureInput& aEvent) { 1.786 + APZC_LOG("%p got a scale-begin in state %d\n", this, mState); 1.787 + 1.788 + if (!TouchActionAllowPinchZoom()) { 1.789 + return nsEventStatus_eIgnore; 1.790 + } 1.791 + 1.792 + if (!mZoomConstraints.mAllowZoom) { 1.793 + return nsEventStatus_eConsumeNoDefault; 1.794 + } 1.795 + 1.796 + SetState(PINCHING); 1.797 + mLastZoomFocus = ToParentLayerCoords(aEvent.mFocusPoint) - mFrameMetrics.mCompositionBounds.TopLeft(); 1.798 + 1.799 + return nsEventStatus_eConsumeNoDefault; 1.800 +} 1.801 + 1.802 +nsEventStatus AsyncPanZoomController::OnScale(const PinchGestureInput& aEvent) { 1.803 + APZC_LOG("%p got a scale in state %d\n", this, mState); 1.804 + if (mState != PINCHING) { 1.805 + return nsEventStatus_eConsumeNoDefault; 1.806 + } 1.807 + 1.808 + float prevSpan = aEvent.mPreviousSpan; 1.809 + if (fabsf(prevSpan) <= EPSILON || fabsf(aEvent.mCurrentSpan) <= EPSILON) { 1.810 + // We're still handling it; we've just decided to throw this event away. 1.811 + return nsEventStatus_eConsumeNoDefault; 1.812 + } 1.813 + 1.814 + float spanRatio = aEvent.mCurrentSpan / aEvent.mPreviousSpan; 1.815 + 1.816 + { 1.817 + ReentrantMonitorAutoEnter lock(mMonitor); 1.818 + 1.819 + CSSToParentLayerScale userZoom = mFrameMetrics.GetZoomToParent(); 1.820 + ParentLayerPoint focusPoint = ToParentLayerCoords(aEvent.mFocusPoint) - mFrameMetrics.mCompositionBounds.TopLeft(); 1.821 + CSSPoint cssFocusPoint = focusPoint / userZoom; 1.822 + 1.823 + CSSPoint focusChange = (mLastZoomFocus - focusPoint) / userZoom; 1.824 + // If displacing by the change in focus point will take us off page bounds, 1.825 + // then reduce the displacement such that it doesn't. 1.826 + if (mX.DisplacementWillOverscroll(focusChange.x) != Axis::OVERSCROLL_NONE) { 1.827 + focusChange.x -= mX.DisplacementWillOverscrollAmount(focusChange.x); 1.828 + } 1.829 + if (mY.DisplacementWillOverscroll(focusChange.y) != Axis::OVERSCROLL_NONE) { 1.830 + focusChange.y -= mY.DisplacementWillOverscrollAmount(focusChange.y); 1.831 + } 1.832 + ScrollBy(focusChange); 1.833 + 1.834 + // When we zoom in with focus, we can zoom too much towards the boundaries 1.835 + // that we actually go over them. These are the needed displacements along 1.836 + // either axis such that we don't overscroll the boundaries when zooming. 1.837 + CSSPoint neededDisplacement; 1.838 + 1.839 + CSSToParentLayerScale realMinZoom = mZoomConstraints.mMinZoom * mFrameMetrics.mTransformScale; 1.840 + CSSToParentLayerScale realMaxZoom = mZoomConstraints.mMaxZoom * mFrameMetrics.mTransformScale; 1.841 + realMinZoom.scale = std::max(realMinZoom.scale, 1.842 + mFrameMetrics.mCompositionBounds.width / mFrameMetrics.mScrollableRect.width); 1.843 + realMinZoom.scale = std::max(realMinZoom.scale, 1.844 + mFrameMetrics.mCompositionBounds.height / mFrameMetrics.mScrollableRect.height); 1.845 + if (realMaxZoom < realMinZoom) { 1.846 + realMaxZoom = realMinZoom; 1.847 + } 1.848 + 1.849 + bool doScale = (spanRatio > 1.0 && userZoom < realMaxZoom) || 1.850 + (spanRatio < 1.0 && userZoom > realMinZoom); 1.851 + 1.852 + if (doScale) { 1.853 + spanRatio = clamped(spanRatio, 1.854 + realMinZoom.scale / userZoom.scale, 1.855 + realMaxZoom.scale / userZoom.scale); 1.856 + 1.857 + // Note that the spanRatio here should never put us into OVERSCROLL_BOTH because 1.858 + // up above we clamped it. 1.859 + neededDisplacement.x = -mX.ScaleWillOverscrollAmount(spanRatio, cssFocusPoint.x); 1.860 + neededDisplacement.y = -mY.ScaleWillOverscrollAmount(spanRatio, cssFocusPoint.y); 1.861 + 1.862 + ScaleWithFocus(spanRatio, cssFocusPoint); 1.863 + 1.864 + if (neededDisplacement != CSSPoint()) { 1.865 + ScrollBy(neededDisplacement); 1.866 + } 1.867 + 1.868 + ScheduleComposite(); 1.869 + // We don't want to redraw on every scale, so don't use 1.870 + // RequestContentRepaint() 1.871 + UpdateSharedCompositorFrameMetrics(); 1.872 + } 1.873 + 1.874 + mLastZoomFocus = focusPoint; 1.875 + } 1.876 + 1.877 + return nsEventStatus_eConsumeNoDefault; 1.878 +} 1.879 + 1.880 +nsEventStatus AsyncPanZoomController::OnScaleEnd(const PinchGestureInput& aEvent) { 1.881 + APZC_LOG("%p got a scale-end in state %d\n", this, mState); 1.882 + 1.883 + SetState(NOTHING); 1.884 + 1.885 + { 1.886 + ReentrantMonitorAutoEnter lock(mMonitor); 1.887 + ScheduleComposite(); 1.888 + RequestContentRepaint(); 1.889 + UpdateSharedCompositorFrameMetrics(); 1.890 + } 1.891 + 1.892 + return nsEventStatus_eConsumeNoDefault; 1.893 +} 1.894 + 1.895 +bool 1.896 +AsyncPanZoomController::ConvertToGecko(const ScreenPoint& aPoint, CSSPoint* aOut) 1.897 +{ 1.898 + APZCTreeManager* treeManagerLocal = mTreeManager; 1.899 + if (treeManagerLocal) { 1.900 + gfx3DMatrix transformToApzc; 1.901 + gfx3DMatrix transformToGecko; 1.902 + treeManagerLocal->GetInputTransforms(this, transformToApzc, transformToGecko); 1.903 + gfxPoint result = transformToGecko.Transform(gfxPoint(aPoint.x, aPoint.y)); 1.904 + // NOTE: This isn't *quite* LayoutDevicePoint, we just don't have a name 1.905 + // for this coordinate space and it maps the closest to LayoutDevicePoint. 1.906 + LayoutDevicePoint layoutPoint = LayoutDevicePoint(result.x, result.y); 1.907 + { // scoped lock to access mFrameMetrics 1.908 + ReentrantMonitorAutoEnter lock(mMonitor); 1.909 + *aOut = layoutPoint / mFrameMetrics.mDevPixelsPerCSSPixel; 1.910 + } 1.911 + return true; 1.912 + } 1.913 + return false; 1.914 +} 1.915 + 1.916 +nsEventStatus AsyncPanZoomController::OnLongPress(const TapGestureInput& aEvent) { 1.917 + APZC_LOG("%p got a long-press in state %d\n", this, mState); 1.918 + nsRefPtr<GeckoContentController> controller = GetGeckoContentController(); 1.919 + if (controller) { 1.920 + int32_t modifiers = WidgetModifiersToDOMModifiers(aEvent.modifiers); 1.921 + CSSPoint geckoScreenPoint; 1.922 + if (ConvertToGecko(aEvent.mPoint, &geckoScreenPoint)) { 1.923 + SetState(WAITING_CONTENT_RESPONSE); 1.924 + SetContentResponseTimer(); 1.925 + controller->HandleLongTap(geckoScreenPoint, modifiers, GetGuid()); 1.926 + return nsEventStatus_eConsumeNoDefault; 1.927 + } 1.928 + } 1.929 + return nsEventStatus_eIgnore; 1.930 +} 1.931 + 1.932 +nsEventStatus AsyncPanZoomController::OnLongPressUp(const TapGestureInput& aEvent) { 1.933 + APZC_LOG("%p got a long-tap-up in state %d\n", this, mState); 1.934 + nsRefPtr<GeckoContentController> controller = GetGeckoContentController(); 1.935 + if (controller) { 1.936 + int32_t modifiers = WidgetModifiersToDOMModifiers(aEvent.modifiers); 1.937 + CSSPoint geckoScreenPoint; 1.938 + if (ConvertToGecko(aEvent.mPoint, &geckoScreenPoint)) { 1.939 + controller->HandleLongTapUp(geckoScreenPoint, modifiers, GetGuid()); 1.940 + return nsEventStatus_eConsumeNoDefault; 1.941 + } 1.942 + } 1.943 + return nsEventStatus_eIgnore; 1.944 +} 1.945 + 1.946 +nsEventStatus AsyncPanZoomController::GenerateSingleTap(const ScreenIntPoint& aPoint, mozilla::Modifiers aModifiers) { 1.947 + nsRefPtr<GeckoContentController> controller = GetGeckoContentController(); 1.948 + if (controller) { 1.949 + CSSPoint geckoScreenPoint; 1.950 + if (ConvertToGecko(aPoint, &geckoScreenPoint)) { 1.951 + int32_t modifiers = WidgetModifiersToDOMModifiers(aModifiers); 1.952 + // Because this may be being running as part of APZCTreeManager::ReceiveInputEvent, 1.953 + // calling controller->HandleSingleTap directly might mean that content receives 1.954 + // the single tap message before the corresponding touch-up. To avoid that we 1.955 + // schedule the singletap message to run on the next spin of the event loop. 1.956 + // See bug 965381 for the issue this was causing. 1.957 + controller->PostDelayedTask( 1.958 + NewRunnableMethod(controller.get(), &GeckoContentController::HandleSingleTap, 1.959 + geckoScreenPoint, modifiers, GetGuid()), 1.960 + 0); 1.961 + mTouchBlockState.mSingleTapOccurred = true; 1.962 + return nsEventStatus_eConsumeNoDefault; 1.963 + } 1.964 + } 1.965 + return nsEventStatus_eIgnore; 1.966 +} 1.967 + 1.968 +void AsyncPanZoomController::OnTouchEndOrCancel() { 1.969 + if (nsRefPtr<GeckoContentController> controller = GetGeckoContentController()) { 1.970 + controller->NotifyAPZStateChange( 1.971 + GetGuid(), APZStateChange::EndTouch, mTouchBlockState.mSingleTapOccurred); 1.972 + } 1.973 +} 1.974 + 1.975 +nsEventStatus AsyncPanZoomController::OnSingleTapUp(const TapGestureInput& aEvent) { 1.976 + APZC_LOG("%p got a single-tap-up in state %d\n", this, mState); 1.977 + // If mZoomConstraints.mAllowDoubleTapZoom is true we wait for a call to OnSingleTapConfirmed before 1.978 + // sending event to content 1.979 + if (!(mZoomConstraints.mAllowDoubleTapZoom && TouchActionAllowDoubleTapZoom())) { 1.980 + return GenerateSingleTap(aEvent.mPoint, aEvent.modifiers); 1.981 + } 1.982 + return nsEventStatus_eIgnore; 1.983 +} 1.984 + 1.985 +nsEventStatus AsyncPanZoomController::OnSingleTapConfirmed(const TapGestureInput& aEvent) { 1.986 + APZC_LOG("%p got a single-tap-confirmed in state %d\n", this, mState); 1.987 + return GenerateSingleTap(aEvent.mPoint, aEvent.modifiers); 1.988 +} 1.989 + 1.990 +nsEventStatus AsyncPanZoomController::OnDoubleTap(const TapGestureInput& aEvent) { 1.991 + APZC_LOG("%p got a double-tap in state %d\n", this, mState); 1.992 + nsRefPtr<GeckoContentController> controller = GetGeckoContentController(); 1.993 + if (controller) { 1.994 + if (mZoomConstraints.mAllowDoubleTapZoom && TouchActionAllowDoubleTapZoom()) { 1.995 + int32_t modifiers = WidgetModifiersToDOMModifiers(aEvent.modifiers); 1.996 + CSSPoint geckoScreenPoint; 1.997 + if (ConvertToGecko(aEvent.mPoint, &geckoScreenPoint)) { 1.998 + controller->HandleDoubleTap(geckoScreenPoint, modifiers, GetGuid()); 1.999 + } 1.1000 + } 1.1001 + return nsEventStatus_eConsumeNoDefault; 1.1002 + } 1.1003 + return nsEventStatus_eIgnore; 1.1004 +} 1.1005 + 1.1006 +nsEventStatus AsyncPanZoomController::OnCancelTap(const TapGestureInput& aEvent) { 1.1007 + APZC_LOG("%p got a cancel-tap in state %d\n", this, mState); 1.1008 + // XXX: Implement this. 1.1009 + return nsEventStatus_eIgnore; 1.1010 +} 1.1011 + 1.1012 +float AsyncPanZoomController::PanDistance() { 1.1013 + ReentrantMonitorAutoEnter lock(mMonitor); 1.1014 + return NS_hypot(mX.PanDistance(), mY.PanDistance()); 1.1015 +} 1.1016 + 1.1017 +const ScreenPoint AsyncPanZoomController::GetVelocityVector() { 1.1018 + return ScreenPoint(mX.GetVelocity(), mY.GetVelocity()); 1.1019 +} 1.1020 + 1.1021 +void AsyncPanZoomController::HandlePanningWithTouchAction(double aAngle, TouchBehaviorFlags aBehavior) { 1.1022 + // Handling of cross sliding will need to be added in this method after touch-action released 1.1023 + // enabled by default. 1.1024 + if ((aBehavior & AllowedTouchBehavior::VERTICAL_PAN) && (aBehavior & AllowedTouchBehavior::HORIZONTAL_PAN)) { 1.1025 + if (mX.Scrollable() && mY.Scrollable()) { 1.1026 + if (IsCloseToHorizontal(aAngle, AXIS_LOCK_ANGLE)) { 1.1027 + mY.SetAxisLocked(true); 1.1028 + SetState(PANNING_LOCKED_X); 1.1029 + } else if (IsCloseToVertical(aAngle, AXIS_LOCK_ANGLE)) { 1.1030 + mX.SetAxisLocked(true); 1.1031 + SetState(PANNING_LOCKED_Y); 1.1032 + } else { 1.1033 + SetState(PANNING); 1.1034 + } 1.1035 + } else if (mX.Scrollable() || mY.Scrollable()) { 1.1036 + SetState(PANNING); 1.1037 + } else { 1.1038 + SetState(NOTHING); 1.1039 + } 1.1040 + } else if (aBehavior & AllowedTouchBehavior::HORIZONTAL_PAN) { 1.1041 + // Using bigger angle for panning to keep behavior consistent 1.1042 + // with IE. 1.1043 + if (IsCloseToHorizontal(aAngle, ALLOWED_DIRECT_PAN_ANGLE)) { 1.1044 + mY.SetAxisLocked(true); 1.1045 + SetState(PANNING_LOCKED_X); 1.1046 + mPanDirRestricted = true; 1.1047 + } else { 1.1048 + // Don't treat these touches as pan/zoom movements since 'touch-action' value 1.1049 + // requires it. 1.1050 + SetState(NOTHING); 1.1051 + } 1.1052 + } else if (aBehavior & AllowedTouchBehavior::VERTICAL_PAN) { 1.1053 + if (IsCloseToVertical(aAngle, ALLOWED_DIRECT_PAN_ANGLE)) { 1.1054 + mX.SetAxisLocked(true); 1.1055 + SetState(PANNING_LOCKED_Y); 1.1056 + mPanDirRestricted = true; 1.1057 + } else { 1.1058 + SetState(NOTHING); 1.1059 + } 1.1060 + } else { 1.1061 + SetState(NOTHING); 1.1062 + } 1.1063 +} 1.1064 + 1.1065 +void AsyncPanZoomController::HandlePanning(double aAngle) { 1.1066 + if (!gfxPrefs::APZCrossSlideEnabled() && (!mX.Scrollable() || !mY.Scrollable())) { 1.1067 + SetState(PANNING); 1.1068 + } else if (IsCloseToHorizontal(aAngle, AXIS_LOCK_ANGLE)) { 1.1069 + mY.SetAxisLocked(true); 1.1070 + if (mX.Scrollable()) { 1.1071 + SetState(PANNING_LOCKED_X); 1.1072 + } else { 1.1073 + SetState(CROSS_SLIDING_X); 1.1074 + mX.SetAxisLocked(true); 1.1075 + } 1.1076 + } else if (IsCloseToVertical(aAngle, AXIS_LOCK_ANGLE)) { 1.1077 + mX.SetAxisLocked(true); 1.1078 + if (mY.Scrollable()) { 1.1079 + SetState(PANNING_LOCKED_Y); 1.1080 + } else { 1.1081 + SetState(CROSS_SLIDING_Y); 1.1082 + mY.SetAxisLocked(true); 1.1083 + } 1.1084 + } else { 1.1085 + SetState(PANNING); 1.1086 + } 1.1087 +} 1.1088 + 1.1089 +nsEventStatus AsyncPanZoomController::StartPanning(const MultiTouchInput& aEvent) { 1.1090 + ReentrantMonitorAutoEnter lock(mMonitor); 1.1091 + 1.1092 + ScreenIntPoint point = GetFirstTouchScreenPoint(aEvent); 1.1093 + float dx = mX.PanDistance(point.x); 1.1094 + float dy = mY.PanDistance(point.y); 1.1095 + 1.1096 + // When the touch move breaks through the pan threshold, reposition the touch down origin 1.1097 + // so the page won't jump when we start panning. 1.1098 + mX.StartTouch(point.x); 1.1099 + mY.StartTouch(point.y); 1.1100 + mLastEventTime = aEvent.mTime; 1.1101 + 1.1102 + double angle = atan2(dy, dx); // range [-pi, pi] 1.1103 + angle = fabs(angle); // range [0, pi] 1.1104 + 1.1105 + if (mTouchActionPropertyEnabled) { 1.1106 + HandlePanningWithTouchAction(angle, GetTouchBehavior(0)); 1.1107 + } else { 1.1108 + if (GetAxisLockMode() == FREE) { 1.1109 + SetState(PANNING); 1.1110 + } else { 1.1111 + HandlePanning(angle); 1.1112 + } 1.1113 + } 1.1114 + 1.1115 + if (IsPanningState(mState)) { 1.1116 + if (nsRefPtr<GeckoContentController> controller = GetGeckoContentController()) { 1.1117 + controller->NotifyAPZStateChange(GetGuid(), APZStateChange::StartPanning); 1.1118 + } 1.1119 + return nsEventStatus_eConsumeNoDefault; 1.1120 + } 1.1121 + // Don't consume an event that didn't trigger a panning. 1.1122 + return nsEventStatus_eIgnore; 1.1123 +} 1.1124 + 1.1125 +void AsyncPanZoomController::UpdateWithTouchAtDevicePoint(const MultiTouchInput& aEvent) { 1.1126 + ScreenIntPoint point = GetFirstTouchScreenPoint(aEvent); 1.1127 + TimeDuration timeDelta = TimeDuration().FromMilliseconds(aEvent.mTime - mLastEventTime); 1.1128 + 1.1129 + // Probably a duplicate event, just throw it away. 1.1130 + if (timeDelta.ToMilliseconds() <= EPSILON) { 1.1131 + return; 1.1132 + } 1.1133 + 1.1134 + mX.UpdateWithTouchAtDevicePoint(point.x, timeDelta); 1.1135 + mY.UpdateWithTouchAtDevicePoint(point.y, timeDelta); 1.1136 +} 1.1137 + 1.1138 +void AsyncPanZoomController::AttemptScroll(const ScreenPoint& aStartPoint, 1.1139 + const ScreenPoint& aEndPoint, 1.1140 + uint32_t aOverscrollHandoffChainIndex) { 1.1141 + 1.1142 + // "start - end" rather than "end - start" because e.g. moving your finger 1.1143 + // down (*positive* direction along y axis) causes the vertical scroll offset 1.1144 + // to *decrease* as the page follows your finger. 1.1145 + ScreenPoint displacement = aStartPoint - aEndPoint; 1.1146 + 1.1147 + ScreenPoint overscroll; // will be used outside monitor block 1.1148 + { 1.1149 + ReentrantMonitorAutoEnter lock(mMonitor); 1.1150 + 1.1151 + CSSToScreenScale zoom = mFrameMetrics.GetZoom(); 1.1152 + 1.1153 + // Inversely scale the offset by the resolution (when you're zoomed further in, 1.1154 + // the same swipe should move you a shorter distance). 1.1155 + CSSPoint cssDisplacement = displacement / zoom; 1.1156 + 1.1157 + CSSPoint cssOverscroll; 1.1158 + CSSPoint allowedDisplacement(mX.AdjustDisplacement(cssDisplacement.x, 1.1159 + cssOverscroll.x), 1.1160 + mY.AdjustDisplacement(cssDisplacement.y, 1.1161 + cssOverscroll.y)); 1.1162 + overscroll = cssOverscroll * zoom; 1.1163 + 1.1164 + if (!IsZero(allowedDisplacement)) { 1.1165 + ScrollBy(allowedDisplacement); 1.1166 + ScheduleComposite(); 1.1167 + 1.1168 + TimeDuration timePaintDelta = mPaintThrottler.TimeSinceLastRequest(GetFrameTime()); 1.1169 + if (timePaintDelta.ToMilliseconds() > gfxPrefs::APZPanRepaintInterval()) { 1.1170 + RequestContentRepaint(); 1.1171 + } 1.1172 + UpdateSharedCompositorFrameMetrics(); 1.1173 + } 1.1174 + } 1.1175 + 1.1176 + if (!IsZero(overscroll)) { 1.1177 + // "+ overscroll" rather than "- overscroll" because "overscroll" is what's 1.1178 + // left of "displacement", and "displacement" is "start - end". 1.1179 + CallDispatchScroll(aEndPoint + overscroll, aEndPoint, aOverscrollHandoffChainIndex + 1); 1.1180 + } 1.1181 +} 1.1182 + 1.1183 +void AsyncPanZoomController::TakeOverFling(ScreenPoint aVelocity) { 1.1184 + // We may have a pre-existing velocity for whatever reason (for example, 1.1185 + // a previously handed off fling). We don't want to clobber that. 1.1186 + mX.SetVelocity(mX.GetVelocity() + aVelocity.x); 1.1187 + mY.SetVelocity(mY.GetVelocity() + aVelocity.y); 1.1188 + SetState(FLING); 1.1189 + StartAnimation(new FlingAnimation(*this)); 1.1190 +} 1.1191 + 1.1192 +void AsyncPanZoomController::CallDispatchScroll(const ScreenPoint& aStartPoint, const ScreenPoint& aEndPoint, 1.1193 + uint32_t aOverscrollHandoffChainIndex) { 1.1194 + // Make a local copy of the tree manager pointer and check if it's not 1.1195 + // null before calling DispatchScroll(). This is necessary because 1.1196 + // Destroy(), which nulls out mTreeManager, could be called concurrently. 1.1197 + APZCTreeManager* treeManagerLocal = mTreeManager; 1.1198 + if (treeManagerLocal) { 1.1199 + treeManagerLocal->DispatchScroll(this, aStartPoint, aEndPoint, 1.1200 + aOverscrollHandoffChainIndex); 1.1201 + } 1.1202 +} 1.1203 + 1.1204 +void AsyncPanZoomController::TrackTouch(const MultiTouchInput& aEvent) { 1.1205 + ScreenIntPoint prevTouchPoint(mX.GetPos(), mY.GetPos()); 1.1206 + ScreenIntPoint touchPoint = GetFirstTouchScreenPoint(aEvent); 1.1207 + TimeDuration timeDelta = TimeDuration().FromMilliseconds(aEvent.mTime - mLastEventTime); 1.1208 + 1.1209 + // Probably a duplicate event, just throw it away. 1.1210 + if (timeDelta.ToMilliseconds() <= EPSILON) { 1.1211 + return; 1.1212 + } 1.1213 + 1.1214 + // If we're axis-locked, check if the user is trying to break the lock 1.1215 + if (GetAxisLockMode() == STICKY && !mPanDirRestricted) { 1.1216 + ScreenIntPoint point = GetFirstTouchScreenPoint(aEvent); 1.1217 + float dx = mX.PanDistance(point.x); 1.1218 + float dy = mY.PanDistance(point.y); 1.1219 + 1.1220 + double angle = atan2(dy, dx); // range [-pi, pi] 1.1221 + angle = fabs(angle); // range [0, pi] 1.1222 + 1.1223 + float breakThreshold = AXIS_BREAKOUT_THRESHOLD * APZCTreeManager::GetDPI(); 1.1224 + 1.1225 + if (fabs(dx) > breakThreshold || fabs(dy) > breakThreshold) { 1.1226 + if (mState == PANNING_LOCKED_X || mState == CROSS_SLIDING_X) { 1.1227 + if (!IsCloseToHorizontal(angle, AXIS_BREAKOUT_ANGLE)) { 1.1228 + mY.SetAxisLocked(false); 1.1229 + SetState(PANNING); 1.1230 + } 1.1231 + } else if (mState == PANNING_LOCKED_Y || mState == CROSS_SLIDING_Y) { 1.1232 + if (!IsCloseToVertical(angle, AXIS_BREAKOUT_ANGLE)) { 1.1233 + mX.SetAxisLocked(false); 1.1234 + SetState(PANNING); 1.1235 + } 1.1236 + } 1.1237 + } 1.1238 + } 1.1239 + 1.1240 + UpdateWithTouchAtDevicePoint(aEvent); 1.1241 + 1.1242 + CallDispatchScroll(prevTouchPoint, touchPoint, 0); 1.1243 +} 1.1244 + 1.1245 +ScreenIntPoint& AsyncPanZoomController::GetFirstTouchScreenPoint(const MultiTouchInput& aEvent) { 1.1246 + return ((SingleTouchData&)aEvent.mTouches[0]).mScreenPoint; 1.1247 +} 1.1248 + 1.1249 +bool FlingAnimation::Sample(FrameMetrics& aFrameMetrics, 1.1250 + const TimeDuration& aDelta) { 1.1251 + 1.1252 + // If the fling is handed off to our APZC from a child, on the first call to 1.1253 + // Sample() aDelta might be negative because it's computed as the sample time 1.1254 + // from SampleContentTransformForFrame() minus our APZC's mLastSampleTime 1.1255 + // which is the time the child handed off the fling from its call to 1.1256 + // SampleContentTransformForFrame() with the same sample time. If we allow 1.1257 + // the negative aDelta to be processed, it will yield a displacement in the 1.1258 + // direction opposite to the fling, which can cause us to overscroll and 1.1259 + // hand off the fling to _our_ parent, which effectively kills the fling. 1.1260 + if (aDelta.ToMilliseconds() <= 0) { 1.1261 + return true; 1.1262 + } 1.1263 + 1.1264 + bool shouldContinueFlingX = mApzc.mX.FlingApplyFrictionOrCancel(aDelta), 1.1265 + shouldContinueFlingY = mApzc.mY.FlingApplyFrictionOrCancel(aDelta); 1.1266 + // If we shouldn't continue the fling, let's just stop and repaint. 1.1267 + if (!shouldContinueFlingX && !shouldContinueFlingY) { 1.1268 + return false; 1.1269 + } 1.1270 + 1.1271 + // AdjustDisplacement() zeroes out the Axis velocity if we're in overscroll. 1.1272 + // Since we need to hand off the velocity to the tree manager in such a case, 1.1273 + // we save it here. Would be ScreenVector instead of ScreenPoint if we had 1.1274 + // vector classes. 1.1275 + ScreenPoint velocity(mApzc.mX.GetVelocity(), mApzc.mY.GetVelocity()); 1.1276 + 1.1277 + ScreenPoint offset = velocity * aDelta.ToMilliseconds(); 1.1278 + 1.1279 + // Inversely scale the offset by the resolution (when you're zoomed further in, 1.1280 + // the same swipe should move you a shorter distance). 1.1281 + CSSPoint cssOffset = offset / aFrameMetrics.GetZoom(); 1.1282 + CSSPoint overscroll; 1.1283 + aFrameMetrics.ScrollBy(CSSPoint( 1.1284 + mApzc.mX.AdjustDisplacement(cssOffset.x, overscroll.x), 1.1285 + mApzc.mY.AdjustDisplacement(cssOffset.y, overscroll.y) 1.1286 + )); 1.1287 + 1.1288 + // If the fling has caused us to reach the end of our scroll range, hand 1.1289 + // off the fling to the next APZC in the overscroll handoff chain. 1.1290 + if (!IsZero(overscroll)) { 1.1291 + // We may have reached the end of the scroll range along one axis but 1.1292 + // not the other. In such a case we only want to hand off the relevant 1.1293 + // component of the fling. 1.1294 + if (FuzzyEqualsMultiplicative(overscroll.x, 0.0f)) { 1.1295 + velocity.x = 0; 1.1296 + } else if (FuzzyEqualsMultiplicative(overscroll.y, 0.0f)) { 1.1297 + velocity.y = 0; 1.1298 + } 1.1299 + 1.1300 + // To hand off the fling, we call APZCTreeManager::HandleFlingOverscroll() 1.1301 + // which starts a new fling in the next APZC in the handoff chain with 1.1302 + // the same velocity. For simplicity, the actual overscroll of the current 1.1303 + // sample is discarded rather than being handed off. The compositor should 1.1304 + // sample animations sufficiently frequently that this is not noticeable. 1.1305 + 1.1306 + // Make a local copy of the tree manager pointer and check if it's not 1.1307 + // null before calling HandleFlingOverscroll(). This is necessary because 1.1308 + // Destroy(), which nulls out mTreeManager, could be called concurrently. 1.1309 + APZCTreeManager* treeManagerLocal = mApzc.mTreeManager; 1.1310 + if (treeManagerLocal) { 1.1311 + // APZC is holding mMonitor, so directly calling HandleFlingOverscroll() 1.1312 + // (which acquires the tree lock) would violate the lock ordering. Instead 1.1313 + // we schedule HandleFlingOverscroll() to be called after mMonitor is 1.1314 + // released. 1.1315 + mDeferredTasks.append(NewRunnableMethod(treeManagerLocal, 1.1316 + &APZCTreeManager::HandOffFling, 1.1317 + &mApzc, 1.1318 + velocity)); 1.1319 + } 1.1320 + } 1.1321 + 1.1322 + return true; 1.1323 +} 1.1324 + 1.1325 +void AsyncPanZoomController::StartAnimation(AsyncPanZoomAnimation* aAnimation) 1.1326 +{ 1.1327 + ReentrantMonitorAutoEnter lock(mMonitor); 1.1328 + mAnimation = aAnimation; 1.1329 + mLastSampleTime = GetFrameTime(); 1.1330 + ScheduleComposite(); 1.1331 +} 1.1332 + 1.1333 +void AsyncPanZoomController::CancelAnimation() { 1.1334 + ReentrantMonitorAutoEnter lock(mMonitor); 1.1335 + SetState(NOTHING); 1.1336 + mAnimation = nullptr; 1.1337 +} 1.1338 + 1.1339 +void AsyncPanZoomController::SetCompositorParent(CompositorParent* aCompositorParent) { 1.1340 + mCompositorParent = aCompositorParent; 1.1341 +} 1.1342 + 1.1343 +void AsyncPanZoomController::SetCrossProcessCompositorParent(PCompositorParent* aCrossProcessCompositorParent) { 1.1344 + mCrossProcessCompositorParent = aCrossProcessCompositorParent; 1.1345 +} 1.1346 + 1.1347 +void AsyncPanZoomController::ScrollBy(const CSSPoint& aOffset) { 1.1348 + mFrameMetrics.ScrollBy(aOffset); 1.1349 +} 1.1350 + 1.1351 +void AsyncPanZoomController::ScaleWithFocus(float aScale, 1.1352 + const CSSPoint& aFocus) { 1.1353 + mFrameMetrics.ZoomBy(aScale); 1.1354 + // We want to adjust the scroll offset such that the CSS point represented by aFocus remains 1.1355 + // at the same position on the screen before and after the change in zoom. The below code 1.1356 + // accomplishes this; see https://bugzilla.mozilla.org/show_bug.cgi?id=923431#c6 for an 1.1357 + // in-depth explanation of how. 1.1358 + mFrameMetrics.SetScrollOffset((mFrameMetrics.GetScrollOffset() + aFocus) - (aFocus / aScale)); 1.1359 +} 1.1360 + 1.1361 +/** 1.1362 + * Enlarges the displayport along both axes based on the velocity. 1.1363 + */ 1.1364 +static CSSSize 1.1365 +CalculateDisplayPortSize(const CSSSize& aCompositionSize, 1.1366 + const CSSPoint& aVelocity) 1.1367 +{ 1.1368 + float xMultiplier = fabsf(aVelocity.x) < gfxPrefs::APZMinSkateSpeed() 1.1369 + ? gfxPrefs::APZXStationarySizeMultiplier() 1.1370 + : gfxPrefs::APZXSkateSizeMultiplier(); 1.1371 + float yMultiplier = fabsf(aVelocity.y) < gfxPrefs::APZMinSkateSpeed() 1.1372 + ? gfxPrefs::APZYStationarySizeMultiplier() 1.1373 + : gfxPrefs::APZYSkateSizeMultiplier(); 1.1374 + return CSSSize(aCompositionSize.width * xMultiplier, 1.1375 + aCompositionSize.height * yMultiplier); 1.1376 +} 1.1377 + 1.1378 +/** 1.1379 + * Attempts to redistribute any area in the displayport that would get clipped 1.1380 + * by the scrollable rect, or be inaccessible due to disabled scrolling, to the 1.1381 + * other axis, while maintaining total displayport area. 1.1382 + */ 1.1383 +static void 1.1384 +RedistributeDisplayPortExcess(CSSSize& aDisplayPortSize, 1.1385 + const CSSRect& aScrollableRect) 1.1386 +{ 1.1387 + float xSlack = std::max(0.0f, aDisplayPortSize.width - aScrollableRect.width); 1.1388 + float ySlack = std::max(0.0f, aDisplayPortSize.height - aScrollableRect.height); 1.1389 + 1.1390 + if (ySlack > 0) { 1.1391 + // Reassign wasted y-axis displayport to the x-axis 1.1392 + aDisplayPortSize.height -= ySlack; 1.1393 + float xExtra = ySlack * aDisplayPortSize.width / aDisplayPortSize.height; 1.1394 + aDisplayPortSize.width += xExtra; 1.1395 + } else if (xSlack > 0) { 1.1396 + // Reassign wasted x-axis displayport to the y-axis 1.1397 + aDisplayPortSize.width -= xSlack; 1.1398 + float yExtra = xSlack * aDisplayPortSize.height / aDisplayPortSize.width; 1.1399 + aDisplayPortSize.height += yExtra; 1.1400 + } 1.1401 +} 1.1402 + 1.1403 +/* static */ 1.1404 +const LayerMargin AsyncPanZoomController::CalculatePendingDisplayPort( 1.1405 + const FrameMetrics& aFrameMetrics, 1.1406 + const ScreenPoint& aVelocity, 1.1407 + double aEstimatedPaintDuration) 1.1408 +{ 1.1409 + CSSSize compositionBounds = aFrameMetrics.CalculateCompositedSizeInCssPixels(); 1.1410 + CSSSize compositionSize = aFrameMetrics.GetRootCompositionSize(); 1.1411 + compositionSize = 1.1412 + CSSSize(std::min(compositionBounds.width, compositionSize.width), 1.1413 + std::min(compositionBounds.height, compositionSize.height)); 1.1414 + CSSPoint velocity = aVelocity / aFrameMetrics.GetZoom(); 1.1415 + CSSPoint scrollOffset = aFrameMetrics.GetScrollOffset(); 1.1416 + CSSRect scrollableRect = aFrameMetrics.GetExpandedScrollableRect(); 1.1417 + 1.1418 + // Calculate the displayport size based on how fast we're moving along each axis. 1.1419 + CSSSize displayPortSize = CalculateDisplayPortSize(compositionSize, velocity); 1.1420 + 1.1421 + if (gfxPrefs::APZEnlargeDisplayPortWhenClipped()) { 1.1422 + RedistributeDisplayPortExcess(displayPortSize, scrollableRect); 1.1423 + } 1.1424 + 1.1425 + // Offset the displayport, depending on how fast we're moving and the 1.1426 + // estimated time it takes to paint, to try to minimise checkerboarding. 1.1427 + float estimatedPaintDurationMillis = (float)(aEstimatedPaintDuration * 1000.0); 1.1428 + float paintFactor = (gfxPrefs::APZUsePaintDuration() ? estimatedPaintDurationMillis : 50.0f); 1.1429 + CSSRect displayPort = CSSRect(scrollOffset + (velocity * paintFactor * gfxPrefs::APZVelocityBias()), 1.1430 + displayPortSize); 1.1431 + 1.1432 + // Re-center the displayport based on its expansion over the composition size. 1.1433 + displayPort.MoveBy((compositionSize.width - displayPort.width)/2.0f, 1.1434 + (compositionSize.height - displayPort.height)/2.0f); 1.1435 + 1.1436 + // Make sure the displayport remains within the scrollable rect. 1.1437 + displayPort = displayPort.ForceInside(scrollableRect) - scrollOffset; 1.1438 + 1.1439 + APZC_LOG_FM(aFrameMetrics, 1.1440 + "Calculated displayport as (%f %f %f %f) from velocity (%f %f) paint time %f metrics", 1.1441 + displayPort.x, displayPort.y, displayPort.width, displayPort.height, 1.1442 + aVelocity.x, aVelocity.y, (float)estimatedPaintDurationMillis); 1.1443 + 1.1444 + CSSMargin cssMargins; 1.1445 + cssMargins.left = -displayPort.x; 1.1446 + cssMargins.top = -displayPort.y; 1.1447 + cssMargins.right = displayPort.width - compositionSize.width - cssMargins.left; 1.1448 + cssMargins.bottom = displayPort.height - compositionSize.height - cssMargins.top; 1.1449 + 1.1450 + LayerMargin layerMargins = cssMargins * aFrameMetrics.LayersPixelsPerCSSPixel(); 1.1451 + 1.1452 + return layerMargins; 1.1453 +} 1.1454 + 1.1455 +void AsyncPanZoomController::ScheduleComposite() { 1.1456 + if (mCompositorParent) { 1.1457 + mCompositorParent->ScheduleRenderOnCompositorThread(); 1.1458 + } 1.1459 +} 1.1460 + 1.1461 +void AsyncPanZoomController::FlushRepaintForOverscrollHandoff() { 1.1462 + ReentrantMonitorAutoEnter lock(mMonitor); 1.1463 + RequestContentRepaint(); 1.1464 + UpdateSharedCompositorFrameMetrics(); 1.1465 +} 1.1466 + 1.1467 +bool AsyncPanZoomController::IsPannable() const { 1.1468 + ReentrantMonitorAutoEnter lock(mMonitor); 1.1469 + return mX.HasRoomToPan() || mY.HasRoomToPan(); 1.1470 +} 1.1471 + 1.1472 +void AsyncPanZoomController::RequestContentRepaint() { 1.1473 + RequestContentRepaint(mFrameMetrics); 1.1474 +} 1.1475 + 1.1476 +void AsyncPanZoomController::RequestContentRepaint(FrameMetrics& aFrameMetrics) { 1.1477 + aFrameMetrics.SetDisplayPortMargins( 1.1478 + CalculatePendingDisplayPort(aFrameMetrics, 1.1479 + GetVelocityVector(), 1.1480 + mPaintThrottler.AverageDuration().ToSeconds())); 1.1481 + aFrameMetrics.SetUseDisplayPortMargins(); 1.1482 + 1.1483 + // If we're trying to paint what we already think is painted, discard this 1.1484 + // request since it's a pointless paint. 1.1485 + LayerMargin marginDelta = mLastPaintRequestMetrics.GetDisplayPortMargins() 1.1486 + - aFrameMetrics.GetDisplayPortMargins(); 1.1487 + if (fabsf(marginDelta.left) < EPSILON && 1.1488 + fabsf(marginDelta.top) < EPSILON && 1.1489 + fabsf(marginDelta.right) < EPSILON && 1.1490 + fabsf(marginDelta.bottom) < EPSILON && 1.1491 + fabsf(mLastPaintRequestMetrics.GetScrollOffset().x - 1.1492 + aFrameMetrics.GetScrollOffset().x) < EPSILON && 1.1493 + fabsf(mLastPaintRequestMetrics.GetScrollOffset().y - 1.1494 + aFrameMetrics.GetScrollOffset().y) < EPSILON && 1.1495 + aFrameMetrics.GetZoom() == mLastPaintRequestMetrics.GetZoom() && 1.1496 + fabsf(aFrameMetrics.mViewport.width - mLastPaintRequestMetrics.mViewport.width) < EPSILON && 1.1497 + fabsf(aFrameMetrics.mViewport.height - mLastPaintRequestMetrics.mViewport.height) < EPSILON) { 1.1498 + return; 1.1499 + } 1.1500 + 1.1501 + SendAsyncScrollEvent(); 1.1502 + mPaintThrottler.PostTask( 1.1503 + FROM_HERE, 1.1504 + NewRunnableMethod(this, 1.1505 + &AsyncPanZoomController::DispatchRepaintRequest, 1.1506 + aFrameMetrics), 1.1507 + GetFrameTime()); 1.1508 + 1.1509 + aFrameMetrics.mPresShellId = mLastContentPaintMetrics.mPresShellId; 1.1510 + mLastPaintRequestMetrics = aFrameMetrics; 1.1511 +} 1.1512 + 1.1513 +/*static*/ CSSRect 1.1514 +GetDisplayPortRect(const FrameMetrics& aFrameMetrics) 1.1515 +{ 1.1516 + // This computation is based on what happens in CalculatePendingDisplayPort. If that 1.1517 + // changes then this might need to change too 1.1518 + CSSRect baseRect(aFrameMetrics.GetScrollOffset(), 1.1519 + CSSSize(std::min(aFrameMetrics.CalculateCompositedSizeInCssPixels().width, 1.1520 + aFrameMetrics.GetRootCompositionSize().width), 1.1521 + std::min(aFrameMetrics.CalculateCompositedSizeInCssPixels().height, 1.1522 + aFrameMetrics.GetRootCompositionSize().height))); 1.1523 + baseRect.Inflate(aFrameMetrics.GetDisplayPortMargins() / aFrameMetrics.LayersPixelsPerCSSPixel()); 1.1524 + return baseRect; 1.1525 +} 1.1526 + 1.1527 +void 1.1528 +AsyncPanZoomController::DispatchRepaintRequest(const FrameMetrics& aFrameMetrics) { 1.1529 + nsRefPtr<GeckoContentController> controller = GetGeckoContentController(); 1.1530 + if (controller) { 1.1531 + APZC_LOG_FM(aFrameMetrics, "%p requesting content repaint", this); 1.1532 + LogRendertraceRect(GetGuid(), "requested displayport", "yellow", GetDisplayPortRect(aFrameMetrics)); 1.1533 + 1.1534 + controller->RequestContentRepaint(aFrameMetrics); 1.1535 + mLastDispatchedPaintMetrics = aFrameMetrics; 1.1536 + } 1.1537 +} 1.1538 + 1.1539 +void 1.1540 +AsyncPanZoomController::FireAsyncScrollOnTimeout() 1.1541 +{ 1.1542 + if (mCurrentAsyncScrollOffset != mLastAsyncScrollOffset) { 1.1543 + ReentrantMonitorAutoEnter lock(mMonitor); 1.1544 + SendAsyncScrollEvent(); 1.1545 + } 1.1546 + mAsyncScrollTimeoutTask = nullptr; 1.1547 +} 1.1548 + 1.1549 +bool ZoomAnimation::Sample(FrameMetrics& aFrameMetrics, 1.1550 + const TimeDuration& aDelta) { 1.1551 + mDuration += aDelta; 1.1552 + double animPosition = mDuration / ZOOM_TO_DURATION; 1.1553 + 1.1554 + if (animPosition >= 1.0) { 1.1555 + aFrameMetrics.SetZoom(mEndZoom); 1.1556 + aFrameMetrics.SetScrollOffset(mEndOffset); 1.1557 + return false; 1.1558 + } 1.1559 + 1.1560 + // Sample the zoom at the current time point. The sampled zoom 1.1561 + // will affect the final computed resolution. 1.1562 + double sampledPosition = gComputedTimingFunction->GetValue(animPosition); 1.1563 + 1.1564 + // We scale the scrollOffset linearly with sampledPosition, so the zoom 1.1565 + // needs to scale inversely to match. 1.1566 + aFrameMetrics.SetZoom(CSSToScreenScale(1 / 1.1567 + (sampledPosition / mEndZoom.scale + 1.1568 + (1 - sampledPosition) / mStartZoom.scale))); 1.1569 + 1.1570 + aFrameMetrics.SetScrollOffset(CSSPoint::FromUnknownPoint(gfx::Point( 1.1571 + mEndOffset.x * sampledPosition + mStartOffset.x * (1 - sampledPosition), 1.1572 + mEndOffset.y * sampledPosition + mStartOffset.y * (1 - sampledPosition) 1.1573 + ))); 1.1574 + 1.1575 + return true; 1.1576 +} 1.1577 + 1.1578 +bool AsyncPanZoomController::UpdateAnimation(const TimeStamp& aSampleTime) 1.1579 +{ 1.1580 + if (mAnimation) { 1.1581 + if (mAnimation->Sample(mFrameMetrics, aSampleTime - mLastSampleTime)) { 1.1582 + if (mPaintThrottler.TimeSinceLastRequest(aSampleTime) > 1.1583 + mAnimation->mRepaintInterval) { 1.1584 + RequestContentRepaint(); 1.1585 + } 1.1586 + } else { 1.1587 + mAnimation = nullptr; 1.1588 + SetState(NOTHING); 1.1589 + SendAsyncScrollEvent(); 1.1590 + RequestContentRepaint(); 1.1591 + } 1.1592 + UpdateSharedCompositorFrameMetrics(); 1.1593 + mLastSampleTime = aSampleTime; 1.1594 + return true; 1.1595 + } 1.1596 + return false; 1.1597 +} 1.1598 + 1.1599 +bool AsyncPanZoomController::SampleContentTransformForFrame(const TimeStamp& aSampleTime, 1.1600 + ViewTransform* aNewTransform, 1.1601 + ScreenPoint& aScrollOffset) { 1.1602 + // The eventual return value of this function. The compositor needs to know 1.1603 + // whether or not to advance by a frame as soon as it can. For example, if a 1.1604 + // fling is happening, it has to keep compositing so that the animation is 1.1605 + // smooth. If an animation frame is requested, it is the compositor's 1.1606 + // responsibility to schedule a composite. 1.1607 + bool requestAnimationFrame = false; 1.1608 + Vector<Task*> deferredTasks; 1.1609 + 1.1610 + { 1.1611 + ReentrantMonitorAutoEnter lock(mMonitor); 1.1612 + 1.1613 + requestAnimationFrame = UpdateAnimation(aSampleTime); 1.1614 + 1.1615 + aScrollOffset = mFrameMetrics.GetScrollOffset() * mFrameMetrics.GetZoom(); 1.1616 + *aNewTransform = GetCurrentAsyncTransform(); 1.1617 + 1.1618 + LogRendertraceRect(GetGuid(), "viewport", "red", 1.1619 + CSSRect(mFrameMetrics.GetScrollOffset(), 1.1620 + ParentLayerSize(mFrameMetrics.mCompositionBounds.Size()) / mFrameMetrics.GetZoomToParent())); 1.1621 + 1.1622 + mCurrentAsyncScrollOffset = mFrameMetrics.GetScrollOffset(); 1.1623 + 1.1624 + // Get any deferred tasks queued up by mAnimation's Sample() (called by 1.1625 + // UpdateAnimation()). This needs to be done here since mAnimation can 1.1626 + // be destroyed by another thread when we release the monitor, but 1.1627 + // the tasks need to be executed after we release the monitor since they 1.1628 + // are allowed to call APZCTreeManager methods which can grab the tree lock. 1.1629 + if (mAnimation) { 1.1630 + deferredTasks = mAnimation->TakeDeferredTasks(); 1.1631 + } 1.1632 + } 1.1633 + 1.1634 + for (uint32_t i = 0; i < deferredTasks.length(); ++i) { 1.1635 + deferredTasks[i]->Run(); 1.1636 + delete deferredTasks[i]; 1.1637 + } 1.1638 + 1.1639 + // Cancel the mAsyncScrollTimeoutTask because we will fire a 1.1640 + // mozbrowserasyncscroll event or renew the mAsyncScrollTimeoutTask again. 1.1641 + if (mAsyncScrollTimeoutTask) { 1.1642 + mAsyncScrollTimeoutTask->Cancel(); 1.1643 + mAsyncScrollTimeoutTask = nullptr; 1.1644 + } 1.1645 + // Fire the mozbrowserasyncscroll event immediately if it's been 1.1646 + // sAsyncScrollThrottleTime ms since the last time we fired the event and the 1.1647 + // current scroll offset is different than the mLastAsyncScrollOffset we sent 1.1648 + // with the last event. 1.1649 + // Otherwise, start a timer to fire the event sAsyncScrollTimeout ms from now. 1.1650 + TimeDuration delta = aSampleTime - mLastAsyncScrollTime; 1.1651 + if (delta.ToMilliseconds() > gfxPrefs::APZAsyncScrollThrottleTime() && 1.1652 + mCurrentAsyncScrollOffset != mLastAsyncScrollOffset) { 1.1653 + ReentrantMonitorAutoEnter lock(mMonitor); 1.1654 + mLastAsyncScrollTime = aSampleTime; 1.1655 + mLastAsyncScrollOffset = mCurrentAsyncScrollOffset; 1.1656 + SendAsyncScrollEvent(); 1.1657 + } 1.1658 + else { 1.1659 + mAsyncScrollTimeoutTask = 1.1660 + NewRunnableMethod(this, &AsyncPanZoomController::FireAsyncScrollOnTimeout); 1.1661 + MessageLoop::current()->PostDelayedTask(FROM_HERE, 1.1662 + mAsyncScrollTimeoutTask, 1.1663 + gfxPrefs::APZAsyncScrollTimeout()); 1.1664 + } 1.1665 + 1.1666 + return requestAnimationFrame; 1.1667 +} 1.1668 + 1.1669 +ViewTransform AsyncPanZoomController::GetCurrentAsyncTransform() { 1.1670 + ReentrantMonitorAutoEnter lock(mMonitor); 1.1671 + 1.1672 + CSSPoint lastPaintScrollOffset; 1.1673 + if (mLastContentPaintMetrics.IsScrollable()) { 1.1674 + lastPaintScrollOffset = mLastContentPaintMetrics.GetScrollOffset(); 1.1675 + } 1.1676 + 1.1677 + CSSPoint currentScrollOffset = mFrameMetrics.GetScrollOffset() + 1.1678 + mTestAsyncScrollOffset; 1.1679 + 1.1680 + // If checkerboarding has been disallowed, clamp the scroll position to stay 1.1681 + // within rendered content. 1.1682 + if (!gfxPrefs::APZAllowCheckerboarding() && 1.1683 + !mLastContentPaintMetrics.mDisplayPort.IsEmpty()) { 1.1684 + CSSSize compositedSize = mLastContentPaintMetrics.CalculateCompositedSizeInCssPixels(); 1.1685 + CSSPoint maxScrollOffset = lastPaintScrollOffset + 1.1686 + CSSPoint(mLastContentPaintMetrics.mDisplayPort.XMost() - compositedSize.width, 1.1687 + mLastContentPaintMetrics.mDisplayPort.YMost() - compositedSize.height); 1.1688 + CSSPoint minScrollOffset = lastPaintScrollOffset + mLastContentPaintMetrics.mDisplayPort.TopLeft(); 1.1689 + 1.1690 + if (minScrollOffset.x < maxScrollOffset.x) { 1.1691 + currentScrollOffset.x = clamped(currentScrollOffset.x, minScrollOffset.x, maxScrollOffset.x); 1.1692 + } 1.1693 + if (minScrollOffset.y < maxScrollOffset.y) { 1.1694 + currentScrollOffset.y = clamped(currentScrollOffset.y, minScrollOffset.y, maxScrollOffset.y); 1.1695 + } 1.1696 + } 1.1697 + 1.1698 + LayerPoint translation = (currentScrollOffset - lastPaintScrollOffset) 1.1699 + * mLastContentPaintMetrics.LayersPixelsPerCSSPixel(); 1.1700 + 1.1701 + return ViewTransform(-translation, 1.1702 + mFrameMetrics.GetZoom() 1.1703 + / mLastContentPaintMetrics.mDevPixelsPerCSSPixel 1.1704 + / mFrameMetrics.GetParentResolution()); 1.1705 +} 1.1706 + 1.1707 +gfx3DMatrix AsyncPanZoomController::GetNontransientAsyncTransform() { 1.1708 + ReentrantMonitorAutoEnter lock(mMonitor); 1.1709 + return gfx3DMatrix::ScalingMatrix(mLastContentPaintMetrics.mResolution.scale, 1.1710 + mLastContentPaintMetrics.mResolution.scale, 1.1711 + 1.0f); 1.1712 +} 1.1713 + 1.1714 +gfx3DMatrix AsyncPanZoomController::GetTransformToLastDispatchedPaint() { 1.1715 + ReentrantMonitorAutoEnter lock(mMonitor); 1.1716 + LayerPoint scrollChange = (mLastContentPaintMetrics.GetScrollOffset() - mLastDispatchedPaintMetrics.GetScrollOffset()) 1.1717 + * mLastContentPaintMetrics.LayersPixelsPerCSSPixel(); 1.1718 + float zoomChange = mLastContentPaintMetrics.GetZoom().scale / mLastDispatchedPaintMetrics.GetZoom().scale; 1.1719 + return gfx3DMatrix::Translation(scrollChange.x, scrollChange.y, 0) * 1.1720 + gfx3DMatrix::ScalingMatrix(zoomChange, zoomChange, 1); 1.1721 +} 1.1722 + 1.1723 +void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aLayerMetrics, bool aIsFirstPaint) { 1.1724 + ReentrantMonitorAutoEnter lock(mMonitor); 1.1725 + 1.1726 + mLastContentPaintMetrics = aLayerMetrics; 1.1727 + UpdateTransformScale(); 1.1728 + 1.1729 + bool isDefault = mFrameMetrics.IsDefault(); 1.1730 + mFrameMetrics.mMayHaveTouchListeners = aLayerMetrics.mMayHaveTouchListeners; 1.1731 + APZC_LOG_FM(aLayerMetrics, "%p got a NotifyLayersUpdated with aIsFirstPaint=%d", this, aIsFirstPaint); 1.1732 + 1.1733 + LogRendertraceRect(GetGuid(), "page", "brown", aLayerMetrics.mScrollableRect); 1.1734 + LogRendertraceRect(GetGuid(), "painted displayport", "green", 1.1735 + aLayerMetrics.mDisplayPort + aLayerMetrics.GetScrollOffset()); 1.1736 + 1.1737 + mPaintThrottler.TaskComplete(GetFrameTime()); 1.1738 + bool needContentRepaint = false; 1.1739 + if (aLayerMetrics.mCompositionBounds.width == mFrameMetrics.mCompositionBounds.width && 1.1740 + aLayerMetrics.mCompositionBounds.height == mFrameMetrics.mCompositionBounds.height) { 1.1741 + // Remote content has sync'd up to the composition geometry 1.1742 + // change, so we can accept the viewport it's calculated. 1.1743 + if (mFrameMetrics.mViewport.width != aLayerMetrics.mViewport.width || 1.1744 + mFrameMetrics.mViewport.height != aLayerMetrics.mViewport.height) { 1.1745 + needContentRepaint = true; 1.1746 + } 1.1747 + mFrameMetrics.mViewport = aLayerMetrics.mViewport; 1.1748 + } 1.1749 + 1.1750 + // If the layers update was not triggered by our own repaint request, then 1.1751 + // we want to take the new scroll offset. Check the scroll generation as well 1.1752 + // to filter duplicate calls to NotifyLayersUpdated with the same scroll offset 1.1753 + // update message. 1.1754 + bool scrollOffsetUpdated = aLayerMetrics.GetScrollOffsetUpdated() 1.1755 + && (aLayerMetrics.GetScrollGeneration() != mFrameMetrics.GetScrollGeneration()); 1.1756 + 1.1757 + if (aIsFirstPaint || isDefault) { 1.1758 + // Initialize our internal state to something sane when the content 1.1759 + // that was just painted is something we knew nothing about previously 1.1760 + mPaintThrottler.ClearHistory(); 1.1761 + mPaintThrottler.SetMaxDurations(gfxPrefs::APZNumPaintDurationSamples()); 1.1762 + 1.1763 + mX.CancelTouch(); 1.1764 + mY.CancelTouch(); 1.1765 + SetState(NOTHING); 1.1766 + 1.1767 + mFrameMetrics = aLayerMetrics; 1.1768 + mLastDispatchedPaintMetrics = aLayerMetrics; 1.1769 + ShareCompositorFrameMetrics(); 1.1770 + } else { 1.1771 + // If we're not taking the aLayerMetrics wholesale we still need to pull 1.1772 + // in some things into our local mFrameMetrics because these things are 1.1773 + // determined by Gecko and our copy in mFrameMetrics may be stale. 1.1774 + 1.1775 + if (mFrameMetrics.mCompositionBounds.width == aLayerMetrics.mCompositionBounds.width && 1.1776 + mFrameMetrics.mDevPixelsPerCSSPixel == aLayerMetrics.mDevPixelsPerCSSPixel) { 1.1777 + float parentResolutionChange = aLayerMetrics.GetParentResolution().scale 1.1778 + / mFrameMetrics.GetParentResolution().scale; 1.1779 + mFrameMetrics.ZoomBy(parentResolutionChange); 1.1780 + } else { 1.1781 + // Take the new zoom as either device scale or composition width or both 1.1782 + // got changed (e.g. due to orientation change). 1.1783 + mFrameMetrics.SetZoom(aLayerMetrics.GetZoom()); 1.1784 + mFrameMetrics.mDevPixelsPerCSSPixel.scale = aLayerMetrics.mDevPixelsPerCSSPixel.scale; 1.1785 + } 1.1786 + mFrameMetrics.mScrollableRect = aLayerMetrics.mScrollableRect; 1.1787 + mFrameMetrics.mCompositionBounds = aLayerMetrics.mCompositionBounds; 1.1788 + mFrameMetrics.SetRootCompositionSize(aLayerMetrics.GetRootCompositionSize()); 1.1789 + mFrameMetrics.mResolution = aLayerMetrics.mResolution; 1.1790 + mFrameMetrics.mCumulativeResolution = aLayerMetrics.mCumulativeResolution; 1.1791 + mFrameMetrics.mHasScrollgrab = aLayerMetrics.mHasScrollgrab; 1.1792 + 1.1793 + if (scrollOffsetUpdated) { 1.1794 + APZC_LOG("%p updating scroll offset from (%f, %f) to (%f, %f)\n", this, 1.1795 + mFrameMetrics.GetScrollOffset().x, mFrameMetrics.GetScrollOffset().y, 1.1796 + aLayerMetrics.GetScrollOffset().x, aLayerMetrics.GetScrollOffset().y); 1.1797 + 1.1798 + mFrameMetrics.CopyScrollInfoFrom(aLayerMetrics); 1.1799 + 1.1800 + // Because of the scroll offset update, any inflight paint requests are 1.1801 + // going to be ignored by layout, and so mLastDispatchedPaintMetrics 1.1802 + // becomes incorrect for the purposes of calculating the LD transform. To 1.1803 + // correct this we need to update mLastDispatchedPaintMetrics to be the 1.1804 + // last thing we know was painted by Gecko. 1.1805 + mLastDispatchedPaintMetrics = aLayerMetrics; 1.1806 + } 1.1807 + } 1.1808 + 1.1809 + if (scrollOffsetUpdated) { 1.1810 + // Once layout issues a scroll offset update, it becomes impervious to 1.1811 + // scroll offset updates from APZ until we acknowledge the update it sent. 1.1812 + // This prevents APZ updates from clobbering scroll updates from other 1.1813 + // more "legitimate" sources like content scripts. 1.1814 + nsRefPtr<GeckoContentController> controller = GetGeckoContentController(); 1.1815 + if (controller) { 1.1816 + APZC_LOG("%p sending scroll update acknowledgement with gen %lu\n", this, aLayerMetrics.GetScrollGeneration()); 1.1817 + controller->AcknowledgeScrollUpdate(aLayerMetrics.GetScrollId(), 1.1818 + aLayerMetrics.GetScrollGeneration()); 1.1819 + } 1.1820 + } 1.1821 + 1.1822 + if (needContentRepaint) { 1.1823 + RequestContentRepaint(); 1.1824 + } 1.1825 + UpdateSharedCompositorFrameMetrics(); 1.1826 +} 1.1827 + 1.1828 +const FrameMetrics& AsyncPanZoomController::GetFrameMetrics() { 1.1829 + mMonitor.AssertCurrentThreadIn(); 1.1830 + return mFrameMetrics; 1.1831 +} 1.1832 + 1.1833 +void AsyncPanZoomController::ZoomToRect(CSSRect aRect) { 1.1834 + if (!aRect.IsFinite()) { 1.1835 + NS_WARNING("ZoomToRect got called with a non-finite rect; ignoring...\n"); 1.1836 + return; 1.1837 + } 1.1838 + 1.1839 + SetState(ANIMATING_ZOOM); 1.1840 + 1.1841 + { 1.1842 + ReentrantMonitorAutoEnter lock(mMonitor); 1.1843 + 1.1844 + ParentLayerIntRect compositionBounds = mFrameMetrics.mCompositionBounds; 1.1845 + CSSRect cssPageRect = mFrameMetrics.mScrollableRect; 1.1846 + CSSPoint scrollOffset = mFrameMetrics.GetScrollOffset(); 1.1847 + CSSToParentLayerScale currentZoom = mFrameMetrics.GetZoomToParent(); 1.1848 + CSSToParentLayerScale targetZoom; 1.1849 + 1.1850 + // The minimum zoom to prevent over-zoom-out. 1.1851 + // If the zoom factor is lower than this (i.e. we are zoomed more into the page), 1.1852 + // then the CSS content rect, in layers pixels, will be smaller than the 1.1853 + // composition bounds. If this happens, we can't fill the target composited 1.1854 + // area with this frame. 1.1855 + CSSToParentLayerScale localMinZoom(std::max((mZoomConstraints.mMinZoom * mFrameMetrics.mTransformScale).scale, 1.1856 + std::max(compositionBounds.width / cssPageRect.width, 1.1857 + compositionBounds.height / cssPageRect.height))); 1.1858 + CSSToParentLayerScale localMaxZoom = mZoomConstraints.mMaxZoom * mFrameMetrics.mTransformScale; 1.1859 + 1.1860 + if (!aRect.IsEmpty()) { 1.1861 + // Intersect the zoom-to-rect to the CSS rect to make sure it fits. 1.1862 + aRect = aRect.Intersect(cssPageRect); 1.1863 + targetZoom = CSSToParentLayerScale(std::min(compositionBounds.width / aRect.width, 1.1864 + compositionBounds.height / aRect.height)); 1.1865 + } 1.1866 + // 1. If the rect is empty, request received from browserElementScrolling.js 1.1867 + // 2. currentZoom is equal to mZoomConstraints.mMaxZoom and user still double-tapping it 1.1868 + // 3. currentZoom is equal to localMinZoom and user still double-tapping it 1.1869 + // Treat these three cases as a request to zoom out as much as possible. 1.1870 + if (aRect.IsEmpty() || 1.1871 + (currentZoom == localMaxZoom && targetZoom >= localMaxZoom) || 1.1872 + (currentZoom == localMinZoom && targetZoom <= localMinZoom)) { 1.1873 + CSSSize compositedSize = mFrameMetrics.CalculateCompositedSizeInCssPixels(); 1.1874 + float y = scrollOffset.y; 1.1875 + float newHeight = 1.1876 + cssPageRect.width * (compositedSize.height / compositedSize.width); 1.1877 + float dh = compositedSize.height - newHeight; 1.1878 + 1.1879 + aRect = CSSRect(0.0f, 1.1880 + y + dh/2, 1.1881 + cssPageRect.width, 1.1882 + newHeight); 1.1883 + aRect = aRect.Intersect(cssPageRect); 1.1884 + targetZoom = CSSToParentLayerScale(std::min(compositionBounds.width / aRect.width, 1.1885 + compositionBounds.height / aRect.height)); 1.1886 + } 1.1887 + 1.1888 + targetZoom.scale = clamped(targetZoom.scale, localMinZoom.scale, localMaxZoom.scale); 1.1889 + FrameMetrics endZoomToMetrics = mFrameMetrics; 1.1890 + endZoomToMetrics.SetZoom(targetZoom / mFrameMetrics.mTransformScale); 1.1891 + 1.1892 + // Adjust the zoomToRect to a sensible position to prevent overscrolling. 1.1893 + CSSSize sizeAfterZoom = endZoomToMetrics.CalculateCompositedSizeInCssPixels(); 1.1894 + 1.1895 + // If either of these conditions are met, the page will be 1.1896 + // overscrolled after zoomed 1.1897 + if (aRect.y + sizeAfterZoom.height > cssPageRect.height) { 1.1898 + aRect.y = cssPageRect.height - sizeAfterZoom.height; 1.1899 + aRect.y = aRect.y > 0 ? aRect.y : 0; 1.1900 + } 1.1901 + if (aRect.x + sizeAfterZoom.width > cssPageRect.width) { 1.1902 + aRect.x = cssPageRect.width - sizeAfterZoom.width; 1.1903 + aRect.x = aRect.x > 0 ? aRect.x : 0; 1.1904 + } 1.1905 + 1.1906 + endZoomToMetrics.SetScrollOffset(aRect.TopLeft()); 1.1907 + endZoomToMetrics.SetDisplayPortMargins( 1.1908 + CalculatePendingDisplayPort(endZoomToMetrics, 1.1909 + ScreenPoint(0,0), 1.1910 + 0)); 1.1911 + endZoomToMetrics.SetUseDisplayPortMargins(); 1.1912 + 1.1913 + StartAnimation(new ZoomAnimation( 1.1914 + mFrameMetrics.GetScrollOffset(), 1.1915 + mFrameMetrics.GetZoom(), 1.1916 + endZoomToMetrics.GetScrollOffset(), 1.1917 + endZoomToMetrics.GetZoom())); 1.1918 + 1.1919 + // Schedule a repaint now, so the new displayport will be painted before the 1.1920 + // animation finishes. 1.1921 + RequestContentRepaint(endZoomToMetrics); 1.1922 + } 1.1923 +} 1.1924 + 1.1925 +void AsyncPanZoomController::ContentReceivedTouch(bool aPreventDefault) { 1.1926 + mTouchBlockState.mPreventDefaultSet = true; 1.1927 + mTouchBlockState.mPreventDefault = aPreventDefault; 1.1928 + CheckContentResponse(); 1.1929 +} 1.1930 + 1.1931 +void AsyncPanZoomController::CheckContentResponse() { 1.1932 + bool canProceedToTouchState = true; 1.1933 + 1.1934 + if (mFrameMetrics.mMayHaveTouchListeners) { 1.1935 + canProceedToTouchState &= mTouchBlockState.mPreventDefaultSet; 1.1936 + } 1.1937 + 1.1938 + if (mTouchActionPropertyEnabled) { 1.1939 + canProceedToTouchState &= mTouchBlockState.mAllowedTouchBehaviorSet; 1.1940 + } 1.1941 + 1.1942 + if (!canProceedToTouchState) { 1.1943 + return; 1.1944 + } 1.1945 + 1.1946 + if (mContentResponseTimeoutTask) { 1.1947 + mContentResponseTimeoutTask->Cancel(); 1.1948 + mContentResponseTimeoutTask = nullptr; 1.1949 + } 1.1950 + 1.1951 + if (mState == WAITING_CONTENT_RESPONSE) { 1.1952 + if (!mTouchBlockState.mPreventDefault) { 1.1953 + SetState(NOTHING); 1.1954 + } 1.1955 + 1.1956 + mHandlingTouchQueue = true; 1.1957 + 1.1958 + while (!mTouchQueue.IsEmpty()) { 1.1959 + if (!mTouchBlockState.mPreventDefault) { 1.1960 + HandleInputEvent(mTouchQueue[0]); 1.1961 + } 1.1962 + 1.1963 + if (mTouchQueue[0].mType == MultiTouchInput::MULTITOUCH_END || 1.1964 + mTouchQueue[0].mType == MultiTouchInput::MULTITOUCH_CANCEL) { 1.1965 + mTouchQueue.RemoveElementAt(0); 1.1966 + break; 1.1967 + } 1.1968 + 1.1969 + mTouchQueue.RemoveElementAt(0); 1.1970 + } 1.1971 + 1.1972 + mHandlingTouchQueue = false; 1.1973 + } 1.1974 +} 1.1975 + 1.1976 +bool AsyncPanZoomController::TouchActionAllowPinchZoom() { 1.1977 + if (!mTouchActionPropertyEnabled) { 1.1978 + return true; 1.1979 + } 1.1980 + // Pointer events specification implies all touch points to allow zoom 1.1981 + // to perform it. 1.1982 + for (size_t i = 0; i < mTouchBlockState.mAllowedTouchBehaviors.Length(); i++) { 1.1983 + if (!(mTouchBlockState.mAllowedTouchBehaviors[i] & AllowedTouchBehavior::PINCH_ZOOM)) { 1.1984 + return false; 1.1985 + } 1.1986 + } 1.1987 + return true; 1.1988 +} 1.1989 + 1.1990 +bool AsyncPanZoomController::TouchActionAllowDoubleTapZoom() { 1.1991 + if (!mTouchActionPropertyEnabled) { 1.1992 + return true; 1.1993 + } 1.1994 + for (size_t i = 0; i < mTouchBlockState.mAllowedTouchBehaviors.Length(); i++) { 1.1995 + if (!(mTouchBlockState.mAllowedTouchBehaviors[i] & AllowedTouchBehavior::DOUBLE_TAP_ZOOM)) { 1.1996 + return false; 1.1997 + } 1.1998 + } 1.1999 + return true; 1.2000 +} 1.2001 + 1.2002 +AsyncPanZoomController::TouchBehaviorFlags 1.2003 +AsyncPanZoomController::GetTouchBehavior(uint32_t touchIndex) { 1.2004 + if (touchIndex < mTouchBlockState.mAllowedTouchBehaviors.Length()) { 1.2005 + return mTouchBlockState.mAllowedTouchBehaviors[touchIndex]; 1.2006 + } 1.2007 + return DefaultTouchBehavior; 1.2008 +} 1.2009 + 1.2010 +AsyncPanZoomController::TouchBehaviorFlags 1.2011 +AsyncPanZoomController::GetAllowedTouchBehavior(ScreenIntPoint& aPoint) { 1.2012 + // Here we need to perform a hit testing over the touch-action regions attached to the 1.2013 + // layer associated with current apzc. 1.2014 + // Currently they are in progress, for more info see bug 928833. 1.2015 + return AllowedTouchBehavior::UNKNOWN; 1.2016 +} 1.2017 + 1.2018 +void AsyncPanZoomController::SetAllowedTouchBehavior(const nsTArray<TouchBehaviorFlags>& aBehaviors) { 1.2019 + mTouchBlockState.mAllowedTouchBehaviors.Clear(); 1.2020 + mTouchBlockState.mAllowedTouchBehaviors.AppendElements(aBehaviors); 1.2021 + mTouchBlockState.mAllowedTouchBehaviorSet = true; 1.2022 + CheckContentResponse(); 1.2023 +} 1.2024 + 1.2025 +void AsyncPanZoomController::SetState(PanZoomState aNewState) { 1.2026 + 1.2027 + PanZoomState oldState; 1.2028 + 1.2029 + // Intentional scoping for mutex 1.2030 + { 1.2031 + ReentrantMonitorAutoEnter lock(mMonitor); 1.2032 + oldState = mState; 1.2033 + mState = aNewState; 1.2034 + } 1.2035 + 1.2036 + if (nsRefPtr<GeckoContentController> controller = GetGeckoContentController()) { 1.2037 + if (!IsTransformingState(oldState) && IsTransformingState(aNewState)) { 1.2038 + controller->NotifyAPZStateChange( 1.2039 + GetGuid(), APZStateChange::TransformBegin); 1.2040 + } else if (IsTransformingState(oldState) && !IsTransformingState(aNewState)) { 1.2041 + controller->NotifyAPZStateChange( 1.2042 + GetGuid(), APZStateChange::TransformEnd); 1.2043 + } 1.2044 + } 1.2045 +} 1.2046 + 1.2047 +bool AsyncPanZoomController::IsTransformingState(PanZoomState aState) { 1.2048 + return !(aState == NOTHING || aState == TOUCHING || aState == WAITING_CONTENT_RESPONSE); 1.2049 +} 1.2050 + 1.2051 +bool AsyncPanZoomController::IsPanningState(PanZoomState aState) { 1.2052 + return (aState == PANNING || aState == PANNING_LOCKED_X || aState == PANNING_LOCKED_Y); 1.2053 +} 1.2054 + 1.2055 +void AsyncPanZoomController::SetContentResponseTimer() { 1.2056 + if (!mContentResponseTimeoutTask) { 1.2057 + mContentResponseTimeoutTask = 1.2058 + NewRunnableMethod(this, &AsyncPanZoomController::TimeoutContentResponse); 1.2059 + 1.2060 + PostDelayedTask(mContentResponseTimeoutTask, gfxPrefs::APZContentResponseTimeout()); 1.2061 + } 1.2062 +} 1.2063 + 1.2064 +void AsyncPanZoomController::TimeoutContentResponse() { 1.2065 + mContentResponseTimeoutTask = nullptr; 1.2066 + ContentReceivedTouch(false); 1.2067 +} 1.2068 + 1.2069 +void AsyncPanZoomController::UpdateZoomConstraints(const ZoomConstraints& aConstraints) { 1.2070 + APZC_LOG("%p updating zoom constraints to %d %d %f %f\n", this, aConstraints.mAllowZoom, 1.2071 + aConstraints.mAllowDoubleTapZoom, aConstraints.mMinZoom.scale, aConstraints.mMaxZoom.scale); 1.2072 + if (IsNaN(aConstraints.mMinZoom.scale) || IsNaN(aConstraints.mMaxZoom.scale)) { 1.2073 + NS_WARNING("APZC received zoom constraints with NaN values; dropping...\n"); 1.2074 + return; 1.2075 + } 1.2076 + // inf float values and other bad cases should be sanitized by the code below. 1.2077 + mZoomConstraints.mAllowZoom = aConstraints.mAllowZoom; 1.2078 + mZoomConstraints.mAllowDoubleTapZoom = aConstraints.mAllowDoubleTapZoom; 1.2079 + mZoomConstraints.mMinZoom = (MIN_ZOOM > aConstraints.mMinZoom ? MIN_ZOOM : aConstraints.mMinZoom); 1.2080 + mZoomConstraints.mMaxZoom = (MAX_ZOOM > aConstraints.mMaxZoom ? aConstraints.mMaxZoom : MAX_ZOOM); 1.2081 + if (mZoomConstraints.mMaxZoom < mZoomConstraints.mMinZoom) { 1.2082 + mZoomConstraints.mMaxZoom = mZoomConstraints.mMinZoom; 1.2083 + } 1.2084 +} 1.2085 + 1.2086 +ZoomConstraints 1.2087 +AsyncPanZoomController::GetZoomConstraints() const 1.2088 +{ 1.2089 + return mZoomConstraints; 1.2090 +} 1.2091 + 1.2092 + 1.2093 +void AsyncPanZoomController::PostDelayedTask(Task* aTask, int aDelayMs) { 1.2094 + nsRefPtr<GeckoContentController> controller = GetGeckoContentController(); 1.2095 + if (controller) { 1.2096 + controller->PostDelayedTask(aTask, aDelayMs); 1.2097 + } 1.2098 +} 1.2099 + 1.2100 +void AsyncPanZoomController::SendAsyncScrollEvent() { 1.2101 + nsRefPtr<GeckoContentController> controller = GetGeckoContentController(); 1.2102 + if (!controller) { 1.2103 + return; 1.2104 + } 1.2105 + 1.2106 + bool isRoot; 1.2107 + CSSRect contentRect; 1.2108 + CSSSize scrollableSize; 1.2109 + { 1.2110 + ReentrantMonitorAutoEnter lock(mMonitor); 1.2111 + 1.2112 + isRoot = mFrameMetrics.mIsRoot; 1.2113 + scrollableSize = mFrameMetrics.mScrollableRect.Size(); 1.2114 + contentRect = mFrameMetrics.CalculateCompositedRectInCssPixels(); 1.2115 + contentRect.MoveTo(mCurrentAsyncScrollOffset); 1.2116 + } 1.2117 + 1.2118 + controller->SendAsyncScrollDOMEvent(isRoot, contentRect, scrollableSize); 1.2119 +} 1.2120 + 1.2121 +bool AsyncPanZoomController::Matches(const ScrollableLayerGuid& aGuid) 1.2122 +{ 1.2123 + return aGuid == GetGuid(); 1.2124 +} 1.2125 + 1.2126 +void AsyncPanZoomController::GetGuid(ScrollableLayerGuid* aGuidOut) 1.2127 +{ 1.2128 + if (aGuidOut) { 1.2129 + *aGuidOut = GetGuid(); 1.2130 + } 1.2131 +} 1.2132 + 1.2133 +ScrollableLayerGuid AsyncPanZoomController::GetGuid() 1.2134 +{ 1.2135 + return ScrollableLayerGuid(mLayersId, mFrameMetrics); 1.2136 +} 1.2137 + 1.2138 +void AsyncPanZoomController::UpdateSharedCompositorFrameMetrics() 1.2139 +{ 1.2140 + mMonitor.AssertCurrentThreadIn(); 1.2141 + 1.2142 + FrameMetrics* frame = mSharedFrameMetricsBuffer ? 1.2143 + static_cast<FrameMetrics*>(mSharedFrameMetricsBuffer->memory()) : nullptr; 1.2144 + 1.2145 + if (frame && mSharedLock && gfxPrefs::UseProgressiveTilePainting()) { 1.2146 + mSharedLock->Lock(); 1.2147 + *frame = mFrameMetrics; 1.2148 + mSharedLock->Unlock(); 1.2149 + } 1.2150 +} 1.2151 + 1.2152 +void AsyncPanZoomController::ShareCompositorFrameMetrics() { 1.2153 + 1.2154 + PCompositorParent* compositor = 1.2155 + (mCrossProcessCompositorParent ? mCrossProcessCompositorParent : mCompositorParent.get()); 1.2156 + 1.2157 + // Only create the shared memory buffer if it hasn't already been created, 1.2158 + // we are using progressive tile painting, and we have a 1.2159 + // compositor to pass the shared memory back to the content process/thread. 1.2160 + if (!mSharedFrameMetricsBuffer && compositor && gfxPrefs::UseProgressiveTilePainting()) { 1.2161 + 1.2162 + // Create shared memory and initialize it with the current FrameMetrics value 1.2163 + mSharedFrameMetricsBuffer = new ipc::SharedMemoryBasic; 1.2164 + FrameMetrics* frame = nullptr; 1.2165 + mSharedFrameMetricsBuffer->Create(sizeof(FrameMetrics)); 1.2166 + mSharedFrameMetricsBuffer->Map(sizeof(FrameMetrics)); 1.2167 + frame = static_cast<FrameMetrics*>(mSharedFrameMetricsBuffer->memory()); 1.2168 + 1.2169 + if (frame) { 1.2170 + 1.2171 + { // scope the monitor, only needed to copy the FrameMetrics. 1.2172 + ReentrantMonitorAutoEnter lock(mMonitor); 1.2173 + *frame = mFrameMetrics; 1.2174 + } 1.2175 + 1.2176 + // Get the process id of the content process 1.2177 + base::ProcessHandle processHandle = compositor->OtherProcess(); 1.2178 + ipc::SharedMemoryBasic::Handle mem = ipc::SharedMemoryBasic::NULLHandle(); 1.2179 + 1.2180 + // Get the shared memory handle to share with the content process 1.2181 + mSharedFrameMetricsBuffer->ShareToProcess(processHandle, &mem); 1.2182 + 1.2183 + // Get the cross process mutex handle to share with the content process 1.2184 + mSharedLock = new CrossProcessMutex("AsyncPanZoomControlLock"); 1.2185 + CrossProcessMutexHandle handle = mSharedLock->ShareToProcess(processHandle); 1.2186 + 1.2187 + // Send the shared memory handle and cross process handle to the content 1.2188 + // process by an asynchronous ipc call. Include the APZC unique ID 1.2189 + // so the content process know which APZC sent this shared FrameMetrics. 1.2190 + if (!compositor->SendSharedCompositorFrameMetrics(mem, handle, mAPZCId)) { 1.2191 + APZC_LOG("%p failed to share FrameMetrics with content process.", this); 1.2192 + } 1.2193 + } 1.2194 + } 1.2195 +} 1.2196 + 1.2197 +ParentLayerPoint AsyncPanZoomController::ToParentLayerCoords(const ScreenPoint& aPoint) 1.2198 +{ 1.2199 + return TransformTo<ParentLayerPixel>(GetNontransientAsyncTransform() * GetCSSTransform(), aPoint); 1.2200 +} 1.2201 + 1.2202 +void AsyncPanZoomController::UpdateTransformScale() 1.2203 +{ 1.2204 + gfx3DMatrix nontransientTransforms = GetNontransientAsyncTransform() * GetCSSTransform(); 1.2205 + if (!FuzzyEqualsMultiplicative(nontransientTransforms.GetXScale(), nontransientTransforms.GetYScale())) { 1.2206 + NS_WARNING("The x- and y-scales of the nontransient transforms should be equal"); 1.2207 + } 1.2208 + mFrameMetrics.mTransformScale.scale = nontransientTransforms.GetXScale(); 1.2209 +} 1.2210 + 1.2211 +} 1.2212 +}