Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* vim: set sw=2 ts=8 et tw=80 : */ |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | #include <math.h> // for fabsf, fabs, atan2 |
michael@0 | 8 | #include <stdint.h> // for uint32_t, uint64_t |
michael@0 | 9 | #include <sys/types.h> // for int32_t |
michael@0 | 10 | #include <algorithm> // for max, min |
michael@0 | 11 | #include "AnimationCommon.h" // for ComputedTimingFunction |
michael@0 | 12 | #include "AsyncPanZoomController.h" // for AsyncPanZoomController, etc |
michael@0 | 13 | #include "CompositorParent.h" // for CompositorParent |
michael@0 | 14 | #include "FrameMetrics.h" // for FrameMetrics, etc |
michael@0 | 15 | #include "GestureEventListener.h" // for GestureEventListener |
michael@0 | 16 | #include "InputData.h" // for MultiTouchInput, etc |
michael@0 | 17 | #include "Units.h" // for CSSRect, CSSPoint, etc |
michael@0 | 18 | #include "UnitTransforms.h" // for TransformTo |
michael@0 | 19 | #include "base/message_loop.h" // for MessageLoop |
michael@0 | 20 | #include "base/task.h" // for NewRunnableMethod, etc |
michael@0 | 21 | #include "base/tracked.h" // for FROM_HERE |
michael@0 | 22 | #include "gfxPrefs.h" // for gfxPrefs |
michael@0 | 23 | #include "gfxTypes.h" // for gfxFloat |
michael@0 | 24 | #include "mozilla/Assertions.h" // for MOZ_ASSERT, etc |
michael@0 | 25 | #include "mozilla/BasicEvents.h" // for Modifiers, MODIFIER_* |
michael@0 | 26 | #include "mozilla/ClearOnShutdown.h" // for ClearOnShutdown |
michael@0 | 27 | #include "mozilla/Constants.h" // for M_PI |
michael@0 | 28 | #include "mozilla/EventForwards.h" // for nsEventStatus_* |
michael@0 | 29 | #include "mozilla/Preferences.h" // for Preferences |
michael@0 | 30 | #include "mozilla/ReentrantMonitor.h" // for ReentrantMonitorAutoEnter, etc |
michael@0 | 31 | #include "mozilla/StaticPtr.h" // for StaticAutoPtr |
michael@0 | 32 | #include "mozilla/TimeStamp.h" // for TimeDuration, TimeStamp |
michael@0 | 33 | #include "mozilla/dom/Touch.h" // for Touch |
michael@0 | 34 | #include "mozilla/gfx/BasePoint.h" // for BasePoint |
michael@0 | 35 | #include "mozilla/gfx/BaseRect.h" // for BaseRect |
michael@0 | 36 | #include "mozilla/gfx/Point.h" // for Point, RoundedToInt, etc |
michael@0 | 37 | #include "mozilla/gfx/Rect.h" // for RoundedIn |
michael@0 | 38 | #include "mozilla/gfx/ScaleFactor.h" // for ScaleFactor |
michael@0 | 39 | #include "mozilla/layers/APZCTreeManager.h" // for ScrollableLayerGuid |
michael@0 | 40 | #include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform |
michael@0 | 41 | #include "mozilla/layers/Axis.h" // for AxisX, AxisY, Axis, etc |
michael@0 | 42 | #include "mozilla/layers/LayerTransactionParent.h" // for LayerTransactionParent |
michael@0 | 43 | #include "mozilla/layers/PCompositorParent.h" // for PCompositorParent |
michael@0 | 44 | #include "mozilla/layers/TaskThrottler.h" // for TaskThrottler |
michael@0 | 45 | #include "mozilla/mozalloc.h" // for operator new, etc |
michael@0 | 46 | #include "mozilla/unused.h" // for unused |
michael@0 | 47 | #include "mozilla/FloatingPoint.h" // for FuzzyEqualsMultiplicative |
michael@0 | 48 | #include "nsAlgorithm.h" // for clamped |
michael@0 | 49 | #include "nsAutoPtr.h" // for nsRefPtr |
michael@0 | 50 | #include "nsCOMPtr.h" // for already_AddRefed |
michael@0 | 51 | #include "nsDebug.h" // for NS_WARNING |
michael@0 | 52 | #include "nsIDOMWindowUtils.h" // for nsIDOMWindowUtils |
michael@0 | 53 | #include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc |
michael@0 | 54 | #include "nsMathUtils.h" // for NS_hypot |
michael@0 | 55 | #include "nsPoint.h" // for nsIntPoint |
michael@0 | 56 | #include "nsStyleConsts.h" |
michael@0 | 57 | #include "nsStyleStruct.h" // for nsTimingFunction |
michael@0 | 58 | #include "nsTArray.h" // for nsTArray, nsTArray_Impl, etc |
michael@0 | 59 | #include "nsThreadUtils.h" // for NS_IsMainThread |
michael@0 | 60 | #include "SharedMemoryBasic.h" // for SharedMemoryBasic |
michael@0 | 61 | |
michael@0 | 62 | // #define APZC_ENABLE_RENDERTRACE |
michael@0 | 63 | |
michael@0 | 64 | #define APZC_LOG(...) |
michael@0 | 65 | // #define APZC_LOG(...) printf_stderr("APZC: " __VA_ARGS__) |
michael@0 | 66 | #define APZC_LOG_FM(fm, prefix, ...) \ |
michael@0 | 67 | APZC_LOG(prefix ":" \ |
michael@0 | 68 | " i=(%ld %lld) cb=(%d %d %d %d) rcs=(%.3f %.3f) dp=(%.3f %.3f %.3f %.3f) dpm=(%.3f %.3f %.3f %.3f) um=%d " \ |
michael@0 | 69 | "v=(%.3f %.3f %.3f %.3f) s=(%.3f %.3f) sr=(%.3f %.3f %.3f %.3f) z=(%.3f %.3f %.3f %.3f) u=(%d %lu)\n", \ |
michael@0 | 70 | __VA_ARGS__, \ |
michael@0 | 71 | fm.mPresShellId, fm.GetScrollId(), \ |
michael@0 | 72 | fm.mCompositionBounds.x, fm.mCompositionBounds.y, fm.mCompositionBounds.width, fm.mCompositionBounds.height, \ |
michael@0 | 73 | fm.GetRootCompositionSize().width, fm.GetRootCompositionSize().height, \ |
michael@0 | 74 | fm.mDisplayPort.x, fm.mDisplayPort.y, fm.mDisplayPort.width, fm.mDisplayPort.height, \ |
michael@0 | 75 | fm.GetDisplayPortMargins().top, fm.GetDisplayPortMargins().right, fm.GetDisplayPortMargins().bottom, fm.GetDisplayPortMargins().left, \ |
michael@0 | 76 | fm.GetUseDisplayPortMargins() ? 1 : 0, \ |
michael@0 | 77 | fm.mViewport.x, fm.mViewport.y, fm.mViewport.width, fm.mViewport.height, \ |
michael@0 | 78 | fm.GetScrollOffset().x, fm.GetScrollOffset().y, \ |
michael@0 | 79 | fm.mScrollableRect.x, fm.mScrollableRect.y, fm.mScrollableRect.width, fm.mScrollableRect.height, \ |
michael@0 | 80 | fm.mDevPixelsPerCSSPixel.scale, fm.mResolution.scale, fm.mCumulativeResolution.scale, fm.GetZoom().scale, \ |
michael@0 | 81 | fm.GetScrollOffsetUpdated(), fm.GetScrollGeneration()); \ |
michael@0 | 82 | |
michael@0 | 83 | // Static helper functions |
michael@0 | 84 | namespace { |
michael@0 | 85 | |
michael@0 | 86 | int32_t |
michael@0 | 87 | WidgetModifiersToDOMModifiers(mozilla::Modifiers aModifiers) |
michael@0 | 88 | { |
michael@0 | 89 | int32_t result = 0; |
michael@0 | 90 | if (aModifiers & mozilla::MODIFIER_SHIFT) { |
michael@0 | 91 | result |= nsIDOMWindowUtils::MODIFIER_SHIFT; |
michael@0 | 92 | } |
michael@0 | 93 | if (aModifiers & mozilla::MODIFIER_CONTROL) { |
michael@0 | 94 | result |= nsIDOMWindowUtils::MODIFIER_CONTROL; |
michael@0 | 95 | } |
michael@0 | 96 | if (aModifiers & mozilla::MODIFIER_ALT) { |
michael@0 | 97 | result |= nsIDOMWindowUtils::MODIFIER_ALT; |
michael@0 | 98 | } |
michael@0 | 99 | if (aModifiers & mozilla::MODIFIER_META) { |
michael@0 | 100 | result |= nsIDOMWindowUtils::MODIFIER_META; |
michael@0 | 101 | } |
michael@0 | 102 | if (aModifiers & mozilla::MODIFIER_ALTGRAPH) { |
michael@0 | 103 | result |= nsIDOMWindowUtils::MODIFIER_ALTGRAPH; |
michael@0 | 104 | } |
michael@0 | 105 | if (aModifiers & mozilla::MODIFIER_CAPSLOCK) { |
michael@0 | 106 | result |= nsIDOMWindowUtils::MODIFIER_CAPSLOCK; |
michael@0 | 107 | } |
michael@0 | 108 | if (aModifiers & mozilla::MODIFIER_FN) { |
michael@0 | 109 | result |= nsIDOMWindowUtils::MODIFIER_FN; |
michael@0 | 110 | } |
michael@0 | 111 | if (aModifiers & mozilla::MODIFIER_NUMLOCK) { |
michael@0 | 112 | result |= nsIDOMWindowUtils::MODIFIER_NUMLOCK; |
michael@0 | 113 | } |
michael@0 | 114 | if (aModifiers & mozilla::MODIFIER_SCROLLLOCK) { |
michael@0 | 115 | result |= nsIDOMWindowUtils::MODIFIER_SCROLLLOCK; |
michael@0 | 116 | } |
michael@0 | 117 | if (aModifiers & mozilla::MODIFIER_SYMBOLLOCK) { |
michael@0 | 118 | result |= nsIDOMWindowUtils::MODIFIER_SYMBOLLOCK; |
michael@0 | 119 | } |
michael@0 | 120 | if (aModifiers & mozilla::MODIFIER_OS) { |
michael@0 | 121 | result |= nsIDOMWindowUtils::MODIFIER_OS; |
michael@0 | 122 | } |
michael@0 | 123 | return result; |
michael@0 | 124 | } |
michael@0 | 125 | |
michael@0 | 126 | } |
michael@0 | 127 | |
michael@0 | 128 | using namespace mozilla::css; |
michael@0 | 129 | |
michael@0 | 130 | namespace mozilla { |
michael@0 | 131 | namespace layers { |
michael@0 | 132 | |
michael@0 | 133 | typedef mozilla::layers::AllowedTouchBehavior AllowedTouchBehavior; |
michael@0 | 134 | typedef GeckoContentController::APZStateChange APZStateChange; |
michael@0 | 135 | |
michael@0 | 136 | /* |
michael@0 | 137 | * The following prefs are used to control the behaviour of the APZC. |
michael@0 | 138 | * The default values are provided in gfxPrefs.h. |
michael@0 | 139 | * |
michael@0 | 140 | * "apz.allow-checkerboarding" |
michael@0 | 141 | * Pref that allows or disallows checkerboarding |
michael@0 | 142 | * |
michael@0 | 143 | * "apz.asyncscroll.throttle" |
michael@0 | 144 | * The time period in ms that throttles mozbrowserasyncscroll event. |
michael@0 | 145 | * |
michael@0 | 146 | * "apz.asyncscroll.timeout" |
michael@0 | 147 | * The timeout in ms for mAsyncScrollTimeoutTask delay task. |
michael@0 | 148 | * |
michael@0 | 149 | * "apz.axis_lock_mode" |
michael@0 | 150 | * The preferred axis locking style. See AxisLockMode for possible values. |
michael@0 | 151 | * |
michael@0 | 152 | * "apz.content_response_timeout" |
michael@0 | 153 | * Amount of time before we timeout response from content. For example, if |
michael@0 | 154 | * content is being unruly/slow and we don't get a response back within this |
michael@0 | 155 | * time, we will just pretend that content did not preventDefault any touch |
michael@0 | 156 | * events we dispatched to it. |
michael@0 | 157 | * |
michael@0 | 158 | * "apz.cross_slide_enabled" |
michael@0 | 159 | * Pref that enables integration with the Metro "cross-slide" gesture. |
michael@0 | 160 | * |
michael@0 | 161 | * "apz.enlarge_displayport_when_clipped" |
michael@0 | 162 | * Pref that enables enlarging of the displayport along one axis when the |
michael@0 | 163 | * generated displayport's size is beyond that of the scrollable rect on the |
michael@0 | 164 | * opposite axis. |
michael@0 | 165 | * |
michael@0 | 166 | * "apz.fling_friction" |
michael@0 | 167 | * Amount of friction applied during flings. |
michael@0 | 168 | * |
michael@0 | 169 | * "apz.fling_repaint_interval" |
michael@0 | 170 | * Maximum amount of time flinging before sending a viewport change. This will |
michael@0 | 171 | * asynchronously repaint the page. |
michael@0 | 172 | * |
michael@0 | 173 | * "apz.fling_stopped_threshold" |
michael@0 | 174 | * When flinging, if the velocity goes below this number, we just stop the |
michael@0 | 175 | * animation completely. This is to prevent asymptotically approaching 0 |
michael@0 | 176 | * velocity and rerendering unnecessarily. |
michael@0 | 177 | * |
michael@0 | 178 | * "apz.max_velocity_inches_per_ms" |
michael@0 | 179 | * Maximum velocity in inches per millisecond. Velocity will be capped at this |
michael@0 | 180 | * value if a faster fling occurs. Negative values indicate unlimited velocity. |
michael@0 | 181 | * |
michael@0 | 182 | * "apz.max_velocity_queue_size" |
michael@0 | 183 | * Maximum size of velocity queue. The queue contains last N velocity records. |
michael@0 | 184 | * On touch end we calculate the average velocity in order to compensate |
michael@0 | 185 | * touch/mouse drivers misbehaviour. |
michael@0 | 186 | * |
michael@0 | 187 | * "apz.min_skate_speed" |
michael@0 | 188 | * Minimum amount of speed along an axis before we switch to "skate" multipliers |
michael@0 | 189 | * rather than using the "stationary" multipliers. |
michael@0 | 190 | * |
michael@0 | 191 | * "apz.num_paint_duration_samples" |
michael@0 | 192 | * Number of samples to store of how long it took to paint after the previous |
michael@0 | 193 | * requests. |
michael@0 | 194 | * |
michael@0 | 195 | * "apz.pan_repaint_interval" |
michael@0 | 196 | * Maximum amount of time while panning before sending a viewport change. This |
michael@0 | 197 | * will asynchronously repaint the page. It is also forced when panning stops. |
michael@0 | 198 | * |
michael@0 | 199 | * "apz.touch_start_tolerance" |
michael@0 | 200 | * Constant describing the tolerance in distance we use, multiplied by the |
michael@0 | 201 | * device DPI, before we start panning the screen. This is to prevent us from |
michael@0 | 202 | * accidentally processing taps as touch moves, and from very short/accidental |
michael@0 | 203 | * touches moving the screen. |
michael@0 | 204 | * |
michael@0 | 205 | * "apz.use_paint_duration" |
michael@0 | 206 | * Whether or not to use the estimated paint duration as a factor when projecting |
michael@0 | 207 | * the displayport in the direction of scrolling. If this value is set to false, |
michael@0 | 208 | * a constant 50ms paint time is used; the projection can be scaled as desired |
michael@0 | 209 | * using the apz.velocity_bias pref below. |
michael@0 | 210 | * |
michael@0 | 211 | * "apz.velocity_bias" |
michael@0 | 212 | * How much to adjust the displayport in the direction of scrolling. This value |
michael@0 | 213 | * is multiplied by the velocity and added to the displayport offset. |
michael@0 | 214 | * |
michael@0 | 215 | * "apz.x_skate_size_multiplier", "apz.y_skate_size_multiplier" |
michael@0 | 216 | * The multiplier we apply to the displayport size if it is skating (current |
michael@0 | 217 | * velocity is above apz.min_skate_speed). We prefer to increase the size of the |
michael@0 | 218 | * Y axis because it is more natural in the case that a user is reading a page |
michael@0 | 219 | * that scrolls up/down. Note that one, both or neither of these may be used |
michael@0 | 220 | * at any instant. |
michael@0 | 221 | * In general we want apz.[xy]_skate_size_multiplier to be smaller than the corresponding |
michael@0 | 222 | * stationary size multiplier because when panning fast we would like to paint |
michael@0 | 223 | * less and get faster, more predictable paint times. When panning slowly we |
michael@0 | 224 | * can afford to paint more even though it's slower. |
michael@0 | 225 | * |
michael@0 | 226 | * "apz.x_stationary_size_multiplier", "apz.y_stationary_size_multiplier" |
michael@0 | 227 | * The multiplier we apply to the displayport size if it is not skating (see |
michael@0 | 228 | * documentation for the skate size multipliers above). |
michael@0 | 229 | */ |
michael@0 | 230 | |
michael@0 | 231 | /** |
michael@0 | 232 | * Default touch behavior (is used when not touch behavior is set). |
michael@0 | 233 | */ |
michael@0 | 234 | static const uint32_t DefaultTouchBehavior = AllowedTouchBehavior::VERTICAL_PAN | |
michael@0 | 235 | AllowedTouchBehavior::HORIZONTAL_PAN | |
michael@0 | 236 | AllowedTouchBehavior::PINCH_ZOOM | |
michael@0 | 237 | AllowedTouchBehavior::DOUBLE_TAP_ZOOM; |
michael@0 | 238 | |
michael@0 | 239 | /** |
michael@0 | 240 | * Angle from axis within which we stay axis-locked |
michael@0 | 241 | */ |
michael@0 | 242 | static const double AXIS_LOCK_ANGLE = M_PI / 6.0; // 30 degrees |
michael@0 | 243 | |
michael@0 | 244 | /** |
michael@0 | 245 | * The distance in inches the user must pan before axis lock can be broken |
michael@0 | 246 | */ |
michael@0 | 247 | static const float AXIS_BREAKOUT_THRESHOLD = 1.0f/32.0f; |
michael@0 | 248 | |
michael@0 | 249 | /** |
michael@0 | 250 | * The angle at which axis lock can be broken |
michael@0 | 251 | */ |
michael@0 | 252 | static const double AXIS_BREAKOUT_ANGLE = M_PI / 8.0; // 22.5 degrees |
michael@0 | 253 | |
michael@0 | 254 | /** |
michael@0 | 255 | * Angle from axis to the line drawn by pan move. |
michael@0 | 256 | * If angle is less than this value we can assume that panning |
michael@0 | 257 | * can be done in allowed direction (horizontal or vertical). |
michael@0 | 258 | * Currently used only for touch-action css property stuff and was |
michael@0 | 259 | * added to keep behavior consistent with IE. |
michael@0 | 260 | */ |
michael@0 | 261 | static const double ALLOWED_DIRECT_PAN_ANGLE = M_PI / 3.0; // 60 degrees |
michael@0 | 262 | |
michael@0 | 263 | /** |
michael@0 | 264 | * Duration of a zoom to animation. |
michael@0 | 265 | */ |
michael@0 | 266 | static const TimeDuration ZOOM_TO_DURATION = TimeDuration::FromSeconds(0.25); |
michael@0 | 267 | |
michael@0 | 268 | /** |
michael@0 | 269 | * Computed time function used for sampling frames of a zoom to animation. |
michael@0 | 270 | */ |
michael@0 | 271 | StaticAutoPtr<ComputedTimingFunction> gComputedTimingFunction; |
michael@0 | 272 | |
michael@0 | 273 | /** |
michael@0 | 274 | * Maximum zoom amount, always used, even if a page asks for higher. |
michael@0 | 275 | */ |
michael@0 | 276 | static const CSSToScreenScale MAX_ZOOM(8.0f); |
michael@0 | 277 | |
michael@0 | 278 | /** |
michael@0 | 279 | * Minimum zoom amount, always used, even if a page asks for lower. |
michael@0 | 280 | */ |
michael@0 | 281 | static const CSSToScreenScale MIN_ZOOM(0.125f); |
michael@0 | 282 | |
michael@0 | 283 | /** |
michael@0 | 284 | * Is aAngle within the given threshold of the horizontal axis? |
michael@0 | 285 | * @param aAngle an angle in radians in the range [0, pi] |
michael@0 | 286 | * @param aThreshold an angle in radians in the range [0, pi/2] |
michael@0 | 287 | */ |
michael@0 | 288 | static bool IsCloseToHorizontal(float aAngle, float aThreshold) |
michael@0 | 289 | { |
michael@0 | 290 | return (aAngle < aThreshold || aAngle > (M_PI - aThreshold)); |
michael@0 | 291 | } |
michael@0 | 292 | |
michael@0 | 293 | // As above, but for the vertical axis. |
michael@0 | 294 | static bool IsCloseToVertical(float aAngle, float aThreshold) |
michael@0 | 295 | { |
michael@0 | 296 | return (fabs(aAngle - (M_PI / 2)) < aThreshold); |
michael@0 | 297 | } |
michael@0 | 298 | |
michael@0 | 299 | template <typename Units> |
michael@0 | 300 | static bool IsZero(const gfx::PointTyped<Units>& aPoint) |
michael@0 | 301 | { |
michael@0 | 302 | return FuzzyEqualsMultiplicative(aPoint.x, 0.0f) |
michael@0 | 303 | && FuzzyEqualsMultiplicative(aPoint.y, 0.0f); |
michael@0 | 304 | } |
michael@0 | 305 | |
michael@0 | 306 | static inline void LogRendertraceRect(const ScrollableLayerGuid& aGuid, const char* aDesc, const char* aColor, const CSSRect& aRect) |
michael@0 | 307 | { |
michael@0 | 308 | #ifdef APZC_ENABLE_RENDERTRACE |
michael@0 | 309 | static const TimeStamp sRenderStart = TimeStamp::Now(); |
michael@0 | 310 | TimeDuration delta = TimeStamp::Now() - sRenderStart; |
michael@0 | 311 | printf_stderr("(%llu,%lu,%llu)%s RENDERTRACE %f rect %s %f %f %f %f\n", |
michael@0 | 312 | aGuid.mLayersId, aGuid.mPresShellId, aGuid.GetScrollId(), |
michael@0 | 313 | aDesc, delta.ToMilliseconds(), aColor, |
michael@0 | 314 | aRect.x, aRect.y, aRect.width, aRect.height); |
michael@0 | 315 | #endif |
michael@0 | 316 | } |
michael@0 | 317 | |
michael@0 | 318 | static TimeStamp sFrameTime; |
michael@0 | 319 | |
michael@0 | 320 | // Counter used to give each APZC a unique id |
michael@0 | 321 | static uint32_t sAsyncPanZoomControllerCount = 0; |
michael@0 | 322 | |
michael@0 | 323 | static TimeStamp |
michael@0 | 324 | GetFrameTime() { |
michael@0 | 325 | if (sFrameTime.IsNull()) { |
michael@0 | 326 | return TimeStamp::Now(); |
michael@0 | 327 | } |
michael@0 | 328 | return sFrameTime; |
michael@0 | 329 | } |
michael@0 | 330 | |
michael@0 | 331 | class FlingAnimation: public AsyncPanZoomAnimation { |
michael@0 | 332 | public: |
michael@0 | 333 | FlingAnimation(AsyncPanZoomController& aApzc) |
michael@0 | 334 | : AsyncPanZoomAnimation(TimeDuration::FromMilliseconds(gfxPrefs::APZFlingRepaintInterval())) |
michael@0 | 335 | , mApzc(aApzc) |
michael@0 | 336 | {} |
michael@0 | 337 | /** |
michael@0 | 338 | * Advances a fling by an interpolated amount based on the passed in |aDelta|. |
michael@0 | 339 | * This should be called whenever sampling the content transform for this |
michael@0 | 340 | * frame. Returns true if the fling animation should be advanced by one frame, |
michael@0 | 341 | * or false if there is no fling or the fling has ended. |
michael@0 | 342 | */ |
michael@0 | 343 | virtual bool Sample(FrameMetrics& aFrameMetrics, |
michael@0 | 344 | const TimeDuration& aDelta); |
michael@0 | 345 | |
michael@0 | 346 | private: |
michael@0 | 347 | AsyncPanZoomController& mApzc; |
michael@0 | 348 | }; |
michael@0 | 349 | |
michael@0 | 350 | class ZoomAnimation: public AsyncPanZoomAnimation { |
michael@0 | 351 | public: |
michael@0 | 352 | ZoomAnimation(CSSPoint aStartOffset, CSSToScreenScale aStartZoom, |
michael@0 | 353 | CSSPoint aEndOffset, CSSToScreenScale aEndZoom) |
michael@0 | 354 | : mStartOffset(aStartOffset) |
michael@0 | 355 | , mStartZoom(aStartZoom) |
michael@0 | 356 | , mEndOffset(aEndOffset) |
michael@0 | 357 | , mEndZoom(aEndZoom) |
michael@0 | 358 | {} |
michael@0 | 359 | |
michael@0 | 360 | virtual bool Sample(FrameMetrics& aFrameMetrics, |
michael@0 | 361 | const TimeDuration& aDelta); |
michael@0 | 362 | |
michael@0 | 363 | private: |
michael@0 | 364 | TimeDuration mDuration; |
michael@0 | 365 | |
michael@0 | 366 | // Old metrics from before we started a zoom animation. This is only valid |
michael@0 | 367 | // when we are in the "ANIMATED_ZOOM" state. This is used so that we can |
michael@0 | 368 | // interpolate between the start and end frames. We only use the |
michael@0 | 369 | // |mViewportScrollOffset| and |mResolution| fields on this. |
michael@0 | 370 | CSSPoint mStartOffset; |
michael@0 | 371 | CSSToScreenScale mStartZoom; |
michael@0 | 372 | |
michael@0 | 373 | // Target metrics for a zoom to animation. This is only valid when we are in |
michael@0 | 374 | // the "ANIMATED_ZOOM" state. We only use the |mViewportScrollOffset| and |
michael@0 | 375 | // |mResolution| fields on this. |
michael@0 | 376 | CSSPoint mEndOffset; |
michael@0 | 377 | CSSToScreenScale mEndZoom; |
michael@0 | 378 | }; |
michael@0 | 379 | |
michael@0 | 380 | void |
michael@0 | 381 | AsyncPanZoomController::SetFrameTime(const TimeStamp& aTime) { |
michael@0 | 382 | sFrameTime = aTime; |
michael@0 | 383 | } |
michael@0 | 384 | |
michael@0 | 385 | /*static*/ void |
michael@0 | 386 | AsyncPanZoomController::InitializeGlobalState() |
michael@0 | 387 | { |
michael@0 | 388 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 389 | |
michael@0 | 390 | static bool sInitialized = false; |
michael@0 | 391 | if (sInitialized) |
michael@0 | 392 | return; |
michael@0 | 393 | sInitialized = true; |
michael@0 | 394 | |
michael@0 | 395 | gComputedTimingFunction = new ComputedTimingFunction(); |
michael@0 | 396 | gComputedTimingFunction->Init( |
michael@0 | 397 | nsTimingFunction(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE)); |
michael@0 | 398 | ClearOnShutdown(&gComputedTimingFunction); |
michael@0 | 399 | } |
michael@0 | 400 | |
michael@0 | 401 | AsyncPanZoomController::AsyncPanZoomController(uint64_t aLayersId, |
michael@0 | 402 | APZCTreeManager* aTreeManager, |
michael@0 | 403 | GeckoContentController* aGeckoContentController, |
michael@0 | 404 | GestureBehavior aGestures) |
michael@0 | 405 | : mLayersId(aLayersId), |
michael@0 | 406 | mCrossProcessCompositorParent(nullptr), |
michael@0 | 407 | mPaintThrottler(GetFrameTime()), |
michael@0 | 408 | mGeckoContentController(aGeckoContentController), |
michael@0 | 409 | mRefPtrMonitor("RefPtrMonitor"), |
michael@0 | 410 | mMonitor("AsyncPanZoomController"), |
michael@0 | 411 | mTouchActionPropertyEnabled(gfxPrefs::TouchActionEnabled()), |
michael@0 | 412 | mContentResponseTimeoutTask(nullptr), |
michael@0 | 413 | mX(MOZ_THIS_IN_INITIALIZER_LIST()), |
michael@0 | 414 | mY(MOZ_THIS_IN_INITIALIZER_LIST()), |
michael@0 | 415 | mPanDirRestricted(false), |
michael@0 | 416 | mZoomConstraints(false, false, MIN_ZOOM, MAX_ZOOM), |
michael@0 | 417 | mLastSampleTime(GetFrameTime()), |
michael@0 | 418 | mState(NOTHING), |
michael@0 | 419 | mLastAsyncScrollTime(GetFrameTime()), |
michael@0 | 420 | mLastAsyncScrollOffset(0, 0), |
michael@0 | 421 | mCurrentAsyncScrollOffset(0, 0), |
michael@0 | 422 | mAsyncScrollTimeoutTask(nullptr), |
michael@0 | 423 | mHandlingTouchQueue(false), |
michael@0 | 424 | mTreeManager(aTreeManager), |
michael@0 | 425 | mScrollParentId(FrameMetrics::NULL_SCROLL_ID), |
michael@0 | 426 | mAPZCId(sAsyncPanZoomControllerCount++), |
michael@0 | 427 | mSharedFrameMetricsBuffer(nullptr), |
michael@0 | 428 | mSharedLock(nullptr) |
michael@0 | 429 | { |
michael@0 | 430 | MOZ_COUNT_CTOR(AsyncPanZoomController); |
michael@0 | 431 | |
michael@0 | 432 | if (aGestures == USE_GESTURE_DETECTOR) { |
michael@0 | 433 | mGestureEventListener = new GestureEventListener(this); |
michael@0 | 434 | } |
michael@0 | 435 | } |
michael@0 | 436 | |
michael@0 | 437 | AsyncPanZoomController::~AsyncPanZoomController() { |
michael@0 | 438 | |
michael@0 | 439 | PCompositorParent* compositor = |
michael@0 | 440 | (mCrossProcessCompositorParent ? mCrossProcessCompositorParent : mCompositorParent.get()); |
michael@0 | 441 | |
michael@0 | 442 | // Only send the release message if the SharedFrameMetrics has been created. |
michael@0 | 443 | if (compositor && mSharedFrameMetricsBuffer) { |
michael@0 | 444 | unused << compositor->SendReleaseSharedCompositorFrameMetrics(mFrameMetrics.GetScrollId(), mAPZCId); |
michael@0 | 445 | } |
michael@0 | 446 | |
michael@0 | 447 | delete mSharedFrameMetricsBuffer; |
michael@0 | 448 | delete mSharedLock; |
michael@0 | 449 | |
michael@0 | 450 | MOZ_COUNT_DTOR(AsyncPanZoomController); |
michael@0 | 451 | } |
michael@0 | 452 | |
michael@0 | 453 | already_AddRefed<GeckoContentController> |
michael@0 | 454 | AsyncPanZoomController::GetGeckoContentController() { |
michael@0 | 455 | MonitorAutoLock lock(mRefPtrMonitor); |
michael@0 | 456 | nsRefPtr<GeckoContentController> controller = mGeckoContentController; |
michael@0 | 457 | return controller.forget(); |
michael@0 | 458 | } |
michael@0 | 459 | |
michael@0 | 460 | already_AddRefed<GestureEventListener> |
michael@0 | 461 | AsyncPanZoomController::GetGestureEventListener() { |
michael@0 | 462 | MonitorAutoLock lock(mRefPtrMonitor); |
michael@0 | 463 | nsRefPtr<GestureEventListener> listener = mGestureEventListener; |
michael@0 | 464 | return listener.forget(); |
michael@0 | 465 | } |
michael@0 | 466 | |
michael@0 | 467 | void |
michael@0 | 468 | AsyncPanZoomController::Destroy() |
michael@0 | 469 | { |
michael@0 | 470 | { // scope the lock |
michael@0 | 471 | MonitorAutoLock lock(mRefPtrMonitor); |
michael@0 | 472 | mGeckoContentController = nullptr; |
michael@0 | 473 | mGestureEventListener = nullptr; |
michael@0 | 474 | } |
michael@0 | 475 | mPrevSibling = nullptr; |
michael@0 | 476 | mLastChild = nullptr; |
michael@0 | 477 | mParent = nullptr; |
michael@0 | 478 | mTreeManager = nullptr; |
michael@0 | 479 | } |
michael@0 | 480 | |
michael@0 | 481 | bool |
michael@0 | 482 | AsyncPanZoomController::IsDestroyed() |
michael@0 | 483 | { |
michael@0 | 484 | return mTreeManager == nullptr; |
michael@0 | 485 | } |
michael@0 | 486 | |
michael@0 | 487 | /* static */float |
michael@0 | 488 | AsyncPanZoomController::GetTouchStartTolerance() |
michael@0 | 489 | { |
michael@0 | 490 | return (gfxPrefs::APZTouchStartTolerance() * APZCTreeManager::GetDPI()); |
michael@0 | 491 | } |
michael@0 | 492 | |
michael@0 | 493 | /* static */AsyncPanZoomController::AxisLockMode AsyncPanZoomController::GetAxisLockMode() |
michael@0 | 494 | { |
michael@0 | 495 | return static_cast<AxisLockMode>(gfxPrefs::APZAxisLockMode()); |
michael@0 | 496 | } |
michael@0 | 497 | |
michael@0 | 498 | nsEventStatus AsyncPanZoomController::ReceiveInputEvent(const InputData& aEvent) { |
michael@0 | 499 | if (aEvent.mInputType == MULTITOUCH_INPUT && |
michael@0 | 500 | aEvent.AsMultiTouchInput().mType == MultiTouchInput::MULTITOUCH_START) { |
michael@0 | 501 | // Starting a new touch block, clear old touch block state. |
michael@0 | 502 | mTouchBlockState = TouchBlockState(); |
michael@0 | 503 | } |
michael@0 | 504 | |
michael@0 | 505 | // If we may have touch listeners and touch action property is enabled, we |
michael@0 | 506 | // enable the machinery that allows touch listeners to preventDefault any touch inputs |
michael@0 | 507 | // and also waits for the allowed touch behavior values to be received from the outside. |
michael@0 | 508 | // This should not happen unless there are actually touch listeners and touch-action property |
michael@0 | 509 | // enable as it introduces potentially unbounded lag because it causes a round-trip through |
michael@0 | 510 | // content. Usually, if content is responding in a timely fashion, this only introduces a |
michael@0 | 511 | // nearly constant few hundred ms of lag. |
michael@0 | 512 | if (mFrameMetrics.mMayHaveTouchListeners && aEvent.mInputType == MULTITOUCH_INPUT && |
michael@0 | 513 | (mState == NOTHING || mState == TOUCHING || IsPanningState(mState))) { |
michael@0 | 514 | const MultiTouchInput& multiTouchInput = aEvent.AsMultiTouchInput(); |
michael@0 | 515 | if (multiTouchInput.mType == MultiTouchInput::MULTITOUCH_START) { |
michael@0 | 516 | SetState(WAITING_CONTENT_RESPONSE); |
michael@0 | 517 | } |
michael@0 | 518 | } |
michael@0 | 519 | |
michael@0 | 520 | if (mState == WAITING_CONTENT_RESPONSE || mHandlingTouchQueue) { |
michael@0 | 521 | if (aEvent.mInputType == MULTITOUCH_INPUT) { |
michael@0 | 522 | const MultiTouchInput& multiTouchInput = aEvent.AsMultiTouchInput(); |
michael@0 | 523 | mTouchQueue.AppendElement(multiTouchInput); |
michael@0 | 524 | |
michael@0 | 525 | SetContentResponseTimer(); |
michael@0 | 526 | } |
michael@0 | 527 | return nsEventStatus_eIgnore; |
michael@0 | 528 | } |
michael@0 | 529 | |
michael@0 | 530 | return HandleInputEvent(aEvent); |
michael@0 | 531 | } |
michael@0 | 532 | |
michael@0 | 533 | nsEventStatus AsyncPanZoomController::HandleInputEvent(const InputData& aEvent) { |
michael@0 | 534 | nsEventStatus rv = nsEventStatus_eIgnore; |
michael@0 | 535 | |
michael@0 | 536 | switch (aEvent.mInputType) { |
michael@0 | 537 | case MULTITOUCH_INPUT: { |
michael@0 | 538 | const MultiTouchInput& multiTouchInput = aEvent.AsMultiTouchInput(); |
michael@0 | 539 | |
michael@0 | 540 | nsRefPtr<GestureEventListener> listener = GetGestureEventListener(); |
michael@0 | 541 | if (listener) { |
michael@0 | 542 | rv = listener->HandleInputEvent(multiTouchInput); |
michael@0 | 543 | if (rv == nsEventStatus_eConsumeNoDefault) { |
michael@0 | 544 | return rv; |
michael@0 | 545 | } |
michael@0 | 546 | } |
michael@0 | 547 | |
michael@0 | 548 | switch (multiTouchInput.mType) { |
michael@0 | 549 | case MultiTouchInput::MULTITOUCH_START: rv = OnTouchStart(multiTouchInput); break; |
michael@0 | 550 | case MultiTouchInput::MULTITOUCH_MOVE: rv = OnTouchMove(multiTouchInput); break; |
michael@0 | 551 | case MultiTouchInput::MULTITOUCH_END: rv = OnTouchEnd(multiTouchInput); break; |
michael@0 | 552 | case MultiTouchInput::MULTITOUCH_CANCEL: rv = OnTouchCancel(multiTouchInput); break; |
michael@0 | 553 | default: NS_WARNING("Unhandled multitouch"); break; |
michael@0 | 554 | } |
michael@0 | 555 | break; |
michael@0 | 556 | } |
michael@0 | 557 | default: NS_WARNING("Unhandled input event"); break; |
michael@0 | 558 | } |
michael@0 | 559 | |
michael@0 | 560 | mLastEventTime = aEvent.mTime; |
michael@0 | 561 | return rv; |
michael@0 | 562 | } |
michael@0 | 563 | |
michael@0 | 564 | nsEventStatus AsyncPanZoomController::HandleGestureEvent(const InputData& aEvent) |
michael@0 | 565 | { |
michael@0 | 566 | nsEventStatus rv = nsEventStatus_eIgnore; |
michael@0 | 567 | |
michael@0 | 568 | switch (aEvent.mInputType) { |
michael@0 | 569 | case PINCHGESTURE_INPUT: { |
michael@0 | 570 | const PinchGestureInput& pinchGestureInput = aEvent.AsPinchGestureInput(); |
michael@0 | 571 | switch (pinchGestureInput.mType) { |
michael@0 | 572 | case PinchGestureInput::PINCHGESTURE_START: rv = OnScaleBegin(pinchGestureInput); break; |
michael@0 | 573 | case PinchGestureInput::PINCHGESTURE_SCALE: rv = OnScale(pinchGestureInput); break; |
michael@0 | 574 | case PinchGestureInput::PINCHGESTURE_END: rv = OnScaleEnd(pinchGestureInput); break; |
michael@0 | 575 | default: NS_WARNING("Unhandled pinch gesture"); break; |
michael@0 | 576 | } |
michael@0 | 577 | break; |
michael@0 | 578 | } |
michael@0 | 579 | case TAPGESTURE_INPUT: { |
michael@0 | 580 | const TapGestureInput& tapGestureInput = aEvent.AsTapGestureInput(); |
michael@0 | 581 | switch (tapGestureInput.mType) { |
michael@0 | 582 | case TapGestureInput::TAPGESTURE_LONG: rv = OnLongPress(tapGestureInput); break; |
michael@0 | 583 | case TapGestureInput::TAPGESTURE_LONG_UP: rv = OnLongPressUp(tapGestureInput); break; |
michael@0 | 584 | case TapGestureInput::TAPGESTURE_UP: rv = OnSingleTapUp(tapGestureInput); break; |
michael@0 | 585 | case TapGestureInput::TAPGESTURE_CONFIRMED: rv = OnSingleTapConfirmed(tapGestureInput); break; |
michael@0 | 586 | case TapGestureInput::TAPGESTURE_DOUBLE: rv = OnDoubleTap(tapGestureInput); break; |
michael@0 | 587 | case TapGestureInput::TAPGESTURE_CANCEL: rv = OnCancelTap(tapGestureInput); break; |
michael@0 | 588 | default: NS_WARNING("Unhandled tap gesture"); break; |
michael@0 | 589 | } |
michael@0 | 590 | break; |
michael@0 | 591 | } |
michael@0 | 592 | default: NS_WARNING("Unhandled input event"); break; |
michael@0 | 593 | } |
michael@0 | 594 | |
michael@0 | 595 | mLastEventTime = aEvent.mTime; |
michael@0 | 596 | return rv; |
michael@0 | 597 | } |
michael@0 | 598 | |
michael@0 | 599 | nsEventStatus AsyncPanZoomController::OnTouchStart(const MultiTouchInput& aEvent) { |
michael@0 | 600 | APZC_LOG("%p got a touch-start in state %d\n", this, mState); |
michael@0 | 601 | mPanDirRestricted = false; |
michael@0 | 602 | ScreenIntPoint point = GetFirstTouchScreenPoint(aEvent); |
michael@0 | 603 | |
michael@0 | 604 | switch (mState) { |
michael@0 | 605 | case ANIMATING_ZOOM: |
michael@0 | 606 | // We just interrupted a double-tap animation, so force a redraw in case |
michael@0 | 607 | // this touchstart is just a tap that doesn't end up triggering a redraw. |
michael@0 | 608 | { |
michael@0 | 609 | ReentrantMonitorAutoEnter lock(mMonitor); |
michael@0 | 610 | RequestContentRepaint(); |
michael@0 | 611 | ScheduleComposite(); |
michael@0 | 612 | UpdateSharedCompositorFrameMetrics(); |
michael@0 | 613 | } |
michael@0 | 614 | // Fall through. |
michael@0 | 615 | case FLING: |
michael@0 | 616 | CancelAnimation(); |
michael@0 | 617 | // Fall through. |
michael@0 | 618 | case NOTHING: { |
michael@0 | 619 | mX.StartTouch(point.x); |
michael@0 | 620 | mY.StartTouch(point.y); |
michael@0 | 621 | APZCTreeManager* treeManagerLocal = mTreeManager; |
michael@0 | 622 | nsRefPtr<GeckoContentController> controller = GetGeckoContentController(); |
michael@0 | 623 | if (treeManagerLocal && controller) { |
michael@0 | 624 | bool touchCanBePan = treeManagerLocal->CanBePanned(this); |
michael@0 | 625 | controller->NotifyAPZStateChange( |
michael@0 | 626 | GetGuid(), APZStateChange::StartTouch, touchCanBePan); |
michael@0 | 627 | } |
michael@0 | 628 | SetState(TOUCHING); |
michael@0 | 629 | break; |
michael@0 | 630 | } |
michael@0 | 631 | case TOUCHING: |
michael@0 | 632 | case PANNING: |
michael@0 | 633 | case PANNING_LOCKED_X: |
michael@0 | 634 | case PANNING_LOCKED_Y: |
michael@0 | 635 | case CROSS_SLIDING_X: |
michael@0 | 636 | case CROSS_SLIDING_Y: |
michael@0 | 637 | case PINCHING: |
michael@0 | 638 | case WAITING_CONTENT_RESPONSE: |
michael@0 | 639 | NS_WARNING("Received impossible touch in OnTouchStart"); |
michael@0 | 640 | break; |
michael@0 | 641 | default: |
michael@0 | 642 | NS_WARNING("Unhandled case in OnTouchStart"); |
michael@0 | 643 | break; |
michael@0 | 644 | } |
michael@0 | 645 | |
michael@0 | 646 | return nsEventStatus_eConsumeNoDefault; |
michael@0 | 647 | } |
michael@0 | 648 | |
michael@0 | 649 | nsEventStatus AsyncPanZoomController::OnTouchMove(const MultiTouchInput& aEvent) { |
michael@0 | 650 | APZC_LOG("%p got a touch-move in state %d\n", this, mState); |
michael@0 | 651 | switch (mState) { |
michael@0 | 652 | case FLING: |
michael@0 | 653 | case NOTHING: |
michael@0 | 654 | case ANIMATING_ZOOM: |
michael@0 | 655 | // May happen if the user double-taps and drags without lifting after the |
michael@0 | 656 | // second tap. Ignore the move if this happens. |
michael@0 | 657 | return nsEventStatus_eIgnore; |
michael@0 | 658 | |
michael@0 | 659 | case CROSS_SLIDING_X: |
michael@0 | 660 | case CROSS_SLIDING_Y: |
michael@0 | 661 | // While cross-sliding, we don't want to consume any touchmove events for |
michael@0 | 662 | // panning or zooming, and let the caller handle them instead. |
michael@0 | 663 | return nsEventStatus_eIgnore; |
michael@0 | 664 | |
michael@0 | 665 | case TOUCHING: { |
michael@0 | 666 | float panThreshold = GetTouchStartTolerance(); |
michael@0 | 667 | UpdateWithTouchAtDevicePoint(aEvent); |
michael@0 | 668 | |
michael@0 | 669 | if (PanDistance() < panThreshold) { |
michael@0 | 670 | return nsEventStatus_eIgnore; |
michael@0 | 671 | } |
michael@0 | 672 | |
michael@0 | 673 | if (mTouchActionPropertyEnabled && |
michael@0 | 674 | (GetTouchBehavior(0) & AllowedTouchBehavior::VERTICAL_PAN) && |
michael@0 | 675 | (GetTouchBehavior(0) & AllowedTouchBehavior::HORIZONTAL_PAN)) { |
michael@0 | 676 | // User tries to trigger a touch behavior. If allowed touch behavior is vertical pan |
michael@0 | 677 | // + horizontal pan (touch-action value is equal to AUTO) we can return ConsumeNoDefault |
michael@0 | 678 | // status immediately to trigger cancel event further. It should happen independent of |
michael@0 | 679 | // the parent type (whether it is scrolling or not). |
michael@0 | 680 | StartPanning(aEvent); |
michael@0 | 681 | return nsEventStatus_eConsumeNoDefault; |
michael@0 | 682 | } |
michael@0 | 683 | |
michael@0 | 684 | return StartPanning(aEvent); |
michael@0 | 685 | } |
michael@0 | 686 | |
michael@0 | 687 | case PANNING: |
michael@0 | 688 | case PANNING_LOCKED_X: |
michael@0 | 689 | case PANNING_LOCKED_Y: |
michael@0 | 690 | TrackTouch(aEvent); |
michael@0 | 691 | return nsEventStatus_eConsumeNoDefault; |
michael@0 | 692 | |
michael@0 | 693 | case PINCHING: |
michael@0 | 694 | // The scale gesture listener should have handled this. |
michael@0 | 695 | NS_WARNING("Gesture listener should have handled pinching in OnTouchMove."); |
michael@0 | 696 | return nsEventStatus_eIgnore; |
michael@0 | 697 | |
michael@0 | 698 | case WAITING_CONTENT_RESPONSE: |
michael@0 | 699 | NS_WARNING("Received impossible touch in OnTouchMove"); |
michael@0 | 700 | break; |
michael@0 | 701 | } |
michael@0 | 702 | |
michael@0 | 703 | return nsEventStatus_eConsumeNoDefault; |
michael@0 | 704 | } |
michael@0 | 705 | |
michael@0 | 706 | nsEventStatus AsyncPanZoomController::OnTouchEnd(const MultiTouchInput& aEvent) { |
michael@0 | 707 | APZC_LOG("%p got a touch-end in state %d\n", this, mState); |
michael@0 | 708 | |
michael@0 | 709 | OnTouchEndOrCancel(); |
michael@0 | 710 | |
michael@0 | 711 | // In case no touch behavior triggered previously we can avoid sending |
michael@0 | 712 | // scroll events or requesting content repaint. This condition is added |
michael@0 | 713 | // to make tests consistent - in case touch-action is NONE (and therefore |
michael@0 | 714 | // no pans/zooms can be performed) we expected neither scroll or repaint |
michael@0 | 715 | // events. |
michael@0 | 716 | if (mState != NOTHING) { |
michael@0 | 717 | ReentrantMonitorAutoEnter lock(mMonitor); |
michael@0 | 718 | SendAsyncScrollEvent(); |
michael@0 | 719 | } |
michael@0 | 720 | |
michael@0 | 721 | switch (mState) { |
michael@0 | 722 | case FLING: |
michael@0 | 723 | // Should never happen. |
michael@0 | 724 | NS_WARNING("Received impossible touch end in OnTouchEnd."); |
michael@0 | 725 | // Fall through. |
michael@0 | 726 | case ANIMATING_ZOOM: |
michael@0 | 727 | case NOTHING: |
michael@0 | 728 | // May happen if the user double-taps and drags without lifting after the |
michael@0 | 729 | // second tap. Ignore if this happens. |
michael@0 | 730 | return nsEventStatus_eIgnore; |
michael@0 | 731 | |
michael@0 | 732 | case TOUCHING: |
michael@0 | 733 | case CROSS_SLIDING_X: |
michael@0 | 734 | case CROSS_SLIDING_Y: |
michael@0 | 735 | SetState(NOTHING); |
michael@0 | 736 | return nsEventStatus_eIgnore; |
michael@0 | 737 | |
michael@0 | 738 | case PANNING: |
michael@0 | 739 | case PANNING_LOCKED_X: |
michael@0 | 740 | case PANNING_LOCKED_Y: |
michael@0 | 741 | { |
michael@0 | 742 | // Make a local copy of the tree manager pointer and check if it's not |
michael@0 | 743 | // null before calling FlushRepaintsForOverscrollHandoffChain(). |
michael@0 | 744 | // This is necessary because Destroy(), which nulls out mTreeManager, |
michael@0 | 745 | // could be called concurrently. |
michael@0 | 746 | APZCTreeManager* treeManagerLocal = mTreeManager; |
michael@0 | 747 | if (treeManagerLocal) { |
michael@0 | 748 | if (!treeManagerLocal->FlushRepaintsForOverscrollHandoffChain()) { |
michael@0 | 749 | NS_WARNING("Overscroll handoff chain was empty during panning! This should not be the case."); |
michael@0 | 750 | // Graceful handling of error condition |
michael@0 | 751 | FlushRepaintForOverscrollHandoff(); |
michael@0 | 752 | } |
michael@0 | 753 | } |
michael@0 | 754 | } |
michael@0 | 755 | mX.EndTouch(); |
michael@0 | 756 | mY.EndTouch(); |
michael@0 | 757 | SetState(FLING); |
michael@0 | 758 | StartAnimation(new FlingAnimation(*this)); |
michael@0 | 759 | return nsEventStatus_eConsumeNoDefault; |
michael@0 | 760 | |
michael@0 | 761 | case PINCHING: |
michael@0 | 762 | SetState(NOTHING); |
michael@0 | 763 | // Scale gesture listener should have handled this. |
michael@0 | 764 | NS_WARNING("Gesture listener should have handled pinching in OnTouchEnd."); |
michael@0 | 765 | return nsEventStatus_eIgnore; |
michael@0 | 766 | |
michael@0 | 767 | case WAITING_CONTENT_RESPONSE: |
michael@0 | 768 | NS_WARNING("Received impossible touch in OnTouchEnd"); |
michael@0 | 769 | break; |
michael@0 | 770 | } |
michael@0 | 771 | |
michael@0 | 772 | return nsEventStatus_eConsumeNoDefault; |
michael@0 | 773 | } |
michael@0 | 774 | |
michael@0 | 775 | nsEventStatus AsyncPanZoomController::OnTouchCancel(const MultiTouchInput& aEvent) { |
michael@0 | 776 | APZC_LOG("%p got a touch-cancel in state %d\n", this, mState); |
michael@0 | 777 | OnTouchEndOrCancel(); |
michael@0 | 778 | SetState(NOTHING); |
michael@0 | 779 | return nsEventStatus_eConsumeNoDefault; |
michael@0 | 780 | } |
michael@0 | 781 | |
michael@0 | 782 | nsEventStatus AsyncPanZoomController::OnScaleBegin(const PinchGestureInput& aEvent) { |
michael@0 | 783 | APZC_LOG("%p got a scale-begin in state %d\n", this, mState); |
michael@0 | 784 | |
michael@0 | 785 | if (!TouchActionAllowPinchZoom()) { |
michael@0 | 786 | return nsEventStatus_eIgnore; |
michael@0 | 787 | } |
michael@0 | 788 | |
michael@0 | 789 | if (!mZoomConstraints.mAllowZoom) { |
michael@0 | 790 | return nsEventStatus_eConsumeNoDefault; |
michael@0 | 791 | } |
michael@0 | 792 | |
michael@0 | 793 | SetState(PINCHING); |
michael@0 | 794 | mLastZoomFocus = ToParentLayerCoords(aEvent.mFocusPoint) - mFrameMetrics.mCompositionBounds.TopLeft(); |
michael@0 | 795 | |
michael@0 | 796 | return nsEventStatus_eConsumeNoDefault; |
michael@0 | 797 | } |
michael@0 | 798 | |
michael@0 | 799 | nsEventStatus AsyncPanZoomController::OnScale(const PinchGestureInput& aEvent) { |
michael@0 | 800 | APZC_LOG("%p got a scale in state %d\n", this, mState); |
michael@0 | 801 | if (mState != PINCHING) { |
michael@0 | 802 | return nsEventStatus_eConsumeNoDefault; |
michael@0 | 803 | } |
michael@0 | 804 | |
michael@0 | 805 | float prevSpan = aEvent.mPreviousSpan; |
michael@0 | 806 | if (fabsf(prevSpan) <= EPSILON || fabsf(aEvent.mCurrentSpan) <= EPSILON) { |
michael@0 | 807 | // We're still handling it; we've just decided to throw this event away. |
michael@0 | 808 | return nsEventStatus_eConsumeNoDefault; |
michael@0 | 809 | } |
michael@0 | 810 | |
michael@0 | 811 | float spanRatio = aEvent.mCurrentSpan / aEvent.mPreviousSpan; |
michael@0 | 812 | |
michael@0 | 813 | { |
michael@0 | 814 | ReentrantMonitorAutoEnter lock(mMonitor); |
michael@0 | 815 | |
michael@0 | 816 | CSSToParentLayerScale userZoom = mFrameMetrics.GetZoomToParent(); |
michael@0 | 817 | ParentLayerPoint focusPoint = ToParentLayerCoords(aEvent.mFocusPoint) - mFrameMetrics.mCompositionBounds.TopLeft(); |
michael@0 | 818 | CSSPoint cssFocusPoint = focusPoint / userZoom; |
michael@0 | 819 | |
michael@0 | 820 | CSSPoint focusChange = (mLastZoomFocus - focusPoint) / userZoom; |
michael@0 | 821 | // If displacing by the change in focus point will take us off page bounds, |
michael@0 | 822 | // then reduce the displacement such that it doesn't. |
michael@0 | 823 | if (mX.DisplacementWillOverscroll(focusChange.x) != Axis::OVERSCROLL_NONE) { |
michael@0 | 824 | focusChange.x -= mX.DisplacementWillOverscrollAmount(focusChange.x); |
michael@0 | 825 | } |
michael@0 | 826 | if (mY.DisplacementWillOverscroll(focusChange.y) != Axis::OVERSCROLL_NONE) { |
michael@0 | 827 | focusChange.y -= mY.DisplacementWillOverscrollAmount(focusChange.y); |
michael@0 | 828 | } |
michael@0 | 829 | ScrollBy(focusChange); |
michael@0 | 830 | |
michael@0 | 831 | // When we zoom in with focus, we can zoom too much towards the boundaries |
michael@0 | 832 | // that we actually go over them. These are the needed displacements along |
michael@0 | 833 | // either axis such that we don't overscroll the boundaries when zooming. |
michael@0 | 834 | CSSPoint neededDisplacement; |
michael@0 | 835 | |
michael@0 | 836 | CSSToParentLayerScale realMinZoom = mZoomConstraints.mMinZoom * mFrameMetrics.mTransformScale; |
michael@0 | 837 | CSSToParentLayerScale realMaxZoom = mZoomConstraints.mMaxZoom * mFrameMetrics.mTransformScale; |
michael@0 | 838 | realMinZoom.scale = std::max(realMinZoom.scale, |
michael@0 | 839 | mFrameMetrics.mCompositionBounds.width / mFrameMetrics.mScrollableRect.width); |
michael@0 | 840 | realMinZoom.scale = std::max(realMinZoom.scale, |
michael@0 | 841 | mFrameMetrics.mCompositionBounds.height / mFrameMetrics.mScrollableRect.height); |
michael@0 | 842 | if (realMaxZoom < realMinZoom) { |
michael@0 | 843 | realMaxZoom = realMinZoom; |
michael@0 | 844 | } |
michael@0 | 845 | |
michael@0 | 846 | bool doScale = (spanRatio > 1.0 && userZoom < realMaxZoom) || |
michael@0 | 847 | (spanRatio < 1.0 && userZoom > realMinZoom); |
michael@0 | 848 | |
michael@0 | 849 | if (doScale) { |
michael@0 | 850 | spanRatio = clamped(spanRatio, |
michael@0 | 851 | realMinZoom.scale / userZoom.scale, |
michael@0 | 852 | realMaxZoom.scale / userZoom.scale); |
michael@0 | 853 | |
michael@0 | 854 | // Note that the spanRatio here should never put us into OVERSCROLL_BOTH because |
michael@0 | 855 | // up above we clamped it. |
michael@0 | 856 | neededDisplacement.x = -mX.ScaleWillOverscrollAmount(spanRatio, cssFocusPoint.x); |
michael@0 | 857 | neededDisplacement.y = -mY.ScaleWillOverscrollAmount(spanRatio, cssFocusPoint.y); |
michael@0 | 858 | |
michael@0 | 859 | ScaleWithFocus(spanRatio, cssFocusPoint); |
michael@0 | 860 | |
michael@0 | 861 | if (neededDisplacement != CSSPoint()) { |
michael@0 | 862 | ScrollBy(neededDisplacement); |
michael@0 | 863 | } |
michael@0 | 864 | |
michael@0 | 865 | ScheduleComposite(); |
michael@0 | 866 | // We don't want to redraw on every scale, so don't use |
michael@0 | 867 | // RequestContentRepaint() |
michael@0 | 868 | UpdateSharedCompositorFrameMetrics(); |
michael@0 | 869 | } |
michael@0 | 870 | |
michael@0 | 871 | mLastZoomFocus = focusPoint; |
michael@0 | 872 | } |
michael@0 | 873 | |
michael@0 | 874 | return nsEventStatus_eConsumeNoDefault; |
michael@0 | 875 | } |
michael@0 | 876 | |
michael@0 | 877 | nsEventStatus AsyncPanZoomController::OnScaleEnd(const PinchGestureInput& aEvent) { |
michael@0 | 878 | APZC_LOG("%p got a scale-end in state %d\n", this, mState); |
michael@0 | 879 | |
michael@0 | 880 | SetState(NOTHING); |
michael@0 | 881 | |
michael@0 | 882 | { |
michael@0 | 883 | ReentrantMonitorAutoEnter lock(mMonitor); |
michael@0 | 884 | ScheduleComposite(); |
michael@0 | 885 | RequestContentRepaint(); |
michael@0 | 886 | UpdateSharedCompositorFrameMetrics(); |
michael@0 | 887 | } |
michael@0 | 888 | |
michael@0 | 889 | return nsEventStatus_eConsumeNoDefault; |
michael@0 | 890 | } |
michael@0 | 891 | |
michael@0 | 892 | bool |
michael@0 | 893 | AsyncPanZoomController::ConvertToGecko(const ScreenPoint& aPoint, CSSPoint* aOut) |
michael@0 | 894 | { |
michael@0 | 895 | APZCTreeManager* treeManagerLocal = mTreeManager; |
michael@0 | 896 | if (treeManagerLocal) { |
michael@0 | 897 | gfx3DMatrix transformToApzc; |
michael@0 | 898 | gfx3DMatrix transformToGecko; |
michael@0 | 899 | treeManagerLocal->GetInputTransforms(this, transformToApzc, transformToGecko); |
michael@0 | 900 | gfxPoint result = transformToGecko.Transform(gfxPoint(aPoint.x, aPoint.y)); |
michael@0 | 901 | // NOTE: This isn't *quite* LayoutDevicePoint, we just don't have a name |
michael@0 | 902 | // for this coordinate space and it maps the closest to LayoutDevicePoint. |
michael@0 | 903 | LayoutDevicePoint layoutPoint = LayoutDevicePoint(result.x, result.y); |
michael@0 | 904 | { // scoped lock to access mFrameMetrics |
michael@0 | 905 | ReentrantMonitorAutoEnter lock(mMonitor); |
michael@0 | 906 | *aOut = layoutPoint / mFrameMetrics.mDevPixelsPerCSSPixel; |
michael@0 | 907 | } |
michael@0 | 908 | return true; |
michael@0 | 909 | } |
michael@0 | 910 | return false; |
michael@0 | 911 | } |
michael@0 | 912 | |
michael@0 | 913 | nsEventStatus AsyncPanZoomController::OnLongPress(const TapGestureInput& aEvent) { |
michael@0 | 914 | APZC_LOG("%p got a long-press in state %d\n", this, mState); |
michael@0 | 915 | nsRefPtr<GeckoContentController> controller = GetGeckoContentController(); |
michael@0 | 916 | if (controller) { |
michael@0 | 917 | int32_t modifiers = WidgetModifiersToDOMModifiers(aEvent.modifiers); |
michael@0 | 918 | CSSPoint geckoScreenPoint; |
michael@0 | 919 | if (ConvertToGecko(aEvent.mPoint, &geckoScreenPoint)) { |
michael@0 | 920 | SetState(WAITING_CONTENT_RESPONSE); |
michael@0 | 921 | SetContentResponseTimer(); |
michael@0 | 922 | controller->HandleLongTap(geckoScreenPoint, modifiers, GetGuid()); |
michael@0 | 923 | return nsEventStatus_eConsumeNoDefault; |
michael@0 | 924 | } |
michael@0 | 925 | } |
michael@0 | 926 | return nsEventStatus_eIgnore; |
michael@0 | 927 | } |
michael@0 | 928 | |
michael@0 | 929 | nsEventStatus AsyncPanZoomController::OnLongPressUp(const TapGestureInput& aEvent) { |
michael@0 | 930 | APZC_LOG("%p got a long-tap-up in state %d\n", this, mState); |
michael@0 | 931 | nsRefPtr<GeckoContentController> controller = GetGeckoContentController(); |
michael@0 | 932 | if (controller) { |
michael@0 | 933 | int32_t modifiers = WidgetModifiersToDOMModifiers(aEvent.modifiers); |
michael@0 | 934 | CSSPoint geckoScreenPoint; |
michael@0 | 935 | if (ConvertToGecko(aEvent.mPoint, &geckoScreenPoint)) { |
michael@0 | 936 | controller->HandleLongTapUp(geckoScreenPoint, modifiers, GetGuid()); |
michael@0 | 937 | return nsEventStatus_eConsumeNoDefault; |
michael@0 | 938 | } |
michael@0 | 939 | } |
michael@0 | 940 | return nsEventStatus_eIgnore; |
michael@0 | 941 | } |
michael@0 | 942 | |
michael@0 | 943 | nsEventStatus AsyncPanZoomController::GenerateSingleTap(const ScreenIntPoint& aPoint, mozilla::Modifiers aModifiers) { |
michael@0 | 944 | nsRefPtr<GeckoContentController> controller = GetGeckoContentController(); |
michael@0 | 945 | if (controller) { |
michael@0 | 946 | CSSPoint geckoScreenPoint; |
michael@0 | 947 | if (ConvertToGecko(aPoint, &geckoScreenPoint)) { |
michael@0 | 948 | int32_t modifiers = WidgetModifiersToDOMModifiers(aModifiers); |
michael@0 | 949 | // Because this may be being running as part of APZCTreeManager::ReceiveInputEvent, |
michael@0 | 950 | // calling controller->HandleSingleTap directly might mean that content receives |
michael@0 | 951 | // the single tap message before the corresponding touch-up. To avoid that we |
michael@0 | 952 | // schedule the singletap message to run on the next spin of the event loop. |
michael@0 | 953 | // See bug 965381 for the issue this was causing. |
michael@0 | 954 | controller->PostDelayedTask( |
michael@0 | 955 | NewRunnableMethod(controller.get(), &GeckoContentController::HandleSingleTap, |
michael@0 | 956 | geckoScreenPoint, modifiers, GetGuid()), |
michael@0 | 957 | 0); |
michael@0 | 958 | mTouchBlockState.mSingleTapOccurred = true; |
michael@0 | 959 | return nsEventStatus_eConsumeNoDefault; |
michael@0 | 960 | } |
michael@0 | 961 | } |
michael@0 | 962 | return nsEventStatus_eIgnore; |
michael@0 | 963 | } |
michael@0 | 964 | |
michael@0 | 965 | void AsyncPanZoomController::OnTouchEndOrCancel() { |
michael@0 | 966 | if (nsRefPtr<GeckoContentController> controller = GetGeckoContentController()) { |
michael@0 | 967 | controller->NotifyAPZStateChange( |
michael@0 | 968 | GetGuid(), APZStateChange::EndTouch, mTouchBlockState.mSingleTapOccurred); |
michael@0 | 969 | } |
michael@0 | 970 | } |
michael@0 | 971 | |
michael@0 | 972 | nsEventStatus AsyncPanZoomController::OnSingleTapUp(const TapGestureInput& aEvent) { |
michael@0 | 973 | APZC_LOG("%p got a single-tap-up in state %d\n", this, mState); |
michael@0 | 974 | // If mZoomConstraints.mAllowDoubleTapZoom is true we wait for a call to OnSingleTapConfirmed before |
michael@0 | 975 | // sending event to content |
michael@0 | 976 | if (!(mZoomConstraints.mAllowDoubleTapZoom && TouchActionAllowDoubleTapZoom())) { |
michael@0 | 977 | return GenerateSingleTap(aEvent.mPoint, aEvent.modifiers); |
michael@0 | 978 | } |
michael@0 | 979 | return nsEventStatus_eIgnore; |
michael@0 | 980 | } |
michael@0 | 981 | |
michael@0 | 982 | nsEventStatus AsyncPanZoomController::OnSingleTapConfirmed(const TapGestureInput& aEvent) { |
michael@0 | 983 | APZC_LOG("%p got a single-tap-confirmed in state %d\n", this, mState); |
michael@0 | 984 | return GenerateSingleTap(aEvent.mPoint, aEvent.modifiers); |
michael@0 | 985 | } |
michael@0 | 986 | |
michael@0 | 987 | nsEventStatus AsyncPanZoomController::OnDoubleTap(const TapGestureInput& aEvent) { |
michael@0 | 988 | APZC_LOG("%p got a double-tap in state %d\n", this, mState); |
michael@0 | 989 | nsRefPtr<GeckoContentController> controller = GetGeckoContentController(); |
michael@0 | 990 | if (controller) { |
michael@0 | 991 | if (mZoomConstraints.mAllowDoubleTapZoom && TouchActionAllowDoubleTapZoom()) { |
michael@0 | 992 | int32_t modifiers = WidgetModifiersToDOMModifiers(aEvent.modifiers); |
michael@0 | 993 | CSSPoint geckoScreenPoint; |
michael@0 | 994 | if (ConvertToGecko(aEvent.mPoint, &geckoScreenPoint)) { |
michael@0 | 995 | controller->HandleDoubleTap(geckoScreenPoint, modifiers, GetGuid()); |
michael@0 | 996 | } |
michael@0 | 997 | } |
michael@0 | 998 | return nsEventStatus_eConsumeNoDefault; |
michael@0 | 999 | } |
michael@0 | 1000 | return nsEventStatus_eIgnore; |
michael@0 | 1001 | } |
michael@0 | 1002 | |
michael@0 | 1003 | nsEventStatus AsyncPanZoomController::OnCancelTap(const TapGestureInput& aEvent) { |
michael@0 | 1004 | APZC_LOG("%p got a cancel-tap in state %d\n", this, mState); |
michael@0 | 1005 | // XXX: Implement this. |
michael@0 | 1006 | return nsEventStatus_eIgnore; |
michael@0 | 1007 | } |
michael@0 | 1008 | |
michael@0 | 1009 | float AsyncPanZoomController::PanDistance() { |
michael@0 | 1010 | ReentrantMonitorAutoEnter lock(mMonitor); |
michael@0 | 1011 | return NS_hypot(mX.PanDistance(), mY.PanDistance()); |
michael@0 | 1012 | } |
michael@0 | 1013 | |
michael@0 | 1014 | const ScreenPoint AsyncPanZoomController::GetVelocityVector() { |
michael@0 | 1015 | return ScreenPoint(mX.GetVelocity(), mY.GetVelocity()); |
michael@0 | 1016 | } |
michael@0 | 1017 | |
michael@0 | 1018 | void AsyncPanZoomController::HandlePanningWithTouchAction(double aAngle, TouchBehaviorFlags aBehavior) { |
michael@0 | 1019 | // Handling of cross sliding will need to be added in this method after touch-action released |
michael@0 | 1020 | // enabled by default. |
michael@0 | 1021 | if ((aBehavior & AllowedTouchBehavior::VERTICAL_PAN) && (aBehavior & AllowedTouchBehavior::HORIZONTAL_PAN)) { |
michael@0 | 1022 | if (mX.Scrollable() && mY.Scrollable()) { |
michael@0 | 1023 | if (IsCloseToHorizontal(aAngle, AXIS_LOCK_ANGLE)) { |
michael@0 | 1024 | mY.SetAxisLocked(true); |
michael@0 | 1025 | SetState(PANNING_LOCKED_X); |
michael@0 | 1026 | } else if (IsCloseToVertical(aAngle, AXIS_LOCK_ANGLE)) { |
michael@0 | 1027 | mX.SetAxisLocked(true); |
michael@0 | 1028 | SetState(PANNING_LOCKED_Y); |
michael@0 | 1029 | } else { |
michael@0 | 1030 | SetState(PANNING); |
michael@0 | 1031 | } |
michael@0 | 1032 | } else if (mX.Scrollable() || mY.Scrollable()) { |
michael@0 | 1033 | SetState(PANNING); |
michael@0 | 1034 | } else { |
michael@0 | 1035 | SetState(NOTHING); |
michael@0 | 1036 | } |
michael@0 | 1037 | } else if (aBehavior & AllowedTouchBehavior::HORIZONTAL_PAN) { |
michael@0 | 1038 | // Using bigger angle for panning to keep behavior consistent |
michael@0 | 1039 | // with IE. |
michael@0 | 1040 | if (IsCloseToHorizontal(aAngle, ALLOWED_DIRECT_PAN_ANGLE)) { |
michael@0 | 1041 | mY.SetAxisLocked(true); |
michael@0 | 1042 | SetState(PANNING_LOCKED_X); |
michael@0 | 1043 | mPanDirRestricted = true; |
michael@0 | 1044 | } else { |
michael@0 | 1045 | // Don't treat these touches as pan/zoom movements since 'touch-action' value |
michael@0 | 1046 | // requires it. |
michael@0 | 1047 | SetState(NOTHING); |
michael@0 | 1048 | } |
michael@0 | 1049 | } else if (aBehavior & AllowedTouchBehavior::VERTICAL_PAN) { |
michael@0 | 1050 | if (IsCloseToVertical(aAngle, ALLOWED_DIRECT_PAN_ANGLE)) { |
michael@0 | 1051 | mX.SetAxisLocked(true); |
michael@0 | 1052 | SetState(PANNING_LOCKED_Y); |
michael@0 | 1053 | mPanDirRestricted = true; |
michael@0 | 1054 | } else { |
michael@0 | 1055 | SetState(NOTHING); |
michael@0 | 1056 | } |
michael@0 | 1057 | } else { |
michael@0 | 1058 | SetState(NOTHING); |
michael@0 | 1059 | } |
michael@0 | 1060 | } |
michael@0 | 1061 | |
michael@0 | 1062 | void AsyncPanZoomController::HandlePanning(double aAngle) { |
michael@0 | 1063 | if (!gfxPrefs::APZCrossSlideEnabled() && (!mX.Scrollable() || !mY.Scrollable())) { |
michael@0 | 1064 | SetState(PANNING); |
michael@0 | 1065 | } else if (IsCloseToHorizontal(aAngle, AXIS_LOCK_ANGLE)) { |
michael@0 | 1066 | mY.SetAxisLocked(true); |
michael@0 | 1067 | if (mX.Scrollable()) { |
michael@0 | 1068 | SetState(PANNING_LOCKED_X); |
michael@0 | 1069 | } else { |
michael@0 | 1070 | SetState(CROSS_SLIDING_X); |
michael@0 | 1071 | mX.SetAxisLocked(true); |
michael@0 | 1072 | } |
michael@0 | 1073 | } else if (IsCloseToVertical(aAngle, AXIS_LOCK_ANGLE)) { |
michael@0 | 1074 | mX.SetAxisLocked(true); |
michael@0 | 1075 | if (mY.Scrollable()) { |
michael@0 | 1076 | SetState(PANNING_LOCKED_Y); |
michael@0 | 1077 | } else { |
michael@0 | 1078 | SetState(CROSS_SLIDING_Y); |
michael@0 | 1079 | mY.SetAxisLocked(true); |
michael@0 | 1080 | } |
michael@0 | 1081 | } else { |
michael@0 | 1082 | SetState(PANNING); |
michael@0 | 1083 | } |
michael@0 | 1084 | } |
michael@0 | 1085 | |
michael@0 | 1086 | nsEventStatus AsyncPanZoomController::StartPanning(const MultiTouchInput& aEvent) { |
michael@0 | 1087 | ReentrantMonitorAutoEnter lock(mMonitor); |
michael@0 | 1088 | |
michael@0 | 1089 | ScreenIntPoint point = GetFirstTouchScreenPoint(aEvent); |
michael@0 | 1090 | float dx = mX.PanDistance(point.x); |
michael@0 | 1091 | float dy = mY.PanDistance(point.y); |
michael@0 | 1092 | |
michael@0 | 1093 | // When the touch move breaks through the pan threshold, reposition the touch down origin |
michael@0 | 1094 | // so the page won't jump when we start panning. |
michael@0 | 1095 | mX.StartTouch(point.x); |
michael@0 | 1096 | mY.StartTouch(point.y); |
michael@0 | 1097 | mLastEventTime = aEvent.mTime; |
michael@0 | 1098 | |
michael@0 | 1099 | double angle = atan2(dy, dx); // range [-pi, pi] |
michael@0 | 1100 | angle = fabs(angle); // range [0, pi] |
michael@0 | 1101 | |
michael@0 | 1102 | if (mTouchActionPropertyEnabled) { |
michael@0 | 1103 | HandlePanningWithTouchAction(angle, GetTouchBehavior(0)); |
michael@0 | 1104 | } else { |
michael@0 | 1105 | if (GetAxisLockMode() == FREE) { |
michael@0 | 1106 | SetState(PANNING); |
michael@0 | 1107 | } else { |
michael@0 | 1108 | HandlePanning(angle); |
michael@0 | 1109 | } |
michael@0 | 1110 | } |
michael@0 | 1111 | |
michael@0 | 1112 | if (IsPanningState(mState)) { |
michael@0 | 1113 | if (nsRefPtr<GeckoContentController> controller = GetGeckoContentController()) { |
michael@0 | 1114 | controller->NotifyAPZStateChange(GetGuid(), APZStateChange::StartPanning); |
michael@0 | 1115 | } |
michael@0 | 1116 | return nsEventStatus_eConsumeNoDefault; |
michael@0 | 1117 | } |
michael@0 | 1118 | // Don't consume an event that didn't trigger a panning. |
michael@0 | 1119 | return nsEventStatus_eIgnore; |
michael@0 | 1120 | } |
michael@0 | 1121 | |
michael@0 | 1122 | void AsyncPanZoomController::UpdateWithTouchAtDevicePoint(const MultiTouchInput& aEvent) { |
michael@0 | 1123 | ScreenIntPoint point = GetFirstTouchScreenPoint(aEvent); |
michael@0 | 1124 | TimeDuration timeDelta = TimeDuration().FromMilliseconds(aEvent.mTime - mLastEventTime); |
michael@0 | 1125 | |
michael@0 | 1126 | // Probably a duplicate event, just throw it away. |
michael@0 | 1127 | if (timeDelta.ToMilliseconds() <= EPSILON) { |
michael@0 | 1128 | return; |
michael@0 | 1129 | } |
michael@0 | 1130 | |
michael@0 | 1131 | mX.UpdateWithTouchAtDevicePoint(point.x, timeDelta); |
michael@0 | 1132 | mY.UpdateWithTouchAtDevicePoint(point.y, timeDelta); |
michael@0 | 1133 | } |
michael@0 | 1134 | |
michael@0 | 1135 | void AsyncPanZoomController::AttemptScroll(const ScreenPoint& aStartPoint, |
michael@0 | 1136 | const ScreenPoint& aEndPoint, |
michael@0 | 1137 | uint32_t aOverscrollHandoffChainIndex) { |
michael@0 | 1138 | |
michael@0 | 1139 | // "start - end" rather than "end - start" because e.g. moving your finger |
michael@0 | 1140 | // down (*positive* direction along y axis) causes the vertical scroll offset |
michael@0 | 1141 | // to *decrease* as the page follows your finger. |
michael@0 | 1142 | ScreenPoint displacement = aStartPoint - aEndPoint; |
michael@0 | 1143 | |
michael@0 | 1144 | ScreenPoint overscroll; // will be used outside monitor block |
michael@0 | 1145 | { |
michael@0 | 1146 | ReentrantMonitorAutoEnter lock(mMonitor); |
michael@0 | 1147 | |
michael@0 | 1148 | CSSToScreenScale zoom = mFrameMetrics.GetZoom(); |
michael@0 | 1149 | |
michael@0 | 1150 | // Inversely scale the offset by the resolution (when you're zoomed further in, |
michael@0 | 1151 | // the same swipe should move you a shorter distance). |
michael@0 | 1152 | CSSPoint cssDisplacement = displacement / zoom; |
michael@0 | 1153 | |
michael@0 | 1154 | CSSPoint cssOverscroll; |
michael@0 | 1155 | CSSPoint allowedDisplacement(mX.AdjustDisplacement(cssDisplacement.x, |
michael@0 | 1156 | cssOverscroll.x), |
michael@0 | 1157 | mY.AdjustDisplacement(cssDisplacement.y, |
michael@0 | 1158 | cssOverscroll.y)); |
michael@0 | 1159 | overscroll = cssOverscroll * zoom; |
michael@0 | 1160 | |
michael@0 | 1161 | if (!IsZero(allowedDisplacement)) { |
michael@0 | 1162 | ScrollBy(allowedDisplacement); |
michael@0 | 1163 | ScheduleComposite(); |
michael@0 | 1164 | |
michael@0 | 1165 | TimeDuration timePaintDelta = mPaintThrottler.TimeSinceLastRequest(GetFrameTime()); |
michael@0 | 1166 | if (timePaintDelta.ToMilliseconds() > gfxPrefs::APZPanRepaintInterval()) { |
michael@0 | 1167 | RequestContentRepaint(); |
michael@0 | 1168 | } |
michael@0 | 1169 | UpdateSharedCompositorFrameMetrics(); |
michael@0 | 1170 | } |
michael@0 | 1171 | } |
michael@0 | 1172 | |
michael@0 | 1173 | if (!IsZero(overscroll)) { |
michael@0 | 1174 | // "+ overscroll" rather than "- overscroll" because "overscroll" is what's |
michael@0 | 1175 | // left of "displacement", and "displacement" is "start - end". |
michael@0 | 1176 | CallDispatchScroll(aEndPoint + overscroll, aEndPoint, aOverscrollHandoffChainIndex + 1); |
michael@0 | 1177 | } |
michael@0 | 1178 | } |
michael@0 | 1179 | |
michael@0 | 1180 | void AsyncPanZoomController::TakeOverFling(ScreenPoint aVelocity) { |
michael@0 | 1181 | // We may have a pre-existing velocity for whatever reason (for example, |
michael@0 | 1182 | // a previously handed off fling). We don't want to clobber that. |
michael@0 | 1183 | mX.SetVelocity(mX.GetVelocity() + aVelocity.x); |
michael@0 | 1184 | mY.SetVelocity(mY.GetVelocity() + aVelocity.y); |
michael@0 | 1185 | SetState(FLING); |
michael@0 | 1186 | StartAnimation(new FlingAnimation(*this)); |
michael@0 | 1187 | } |
michael@0 | 1188 | |
michael@0 | 1189 | void AsyncPanZoomController::CallDispatchScroll(const ScreenPoint& aStartPoint, const ScreenPoint& aEndPoint, |
michael@0 | 1190 | uint32_t aOverscrollHandoffChainIndex) { |
michael@0 | 1191 | // Make a local copy of the tree manager pointer and check if it's not |
michael@0 | 1192 | // null before calling DispatchScroll(). This is necessary because |
michael@0 | 1193 | // Destroy(), which nulls out mTreeManager, could be called concurrently. |
michael@0 | 1194 | APZCTreeManager* treeManagerLocal = mTreeManager; |
michael@0 | 1195 | if (treeManagerLocal) { |
michael@0 | 1196 | treeManagerLocal->DispatchScroll(this, aStartPoint, aEndPoint, |
michael@0 | 1197 | aOverscrollHandoffChainIndex); |
michael@0 | 1198 | } |
michael@0 | 1199 | } |
michael@0 | 1200 | |
michael@0 | 1201 | void AsyncPanZoomController::TrackTouch(const MultiTouchInput& aEvent) { |
michael@0 | 1202 | ScreenIntPoint prevTouchPoint(mX.GetPos(), mY.GetPos()); |
michael@0 | 1203 | ScreenIntPoint touchPoint = GetFirstTouchScreenPoint(aEvent); |
michael@0 | 1204 | TimeDuration timeDelta = TimeDuration().FromMilliseconds(aEvent.mTime - mLastEventTime); |
michael@0 | 1205 | |
michael@0 | 1206 | // Probably a duplicate event, just throw it away. |
michael@0 | 1207 | if (timeDelta.ToMilliseconds() <= EPSILON) { |
michael@0 | 1208 | return; |
michael@0 | 1209 | } |
michael@0 | 1210 | |
michael@0 | 1211 | // If we're axis-locked, check if the user is trying to break the lock |
michael@0 | 1212 | if (GetAxisLockMode() == STICKY && !mPanDirRestricted) { |
michael@0 | 1213 | ScreenIntPoint point = GetFirstTouchScreenPoint(aEvent); |
michael@0 | 1214 | float dx = mX.PanDistance(point.x); |
michael@0 | 1215 | float dy = mY.PanDistance(point.y); |
michael@0 | 1216 | |
michael@0 | 1217 | double angle = atan2(dy, dx); // range [-pi, pi] |
michael@0 | 1218 | angle = fabs(angle); // range [0, pi] |
michael@0 | 1219 | |
michael@0 | 1220 | float breakThreshold = AXIS_BREAKOUT_THRESHOLD * APZCTreeManager::GetDPI(); |
michael@0 | 1221 | |
michael@0 | 1222 | if (fabs(dx) > breakThreshold || fabs(dy) > breakThreshold) { |
michael@0 | 1223 | if (mState == PANNING_LOCKED_X || mState == CROSS_SLIDING_X) { |
michael@0 | 1224 | if (!IsCloseToHorizontal(angle, AXIS_BREAKOUT_ANGLE)) { |
michael@0 | 1225 | mY.SetAxisLocked(false); |
michael@0 | 1226 | SetState(PANNING); |
michael@0 | 1227 | } |
michael@0 | 1228 | } else if (mState == PANNING_LOCKED_Y || mState == CROSS_SLIDING_Y) { |
michael@0 | 1229 | if (!IsCloseToVertical(angle, AXIS_BREAKOUT_ANGLE)) { |
michael@0 | 1230 | mX.SetAxisLocked(false); |
michael@0 | 1231 | SetState(PANNING); |
michael@0 | 1232 | } |
michael@0 | 1233 | } |
michael@0 | 1234 | } |
michael@0 | 1235 | } |
michael@0 | 1236 | |
michael@0 | 1237 | UpdateWithTouchAtDevicePoint(aEvent); |
michael@0 | 1238 | |
michael@0 | 1239 | CallDispatchScroll(prevTouchPoint, touchPoint, 0); |
michael@0 | 1240 | } |
michael@0 | 1241 | |
michael@0 | 1242 | ScreenIntPoint& AsyncPanZoomController::GetFirstTouchScreenPoint(const MultiTouchInput& aEvent) { |
michael@0 | 1243 | return ((SingleTouchData&)aEvent.mTouches[0]).mScreenPoint; |
michael@0 | 1244 | } |
michael@0 | 1245 | |
michael@0 | 1246 | bool FlingAnimation::Sample(FrameMetrics& aFrameMetrics, |
michael@0 | 1247 | const TimeDuration& aDelta) { |
michael@0 | 1248 | |
michael@0 | 1249 | // If the fling is handed off to our APZC from a child, on the first call to |
michael@0 | 1250 | // Sample() aDelta might be negative because it's computed as the sample time |
michael@0 | 1251 | // from SampleContentTransformForFrame() minus our APZC's mLastSampleTime |
michael@0 | 1252 | // which is the time the child handed off the fling from its call to |
michael@0 | 1253 | // SampleContentTransformForFrame() with the same sample time. If we allow |
michael@0 | 1254 | // the negative aDelta to be processed, it will yield a displacement in the |
michael@0 | 1255 | // direction opposite to the fling, which can cause us to overscroll and |
michael@0 | 1256 | // hand off the fling to _our_ parent, which effectively kills the fling. |
michael@0 | 1257 | if (aDelta.ToMilliseconds() <= 0) { |
michael@0 | 1258 | return true; |
michael@0 | 1259 | } |
michael@0 | 1260 | |
michael@0 | 1261 | bool shouldContinueFlingX = mApzc.mX.FlingApplyFrictionOrCancel(aDelta), |
michael@0 | 1262 | shouldContinueFlingY = mApzc.mY.FlingApplyFrictionOrCancel(aDelta); |
michael@0 | 1263 | // If we shouldn't continue the fling, let's just stop and repaint. |
michael@0 | 1264 | if (!shouldContinueFlingX && !shouldContinueFlingY) { |
michael@0 | 1265 | return false; |
michael@0 | 1266 | } |
michael@0 | 1267 | |
michael@0 | 1268 | // AdjustDisplacement() zeroes out the Axis velocity if we're in overscroll. |
michael@0 | 1269 | // Since we need to hand off the velocity to the tree manager in such a case, |
michael@0 | 1270 | // we save it here. Would be ScreenVector instead of ScreenPoint if we had |
michael@0 | 1271 | // vector classes. |
michael@0 | 1272 | ScreenPoint velocity(mApzc.mX.GetVelocity(), mApzc.mY.GetVelocity()); |
michael@0 | 1273 | |
michael@0 | 1274 | ScreenPoint offset = velocity * aDelta.ToMilliseconds(); |
michael@0 | 1275 | |
michael@0 | 1276 | // Inversely scale the offset by the resolution (when you're zoomed further in, |
michael@0 | 1277 | // the same swipe should move you a shorter distance). |
michael@0 | 1278 | CSSPoint cssOffset = offset / aFrameMetrics.GetZoom(); |
michael@0 | 1279 | CSSPoint overscroll; |
michael@0 | 1280 | aFrameMetrics.ScrollBy(CSSPoint( |
michael@0 | 1281 | mApzc.mX.AdjustDisplacement(cssOffset.x, overscroll.x), |
michael@0 | 1282 | mApzc.mY.AdjustDisplacement(cssOffset.y, overscroll.y) |
michael@0 | 1283 | )); |
michael@0 | 1284 | |
michael@0 | 1285 | // If the fling has caused us to reach the end of our scroll range, hand |
michael@0 | 1286 | // off the fling to the next APZC in the overscroll handoff chain. |
michael@0 | 1287 | if (!IsZero(overscroll)) { |
michael@0 | 1288 | // We may have reached the end of the scroll range along one axis but |
michael@0 | 1289 | // not the other. In such a case we only want to hand off the relevant |
michael@0 | 1290 | // component of the fling. |
michael@0 | 1291 | if (FuzzyEqualsMultiplicative(overscroll.x, 0.0f)) { |
michael@0 | 1292 | velocity.x = 0; |
michael@0 | 1293 | } else if (FuzzyEqualsMultiplicative(overscroll.y, 0.0f)) { |
michael@0 | 1294 | velocity.y = 0; |
michael@0 | 1295 | } |
michael@0 | 1296 | |
michael@0 | 1297 | // To hand off the fling, we call APZCTreeManager::HandleFlingOverscroll() |
michael@0 | 1298 | // which starts a new fling in the next APZC in the handoff chain with |
michael@0 | 1299 | // the same velocity. For simplicity, the actual overscroll of the current |
michael@0 | 1300 | // sample is discarded rather than being handed off. The compositor should |
michael@0 | 1301 | // sample animations sufficiently frequently that this is not noticeable. |
michael@0 | 1302 | |
michael@0 | 1303 | // Make a local copy of the tree manager pointer and check if it's not |
michael@0 | 1304 | // null before calling HandleFlingOverscroll(). This is necessary because |
michael@0 | 1305 | // Destroy(), which nulls out mTreeManager, could be called concurrently. |
michael@0 | 1306 | APZCTreeManager* treeManagerLocal = mApzc.mTreeManager; |
michael@0 | 1307 | if (treeManagerLocal) { |
michael@0 | 1308 | // APZC is holding mMonitor, so directly calling HandleFlingOverscroll() |
michael@0 | 1309 | // (which acquires the tree lock) would violate the lock ordering. Instead |
michael@0 | 1310 | // we schedule HandleFlingOverscroll() to be called after mMonitor is |
michael@0 | 1311 | // released. |
michael@0 | 1312 | mDeferredTasks.append(NewRunnableMethod(treeManagerLocal, |
michael@0 | 1313 | &APZCTreeManager::HandOffFling, |
michael@0 | 1314 | &mApzc, |
michael@0 | 1315 | velocity)); |
michael@0 | 1316 | } |
michael@0 | 1317 | } |
michael@0 | 1318 | |
michael@0 | 1319 | return true; |
michael@0 | 1320 | } |
michael@0 | 1321 | |
michael@0 | 1322 | void AsyncPanZoomController::StartAnimation(AsyncPanZoomAnimation* aAnimation) |
michael@0 | 1323 | { |
michael@0 | 1324 | ReentrantMonitorAutoEnter lock(mMonitor); |
michael@0 | 1325 | mAnimation = aAnimation; |
michael@0 | 1326 | mLastSampleTime = GetFrameTime(); |
michael@0 | 1327 | ScheduleComposite(); |
michael@0 | 1328 | } |
michael@0 | 1329 | |
michael@0 | 1330 | void AsyncPanZoomController::CancelAnimation() { |
michael@0 | 1331 | ReentrantMonitorAutoEnter lock(mMonitor); |
michael@0 | 1332 | SetState(NOTHING); |
michael@0 | 1333 | mAnimation = nullptr; |
michael@0 | 1334 | } |
michael@0 | 1335 | |
michael@0 | 1336 | void AsyncPanZoomController::SetCompositorParent(CompositorParent* aCompositorParent) { |
michael@0 | 1337 | mCompositorParent = aCompositorParent; |
michael@0 | 1338 | } |
michael@0 | 1339 | |
michael@0 | 1340 | void AsyncPanZoomController::SetCrossProcessCompositorParent(PCompositorParent* aCrossProcessCompositorParent) { |
michael@0 | 1341 | mCrossProcessCompositorParent = aCrossProcessCompositorParent; |
michael@0 | 1342 | } |
michael@0 | 1343 | |
michael@0 | 1344 | void AsyncPanZoomController::ScrollBy(const CSSPoint& aOffset) { |
michael@0 | 1345 | mFrameMetrics.ScrollBy(aOffset); |
michael@0 | 1346 | } |
michael@0 | 1347 | |
michael@0 | 1348 | void AsyncPanZoomController::ScaleWithFocus(float aScale, |
michael@0 | 1349 | const CSSPoint& aFocus) { |
michael@0 | 1350 | mFrameMetrics.ZoomBy(aScale); |
michael@0 | 1351 | // We want to adjust the scroll offset such that the CSS point represented by aFocus remains |
michael@0 | 1352 | // at the same position on the screen before and after the change in zoom. The below code |
michael@0 | 1353 | // accomplishes this; see https://bugzilla.mozilla.org/show_bug.cgi?id=923431#c6 for an |
michael@0 | 1354 | // in-depth explanation of how. |
michael@0 | 1355 | mFrameMetrics.SetScrollOffset((mFrameMetrics.GetScrollOffset() + aFocus) - (aFocus / aScale)); |
michael@0 | 1356 | } |
michael@0 | 1357 | |
michael@0 | 1358 | /** |
michael@0 | 1359 | * Enlarges the displayport along both axes based on the velocity. |
michael@0 | 1360 | */ |
michael@0 | 1361 | static CSSSize |
michael@0 | 1362 | CalculateDisplayPortSize(const CSSSize& aCompositionSize, |
michael@0 | 1363 | const CSSPoint& aVelocity) |
michael@0 | 1364 | { |
michael@0 | 1365 | float xMultiplier = fabsf(aVelocity.x) < gfxPrefs::APZMinSkateSpeed() |
michael@0 | 1366 | ? gfxPrefs::APZXStationarySizeMultiplier() |
michael@0 | 1367 | : gfxPrefs::APZXSkateSizeMultiplier(); |
michael@0 | 1368 | float yMultiplier = fabsf(aVelocity.y) < gfxPrefs::APZMinSkateSpeed() |
michael@0 | 1369 | ? gfxPrefs::APZYStationarySizeMultiplier() |
michael@0 | 1370 | : gfxPrefs::APZYSkateSizeMultiplier(); |
michael@0 | 1371 | return CSSSize(aCompositionSize.width * xMultiplier, |
michael@0 | 1372 | aCompositionSize.height * yMultiplier); |
michael@0 | 1373 | } |
michael@0 | 1374 | |
michael@0 | 1375 | /** |
michael@0 | 1376 | * Attempts to redistribute any area in the displayport that would get clipped |
michael@0 | 1377 | * by the scrollable rect, or be inaccessible due to disabled scrolling, to the |
michael@0 | 1378 | * other axis, while maintaining total displayport area. |
michael@0 | 1379 | */ |
michael@0 | 1380 | static void |
michael@0 | 1381 | RedistributeDisplayPortExcess(CSSSize& aDisplayPortSize, |
michael@0 | 1382 | const CSSRect& aScrollableRect) |
michael@0 | 1383 | { |
michael@0 | 1384 | float xSlack = std::max(0.0f, aDisplayPortSize.width - aScrollableRect.width); |
michael@0 | 1385 | float ySlack = std::max(0.0f, aDisplayPortSize.height - aScrollableRect.height); |
michael@0 | 1386 | |
michael@0 | 1387 | if (ySlack > 0) { |
michael@0 | 1388 | // Reassign wasted y-axis displayport to the x-axis |
michael@0 | 1389 | aDisplayPortSize.height -= ySlack; |
michael@0 | 1390 | float xExtra = ySlack * aDisplayPortSize.width / aDisplayPortSize.height; |
michael@0 | 1391 | aDisplayPortSize.width += xExtra; |
michael@0 | 1392 | } else if (xSlack > 0) { |
michael@0 | 1393 | // Reassign wasted x-axis displayport to the y-axis |
michael@0 | 1394 | aDisplayPortSize.width -= xSlack; |
michael@0 | 1395 | float yExtra = xSlack * aDisplayPortSize.height / aDisplayPortSize.width; |
michael@0 | 1396 | aDisplayPortSize.height += yExtra; |
michael@0 | 1397 | } |
michael@0 | 1398 | } |
michael@0 | 1399 | |
michael@0 | 1400 | /* static */ |
michael@0 | 1401 | const LayerMargin AsyncPanZoomController::CalculatePendingDisplayPort( |
michael@0 | 1402 | const FrameMetrics& aFrameMetrics, |
michael@0 | 1403 | const ScreenPoint& aVelocity, |
michael@0 | 1404 | double aEstimatedPaintDuration) |
michael@0 | 1405 | { |
michael@0 | 1406 | CSSSize compositionBounds = aFrameMetrics.CalculateCompositedSizeInCssPixels(); |
michael@0 | 1407 | CSSSize compositionSize = aFrameMetrics.GetRootCompositionSize(); |
michael@0 | 1408 | compositionSize = |
michael@0 | 1409 | CSSSize(std::min(compositionBounds.width, compositionSize.width), |
michael@0 | 1410 | std::min(compositionBounds.height, compositionSize.height)); |
michael@0 | 1411 | CSSPoint velocity = aVelocity / aFrameMetrics.GetZoom(); |
michael@0 | 1412 | CSSPoint scrollOffset = aFrameMetrics.GetScrollOffset(); |
michael@0 | 1413 | CSSRect scrollableRect = aFrameMetrics.GetExpandedScrollableRect(); |
michael@0 | 1414 | |
michael@0 | 1415 | // Calculate the displayport size based on how fast we're moving along each axis. |
michael@0 | 1416 | CSSSize displayPortSize = CalculateDisplayPortSize(compositionSize, velocity); |
michael@0 | 1417 | |
michael@0 | 1418 | if (gfxPrefs::APZEnlargeDisplayPortWhenClipped()) { |
michael@0 | 1419 | RedistributeDisplayPortExcess(displayPortSize, scrollableRect); |
michael@0 | 1420 | } |
michael@0 | 1421 | |
michael@0 | 1422 | // Offset the displayport, depending on how fast we're moving and the |
michael@0 | 1423 | // estimated time it takes to paint, to try to minimise checkerboarding. |
michael@0 | 1424 | float estimatedPaintDurationMillis = (float)(aEstimatedPaintDuration * 1000.0); |
michael@0 | 1425 | float paintFactor = (gfxPrefs::APZUsePaintDuration() ? estimatedPaintDurationMillis : 50.0f); |
michael@0 | 1426 | CSSRect displayPort = CSSRect(scrollOffset + (velocity * paintFactor * gfxPrefs::APZVelocityBias()), |
michael@0 | 1427 | displayPortSize); |
michael@0 | 1428 | |
michael@0 | 1429 | // Re-center the displayport based on its expansion over the composition size. |
michael@0 | 1430 | displayPort.MoveBy((compositionSize.width - displayPort.width)/2.0f, |
michael@0 | 1431 | (compositionSize.height - displayPort.height)/2.0f); |
michael@0 | 1432 | |
michael@0 | 1433 | // Make sure the displayport remains within the scrollable rect. |
michael@0 | 1434 | displayPort = displayPort.ForceInside(scrollableRect) - scrollOffset; |
michael@0 | 1435 | |
michael@0 | 1436 | APZC_LOG_FM(aFrameMetrics, |
michael@0 | 1437 | "Calculated displayport as (%f %f %f %f) from velocity (%f %f) paint time %f metrics", |
michael@0 | 1438 | displayPort.x, displayPort.y, displayPort.width, displayPort.height, |
michael@0 | 1439 | aVelocity.x, aVelocity.y, (float)estimatedPaintDurationMillis); |
michael@0 | 1440 | |
michael@0 | 1441 | CSSMargin cssMargins; |
michael@0 | 1442 | cssMargins.left = -displayPort.x; |
michael@0 | 1443 | cssMargins.top = -displayPort.y; |
michael@0 | 1444 | cssMargins.right = displayPort.width - compositionSize.width - cssMargins.left; |
michael@0 | 1445 | cssMargins.bottom = displayPort.height - compositionSize.height - cssMargins.top; |
michael@0 | 1446 | |
michael@0 | 1447 | LayerMargin layerMargins = cssMargins * aFrameMetrics.LayersPixelsPerCSSPixel(); |
michael@0 | 1448 | |
michael@0 | 1449 | return layerMargins; |
michael@0 | 1450 | } |
michael@0 | 1451 | |
michael@0 | 1452 | void AsyncPanZoomController::ScheduleComposite() { |
michael@0 | 1453 | if (mCompositorParent) { |
michael@0 | 1454 | mCompositorParent->ScheduleRenderOnCompositorThread(); |
michael@0 | 1455 | } |
michael@0 | 1456 | } |
michael@0 | 1457 | |
michael@0 | 1458 | void AsyncPanZoomController::FlushRepaintForOverscrollHandoff() { |
michael@0 | 1459 | ReentrantMonitorAutoEnter lock(mMonitor); |
michael@0 | 1460 | RequestContentRepaint(); |
michael@0 | 1461 | UpdateSharedCompositorFrameMetrics(); |
michael@0 | 1462 | } |
michael@0 | 1463 | |
michael@0 | 1464 | bool AsyncPanZoomController::IsPannable() const { |
michael@0 | 1465 | ReentrantMonitorAutoEnter lock(mMonitor); |
michael@0 | 1466 | return mX.HasRoomToPan() || mY.HasRoomToPan(); |
michael@0 | 1467 | } |
michael@0 | 1468 | |
michael@0 | 1469 | void AsyncPanZoomController::RequestContentRepaint() { |
michael@0 | 1470 | RequestContentRepaint(mFrameMetrics); |
michael@0 | 1471 | } |
michael@0 | 1472 | |
michael@0 | 1473 | void AsyncPanZoomController::RequestContentRepaint(FrameMetrics& aFrameMetrics) { |
michael@0 | 1474 | aFrameMetrics.SetDisplayPortMargins( |
michael@0 | 1475 | CalculatePendingDisplayPort(aFrameMetrics, |
michael@0 | 1476 | GetVelocityVector(), |
michael@0 | 1477 | mPaintThrottler.AverageDuration().ToSeconds())); |
michael@0 | 1478 | aFrameMetrics.SetUseDisplayPortMargins(); |
michael@0 | 1479 | |
michael@0 | 1480 | // If we're trying to paint what we already think is painted, discard this |
michael@0 | 1481 | // request since it's a pointless paint. |
michael@0 | 1482 | LayerMargin marginDelta = mLastPaintRequestMetrics.GetDisplayPortMargins() |
michael@0 | 1483 | - aFrameMetrics.GetDisplayPortMargins(); |
michael@0 | 1484 | if (fabsf(marginDelta.left) < EPSILON && |
michael@0 | 1485 | fabsf(marginDelta.top) < EPSILON && |
michael@0 | 1486 | fabsf(marginDelta.right) < EPSILON && |
michael@0 | 1487 | fabsf(marginDelta.bottom) < EPSILON && |
michael@0 | 1488 | fabsf(mLastPaintRequestMetrics.GetScrollOffset().x - |
michael@0 | 1489 | aFrameMetrics.GetScrollOffset().x) < EPSILON && |
michael@0 | 1490 | fabsf(mLastPaintRequestMetrics.GetScrollOffset().y - |
michael@0 | 1491 | aFrameMetrics.GetScrollOffset().y) < EPSILON && |
michael@0 | 1492 | aFrameMetrics.GetZoom() == mLastPaintRequestMetrics.GetZoom() && |
michael@0 | 1493 | fabsf(aFrameMetrics.mViewport.width - mLastPaintRequestMetrics.mViewport.width) < EPSILON && |
michael@0 | 1494 | fabsf(aFrameMetrics.mViewport.height - mLastPaintRequestMetrics.mViewport.height) < EPSILON) { |
michael@0 | 1495 | return; |
michael@0 | 1496 | } |
michael@0 | 1497 | |
michael@0 | 1498 | SendAsyncScrollEvent(); |
michael@0 | 1499 | mPaintThrottler.PostTask( |
michael@0 | 1500 | FROM_HERE, |
michael@0 | 1501 | NewRunnableMethod(this, |
michael@0 | 1502 | &AsyncPanZoomController::DispatchRepaintRequest, |
michael@0 | 1503 | aFrameMetrics), |
michael@0 | 1504 | GetFrameTime()); |
michael@0 | 1505 | |
michael@0 | 1506 | aFrameMetrics.mPresShellId = mLastContentPaintMetrics.mPresShellId; |
michael@0 | 1507 | mLastPaintRequestMetrics = aFrameMetrics; |
michael@0 | 1508 | } |
michael@0 | 1509 | |
michael@0 | 1510 | /*static*/ CSSRect |
michael@0 | 1511 | GetDisplayPortRect(const FrameMetrics& aFrameMetrics) |
michael@0 | 1512 | { |
michael@0 | 1513 | // This computation is based on what happens in CalculatePendingDisplayPort. If that |
michael@0 | 1514 | // changes then this might need to change too |
michael@0 | 1515 | CSSRect baseRect(aFrameMetrics.GetScrollOffset(), |
michael@0 | 1516 | CSSSize(std::min(aFrameMetrics.CalculateCompositedSizeInCssPixels().width, |
michael@0 | 1517 | aFrameMetrics.GetRootCompositionSize().width), |
michael@0 | 1518 | std::min(aFrameMetrics.CalculateCompositedSizeInCssPixels().height, |
michael@0 | 1519 | aFrameMetrics.GetRootCompositionSize().height))); |
michael@0 | 1520 | baseRect.Inflate(aFrameMetrics.GetDisplayPortMargins() / aFrameMetrics.LayersPixelsPerCSSPixel()); |
michael@0 | 1521 | return baseRect; |
michael@0 | 1522 | } |
michael@0 | 1523 | |
michael@0 | 1524 | void |
michael@0 | 1525 | AsyncPanZoomController::DispatchRepaintRequest(const FrameMetrics& aFrameMetrics) { |
michael@0 | 1526 | nsRefPtr<GeckoContentController> controller = GetGeckoContentController(); |
michael@0 | 1527 | if (controller) { |
michael@0 | 1528 | APZC_LOG_FM(aFrameMetrics, "%p requesting content repaint", this); |
michael@0 | 1529 | LogRendertraceRect(GetGuid(), "requested displayport", "yellow", GetDisplayPortRect(aFrameMetrics)); |
michael@0 | 1530 | |
michael@0 | 1531 | controller->RequestContentRepaint(aFrameMetrics); |
michael@0 | 1532 | mLastDispatchedPaintMetrics = aFrameMetrics; |
michael@0 | 1533 | } |
michael@0 | 1534 | } |
michael@0 | 1535 | |
michael@0 | 1536 | void |
michael@0 | 1537 | AsyncPanZoomController::FireAsyncScrollOnTimeout() |
michael@0 | 1538 | { |
michael@0 | 1539 | if (mCurrentAsyncScrollOffset != mLastAsyncScrollOffset) { |
michael@0 | 1540 | ReentrantMonitorAutoEnter lock(mMonitor); |
michael@0 | 1541 | SendAsyncScrollEvent(); |
michael@0 | 1542 | } |
michael@0 | 1543 | mAsyncScrollTimeoutTask = nullptr; |
michael@0 | 1544 | } |
michael@0 | 1545 | |
michael@0 | 1546 | bool ZoomAnimation::Sample(FrameMetrics& aFrameMetrics, |
michael@0 | 1547 | const TimeDuration& aDelta) { |
michael@0 | 1548 | mDuration += aDelta; |
michael@0 | 1549 | double animPosition = mDuration / ZOOM_TO_DURATION; |
michael@0 | 1550 | |
michael@0 | 1551 | if (animPosition >= 1.0) { |
michael@0 | 1552 | aFrameMetrics.SetZoom(mEndZoom); |
michael@0 | 1553 | aFrameMetrics.SetScrollOffset(mEndOffset); |
michael@0 | 1554 | return false; |
michael@0 | 1555 | } |
michael@0 | 1556 | |
michael@0 | 1557 | // Sample the zoom at the current time point. The sampled zoom |
michael@0 | 1558 | // will affect the final computed resolution. |
michael@0 | 1559 | double sampledPosition = gComputedTimingFunction->GetValue(animPosition); |
michael@0 | 1560 | |
michael@0 | 1561 | // We scale the scrollOffset linearly with sampledPosition, so the zoom |
michael@0 | 1562 | // needs to scale inversely to match. |
michael@0 | 1563 | aFrameMetrics.SetZoom(CSSToScreenScale(1 / |
michael@0 | 1564 | (sampledPosition / mEndZoom.scale + |
michael@0 | 1565 | (1 - sampledPosition) / mStartZoom.scale))); |
michael@0 | 1566 | |
michael@0 | 1567 | aFrameMetrics.SetScrollOffset(CSSPoint::FromUnknownPoint(gfx::Point( |
michael@0 | 1568 | mEndOffset.x * sampledPosition + mStartOffset.x * (1 - sampledPosition), |
michael@0 | 1569 | mEndOffset.y * sampledPosition + mStartOffset.y * (1 - sampledPosition) |
michael@0 | 1570 | ))); |
michael@0 | 1571 | |
michael@0 | 1572 | return true; |
michael@0 | 1573 | } |
michael@0 | 1574 | |
michael@0 | 1575 | bool AsyncPanZoomController::UpdateAnimation(const TimeStamp& aSampleTime) |
michael@0 | 1576 | { |
michael@0 | 1577 | if (mAnimation) { |
michael@0 | 1578 | if (mAnimation->Sample(mFrameMetrics, aSampleTime - mLastSampleTime)) { |
michael@0 | 1579 | if (mPaintThrottler.TimeSinceLastRequest(aSampleTime) > |
michael@0 | 1580 | mAnimation->mRepaintInterval) { |
michael@0 | 1581 | RequestContentRepaint(); |
michael@0 | 1582 | } |
michael@0 | 1583 | } else { |
michael@0 | 1584 | mAnimation = nullptr; |
michael@0 | 1585 | SetState(NOTHING); |
michael@0 | 1586 | SendAsyncScrollEvent(); |
michael@0 | 1587 | RequestContentRepaint(); |
michael@0 | 1588 | } |
michael@0 | 1589 | UpdateSharedCompositorFrameMetrics(); |
michael@0 | 1590 | mLastSampleTime = aSampleTime; |
michael@0 | 1591 | return true; |
michael@0 | 1592 | } |
michael@0 | 1593 | return false; |
michael@0 | 1594 | } |
michael@0 | 1595 | |
michael@0 | 1596 | bool AsyncPanZoomController::SampleContentTransformForFrame(const TimeStamp& aSampleTime, |
michael@0 | 1597 | ViewTransform* aNewTransform, |
michael@0 | 1598 | ScreenPoint& aScrollOffset) { |
michael@0 | 1599 | // The eventual return value of this function. The compositor needs to know |
michael@0 | 1600 | // whether or not to advance by a frame as soon as it can. For example, if a |
michael@0 | 1601 | // fling is happening, it has to keep compositing so that the animation is |
michael@0 | 1602 | // smooth. If an animation frame is requested, it is the compositor's |
michael@0 | 1603 | // responsibility to schedule a composite. |
michael@0 | 1604 | bool requestAnimationFrame = false; |
michael@0 | 1605 | Vector<Task*> deferredTasks; |
michael@0 | 1606 | |
michael@0 | 1607 | { |
michael@0 | 1608 | ReentrantMonitorAutoEnter lock(mMonitor); |
michael@0 | 1609 | |
michael@0 | 1610 | requestAnimationFrame = UpdateAnimation(aSampleTime); |
michael@0 | 1611 | |
michael@0 | 1612 | aScrollOffset = mFrameMetrics.GetScrollOffset() * mFrameMetrics.GetZoom(); |
michael@0 | 1613 | *aNewTransform = GetCurrentAsyncTransform(); |
michael@0 | 1614 | |
michael@0 | 1615 | LogRendertraceRect(GetGuid(), "viewport", "red", |
michael@0 | 1616 | CSSRect(mFrameMetrics.GetScrollOffset(), |
michael@0 | 1617 | ParentLayerSize(mFrameMetrics.mCompositionBounds.Size()) / mFrameMetrics.GetZoomToParent())); |
michael@0 | 1618 | |
michael@0 | 1619 | mCurrentAsyncScrollOffset = mFrameMetrics.GetScrollOffset(); |
michael@0 | 1620 | |
michael@0 | 1621 | // Get any deferred tasks queued up by mAnimation's Sample() (called by |
michael@0 | 1622 | // UpdateAnimation()). This needs to be done here since mAnimation can |
michael@0 | 1623 | // be destroyed by another thread when we release the monitor, but |
michael@0 | 1624 | // the tasks need to be executed after we release the monitor since they |
michael@0 | 1625 | // are allowed to call APZCTreeManager methods which can grab the tree lock. |
michael@0 | 1626 | if (mAnimation) { |
michael@0 | 1627 | deferredTasks = mAnimation->TakeDeferredTasks(); |
michael@0 | 1628 | } |
michael@0 | 1629 | } |
michael@0 | 1630 | |
michael@0 | 1631 | for (uint32_t i = 0; i < deferredTasks.length(); ++i) { |
michael@0 | 1632 | deferredTasks[i]->Run(); |
michael@0 | 1633 | delete deferredTasks[i]; |
michael@0 | 1634 | } |
michael@0 | 1635 | |
michael@0 | 1636 | // Cancel the mAsyncScrollTimeoutTask because we will fire a |
michael@0 | 1637 | // mozbrowserasyncscroll event or renew the mAsyncScrollTimeoutTask again. |
michael@0 | 1638 | if (mAsyncScrollTimeoutTask) { |
michael@0 | 1639 | mAsyncScrollTimeoutTask->Cancel(); |
michael@0 | 1640 | mAsyncScrollTimeoutTask = nullptr; |
michael@0 | 1641 | } |
michael@0 | 1642 | // Fire the mozbrowserasyncscroll event immediately if it's been |
michael@0 | 1643 | // sAsyncScrollThrottleTime ms since the last time we fired the event and the |
michael@0 | 1644 | // current scroll offset is different than the mLastAsyncScrollOffset we sent |
michael@0 | 1645 | // with the last event. |
michael@0 | 1646 | // Otherwise, start a timer to fire the event sAsyncScrollTimeout ms from now. |
michael@0 | 1647 | TimeDuration delta = aSampleTime - mLastAsyncScrollTime; |
michael@0 | 1648 | if (delta.ToMilliseconds() > gfxPrefs::APZAsyncScrollThrottleTime() && |
michael@0 | 1649 | mCurrentAsyncScrollOffset != mLastAsyncScrollOffset) { |
michael@0 | 1650 | ReentrantMonitorAutoEnter lock(mMonitor); |
michael@0 | 1651 | mLastAsyncScrollTime = aSampleTime; |
michael@0 | 1652 | mLastAsyncScrollOffset = mCurrentAsyncScrollOffset; |
michael@0 | 1653 | SendAsyncScrollEvent(); |
michael@0 | 1654 | } |
michael@0 | 1655 | else { |
michael@0 | 1656 | mAsyncScrollTimeoutTask = |
michael@0 | 1657 | NewRunnableMethod(this, &AsyncPanZoomController::FireAsyncScrollOnTimeout); |
michael@0 | 1658 | MessageLoop::current()->PostDelayedTask(FROM_HERE, |
michael@0 | 1659 | mAsyncScrollTimeoutTask, |
michael@0 | 1660 | gfxPrefs::APZAsyncScrollTimeout()); |
michael@0 | 1661 | } |
michael@0 | 1662 | |
michael@0 | 1663 | return requestAnimationFrame; |
michael@0 | 1664 | } |
michael@0 | 1665 | |
michael@0 | 1666 | ViewTransform AsyncPanZoomController::GetCurrentAsyncTransform() { |
michael@0 | 1667 | ReentrantMonitorAutoEnter lock(mMonitor); |
michael@0 | 1668 | |
michael@0 | 1669 | CSSPoint lastPaintScrollOffset; |
michael@0 | 1670 | if (mLastContentPaintMetrics.IsScrollable()) { |
michael@0 | 1671 | lastPaintScrollOffset = mLastContentPaintMetrics.GetScrollOffset(); |
michael@0 | 1672 | } |
michael@0 | 1673 | |
michael@0 | 1674 | CSSPoint currentScrollOffset = mFrameMetrics.GetScrollOffset() + |
michael@0 | 1675 | mTestAsyncScrollOffset; |
michael@0 | 1676 | |
michael@0 | 1677 | // If checkerboarding has been disallowed, clamp the scroll position to stay |
michael@0 | 1678 | // within rendered content. |
michael@0 | 1679 | if (!gfxPrefs::APZAllowCheckerboarding() && |
michael@0 | 1680 | !mLastContentPaintMetrics.mDisplayPort.IsEmpty()) { |
michael@0 | 1681 | CSSSize compositedSize = mLastContentPaintMetrics.CalculateCompositedSizeInCssPixels(); |
michael@0 | 1682 | CSSPoint maxScrollOffset = lastPaintScrollOffset + |
michael@0 | 1683 | CSSPoint(mLastContentPaintMetrics.mDisplayPort.XMost() - compositedSize.width, |
michael@0 | 1684 | mLastContentPaintMetrics.mDisplayPort.YMost() - compositedSize.height); |
michael@0 | 1685 | CSSPoint minScrollOffset = lastPaintScrollOffset + mLastContentPaintMetrics.mDisplayPort.TopLeft(); |
michael@0 | 1686 | |
michael@0 | 1687 | if (minScrollOffset.x < maxScrollOffset.x) { |
michael@0 | 1688 | currentScrollOffset.x = clamped(currentScrollOffset.x, minScrollOffset.x, maxScrollOffset.x); |
michael@0 | 1689 | } |
michael@0 | 1690 | if (minScrollOffset.y < maxScrollOffset.y) { |
michael@0 | 1691 | currentScrollOffset.y = clamped(currentScrollOffset.y, minScrollOffset.y, maxScrollOffset.y); |
michael@0 | 1692 | } |
michael@0 | 1693 | } |
michael@0 | 1694 | |
michael@0 | 1695 | LayerPoint translation = (currentScrollOffset - lastPaintScrollOffset) |
michael@0 | 1696 | * mLastContentPaintMetrics.LayersPixelsPerCSSPixel(); |
michael@0 | 1697 | |
michael@0 | 1698 | return ViewTransform(-translation, |
michael@0 | 1699 | mFrameMetrics.GetZoom() |
michael@0 | 1700 | / mLastContentPaintMetrics.mDevPixelsPerCSSPixel |
michael@0 | 1701 | / mFrameMetrics.GetParentResolution()); |
michael@0 | 1702 | } |
michael@0 | 1703 | |
michael@0 | 1704 | gfx3DMatrix AsyncPanZoomController::GetNontransientAsyncTransform() { |
michael@0 | 1705 | ReentrantMonitorAutoEnter lock(mMonitor); |
michael@0 | 1706 | return gfx3DMatrix::ScalingMatrix(mLastContentPaintMetrics.mResolution.scale, |
michael@0 | 1707 | mLastContentPaintMetrics.mResolution.scale, |
michael@0 | 1708 | 1.0f); |
michael@0 | 1709 | } |
michael@0 | 1710 | |
michael@0 | 1711 | gfx3DMatrix AsyncPanZoomController::GetTransformToLastDispatchedPaint() { |
michael@0 | 1712 | ReentrantMonitorAutoEnter lock(mMonitor); |
michael@0 | 1713 | LayerPoint scrollChange = (mLastContentPaintMetrics.GetScrollOffset() - mLastDispatchedPaintMetrics.GetScrollOffset()) |
michael@0 | 1714 | * mLastContentPaintMetrics.LayersPixelsPerCSSPixel(); |
michael@0 | 1715 | float zoomChange = mLastContentPaintMetrics.GetZoom().scale / mLastDispatchedPaintMetrics.GetZoom().scale; |
michael@0 | 1716 | return gfx3DMatrix::Translation(scrollChange.x, scrollChange.y, 0) * |
michael@0 | 1717 | gfx3DMatrix::ScalingMatrix(zoomChange, zoomChange, 1); |
michael@0 | 1718 | } |
michael@0 | 1719 | |
michael@0 | 1720 | void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aLayerMetrics, bool aIsFirstPaint) { |
michael@0 | 1721 | ReentrantMonitorAutoEnter lock(mMonitor); |
michael@0 | 1722 | |
michael@0 | 1723 | mLastContentPaintMetrics = aLayerMetrics; |
michael@0 | 1724 | UpdateTransformScale(); |
michael@0 | 1725 | |
michael@0 | 1726 | bool isDefault = mFrameMetrics.IsDefault(); |
michael@0 | 1727 | mFrameMetrics.mMayHaveTouchListeners = aLayerMetrics.mMayHaveTouchListeners; |
michael@0 | 1728 | APZC_LOG_FM(aLayerMetrics, "%p got a NotifyLayersUpdated with aIsFirstPaint=%d", this, aIsFirstPaint); |
michael@0 | 1729 | |
michael@0 | 1730 | LogRendertraceRect(GetGuid(), "page", "brown", aLayerMetrics.mScrollableRect); |
michael@0 | 1731 | LogRendertraceRect(GetGuid(), "painted displayport", "green", |
michael@0 | 1732 | aLayerMetrics.mDisplayPort + aLayerMetrics.GetScrollOffset()); |
michael@0 | 1733 | |
michael@0 | 1734 | mPaintThrottler.TaskComplete(GetFrameTime()); |
michael@0 | 1735 | bool needContentRepaint = false; |
michael@0 | 1736 | if (aLayerMetrics.mCompositionBounds.width == mFrameMetrics.mCompositionBounds.width && |
michael@0 | 1737 | aLayerMetrics.mCompositionBounds.height == mFrameMetrics.mCompositionBounds.height) { |
michael@0 | 1738 | // Remote content has sync'd up to the composition geometry |
michael@0 | 1739 | // change, so we can accept the viewport it's calculated. |
michael@0 | 1740 | if (mFrameMetrics.mViewport.width != aLayerMetrics.mViewport.width || |
michael@0 | 1741 | mFrameMetrics.mViewport.height != aLayerMetrics.mViewport.height) { |
michael@0 | 1742 | needContentRepaint = true; |
michael@0 | 1743 | } |
michael@0 | 1744 | mFrameMetrics.mViewport = aLayerMetrics.mViewport; |
michael@0 | 1745 | } |
michael@0 | 1746 | |
michael@0 | 1747 | // If the layers update was not triggered by our own repaint request, then |
michael@0 | 1748 | // we want to take the new scroll offset. Check the scroll generation as well |
michael@0 | 1749 | // to filter duplicate calls to NotifyLayersUpdated with the same scroll offset |
michael@0 | 1750 | // update message. |
michael@0 | 1751 | bool scrollOffsetUpdated = aLayerMetrics.GetScrollOffsetUpdated() |
michael@0 | 1752 | && (aLayerMetrics.GetScrollGeneration() != mFrameMetrics.GetScrollGeneration()); |
michael@0 | 1753 | |
michael@0 | 1754 | if (aIsFirstPaint || isDefault) { |
michael@0 | 1755 | // Initialize our internal state to something sane when the content |
michael@0 | 1756 | // that was just painted is something we knew nothing about previously |
michael@0 | 1757 | mPaintThrottler.ClearHistory(); |
michael@0 | 1758 | mPaintThrottler.SetMaxDurations(gfxPrefs::APZNumPaintDurationSamples()); |
michael@0 | 1759 | |
michael@0 | 1760 | mX.CancelTouch(); |
michael@0 | 1761 | mY.CancelTouch(); |
michael@0 | 1762 | SetState(NOTHING); |
michael@0 | 1763 | |
michael@0 | 1764 | mFrameMetrics = aLayerMetrics; |
michael@0 | 1765 | mLastDispatchedPaintMetrics = aLayerMetrics; |
michael@0 | 1766 | ShareCompositorFrameMetrics(); |
michael@0 | 1767 | } else { |
michael@0 | 1768 | // If we're not taking the aLayerMetrics wholesale we still need to pull |
michael@0 | 1769 | // in some things into our local mFrameMetrics because these things are |
michael@0 | 1770 | // determined by Gecko and our copy in mFrameMetrics may be stale. |
michael@0 | 1771 | |
michael@0 | 1772 | if (mFrameMetrics.mCompositionBounds.width == aLayerMetrics.mCompositionBounds.width && |
michael@0 | 1773 | mFrameMetrics.mDevPixelsPerCSSPixel == aLayerMetrics.mDevPixelsPerCSSPixel) { |
michael@0 | 1774 | float parentResolutionChange = aLayerMetrics.GetParentResolution().scale |
michael@0 | 1775 | / mFrameMetrics.GetParentResolution().scale; |
michael@0 | 1776 | mFrameMetrics.ZoomBy(parentResolutionChange); |
michael@0 | 1777 | } else { |
michael@0 | 1778 | // Take the new zoom as either device scale or composition width or both |
michael@0 | 1779 | // got changed (e.g. due to orientation change). |
michael@0 | 1780 | mFrameMetrics.SetZoom(aLayerMetrics.GetZoom()); |
michael@0 | 1781 | mFrameMetrics.mDevPixelsPerCSSPixel.scale = aLayerMetrics.mDevPixelsPerCSSPixel.scale; |
michael@0 | 1782 | } |
michael@0 | 1783 | mFrameMetrics.mScrollableRect = aLayerMetrics.mScrollableRect; |
michael@0 | 1784 | mFrameMetrics.mCompositionBounds = aLayerMetrics.mCompositionBounds; |
michael@0 | 1785 | mFrameMetrics.SetRootCompositionSize(aLayerMetrics.GetRootCompositionSize()); |
michael@0 | 1786 | mFrameMetrics.mResolution = aLayerMetrics.mResolution; |
michael@0 | 1787 | mFrameMetrics.mCumulativeResolution = aLayerMetrics.mCumulativeResolution; |
michael@0 | 1788 | mFrameMetrics.mHasScrollgrab = aLayerMetrics.mHasScrollgrab; |
michael@0 | 1789 | |
michael@0 | 1790 | if (scrollOffsetUpdated) { |
michael@0 | 1791 | APZC_LOG("%p updating scroll offset from (%f, %f) to (%f, %f)\n", this, |
michael@0 | 1792 | mFrameMetrics.GetScrollOffset().x, mFrameMetrics.GetScrollOffset().y, |
michael@0 | 1793 | aLayerMetrics.GetScrollOffset().x, aLayerMetrics.GetScrollOffset().y); |
michael@0 | 1794 | |
michael@0 | 1795 | mFrameMetrics.CopyScrollInfoFrom(aLayerMetrics); |
michael@0 | 1796 | |
michael@0 | 1797 | // Because of the scroll offset update, any inflight paint requests are |
michael@0 | 1798 | // going to be ignored by layout, and so mLastDispatchedPaintMetrics |
michael@0 | 1799 | // becomes incorrect for the purposes of calculating the LD transform. To |
michael@0 | 1800 | // correct this we need to update mLastDispatchedPaintMetrics to be the |
michael@0 | 1801 | // last thing we know was painted by Gecko. |
michael@0 | 1802 | mLastDispatchedPaintMetrics = aLayerMetrics; |
michael@0 | 1803 | } |
michael@0 | 1804 | } |
michael@0 | 1805 | |
michael@0 | 1806 | if (scrollOffsetUpdated) { |
michael@0 | 1807 | // Once layout issues a scroll offset update, it becomes impervious to |
michael@0 | 1808 | // scroll offset updates from APZ until we acknowledge the update it sent. |
michael@0 | 1809 | // This prevents APZ updates from clobbering scroll updates from other |
michael@0 | 1810 | // more "legitimate" sources like content scripts. |
michael@0 | 1811 | nsRefPtr<GeckoContentController> controller = GetGeckoContentController(); |
michael@0 | 1812 | if (controller) { |
michael@0 | 1813 | APZC_LOG("%p sending scroll update acknowledgement with gen %lu\n", this, aLayerMetrics.GetScrollGeneration()); |
michael@0 | 1814 | controller->AcknowledgeScrollUpdate(aLayerMetrics.GetScrollId(), |
michael@0 | 1815 | aLayerMetrics.GetScrollGeneration()); |
michael@0 | 1816 | } |
michael@0 | 1817 | } |
michael@0 | 1818 | |
michael@0 | 1819 | if (needContentRepaint) { |
michael@0 | 1820 | RequestContentRepaint(); |
michael@0 | 1821 | } |
michael@0 | 1822 | UpdateSharedCompositorFrameMetrics(); |
michael@0 | 1823 | } |
michael@0 | 1824 | |
michael@0 | 1825 | const FrameMetrics& AsyncPanZoomController::GetFrameMetrics() { |
michael@0 | 1826 | mMonitor.AssertCurrentThreadIn(); |
michael@0 | 1827 | return mFrameMetrics; |
michael@0 | 1828 | } |
michael@0 | 1829 | |
michael@0 | 1830 | void AsyncPanZoomController::ZoomToRect(CSSRect aRect) { |
michael@0 | 1831 | if (!aRect.IsFinite()) { |
michael@0 | 1832 | NS_WARNING("ZoomToRect got called with a non-finite rect; ignoring...\n"); |
michael@0 | 1833 | return; |
michael@0 | 1834 | } |
michael@0 | 1835 | |
michael@0 | 1836 | SetState(ANIMATING_ZOOM); |
michael@0 | 1837 | |
michael@0 | 1838 | { |
michael@0 | 1839 | ReentrantMonitorAutoEnter lock(mMonitor); |
michael@0 | 1840 | |
michael@0 | 1841 | ParentLayerIntRect compositionBounds = mFrameMetrics.mCompositionBounds; |
michael@0 | 1842 | CSSRect cssPageRect = mFrameMetrics.mScrollableRect; |
michael@0 | 1843 | CSSPoint scrollOffset = mFrameMetrics.GetScrollOffset(); |
michael@0 | 1844 | CSSToParentLayerScale currentZoom = mFrameMetrics.GetZoomToParent(); |
michael@0 | 1845 | CSSToParentLayerScale targetZoom; |
michael@0 | 1846 | |
michael@0 | 1847 | // The minimum zoom to prevent over-zoom-out. |
michael@0 | 1848 | // If the zoom factor is lower than this (i.e. we are zoomed more into the page), |
michael@0 | 1849 | // then the CSS content rect, in layers pixels, will be smaller than the |
michael@0 | 1850 | // composition bounds. If this happens, we can't fill the target composited |
michael@0 | 1851 | // area with this frame. |
michael@0 | 1852 | CSSToParentLayerScale localMinZoom(std::max((mZoomConstraints.mMinZoom * mFrameMetrics.mTransformScale).scale, |
michael@0 | 1853 | std::max(compositionBounds.width / cssPageRect.width, |
michael@0 | 1854 | compositionBounds.height / cssPageRect.height))); |
michael@0 | 1855 | CSSToParentLayerScale localMaxZoom = mZoomConstraints.mMaxZoom * mFrameMetrics.mTransformScale; |
michael@0 | 1856 | |
michael@0 | 1857 | if (!aRect.IsEmpty()) { |
michael@0 | 1858 | // Intersect the zoom-to-rect to the CSS rect to make sure it fits. |
michael@0 | 1859 | aRect = aRect.Intersect(cssPageRect); |
michael@0 | 1860 | targetZoom = CSSToParentLayerScale(std::min(compositionBounds.width / aRect.width, |
michael@0 | 1861 | compositionBounds.height / aRect.height)); |
michael@0 | 1862 | } |
michael@0 | 1863 | // 1. If the rect is empty, request received from browserElementScrolling.js |
michael@0 | 1864 | // 2. currentZoom is equal to mZoomConstraints.mMaxZoom and user still double-tapping it |
michael@0 | 1865 | // 3. currentZoom is equal to localMinZoom and user still double-tapping it |
michael@0 | 1866 | // Treat these three cases as a request to zoom out as much as possible. |
michael@0 | 1867 | if (aRect.IsEmpty() || |
michael@0 | 1868 | (currentZoom == localMaxZoom && targetZoom >= localMaxZoom) || |
michael@0 | 1869 | (currentZoom == localMinZoom && targetZoom <= localMinZoom)) { |
michael@0 | 1870 | CSSSize compositedSize = mFrameMetrics.CalculateCompositedSizeInCssPixels(); |
michael@0 | 1871 | float y = scrollOffset.y; |
michael@0 | 1872 | float newHeight = |
michael@0 | 1873 | cssPageRect.width * (compositedSize.height / compositedSize.width); |
michael@0 | 1874 | float dh = compositedSize.height - newHeight; |
michael@0 | 1875 | |
michael@0 | 1876 | aRect = CSSRect(0.0f, |
michael@0 | 1877 | y + dh/2, |
michael@0 | 1878 | cssPageRect.width, |
michael@0 | 1879 | newHeight); |
michael@0 | 1880 | aRect = aRect.Intersect(cssPageRect); |
michael@0 | 1881 | targetZoom = CSSToParentLayerScale(std::min(compositionBounds.width / aRect.width, |
michael@0 | 1882 | compositionBounds.height / aRect.height)); |
michael@0 | 1883 | } |
michael@0 | 1884 | |
michael@0 | 1885 | targetZoom.scale = clamped(targetZoom.scale, localMinZoom.scale, localMaxZoom.scale); |
michael@0 | 1886 | FrameMetrics endZoomToMetrics = mFrameMetrics; |
michael@0 | 1887 | endZoomToMetrics.SetZoom(targetZoom / mFrameMetrics.mTransformScale); |
michael@0 | 1888 | |
michael@0 | 1889 | // Adjust the zoomToRect to a sensible position to prevent overscrolling. |
michael@0 | 1890 | CSSSize sizeAfterZoom = endZoomToMetrics.CalculateCompositedSizeInCssPixels(); |
michael@0 | 1891 | |
michael@0 | 1892 | // If either of these conditions are met, the page will be |
michael@0 | 1893 | // overscrolled after zoomed |
michael@0 | 1894 | if (aRect.y + sizeAfterZoom.height > cssPageRect.height) { |
michael@0 | 1895 | aRect.y = cssPageRect.height - sizeAfterZoom.height; |
michael@0 | 1896 | aRect.y = aRect.y > 0 ? aRect.y : 0; |
michael@0 | 1897 | } |
michael@0 | 1898 | if (aRect.x + sizeAfterZoom.width > cssPageRect.width) { |
michael@0 | 1899 | aRect.x = cssPageRect.width - sizeAfterZoom.width; |
michael@0 | 1900 | aRect.x = aRect.x > 0 ? aRect.x : 0; |
michael@0 | 1901 | } |
michael@0 | 1902 | |
michael@0 | 1903 | endZoomToMetrics.SetScrollOffset(aRect.TopLeft()); |
michael@0 | 1904 | endZoomToMetrics.SetDisplayPortMargins( |
michael@0 | 1905 | CalculatePendingDisplayPort(endZoomToMetrics, |
michael@0 | 1906 | ScreenPoint(0,0), |
michael@0 | 1907 | 0)); |
michael@0 | 1908 | endZoomToMetrics.SetUseDisplayPortMargins(); |
michael@0 | 1909 | |
michael@0 | 1910 | StartAnimation(new ZoomAnimation( |
michael@0 | 1911 | mFrameMetrics.GetScrollOffset(), |
michael@0 | 1912 | mFrameMetrics.GetZoom(), |
michael@0 | 1913 | endZoomToMetrics.GetScrollOffset(), |
michael@0 | 1914 | endZoomToMetrics.GetZoom())); |
michael@0 | 1915 | |
michael@0 | 1916 | // Schedule a repaint now, so the new displayport will be painted before the |
michael@0 | 1917 | // animation finishes. |
michael@0 | 1918 | RequestContentRepaint(endZoomToMetrics); |
michael@0 | 1919 | } |
michael@0 | 1920 | } |
michael@0 | 1921 | |
michael@0 | 1922 | void AsyncPanZoomController::ContentReceivedTouch(bool aPreventDefault) { |
michael@0 | 1923 | mTouchBlockState.mPreventDefaultSet = true; |
michael@0 | 1924 | mTouchBlockState.mPreventDefault = aPreventDefault; |
michael@0 | 1925 | CheckContentResponse(); |
michael@0 | 1926 | } |
michael@0 | 1927 | |
michael@0 | 1928 | void AsyncPanZoomController::CheckContentResponse() { |
michael@0 | 1929 | bool canProceedToTouchState = true; |
michael@0 | 1930 | |
michael@0 | 1931 | if (mFrameMetrics.mMayHaveTouchListeners) { |
michael@0 | 1932 | canProceedToTouchState &= mTouchBlockState.mPreventDefaultSet; |
michael@0 | 1933 | } |
michael@0 | 1934 | |
michael@0 | 1935 | if (mTouchActionPropertyEnabled) { |
michael@0 | 1936 | canProceedToTouchState &= mTouchBlockState.mAllowedTouchBehaviorSet; |
michael@0 | 1937 | } |
michael@0 | 1938 | |
michael@0 | 1939 | if (!canProceedToTouchState) { |
michael@0 | 1940 | return; |
michael@0 | 1941 | } |
michael@0 | 1942 | |
michael@0 | 1943 | if (mContentResponseTimeoutTask) { |
michael@0 | 1944 | mContentResponseTimeoutTask->Cancel(); |
michael@0 | 1945 | mContentResponseTimeoutTask = nullptr; |
michael@0 | 1946 | } |
michael@0 | 1947 | |
michael@0 | 1948 | if (mState == WAITING_CONTENT_RESPONSE) { |
michael@0 | 1949 | if (!mTouchBlockState.mPreventDefault) { |
michael@0 | 1950 | SetState(NOTHING); |
michael@0 | 1951 | } |
michael@0 | 1952 | |
michael@0 | 1953 | mHandlingTouchQueue = true; |
michael@0 | 1954 | |
michael@0 | 1955 | while (!mTouchQueue.IsEmpty()) { |
michael@0 | 1956 | if (!mTouchBlockState.mPreventDefault) { |
michael@0 | 1957 | HandleInputEvent(mTouchQueue[0]); |
michael@0 | 1958 | } |
michael@0 | 1959 | |
michael@0 | 1960 | if (mTouchQueue[0].mType == MultiTouchInput::MULTITOUCH_END || |
michael@0 | 1961 | mTouchQueue[0].mType == MultiTouchInput::MULTITOUCH_CANCEL) { |
michael@0 | 1962 | mTouchQueue.RemoveElementAt(0); |
michael@0 | 1963 | break; |
michael@0 | 1964 | } |
michael@0 | 1965 | |
michael@0 | 1966 | mTouchQueue.RemoveElementAt(0); |
michael@0 | 1967 | } |
michael@0 | 1968 | |
michael@0 | 1969 | mHandlingTouchQueue = false; |
michael@0 | 1970 | } |
michael@0 | 1971 | } |
michael@0 | 1972 | |
michael@0 | 1973 | bool AsyncPanZoomController::TouchActionAllowPinchZoom() { |
michael@0 | 1974 | if (!mTouchActionPropertyEnabled) { |
michael@0 | 1975 | return true; |
michael@0 | 1976 | } |
michael@0 | 1977 | // Pointer events specification implies all touch points to allow zoom |
michael@0 | 1978 | // to perform it. |
michael@0 | 1979 | for (size_t i = 0; i < mTouchBlockState.mAllowedTouchBehaviors.Length(); i++) { |
michael@0 | 1980 | if (!(mTouchBlockState.mAllowedTouchBehaviors[i] & AllowedTouchBehavior::PINCH_ZOOM)) { |
michael@0 | 1981 | return false; |
michael@0 | 1982 | } |
michael@0 | 1983 | } |
michael@0 | 1984 | return true; |
michael@0 | 1985 | } |
michael@0 | 1986 | |
michael@0 | 1987 | bool AsyncPanZoomController::TouchActionAllowDoubleTapZoom() { |
michael@0 | 1988 | if (!mTouchActionPropertyEnabled) { |
michael@0 | 1989 | return true; |
michael@0 | 1990 | } |
michael@0 | 1991 | for (size_t i = 0; i < mTouchBlockState.mAllowedTouchBehaviors.Length(); i++) { |
michael@0 | 1992 | if (!(mTouchBlockState.mAllowedTouchBehaviors[i] & AllowedTouchBehavior::DOUBLE_TAP_ZOOM)) { |
michael@0 | 1993 | return false; |
michael@0 | 1994 | } |
michael@0 | 1995 | } |
michael@0 | 1996 | return true; |
michael@0 | 1997 | } |
michael@0 | 1998 | |
michael@0 | 1999 | AsyncPanZoomController::TouchBehaviorFlags |
michael@0 | 2000 | AsyncPanZoomController::GetTouchBehavior(uint32_t touchIndex) { |
michael@0 | 2001 | if (touchIndex < mTouchBlockState.mAllowedTouchBehaviors.Length()) { |
michael@0 | 2002 | return mTouchBlockState.mAllowedTouchBehaviors[touchIndex]; |
michael@0 | 2003 | } |
michael@0 | 2004 | return DefaultTouchBehavior; |
michael@0 | 2005 | } |
michael@0 | 2006 | |
michael@0 | 2007 | AsyncPanZoomController::TouchBehaviorFlags |
michael@0 | 2008 | AsyncPanZoomController::GetAllowedTouchBehavior(ScreenIntPoint& aPoint) { |
michael@0 | 2009 | // Here we need to perform a hit testing over the touch-action regions attached to the |
michael@0 | 2010 | // layer associated with current apzc. |
michael@0 | 2011 | // Currently they are in progress, for more info see bug 928833. |
michael@0 | 2012 | return AllowedTouchBehavior::UNKNOWN; |
michael@0 | 2013 | } |
michael@0 | 2014 | |
michael@0 | 2015 | void AsyncPanZoomController::SetAllowedTouchBehavior(const nsTArray<TouchBehaviorFlags>& aBehaviors) { |
michael@0 | 2016 | mTouchBlockState.mAllowedTouchBehaviors.Clear(); |
michael@0 | 2017 | mTouchBlockState.mAllowedTouchBehaviors.AppendElements(aBehaviors); |
michael@0 | 2018 | mTouchBlockState.mAllowedTouchBehaviorSet = true; |
michael@0 | 2019 | CheckContentResponse(); |
michael@0 | 2020 | } |
michael@0 | 2021 | |
michael@0 | 2022 | void AsyncPanZoomController::SetState(PanZoomState aNewState) { |
michael@0 | 2023 | |
michael@0 | 2024 | PanZoomState oldState; |
michael@0 | 2025 | |
michael@0 | 2026 | // Intentional scoping for mutex |
michael@0 | 2027 | { |
michael@0 | 2028 | ReentrantMonitorAutoEnter lock(mMonitor); |
michael@0 | 2029 | oldState = mState; |
michael@0 | 2030 | mState = aNewState; |
michael@0 | 2031 | } |
michael@0 | 2032 | |
michael@0 | 2033 | if (nsRefPtr<GeckoContentController> controller = GetGeckoContentController()) { |
michael@0 | 2034 | if (!IsTransformingState(oldState) && IsTransformingState(aNewState)) { |
michael@0 | 2035 | controller->NotifyAPZStateChange( |
michael@0 | 2036 | GetGuid(), APZStateChange::TransformBegin); |
michael@0 | 2037 | } else if (IsTransformingState(oldState) && !IsTransformingState(aNewState)) { |
michael@0 | 2038 | controller->NotifyAPZStateChange( |
michael@0 | 2039 | GetGuid(), APZStateChange::TransformEnd); |
michael@0 | 2040 | } |
michael@0 | 2041 | } |
michael@0 | 2042 | } |
michael@0 | 2043 | |
michael@0 | 2044 | bool AsyncPanZoomController::IsTransformingState(PanZoomState aState) { |
michael@0 | 2045 | return !(aState == NOTHING || aState == TOUCHING || aState == WAITING_CONTENT_RESPONSE); |
michael@0 | 2046 | } |
michael@0 | 2047 | |
michael@0 | 2048 | bool AsyncPanZoomController::IsPanningState(PanZoomState aState) { |
michael@0 | 2049 | return (aState == PANNING || aState == PANNING_LOCKED_X || aState == PANNING_LOCKED_Y); |
michael@0 | 2050 | } |
michael@0 | 2051 | |
michael@0 | 2052 | void AsyncPanZoomController::SetContentResponseTimer() { |
michael@0 | 2053 | if (!mContentResponseTimeoutTask) { |
michael@0 | 2054 | mContentResponseTimeoutTask = |
michael@0 | 2055 | NewRunnableMethod(this, &AsyncPanZoomController::TimeoutContentResponse); |
michael@0 | 2056 | |
michael@0 | 2057 | PostDelayedTask(mContentResponseTimeoutTask, gfxPrefs::APZContentResponseTimeout()); |
michael@0 | 2058 | } |
michael@0 | 2059 | } |
michael@0 | 2060 | |
michael@0 | 2061 | void AsyncPanZoomController::TimeoutContentResponse() { |
michael@0 | 2062 | mContentResponseTimeoutTask = nullptr; |
michael@0 | 2063 | ContentReceivedTouch(false); |
michael@0 | 2064 | } |
michael@0 | 2065 | |
michael@0 | 2066 | void AsyncPanZoomController::UpdateZoomConstraints(const ZoomConstraints& aConstraints) { |
michael@0 | 2067 | APZC_LOG("%p updating zoom constraints to %d %d %f %f\n", this, aConstraints.mAllowZoom, |
michael@0 | 2068 | aConstraints.mAllowDoubleTapZoom, aConstraints.mMinZoom.scale, aConstraints.mMaxZoom.scale); |
michael@0 | 2069 | if (IsNaN(aConstraints.mMinZoom.scale) || IsNaN(aConstraints.mMaxZoom.scale)) { |
michael@0 | 2070 | NS_WARNING("APZC received zoom constraints with NaN values; dropping...\n"); |
michael@0 | 2071 | return; |
michael@0 | 2072 | } |
michael@0 | 2073 | // inf float values and other bad cases should be sanitized by the code below. |
michael@0 | 2074 | mZoomConstraints.mAllowZoom = aConstraints.mAllowZoom; |
michael@0 | 2075 | mZoomConstraints.mAllowDoubleTapZoom = aConstraints.mAllowDoubleTapZoom; |
michael@0 | 2076 | mZoomConstraints.mMinZoom = (MIN_ZOOM > aConstraints.mMinZoom ? MIN_ZOOM : aConstraints.mMinZoom); |
michael@0 | 2077 | mZoomConstraints.mMaxZoom = (MAX_ZOOM > aConstraints.mMaxZoom ? aConstraints.mMaxZoom : MAX_ZOOM); |
michael@0 | 2078 | if (mZoomConstraints.mMaxZoom < mZoomConstraints.mMinZoom) { |
michael@0 | 2079 | mZoomConstraints.mMaxZoom = mZoomConstraints.mMinZoom; |
michael@0 | 2080 | } |
michael@0 | 2081 | } |
michael@0 | 2082 | |
michael@0 | 2083 | ZoomConstraints |
michael@0 | 2084 | AsyncPanZoomController::GetZoomConstraints() const |
michael@0 | 2085 | { |
michael@0 | 2086 | return mZoomConstraints; |
michael@0 | 2087 | } |
michael@0 | 2088 | |
michael@0 | 2089 | |
michael@0 | 2090 | void AsyncPanZoomController::PostDelayedTask(Task* aTask, int aDelayMs) { |
michael@0 | 2091 | nsRefPtr<GeckoContentController> controller = GetGeckoContentController(); |
michael@0 | 2092 | if (controller) { |
michael@0 | 2093 | controller->PostDelayedTask(aTask, aDelayMs); |
michael@0 | 2094 | } |
michael@0 | 2095 | } |
michael@0 | 2096 | |
michael@0 | 2097 | void AsyncPanZoomController::SendAsyncScrollEvent() { |
michael@0 | 2098 | nsRefPtr<GeckoContentController> controller = GetGeckoContentController(); |
michael@0 | 2099 | if (!controller) { |
michael@0 | 2100 | return; |
michael@0 | 2101 | } |
michael@0 | 2102 | |
michael@0 | 2103 | bool isRoot; |
michael@0 | 2104 | CSSRect contentRect; |
michael@0 | 2105 | CSSSize scrollableSize; |
michael@0 | 2106 | { |
michael@0 | 2107 | ReentrantMonitorAutoEnter lock(mMonitor); |
michael@0 | 2108 | |
michael@0 | 2109 | isRoot = mFrameMetrics.mIsRoot; |
michael@0 | 2110 | scrollableSize = mFrameMetrics.mScrollableRect.Size(); |
michael@0 | 2111 | contentRect = mFrameMetrics.CalculateCompositedRectInCssPixels(); |
michael@0 | 2112 | contentRect.MoveTo(mCurrentAsyncScrollOffset); |
michael@0 | 2113 | } |
michael@0 | 2114 | |
michael@0 | 2115 | controller->SendAsyncScrollDOMEvent(isRoot, contentRect, scrollableSize); |
michael@0 | 2116 | } |
michael@0 | 2117 | |
michael@0 | 2118 | bool AsyncPanZoomController::Matches(const ScrollableLayerGuid& aGuid) |
michael@0 | 2119 | { |
michael@0 | 2120 | return aGuid == GetGuid(); |
michael@0 | 2121 | } |
michael@0 | 2122 | |
michael@0 | 2123 | void AsyncPanZoomController::GetGuid(ScrollableLayerGuid* aGuidOut) |
michael@0 | 2124 | { |
michael@0 | 2125 | if (aGuidOut) { |
michael@0 | 2126 | *aGuidOut = GetGuid(); |
michael@0 | 2127 | } |
michael@0 | 2128 | } |
michael@0 | 2129 | |
michael@0 | 2130 | ScrollableLayerGuid AsyncPanZoomController::GetGuid() |
michael@0 | 2131 | { |
michael@0 | 2132 | return ScrollableLayerGuid(mLayersId, mFrameMetrics); |
michael@0 | 2133 | } |
michael@0 | 2134 | |
michael@0 | 2135 | void AsyncPanZoomController::UpdateSharedCompositorFrameMetrics() |
michael@0 | 2136 | { |
michael@0 | 2137 | mMonitor.AssertCurrentThreadIn(); |
michael@0 | 2138 | |
michael@0 | 2139 | FrameMetrics* frame = mSharedFrameMetricsBuffer ? |
michael@0 | 2140 | static_cast<FrameMetrics*>(mSharedFrameMetricsBuffer->memory()) : nullptr; |
michael@0 | 2141 | |
michael@0 | 2142 | if (frame && mSharedLock && gfxPrefs::UseProgressiveTilePainting()) { |
michael@0 | 2143 | mSharedLock->Lock(); |
michael@0 | 2144 | *frame = mFrameMetrics; |
michael@0 | 2145 | mSharedLock->Unlock(); |
michael@0 | 2146 | } |
michael@0 | 2147 | } |
michael@0 | 2148 | |
michael@0 | 2149 | void AsyncPanZoomController::ShareCompositorFrameMetrics() { |
michael@0 | 2150 | |
michael@0 | 2151 | PCompositorParent* compositor = |
michael@0 | 2152 | (mCrossProcessCompositorParent ? mCrossProcessCompositorParent : mCompositorParent.get()); |
michael@0 | 2153 | |
michael@0 | 2154 | // Only create the shared memory buffer if it hasn't already been created, |
michael@0 | 2155 | // we are using progressive tile painting, and we have a |
michael@0 | 2156 | // compositor to pass the shared memory back to the content process/thread. |
michael@0 | 2157 | if (!mSharedFrameMetricsBuffer && compositor && gfxPrefs::UseProgressiveTilePainting()) { |
michael@0 | 2158 | |
michael@0 | 2159 | // Create shared memory and initialize it with the current FrameMetrics value |
michael@0 | 2160 | mSharedFrameMetricsBuffer = new ipc::SharedMemoryBasic; |
michael@0 | 2161 | FrameMetrics* frame = nullptr; |
michael@0 | 2162 | mSharedFrameMetricsBuffer->Create(sizeof(FrameMetrics)); |
michael@0 | 2163 | mSharedFrameMetricsBuffer->Map(sizeof(FrameMetrics)); |
michael@0 | 2164 | frame = static_cast<FrameMetrics*>(mSharedFrameMetricsBuffer->memory()); |
michael@0 | 2165 | |
michael@0 | 2166 | if (frame) { |
michael@0 | 2167 | |
michael@0 | 2168 | { // scope the monitor, only needed to copy the FrameMetrics. |
michael@0 | 2169 | ReentrantMonitorAutoEnter lock(mMonitor); |
michael@0 | 2170 | *frame = mFrameMetrics; |
michael@0 | 2171 | } |
michael@0 | 2172 | |
michael@0 | 2173 | // Get the process id of the content process |
michael@0 | 2174 | base::ProcessHandle processHandle = compositor->OtherProcess(); |
michael@0 | 2175 | ipc::SharedMemoryBasic::Handle mem = ipc::SharedMemoryBasic::NULLHandle(); |
michael@0 | 2176 | |
michael@0 | 2177 | // Get the shared memory handle to share with the content process |
michael@0 | 2178 | mSharedFrameMetricsBuffer->ShareToProcess(processHandle, &mem); |
michael@0 | 2179 | |
michael@0 | 2180 | // Get the cross process mutex handle to share with the content process |
michael@0 | 2181 | mSharedLock = new CrossProcessMutex("AsyncPanZoomControlLock"); |
michael@0 | 2182 | CrossProcessMutexHandle handle = mSharedLock->ShareToProcess(processHandle); |
michael@0 | 2183 | |
michael@0 | 2184 | // Send the shared memory handle and cross process handle to the content |
michael@0 | 2185 | // process by an asynchronous ipc call. Include the APZC unique ID |
michael@0 | 2186 | // so the content process know which APZC sent this shared FrameMetrics. |
michael@0 | 2187 | if (!compositor->SendSharedCompositorFrameMetrics(mem, handle, mAPZCId)) { |
michael@0 | 2188 | APZC_LOG("%p failed to share FrameMetrics with content process.", this); |
michael@0 | 2189 | } |
michael@0 | 2190 | } |
michael@0 | 2191 | } |
michael@0 | 2192 | } |
michael@0 | 2193 | |
michael@0 | 2194 | ParentLayerPoint AsyncPanZoomController::ToParentLayerCoords(const ScreenPoint& aPoint) |
michael@0 | 2195 | { |
michael@0 | 2196 | return TransformTo<ParentLayerPixel>(GetNontransientAsyncTransform() * GetCSSTransform(), aPoint); |
michael@0 | 2197 | } |
michael@0 | 2198 | |
michael@0 | 2199 | void AsyncPanZoomController::UpdateTransformScale() |
michael@0 | 2200 | { |
michael@0 | 2201 | gfx3DMatrix nontransientTransforms = GetNontransientAsyncTransform() * GetCSSTransform(); |
michael@0 | 2202 | if (!FuzzyEqualsMultiplicative(nontransientTransforms.GetXScale(), nontransientTransforms.GetYScale())) { |
michael@0 | 2203 | NS_WARNING("The x- and y-scales of the nontransient transforms should be equal"); |
michael@0 | 2204 | } |
michael@0 | 2205 | mFrameMetrics.mTransformScale.scale = nontransientTransforms.GetXScale(); |
michael@0 | 2206 | } |
michael@0 | 2207 | |
michael@0 | 2208 | } |
michael@0 | 2209 | } |