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