Bug 1911926 - Update test expectations for css/css-highlight-api/painting/css-target...
[gecko.git] / layout / base / TouchManager.cpp
blob36df360218a5a6fc37814e8dd9f751ee14ccf38c
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 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 */
8 #include "TouchManager.h"
10 #include "Units.h"
11 #include "mozilla/EventForwards.h"
12 #include "mozilla/PresShell.h"
13 #include "mozilla/StaticPrefs_test.h"
14 #include "mozilla/TimeStamp.h"
15 #include "mozilla/dom/Document.h"
16 #include "mozilla/dom/EventTarget.h"
17 #include "mozilla/dom/PointerEventHandler.h"
18 #include "mozilla/layers/InputAPZContext.h"
19 #include "nsIContent.h"
20 #include "nsIFrame.h"
21 #include "nsLayoutUtils.h"
22 #include "nsView.h"
23 #include "PositionedEventTargeting.h"
25 using namespace mozilla::dom;
27 namespace mozilla {
29 StaticAutoPtr<nsTHashMap<nsUint32HashKey, TouchManager::TouchInfo>>
30 TouchManager::sCaptureTouchList;
31 layers::LayersId TouchManager::sCaptureTouchLayersId;
32 TimeStamp TouchManager::sSingleTouchStartTimeStamp;
33 LayoutDeviceIntPoint TouchManager::sSingleTouchStartPoint;
34 bool TouchManager::sPrecedingTouchPointerDownConsumedByContent = false;
36 /*static*/
37 void TouchManager::InitializeStatics() {
38 NS_ASSERTION(!sCaptureTouchList, "InitializeStatics called multiple times!");
39 sCaptureTouchList = new nsTHashMap<nsUint32HashKey, TouchManager::TouchInfo>;
40 sCaptureTouchLayersId = layers::LayersId{0};
43 /*static*/
44 void TouchManager::ReleaseStatics() {
45 NS_ASSERTION(sCaptureTouchList, "ReleaseStatics called without Initialize!");
46 sCaptureTouchList = nullptr;
49 void TouchManager::Init(PresShell* aPresShell, Document* aDocument) {
50 mPresShell = aPresShell;
51 mDocument = aDocument;
54 void TouchManager::Destroy() {
55 EvictTouches(mDocument);
56 mDocument = nullptr;
57 mPresShell = nullptr;
60 static nsIContent* GetNonAnonymousAncestor(EventTarget* aTarget) {
61 nsIContent* content = nsIContent::FromEventTargetOrNull(aTarget);
62 if (content && content->IsInNativeAnonymousSubtree()) {
63 content = content->FindFirstNonChromeOnlyAccessContent();
65 return content;
68 /*static*/
69 void TouchManager::EvictTouchPoint(RefPtr<Touch>& aTouch,
70 Document* aLimitToDocument) {
71 nsCOMPtr<nsINode> node(
72 nsINode::FromEventTargetOrNull(aTouch->mOriginalTarget));
73 if (node) {
74 Document* doc = node->GetComposedDoc();
75 if (doc && (!aLimitToDocument || aLimitToDocument == doc)) {
76 PresShell* presShell = doc->GetPresShell();
77 if (presShell) {
78 nsIFrame* frame = presShell->GetRootFrame();
79 if (frame) {
80 nsCOMPtr<nsIWidget> widget =
81 frame->GetView()->GetNearestWidget(nullptr);
82 if (widget) {
83 WidgetTouchEvent event(true, eTouchEnd, widget);
84 event.mTouches.AppendElement(aTouch);
85 nsEventStatus status;
86 widget->DispatchEvent(&event, status);
92 if (!node || !aLimitToDocument || node->OwnerDoc() == aLimitToDocument) {
93 sCaptureTouchList->Remove(aTouch->Identifier());
97 /*static*/
98 void TouchManager::AppendToTouchList(
99 WidgetTouchEvent::TouchArrayBase* aTouchList) {
100 for (const auto& data : sCaptureTouchList->Values()) {
101 const RefPtr<Touch>& touch = data.mTouch;
102 touch->mChanged = false;
103 aTouchList->AppendElement(touch);
107 void TouchManager::EvictTouches(Document* aLimitToDocument) {
108 WidgetTouchEvent::AutoTouchArray touches;
109 AppendToTouchList(&touches);
110 for (uint32_t i = 0; i < touches.Length(); ++i) {
111 EvictTouchPoint(touches[i], aLimitToDocument);
113 sCaptureTouchLayersId = layers::LayersId{0};
116 /* static */
117 nsIFrame* TouchManager::SetupTarget(WidgetTouchEvent* aEvent,
118 nsIFrame* aFrame) {
119 MOZ_ASSERT(aEvent);
121 if (!aEvent || aEvent->mMessage != eTouchStart) {
122 // All touch events except for touchstart use a captured target.
123 return aFrame;
126 nsIFrame* target = aFrame;
127 for (int32_t i = aEvent->mTouches.Length(); i;) {
128 --i;
129 dom::Touch* touch = aEvent->mTouches[i];
131 int32_t id = touch->Identifier();
132 if (!TouchManager::HasCapturedTouch(id)) {
133 // find the target for this touch
134 RelativeTo relativeTo{aFrame};
135 nsPoint eventPoint = nsLayoutUtils::GetEventCoordinatesRelativeTo(
136 aEvent, touch->mRefPoint, relativeTo);
137 target = FindFrameTargetedByInputEvent(aEvent, relativeTo, eventPoint);
138 if (target) {
139 nsCOMPtr<nsIContent> targetContent;
140 target->GetContentForEvent(aEvent, getter_AddRefs(targetContent));
141 touch->SetTouchTarget(targetContent
142 ? targetContent->GetAsElementOrParentElement()
143 : nullptr);
144 } else {
145 aEvent->mTouches.RemoveElementAt(i);
147 } else {
148 // This touch is an old touch, we need to ensure that is not
149 // marked as changed and set its target correctly
150 touch->mChanged = false;
151 RefPtr<dom::Touch> oldTouch = TouchManager::GetCapturedTouch(id);
152 if (oldTouch) {
153 touch->SetTouchTarget(oldTouch->mOriginalTarget);
157 return target;
160 /* static */
161 nsIFrame* TouchManager::SuppressInvalidPointsAndGetTargetedFrame(
162 WidgetTouchEvent* aEvent) {
163 MOZ_ASSERT(aEvent);
165 if (!aEvent || aEvent->mMessage != eTouchStart) {
166 // All touch events except for touchstart use a captured target.
167 return nullptr;
170 // if this is a continuing session, ensure that all these events are
171 // in the same document by taking the target of the events already in
172 // the capture list
173 nsCOMPtr<nsIContent> anyTarget;
174 if (aEvent->mTouches.Length() > 1) {
175 anyTarget = TouchManager::GetAnyCapturedTouchTarget();
178 nsIFrame* frame = nullptr;
179 for (uint32_t i = aEvent->mTouches.Length(); i;) {
180 --i;
181 dom::Touch* touch = aEvent->mTouches[i];
182 if (TouchManager::HasCapturedTouch(touch->Identifier())) {
183 continue;
186 MOZ_ASSERT(touch->mOriginalTarget);
187 nsIContent* const targetContent =
188 nsIContent::FromEventTargetOrNull(touch->GetTarget());
189 if (MOZ_UNLIKELY(!targetContent)) {
190 touch->mIsTouchEventSuppressed = true;
191 continue;
194 // Even if the target content is not connected, we should dispatch the touch
195 // start event except when the target content is owned by different
196 // document.
197 if (MOZ_UNLIKELY(!targetContent->IsInComposedDoc())) {
198 if (anyTarget && anyTarget->OwnerDoc() != targetContent->OwnerDoc()) {
199 touch->mIsTouchEventSuppressed = true;
200 continue;
202 if (!anyTarget) {
203 anyTarget = targetContent;
205 touch->SetTouchTarget(targetContent->GetAsElementOrParentElement());
206 if (PresShell* const presShell =
207 targetContent->OwnerDoc()->GetPresShell()) {
208 if (nsIFrame* rootFrame = presShell->GetRootFrame()) {
209 frame = rootFrame;
212 continue;
215 nsIFrame* targetFrame = targetContent->GetPrimaryFrame();
216 if (targetFrame && !anyTarget) {
217 anyTarget = targetContent;
218 } else {
219 nsIFrame* newTargetFrame = nullptr;
220 for (nsIFrame* f = targetFrame; f;
221 f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
222 if (f->PresContext()->Document() == anyTarget->OwnerDoc()) {
223 newTargetFrame = f;
224 break;
226 // We must be in a subdocument so jump directly to the root frame.
227 // GetParentOrPlaceholderForCrossDoc gets called immediately to
228 // jump up to the containing document.
229 f = f->PresShell()->GetRootFrame();
231 // if we couldn't find a target frame in the same document as
232 // anyTarget, remove the touch from the capture touch list, as
233 // well as the event->mTouches array. touchmove events that aren't
234 // in the captured touch list will be discarded
235 if (!newTargetFrame) {
236 touch->mIsTouchEventSuppressed = true;
237 } else {
238 targetFrame = newTargetFrame;
239 nsCOMPtr<nsIContent> newTargetContent;
240 targetFrame->GetContentForEvent(aEvent,
241 getter_AddRefs(newTargetContent));
242 touch->SetTouchTarget(
243 newTargetContent ? newTargetContent->GetAsElementOrParentElement()
244 : nullptr);
247 if (targetFrame) {
248 frame = targetFrame;
251 return frame;
254 bool TouchManager::PreHandleEvent(WidgetEvent* aEvent, nsEventStatus* aStatus,
255 bool& aTouchIsNew,
256 nsCOMPtr<nsIContent>& aCurrentEventContent) {
257 MOZ_DIAGNOSTIC_ASSERT(aEvent->IsTrusted());
259 // NOTE: If you need to handle new event messages here, you need to add new
260 // cases in PresShell::EventHandler::PrepareToDispatchEvent().
261 switch (aEvent->mMessage) {
262 case eTouchStart: {
263 WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
264 // if there is only one touch in this touchstart event, assume that it is
265 // the start of a new touch session and evict any old touches in the
266 // queue
267 if (touchEvent->mTouches.Length() == 1) {
268 EvictTouches();
269 // Per
270 // https://w3c.github.io/touch-events/#touchevent-implementer-s-note,
271 // all touch event should be dispatched to the same document that first
272 // touch event associated to. We cache layers id of the first touchstart
273 // event, all subsequent touch events will use the same layers id.
274 sCaptureTouchLayersId = aEvent->mLayersId;
275 sSingleTouchStartTimeStamp = aEvent->mTimeStamp;
276 sSingleTouchStartPoint = touchEvent->mTouches[0]->mRefPoint;
277 const PointerInfo* pointerInfo = PointerEventHandler::GetPointerInfo(
278 touchEvent->mTouches[0]->Identifier());
279 sPrecedingTouchPointerDownConsumedByContent =
280 pointerInfo && pointerInfo->mPreventMouseEventByContent;
281 } else {
282 touchEvent->mLayersId = sCaptureTouchLayersId;
283 sSingleTouchStartTimeStamp = TimeStamp();
285 // Add any new touches to the queue
286 WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
287 for (int32_t i = touches.Length(); i;) {
288 --i;
289 Touch* touch = touches[i];
290 int32_t id = touch->Identifier();
291 if (!sCaptureTouchList->Get(id, nullptr)) {
292 // If it is not already in the queue, it is a new touch
293 touch->mChanged = true;
295 touch->mMessage = aEvent->mMessage;
296 TouchInfo info = {
297 touch, GetNonAnonymousAncestor(touch->mOriginalTarget), true};
298 sCaptureTouchList->InsertOrUpdate(id, info);
299 if (touch->mIsTouchEventSuppressed) {
300 // We're going to dispatch touch event. Remove this touch instance if
301 // it is suppressed.
302 touches.RemoveElementAt(i);
303 continue;
306 break;
308 case eTouchMove: {
309 // Check for touches that changed. Mark them add to queue
310 WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
311 WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
312 touchEvent->mLayersId = sCaptureTouchLayersId;
313 bool haveChanged = false;
314 for (int32_t i = touches.Length(); i;) {
315 --i;
316 Touch* touch = touches[i];
317 if (!touch) {
318 continue;
320 int32_t id = touch->Identifier();
321 touch->mMessage = aEvent->mMessage;
323 TouchInfo info;
324 if (!sCaptureTouchList->Get(id, &info)) {
325 touches.RemoveElementAt(i);
326 continue;
328 const RefPtr<Touch> oldTouch = info.mTouch;
329 if (!oldTouch->Equals(touch)) {
330 touch->mChanged = true;
331 haveChanged = true;
334 nsCOMPtr<EventTarget> targetPtr = oldTouch->mOriginalTarget;
335 if (!targetPtr) {
336 touches.RemoveElementAt(i);
337 continue;
339 nsCOMPtr<nsINode> targetNode(do_QueryInterface(targetPtr));
340 if (!targetNode->IsInComposedDoc()) {
341 targetPtr = info.mNonAnonymousTarget;
343 touch->SetTouchTarget(targetPtr);
345 info.mTouch = touch;
346 // info.mNonAnonymousTarget is still valid from above
347 sCaptureTouchList->InsertOrUpdate(id, info);
348 // if we're moving from touchstart to touchmove for this touch
349 // we allow preventDefault to prevent mouse events
350 if (oldTouch->mMessage != touch->mMessage) {
351 aTouchIsNew = true;
353 if (oldTouch->mIsTouchEventSuppressed) {
354 touch->mIsTouchEventSuppressed = true;
355 touches.RemoveElementAt(i);
356 continue;
359 // is nothing has changed, we should just return
360 if (!haveChanged) {
361 if (aTouchIsNew) {
362 // however, if this is the first touchmove after a touchstart,
363 // it is special in that preventDefault is allowed on it, so
364 // we must dispatch it to content even if nothing changed. we
365 // arbitrarily pick the first touch point to be the "changed"
366 // touch because firing an event with no changed events doesn't
367 // work.
368 for (uint32_t i = 0; i < touchEvent->mTouches.Length(); ++i) {
369 if (touchEvent->mTouches[i]) {
370 touchEvent->mTouches[i]->mChanged = true;
371 break;
374 } else {
375 // This touch event isn't going to be dispatched on the main-thread,
376 // we need to tell it to APZ because returned nsEventStatus is
377 // unreliable to tell whether the event was preventDefaulted or not.
378 layers::InputAPZContext::SetDropped();
379 return false;
382 break;
384 case eTouchEnd:
385 case eTouchCancel: {
386 // Remove the changed touches
387 // need to make sure we only remove touches that are ending here
388 WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
389 WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
390 touchEvent->mLayersId = sCaptureTouchLayersId;
391 for (int32_t i = touches.Length(); i;) {
392 --i;
393 Touch* touch = touches[i];
394 if (!touch) {
395 continue;
397 touch->mMessage = aEvent->mMessage;
398 touch->mChanged = true;
400 int32_t id = touch->Identifier();
401 TouchInfo info;
402 if (!sCaptureTouchList->Get(id, &info)) {
403 continue;
405 nsCOMPtr<EventTarget> targetPtr = info.mTouch->mOriginalTarget;
406 nsCOMPtr<nsINode> targetNode(do_QueryInterface(targetPtr));
407 if (targetNode && !targetNode->IsInComposedDoc()) {
408 targetPtr = info.mNonAnonymousTarget;
411 aCurrentEventContent = do_QueryInterface(targetPtr);
412 touch->SetTouchTarget(targetPtr);
413 sCaptureTouchList->Remove(id);
414 if (info.mTouch->mIsTouchEventSuppressed) {
415 touches.RemoveElementAt(i);
416 continue;
419 // add any touches left in the touch list, but ensure changed=false
420 AppendToTouchList(&touches);
421 break;
423 case eTouchPointerCancel: {
424 // Don't generate pointer events by touch events after eTouchPointerCancel
425 // is received.
426 WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
427 WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
428 touchEvent->mLayersId = sCaptureTouchLayersId;
429 for (uint32_t i = 0; i < touches.Length(); ++i) {
430 Touch* touch = touches[i];
431 if (!touch) {
432 continue;
434 int32_t id = touch->Identifier();
435 TouchInfo info;
436 if (!sCaptureTouchList->Get(id, &info)) {
437 continue;
439 info.mConvertToPointer = false;
440 sCaptureTouchList->InsertOrUpdate(id, info);
442 break;
444 default:
445 break;
447 return true;
450 void TouchManager::PostHandleEvent(const WidgetEvent* aEvent,
451 const nsEventStatus* aStatus) {
452 switch (aEvent->mMessage) {
453 case eTouchMove: {
454 if (sSingleTouchStartTimeStamp.IsNull()) {
455 break;
457 if (*aStatus == nsEventStatus_eConsumeNoDefault) {
458 sSingleTouchStartTimeStamp = TimeStamp();
459 break;
461 const WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
462 if (touchEvent->mTouches.Length() > 1) {
463 sSingleTouchStartTimeStamp = TimeStamp();
464 break;
466 if (touchEvent->mTouches.Length() == 1) {
467 // If the touch moved too far from the start point, don't treat the
468 // touch as a tap.
469 const float distance =
470 static_cast<float>((sSingleTouchStartPoint -
471 aEvent->AsTouchEvent()->mTouches[0]->mRefPoint)
472 .Length());
473 const float maxDistance =
474 StaticPrefs::apz_touch_start_tolerance() *
475 (MOZ_LIKELY(touchEvent->mWidget) ? touchEvent->mWidget->GetDPI()
476 : 96.0f);
477 if (distance > maxDistance) {
478 sSingleTouchStartTimeStamp = TimeStamp();
481 break;
483 case eTouchStart:
484 case eTouchEnd:
485 if (*aStatus == nsEventStatus_eConsumeNoDefault &&
486 !sSingleTouchStartTimeStamp.IsNull()) {
487 sSingleTouchStartTimeStamp = TimeStamp();
489 break;
490 case eTouchCancel:
491 case eTouchPointerCancel:
492 case eMouseLongTap:
493 case eContextMenu: {
494 if (!sSingleTouchStartTimeStamp.IsNull()) {
495 sSingleTouchStartTimeStamp = TimeStamp();
497 break;
499 default:
500 break;
504 /*static*/
505 already_AddRefed<nsIContent> TouchManager::GetAnyCapturedTouchTarget() {
506 nsCOMPtr<nsIContent> result = nullptr;
507 if (sCaptureTouchList->Count() == 0) {
508 return result.forget();
510 for (const auto& data : sCaptureTouchList->Values()) {
511 const RefPtr<Touch>& touch = data.mTouch;
512 if (touch) {
513 EventTarget* target = touch->GetTarget();
514 if (target) {
515 result = nsIContent::FromEventTargetOrNull(target);
516 break;
520 return result.forget();
523 /*static*/
524 bool TouchManager::HasCapturedTouch(int32_t aId) {
525 return sCaptureTouchList->Contains(aId);
528 /*static*/
529 already_AddRefed<Touch> TouchManager::GetCapturedTouch(int32_t aId) {
530 RefPtr<Touch> touch;
531 TouchInfo info;
532 if (sCaptureTouchList->Get(aId, &info)) {
533 touch = info.mTouch;
535 return touch.forget();
538 /*static*/
539 bool TouchManager::ShouldConvertTouchToPointer(const Touch* aTouch,
540 const WidgetTouchEvent* aEvent) {
541 if (!aTouch || !aTouch->convertToPointer) {
542 return false;
544 TouchInfo info;
545 if (!sCaptureTouchList->Get(aTouch->Identifier(), &info)) {
546 // This check runs before the TouchManager has the touch registered in its
547 // touch list. It's because we dispatching pointer events before handling
548 // touch events. So we convert eTouchStart to pointerdown even it's not
549 // registered.
550 // Check WidgetTouchEvent::mMessage because Touch::mMessage is assigned when
551 // pre-handling touch events.
552 return aEvent->mMessage == eTouchStart;
555 if (!info.mConvertToPointer) {
556 return false;
559 switch (aEvent->mMessage) {
560 case eTouchStart: {
561 // We don't want to fire duplicated pointerdown.
562 return false;
564 case eTouchMove: {
565 return !aTouch->Equals(info.mTouch);
567 default:
568 break;
570 return true;
573 /* static */
574 bool TouchManager::IsSingleTapEndToDoDefault(
575 const WidgetTouchEvent* aTouchEndEvent) {
576 MOZ_ASSERT(aTouchEndEvent);
577 MOZ_ASSERT(aTouchEndEvent->mFlags.mIsSynthesizedForTests);
578 MOZ_ASSERT(!StaticPrefs::test_events_async_enabled());
579 if (sSingleTouchStartTimeStamp.IsNull() ||
580 aTouchEndEvent->mTouches.Length() != 1) {
581 return false;
583 // If it's pressed long time, we should not treat it as a single tap because
584 // a long press should cause opening context menu by default.
585 if ((aTouchEndEvent->mTimeStamp - sSingleTouchStartTimeStamp)
586 .ToMilliseconds() > StaticPrefs::apz_max_tap_time()) {
587 return false;
589 NS_WARNING_ASSERTION(aTouchEndEvent->mTouches[0]->mChanged,
590 "The single tap end should be changed");
591 return true;
594 /* static */
595 bool TouchManager::IsPrecedingTouchPointerDownConsumedByContent() {
596 return sPrecedingTouchPointerDownConsumedByContent;
599 } // namespace mozilla