Bug 1879449 [wpt PR 44489] - [wptrunner] Add `infrastructure/expected-fail/` test...
[gecko.git] / layout / base / TouchManager.cpp
blob42ea78eb2e5fbaa3a441c5ab03c4a0eddaa43bfc
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/layers/InputAPZContext.h"
18 #include "nsIContent.h"
19 #include "nsIFrame.h"
20 #include "nsLayoutUtils.h"
21 #include "nsView.h"
22 #include "PositionedEventTargeting.h"
24 using namespace mozilla::dom;
26 namespace mozilla {
28 StaticAutoPtr<nsTHashMap<nsUint32HashKey, TouchManager::TouchInfo>>
29 TouchManager::sCaptureTouchList;
30 layers::LayersId TouchManager::sCaptureTouchLayersId;
31 TimeStamp TouchManager::sSingleTouchStartTimeStamp;
32 LayoutDeviceIntPoint TouchManager::sSingleTouchStartPoint;
34 /*static*/
35 void TouchManager::InitializeStatics() {
36 NS_ASSERTION(!sCaptureTouchList, "InitializeStatics called multiple times!");
37 sCaptureTouchList = new nsTHashMap<nsUint32HashKey, TouchManager::TouchInfo>;
38 sCaptureTouchLayersId = layers::LayersId{0};
41 /*static*/
42 void TouchManager::ReleaseStatics() {
43 NS_ASSERTION(sCaptureTouchList, "ReleaseStatics called without Initialize!");
44 sCaptureTouchList = nullptr;
47 void TouchManager::Init(PresShell* aPresShell, Document* aDocument) {
48 mPresShell = aPresShell;
49 mDocument = aDocument;
52 void TouchManager::Destroy() {
53 EvictTouches(mDocument);
54 mDocument = nullptr;
55 mPresShell = nullptr;
58 static nsIContent* GetNonAnonymousAncestor(EventTarget* aTarget) {
59 nsIContent* content = nsIContent::FromEventTargetOrNull(aTarget);
60 if (content && content->IsInNativeAnonymousSubtree()) {
61 content = content->FindFirstNonChromeOnlyAccessContent();
63 return content;
66 /*static*/
67 void TouchManager::EvictTouchPoint(RefPtr<Touch>& aTouch,
68 Document* aLimitToDocument) {
69 nsCOMPtr<nsINode> node(
70 nsINode::FromEventTargetOrNull(aTouch->mOriginalTarget));
71 if (node) {
72 Document* doc = node->GetComposedDoc();
73 if (doc && (!aLimitToDocument || aLimitToDocument == doc)) {
74 PresShell* presShell = doc->GetPresShell();
75 if (presShell) {
76 nsIFrame* frame = presShell->GetRootFrame();
77 if (frame) {
78 nsCOMPtr<nsIWidget> widget =
79 frame->GetView()->GetNearestWidget(nullptr);
80 if (widget) {
81 WidgetTouchEvent event(true, eTouchEnd, widget);
82 event.mTouches.AppendElement(aTouch);
83 nsEventStatus status;
84 widget->DispatchEvent(&event, status);
90 if (!node || !aLimitToDocument || node->OwnerDoc() == aLimitToDocument) {
91 sCaptureTouchList->Remove(aTouch->Identifier());
95 /*static*/
96 void TouchManager::AppendToTouchList(
97 WidgetTouchEvent::TouchArrayBase* aTouchList) {
98 for (const auto& data : sCaptureTouchList->Values()) {
99 const RefPtr<Touch>& touch = data.mTouch;
100 touch->mChanged = false;
101 aTouchList->AppendElement(touch);
105 void TouchManager::EvictTouches(Document* aLimitToDocument) {
106 WidgetTouchEvent::AutoTouchArray touches;
107 AppendToTouchList(&touches);
108 for (uint32_t i = 0; i < touches.Length(); ++i) {
109 EvictTouchPoint(touches[i], aLimitToDocument);
111 sCaptureTouchLayersId = layers::LayersId{0};
114 /* static */
115 nsIFrame* TouchManager::SetupTarget(WidgetTouchEvent* aEvent,
116 nsIFrame* aFrame) {
117 MOZ_ASSERT(aEvent);
119 if (!aEvent || aEvent->mMessage != eTouchStart) {
120 // All touch events except for touchstart use a captured target.
121 return aFrame;
124 nsIFrame* target = aFrame;
125 for (int32_t i = aEvent->mTouches.Length(); i;) {
126 --i;
127 dom::Touch* touch = aEvent->mTouches[i];
129 int32_t id = touch->Identifier();
130 if (!TouchManager::HasCapturedTouch(id)) {
131 // find the target for this touch
132 RelativeTo relativeTo{aFrame};
133 nsPoint eventPoint = nsLayoutUtils::GetEventCoordinatesRelativeTo(
134 aEvent, touch->mRefPoint, relativeTo);
135 target = FindFrameTargetedByInputEvent(aEvent, relativeTo, eventPoint);
136 if (target) {
137 nsCOMPtr<nsIContent> targetContent;
138 target->GetContentForEvent(aEvent, getter_AddRefs(targetContent));
139 touch->SetTouchTarget(targetContent
140 ? targetContent->GetAsElementOrParentElement()
141 : nullptr);
142 } else {
143 aEvent->mTouches.RemoveElementAt(i);
145 } else {
146 // This touch is an old touch, we need to ensure that is not
147 // marked as changed and set its target correctly
148 touch->mChanged = false;
149 RefPtr<dom::Touch> oldTouch = TouchManager::GetCapturedTouch(id);
150 if (oldTouch) {
151 touch->SetTouchTarget(oldTouch->mOriginalTarget);
155 return target;
158 /* static */
159 nsIFrame* TouchManager::SuppressInvalidPointsAndGetTargetedFrame(
160 WidgetTouchEvent* aEvent) {
161 MOZ_ASSERT(aEvent);
163 if (!aEvent || aEvent->mMessage != eTouchStart) {
164 // All touch events except for touchstart use a captured target.
165 return nullptr;
168 // if this is a continuing session, ensure that all these events are
169 // in the same document by taking the target of the events already in
170 // the capture list
171 nsCOMPtr<nsIContent> anyTarget;
172 if (aEvent->mTouches.Length() > 1) {
173 anyTarget = TouchManager::GetAnyCapturedTouchTarget();
176 nsIFrame* frame = nullptr;
177 for (uint32_t i = aEvent->mTouches.Length(); i;) {
178 --i;
179 dom::Touch* touch = aEvent->mTouches[i];
180 if (TouchManager::HasCapturedTouch(touch->Identifier())) {
181 continue;
184 MOZ_ASSERT(touch->mOriginalTarget);
185 nsIContent* const targetContent =
186 nsIContent::FromEventTargetOrNull(touch->GetTarget());
187 if (MOZ_UNLIKELY(!targetContent)) {
188 touch->mIsTouchEventSuppressed = true;
189 continue;
192 // Even if the target content is not connected, we should dispatch the touch
193 // start event except when the target content is owned by different
194 // document.
195 if (MOZ_UNLIKELY(!targetContent->IsInComposedDoc())) {
196 if (anyTarget && anyTarget->OwnerDoc() != targetContent->OwnerDoc()) {
197 touch->mIsTouchEventSuppressed = true;
198 continue;
200 if (!anyTarget) {
201 anyTarget = targetContent;
203 touch->SetTouchTarget(targetContent->GetAsElementOrParentElement());
204 if (PresShell* const presShell =
205 targetContent->OwnerDoc()->GetPresShell()) {
206 if (nsIFrame* rootFrame = presShell->GetRootFrame()) {
207 frame = rootFrame;
210 continue;
213 nsIFrame* targetFrame = targetContent->GetPrimaryFrame();
214 if (targetFrame && !anyTarget) {
215 anyTarget = targetContent;
216 } else {
217 nsIFrame* newTargetFrame = nullptr;
218 for (nsIFrame* f = targetFrame; f;
219 f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
220 if (f->PresContext()->Document() == anyTarget->OwnerDoc()) {
221 newTargetFrame = f;
222 break;
224 // We must be in a subdocument so jump directly to the root frame.
225 // GetParentOrPlaceholderForCrossDoc gets called immediately to
226 // jump up to the containing document.
227 f = f->PresShell()->GetRootFrame();
229 // if we couldn't find a target frame in the same document as
230 // anyTarget, remove the touch from the capture touch list, as
231 // well as the event->mTouches array. touchmove events that aren't
232 // in the captured touch list will be discarded
233 if (!newTargetFrame) {
234 touch->mIsTouchEventSuppressed = true;
235 } else {
236 targetFrame = newTargetFrame;
237 nsCOMPtr<nsIContent> newTargetContent;
238 targetFrame->GetContentForEvent(aEvent,
239 getter_AddRefs(newTargetContent));
240 touch->SetTouchTarget(
241 newTargetContent ? newTargetContent->GetAsElementOrParentElement()
242 : nullptr);
245 if (targetFrame) {
246 frame = targetFrame;
249 return frame;
252 bool TouchManager::PreHandleEvent(WidgetEvent* aEvent, nsEventStatus* aStatus,
253 bool& aTouchIsNew,
254 nsCOMPtr<nsIContent>& aCurrentEventContent) {
255 MOZ_DIAGNOSTIC_ASSERT(aEvent->IsTrusted());
257 // NOTE: If you need to handle new event messages here, you need to add new
258 // cases in PresShell::EventHandler::PrepareToDispatchEvent().
259 switch (aEvent->mMessage) {
260 case eTouchStart: {
261 WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
262 // if there is only one touch in this touchstart event, assume that it is
263 // the start of a new touch session and evict any old touches in the
264 // queue
265 if (touchEvent->mTouches.Length() == 1) {
266 EvictTouches();
267 // Per
268 // https://w3c.github.io/touch-events/#touchevent-implementer-s-note,
269 // all touch event should be dispatched to the same document that first
270 // touch event associated to. We cache layers id of the first touchstart
271 // event, all subsequent touch events will use the same layers id.
272 sCaptureTouchLayersId = aEvent->mLayersId;
273 sSingleTouchStartTimeStamp = aEvent->mTimeStamp;
274 sSingleTouchStartPoint = aEvent->AsTouchEvent()->mTouches[0]->mRefPoint;
275 } else {
276 touchEvent->mLayersId = sCaptureTouchLayersId;
277 sSingleTouchStartTimeStamp = TimeStamp();
279 // Add any new touches to the queue
280 WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
281 for (int32_t i = touches.Length(); i;) {
282 --i;
283 Touch* touch = touches[i];
284 int32_t id = touch->Identifier();
285 if (!sCaptureTouchList->Get(id, nullptr)) {
286 // If it is not already in the queue, it is a new touch
287 touch->mChanged = true;
289 touch->mMessage = aEvent->mMessage;
290 TouchInfo info = {
291 touch, GetNonAnonymousAncestor(touch->mOriginalTarget), true};
292 sCaptureTouchList->InsertOrUpdate(id, info);
293 if (touch->mIsTouchEventSuppressed) {
294 // We're going to dispatch touch event. Remove this touch instance if
295 // it is suppressed.
296 touches.RemoveElementAt(i);
297 continue;
300 break;
302 case eTouchMove: {
303 // Check for touches that changed. Mark them add to queue
304 WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
305 WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
306 touchEvent->mLayersId = sCaptureTouchLayersId;
307 bool haveChanged = false;
308 for (int32_t i = touches.Length(); i;) {
309 --i;
310 Touch* touch = touches[i];
311 if (!touch) {
312 continue;
314 int32_t id = touch->Identifier();
315 touch->mMessage = aEvent->mMessage;
317 TouchInfo info;
318 if (!sCaptureTouchList->Get(id, &info)) {
319 touches.RemoveElementAt(i);
320 continue;
322 const RefPtr<Touch> oldTouch = info.mTouch;
323 if (!oldTouch->Equals(touch)) {
324 touch->mChanged = true;
325 haveChanged = true;
328 nsCOMPtr<EventTarget> targetPtr = oldTouch->mOriginalTarget;
329 if (!targetPtr) {
330 touches.RemoveElementAt(i);
331 continue;
333 nsCOMPtr<nsINode> targetNode(do_QueryInterface(targetPtr));
334 if (!targetNode->IsInComposedDoc()) {
335 targetPtr = info.mNonAnonymousTarget;
337 touch->SetTouchTarget(targetPtr);
339 info.mTouch = touch;
340 // info.mNonAnonymousTarget is still valid from above
341 sCaptureTouchList->InsertOrUpdate(id, info);
342 // if we're moving from touchstart to touchmove for this touch
343 // we allow preventDefault to prevent mouse events
344 if (oldTouch->mMessage != touch->mMessage) {
345 aTouchIsNew = true;
347 if (oldTouch->mIsTouchEventSuppressed) {
348 touch->mIsTouchEventSuppressed = true;
349 touches.RemoveElementAt(i);
350 continue;
353 // is nothing has changed, we should just return
354 if (!haveChanged) {
355 if (aTouchIsNew) {
356 // however, if this is the first touchmove after a touchstart,
357 // it is special in that preventDefault is allowed on it, so
358 // we must dispatch it to content even if nothing changed. we
359 // arbitrarily pick the first touch point to be the "changed"
360 // touch because firing an event with no changed events doesn't
361 // work.
362 for (uint32_t i = 0; i < touchEvent->mTouches.Length(); ++i) {
363 if (touchEvent->mTouches[i]) {
364 touchEvent->mTouches[i]->mChanged = true;
365 break;
368 } else {
369 // This touch event isn't going to be dispatched on the main-thread,
370 // we need to tell it to APZ because returned nsEventStatus is
371 // unreliable to tell whether the event was preventDefaulted or not.
372 layers::InputAPZContext::SetDropped();
373 return false;
376 break;
378 case eTouchEnd:
379 case eTouchCancel: {
380 // Remove the changed touches
381 // need to make sure we only remove touches that are ending here
382 WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
383 WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
384 touchEvent->mLayersId = sCaptureTouchLayersId;
385 for (int32_t i = touches.Length(); i;) {
386 --i;
387 Touch* touch = touches[i];
388 if (!touch) {
389 continue;
391 touch->mMessage = aEvent->mMessage;
392 touch->mChanged = true;
394 int32_t id = touch->Identifier();
395 TouchInfo info;
396 if (!sCaptureTouchList->Get(id, &info)) {
397 continue;
399 nsCOMPtr<EventTarget> targetPtr = info.mTouch->mOriginalTarget;
400 nsCOMPtr<nsINode> targetNode(do_QueryInterface(targetPtr));
401 if (targetNode && !targetNode->IsInComposedDoc()) {
402 targetPtr = info.mNonAnonymousTarget;
405 aCurrentEventContent = do_QueryInterface(targetPtr);
406 touch->SetTouchTarget(targetPtr);
407 sCaptureTouchList->Remove(id);
408 if (info.mTouch->mIsTouchEventSuppressed) {
409 touches.RemoveElementAt(i);
410 continue;
413 // add any touches left in the touch list, but ensure changed=false
414 AppendToTouchList(&touches);
415 break;
417 case eTouchPointerCancel: {
418 // Don't generate pointer events by touch events after eTouchPointerCancel
419 // is received.
420 WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
421 WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
422 touchEvent->mLayersId = sCaptureTouchLayersId;
423 for (uint32_t i = 0; i < touches.Length(); ++i) {
424 Touch* touch = touches[i];
425 if (!touch) {
426 continue;
428 int32_t id = touch->Identifier();
429 TouchInfo info;
430 if (!sCaptureTouchList->Get(id, &info)) {
431 continue;
433 info.mConvertToPointer = false;
434 sCaptureTouchList->InsertOrUpdate(id, info);
436 break;
438 default:
439 break;
441 return true;
444 void TouchManager::PostHandleEvent(const WidgetEvent* aEvent,
445 const nsEventStatus* aStatus) {
446 switch (aEvent->mMessage) {
447 case eTouchMove: {
448 if (sSingleTouchStartTimeStamp.IsNull()) {
449 break;
451 if (*aStatus == nsEventStatus_eConsumeNoDefault) {
452 sSingleTouchStartTimeStamp = TimeStamp();
453 break;
455 const WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
456 if (touchEvent->mTouches.Length() > 1) {
457 sSingleTouchStartTimeStamp = TimeStamp();
458 break;
460 if (touchEvent->mTouches.Length() == 1) {
461 // If the touch moved too far from the start point, don't treat the
462 // touch as a tap.
463 const float distance =
464 static_cast<float>((sSingleTouchStartPoint -
465 aEvent->AsTouchEvent()->mTouches[0]->mRefPoint)
466 .Length());
467 const float maxDistance =
468 StaticPrefs::apz_touch_start_tolerance() *
469 (MOZ_LIKELY(touchEvent->mWidget) ? touchEvent->mWidget->GetDPI()
470 : 96.0f);
471 if (distance > maxDistance) {
472 sSingleTouchStartTimeStamp = TimeStamp();
475 break;
477 case eTouchStart:
478 case eTouchEnd:
479 if (*aStatus == nsEventStatus_eConsumeNoDefault &&
480 !sSingleTouchStartTimeStamp.IsNull()) {
481 sSingleTouchStartTimeStamp = TimeStamp();
483 break;
484 case eTouchCancel:
485 case eTouchPointerCancel:
486 case eMouseLongTap:
487 case eContextMenu: {
488 if (!sSingleTouchStartTimeStamp.IsNull()) {
489 sSingleTouchStartTimeStamp = TimeStamp();
491 break;
493 default:
494 break;
498 /*static*/
499 already_AddRefed<nsIContent> TouchManager::GetAnyCapturedTouchTarget() {
500 nsCOMPtr<nsIContent> result = nullptr;
501 if (sCaptureTouchList->Count() == 0) {
502 return result.forget();
504 for (const auto& data : sCaptureTouchList->Values()) {
505 const RefPtr<Touch>& touch = data.mTouch;
506 if (touch) {
507 EventTarget* target = touch->GetTarget();
508 if (target) {
509 result = nsIContent::FromEventTargetOrNull(target);
510 break;
514 return result.forget();
517 /*static*/
518 bool TouchManager::HasCapturedTouch(int32_t aId) {
519 return sCaptureTouchList->Contains(aId);
522 /*static*/
523 already_AddRefed<Touch> TouchManager::GetCapturedTouch(int32_t aId) {
524 RefPtr<Touch> touch;
525 TouchInfo info;
526 if (sCaptureTouchList->Get(aId, &info)) {
527 touch = info.mTouch;
529 return touch.forget();
532 /*static*/
533 bool TouchManager::ShouldConvertTouchToPointer(const Touch* aTouch,
534 const WidgetTouchEvent* aEvent) {
535 if (!aTouch || !aTouch->convertToPointer) {
536 return false;
538 TouchInfo info;
539 if (!sCaptureTouchList->Get(aTouch->Identifier(), &info)) {
540 // This check runs before the TouchManager has the touch registered in its
541 // touch list. It's because we dispatching pointer events before handling
542 // touch events. So we convert eTouchStart to pointerdown even it's not
543 // registered.
544 // Check WidgetTouchEvent::mMessage because Touch::mMessage is assigned when
545 // pre-handling touch events.
546 return aEvent->mMessage == eTouchStart;
549 if (!info.mConvertToPointer) {
550 return false;
553 switch (aEvent->mMessage) {
554 case eTouchStart: {
555 // We don't want to fire duplicated pointerdown.
556 return false;
558 case eTouchMove: {
559 return !aTouch->Equals(info.mTouch);
561 default:
562 break;
564 return true;
567 /* static */
568 bool TouchManager::IsSingleTapEndToDoDefault(
569 const WidgetTouchEvent* aTouchEndEvent) {
570 MOZ_ASSERT(aTouchEndEvent);
571 MOZ_ASSERT(aTouchEndEvent->mFlags.mIsSynthesizedForTests);
572 MOZ_ASSERT(!StaticPrefs::test_events_async_enabled());
573 if (sSingleTouchStartTimeStamp.IsNull() ||
574 aTouchEndEvent->mTouches.Length() != 1) {
575 return false;
577 // If it's pressed long time, we should not treat it as a single tap because
578 // a long press should cause opening context menu by default.
579 if ((aTouchEndEvent->mTimeStamp - sSingleTouchStartTimeStamp)
580 .ToMilliseconds() > StaticPrefs::apz_max_tap_time()) {
581 return false;
583 NS_WARNING_ASSERTION(aTouchEndEvent->mTouches[0]->mChanged,
584 "The single tap end should be changed");
585 return true;
588 } // namespace mozilla