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/.
8 #include "TouchManager.h"
10 #include "mozilla/dom/Document.h"
11 #include "mozilla/dom/EventTarget.h"
12 #include "mozilla/PresShell.h"
13 #include "mozilla/layers/InputAPZContext.h"
14 #include "nsIContent.h"
16 #include "nsLayoutUtils.h"
18 #include "PositionedEventTargeting.h"
20 using namespace mozilla::dom
;
24 StaticAutoPtr
<nsTHashMap
<nsUint32HashKey
, TouchManager::TouchInfo
>>
25 TouchManager::sCaptureTouchList
;
26 layers::LayersId
TouchManager::sCaptureTouchLayersId
;
29 void TouchManager::InitializeStatics() {
30 NS_ASSERTION(!sCaptureTouchList
, "InitializeStatics called multiple times!");
31 sCaptureTouchList
= new nsTHashMap
<nsUint32HashKey
, TouchManager::TouchInfo
>;
32 sCaptureTouchLayersId
= layers::LayersId
{0};
36 void TouchManager::ReleaseStatics() {
37 NS_ASSERTION(sCaptureTouchList
, "ReleaseStatics called without Initialize!");
38 sCaptureTouchList
= nullptr;
41 void TouchManager::Init(PresShell
* aPresShell
, Document
* aDocument
) {
42 mPresShell
= aPresShell
;
43 mDocument
= aDocument
;
46 void TouchManager::Destroy() {
47 EvictTouches(mDocument
);
52 static nsIContent
* GetNonAnonymousAncestor(EventTarget
* aTarget
) {
53 nsIContent
* content
= nsIContent::FromEventTargetOrNull(aTarget
);
54 if (content
&& content
->IsInNativeAnonymousSubtree()) {
55 content
= content
->FindFirstNonChromeOnlyAccessContent();
61 void TouchManager::EvictTouchPoint(RefPtr
<Touch
>& aTouch
,
62 Document
* aLimitToDocument
) {
63 nsCOMPtr
<nsINode
> node(
64 nsINode::FromEventTargetOrNull(aTouch
->mOriginalTarget
));
66 Document
* doc
= node
->GetComposedDoc();
67 if (doc
&& (!aLimitToDocument
|| aLimitToDocument
== doc
)) {
68 PresShell
* presShell
= doc
->GetPresShell();
70 nsIFrame
* frame
= presShell
->GetRootFrame();
72 nsPoint
pt(aTouch
->mRefPoint
.x
, aTouch
->mRefPoint
.y
);
73 nsCOMPtr
<nsIWidget
> widget
= frame
->GetView()->GetNearestWidget(&pt
);
75 WidgetTouchEvent
event(true, eTouchEnd
, widget
);
76 event
.mTouches
.AppendElement(aTouch
);
78 widget
->DispatchEvent(&event
, status
);
84 if (!node
|| !aLimitToDocument
|| node
->OwnerDoc() == aLimitToDocument
) {
85 sCaptureTouchList
->Remove(aTouch
->Identifier());
90 void TouchManager::AppendToTouchList(
91 WidgetTouchEvent::TouchArrayBase
* aTouchList
) {
92 for (const auto& data
: sCaptureTouchList
->Values()) {
93 const RefPtr
<Touch
>& touch
= data
.mTouch
;
94 touch
->mChanged
= false;
95 aTouchList
->AppendElement(touch
);
99 void TouchManager::EvictTouches(Document
* aLimitToDocument
) {
100 WidgetTouchEvent::AutoTouchArray touches
;
101 AppendToTouchList(&touches
);
102 for (uint32_t i
= 0; i
< touches
.Length(); ++i
) {
103 EvictTouchPoint(touches
[i
], aLimitToDocument
);
105 sCaptureTouchLayersId
= layers::LayersId
{0};
109 nsIFrame
* TouchManager::SetupTarget(WidgetTouchEvent
* aEvent
,
113 if (!aEvent
|| aEvent
->mMessage
!= eTouchStart
) {
114 // All touch events except for touchstart use a captured target.
118 nsIFrame
* target
= aFrame
;
119 for (int32_t i
= aEvent
->mTouches
.Length(); i
;) {
121 dom::Touch
* touch
= aEvent
->mTouches
[i
];
123 int32_t id
= touch
->Identifier();
124 if (!TouchManager::HasCapturedTouch(id
)) {
125 // find the target for this touch
126 RelativeTo relativeTo
{aFrame
};
127 nsPoint eventPoint
= nsLayoutUtils::GetEventCoordinatesRelativeTo(
128 aEvent
, touch
->mRefPoint
, relativeTo
);
129 target
= FindFrameTargetedByInputEvent(aEvent
, relativeTo
, eventPoint
);
131 nsCOMPtr
<nsIContent
> targetContent
;
132 target
->GetContentForEvent(aEvent
, getter_AddRefs(targetContent
));
133 touch
->SetTouchTarget(targetContent
134 ? targetContent
->GetAsElementOrParentElement()
137 aEvent
->mTouches
.RemoveElementAt(i
);
140 // This touch is an old touch, we need to ensure that is not
141 // marked as changed and set its target correctly
142 touch
->mChanged
= false;
143 RefPtr
<dom::Touch
> oldTouch
= TouchManager::GetCapturedTouch(id
);
145 touch
->SetTouchTarget(oldTouch
->mOriginalTarget
);
153 nsIFrame
* TouchManager::SuppressInvalidPointsAndGetTargetedFrame(
154 WidgetTouchEvent
* aEvent
) {
157 if (!aEvent
|| aEvent
->mMessage
!= eTouchStart
) {
158 // All touch events except for touchstart use a captured target.
162 // if this is a continuing session, ensure that all these events are
163 // in the same document by taking the target of the events already in
165 nsCOMPtr
<nsIContent
> anyTarget
;
166 if (aEvent
->mTouches
.Length() > 1) {
167 anyTarget
= TouchManager::GetAnyCapturedTouchTarget();
170 nsIFrame
* frame
= nullptr;
171 for (int32_t i
= aEvent
->mTouches
.Length(); i
;) {
173 dom::Touch
* touch
= aEvent
->mTouches
[i
];
174 if (TouchManager::HasCapturedTouch(touch
->Identifier())) {
178 MOZ_ASSERT(touch
->mOriginalTarget
);
179 nsCOMPtr
<nsIContent
> targetContent
= do_QueryInterface(touch
->GetTarget());
180 nsIFrame
* targetFrame
=
181 targetContent
? targetContent
->GetPrimaryFrame() : nullptr;
182 if (targetFrame
&& !anyTarget
) {
183 anyTarget
= targetContent
;
185 nsIFrame
* newTargetFrame
= nullptr;
186 for (nsIFrame
* f
= targetFrame
; f
;
187 f
= nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f
)) {
188 if (f
->PresContext()->Document() == anyTarget
->OwnerDoc()) {
192 // We must be in a subdocument so jump directly to the root frame.
193 // GetParentOrPlaceholderForCrossDoc gets called immediately to
194 // jump up to the containing document.
195 f
= f
->PresShell()->GetRootFrame();
197 // if we couldn't find a target frame in the same document as
198 // anyTarget, remove the touch from the capture touch list, as
199 // well as the event->mTouches array. touchmove events that aren't
200 // in the captured touch list will be discarded
201 if (!newTargetFrame
) {
202 touch
->mIsTouchEventSuppressed
= true;
204 targetFrame
= newTargetFrame
;
205 targetFrame
->GetContentForEvent(aEvent
, getter_AddRefs(targetContent
));
206 touch
->SetTouchTarget(targetContent
207 ? targetContent
->GetAsElementOrParentElement()
218 bool TouchManager::PreHandleEvent(WidgetEvent
* aEvent
, nsEventStatus
* aStatus
,
220 nsCOMPtr
<nsIContent
>& aCurrentEventContent
) {
221 MOZ_DIAGNOSTIC_ASSERT(aEvent
->IsTrusted());
223 // NOTE: If you need to handle new event messages here, you need to add new
224 // cases in PresShell::EventHandler::PrepareToDispatchEvent().
225 switch (aEvent
->mMessage
) {
227 WidgetTouchEvent
* touchEvent
= aEvent
->AsTouchEvent();
228 // if there is only one touch in this touchstart event, assume that it is
229 // the start of a new touch session and evict any old touches in the
231 if (touchEvent
->mTouches
.Length() == 1) {
234 // https://w3c.github.io/touch-events/#touchevent-implementer-s-note,
235 // all touch event should be dispatched to the same document that first
236 // touch event associated to. We cache layers id of the first touchstart
237 // event, all subsequent touch events will use the same layers id.
238 sCaptureTouchLayersId
= aEvent
->mLayersId
;
240 touchEvent
->mLayersId
= sCaptureTouchLayersId
;
242 // Add any new touches to the queue
243 WidgetTouchEvent::TouchArray
& touches
= touchEvent
->mTouches
;
244 for (int32_t i
= touches
.Length(); i
;) {
246 Touch
* touch
= touches
[i
];
247 int32_t id
= touch
->Identifier();
248 if (!sCaptureTouchList
->Get(id
, nullptr)) {
249 // If it is not already in the queue, it is a new touch
250 touch
->mChanged
= true;
252 touch
->mMessage
= aEvent
->mMessage
;
254 touch
, GetNonAnonymousAncestor(touch
->mOriginalTarget
), true};
255 sCaptureTouchList
->InsertOrUpdate(id
, info
);
256 if (touch
->mIsTouchEventSuppressed
) {
257 // We're going to dispatch touch event. Remove this touch instance if
259 touches
.RemoveElementAt(i
);
266 // Check for touches that changed. Mark them add to queue
267 WidgetTouchEvent
* touchEvent
= aEvent
->AsTouchEvent();
268 WidgetTouchEvent::TouchArray
& touches
= touchEvent
->mTouches
;
269 touchEvent
->mLayersId
= sCaptureTouchLayersId
;
270 bool haveChanged
= false;
271 for (int32_t i
= touches
.Length(); i
;) {
273 Touch
* touch
= touches
[i
];
277 int32_t id
= touch
->Identifier();
278 touch
->mMessage
= aEvent
->mMessage
;
281 if (!sCaptureTouchList
->Get(id
, &info
)) {
282 touches
.RemoveElementAt(i
);
285 const RefPtr
<Touch
> oldTouch
= info
.mTouch
;
286 if (!oldTouch
->Equals(touch
)) {
287 touch
->mChanged
= true;
291 nsCOMPtr
<EventTarget
> targetPtr
= oldTouch
->mOriginalTarget
;
293 touches
.RemoveElementAt(i
);
296 nsCOMPtr
<nsINode
> targetNode(do_QueryInterface(targetPtr
));
297 if (!targetNode
->IsInComposedDoc()) {
298 targetPtr
= info
.mNonAnonymousTarget
;
300 touch
->SetTouchTarget(targetPtr
);
303 // info.mNonAnonymousTarget is still valid from above
304 sCaptureTouchList
->InsertOrUpdate(id
, info
);
305 // if we're moving from touchstart to touchmove for this touch
306 // we allow preventDefault to prevent mouse events
307 if (oldTouch
->mMessage
!= touch
->mMessage
) {
310 if (oldTouch
->mIsTouchEventSuppressed
) {
311 touch
->mIsTouchEventSuppressed
= true;
312 touches
.RemoveElementAt(i
);
316 // is nothing has changed, we should just return
319 // however, if this is the first touchmove after a touchstart,
320 // it is special in that preventDefault is allowed on it, so
321 // we must dispatch it to content even if nothing changed. we
322 // arbitrarily pick the first touch point to be the "changed"
323 // touch because firing an event with no changed events doesn't
325 for (uint32_t i
= 0; i
< touchEvent
->mTouches
.Length(); ++i
) {
326 if (touchEvent
->mTouches
[i
]) {
327 touchEvent
->mTouches
[i
]->mChanged
= true;
332 // This touch event isn't going to be dispatched on the main-thread,
333 // we need to tell it to APZ because returned nsEventStatus is
334 // unreliable to tell whether the event was preventDefaulted or not.
335 layers::InputAPZContext::SetDropped();
343 // Remove the changed touches
344 // need to make sure we only remove touches that are ending here
345 WidgetTouchEvent
* touchEvent
= aEvent
->AsTouchEvent();
346 WidgetTouchEvent::TouchArray
& touches
= touchEvent
->mTouches
;
347 touchEvent
->mLayersId
= sCaptureTouchLayersId
;
348 for (int32_t i
= touches
.Length(); i
;) {
350 Touch
* touch
= touches
[i
];
354 touch
->mMessage
= aEvent
->mMessage
;
355 touch
->mChanged
= true;
357 int32_t id
= touch
->Identifier();
359 if (!sCaptureTouchList
->Get(id
, &info
)) {
362 nsCOMPtr
<EventTarget
> targetPtr
= info
.mTouch
->mOriginalTarget
;
363 nsCOMPtr
<nsINode
> targetNode(do_QueryInterface(targetPtr
));
364 if (targetNode
&& !targetNode
->IsInComposedDoc()) {
365 targetPtr
= info
.mNonAnonymousTarget
;
368 aCurrentEventContent
= do_QueryInterface(targetPtr
);
369 touch
->SetTouchTarget(targetPtr
);
370 sCaptureTouchList
->Remove(id
);
371 if (info
.mTouch
->mIsTouchEventSuppressed
) {
372 touches
.RemoveElementAt(i
);
376 // add any touches left in the touch list, but ensure changed=false
377 AppendToTouchList(&touches
);
380 case eTouchPointerCancel
: {
381 // Don't generate pointer events by touch events after eTouchPointerCancel
383 WidgetTouchEvent
* touchEvent
= aEvent
->AsTouchEvent();
384 WidgetTouchEvent::TouchArray
& touches
= touchEvent
->mTouches
;
385 touchEvent
->mLayersId
= sCaptureTouchLayersId
;
386 for (uint32_t i
= 0; i
< touches
.Length(); ++i
) {
387 Touch
* touch
= touches
[i
];
391 int32_t id
= touch
->Identifier();
393 if (!sCaptureTouchList
->Get(id
, &info
)) {
396 info
.mConvertToPointer
= false;
397 sCaptureTouchList
->InsertOrUpdate(id
, info
);
408 already_AddRefed
<nsIContent
> TouchManager::GetAnyCapturedTouchTarget() {
409 nsCOMPtr
<nsIContent
> result
= nullptr;
410 if (sCaptureTouchList
->Count() == 0) {
411 return result
.forget();
413 for (const auto& data
: sCaptureTouchList
->Values()) {
414 const RefPtr
<Touch
>& touch
= data
.mTouch
;
416 EventTarget
* target
= touch
->GetTarget();
418 result
= nsIContent::FromEventTargetOrNull(target
);
423 return result
.forget();
427 bool TouchManager::HasCapturedTouch(int32_t aId
) {
428 return sCaptureTouchList
->Contains(aId
);
432 already_AddRefed
<Touch
> TouchManager::GetCapturedTouch(int32_t aId
) {
435 if (sCaptureTouchList
->Get(aId
, &info
)) {
438 return touch
.forget();
442 bool TouchManager::ShouldConvertTouchToPointer(const Touch
* aTouch
,
443 const WidgetTouchEvent
* aEvent
) {
444 if (!aTouch
|| !aTouch
->convertToPointer
) {
448 if (!sCaptureTouchList
->Get(aTouch
->Identifier(), &info
)) {
449 // This check runs before the TouchManager has the touch registered in its
450 // touch list. It's because we dispatching pointer events before handling
451 // touch events. So we convert eTouchStart to pointerdown even it's not
453 // Check WidgetTouchEvent::mMessage because Touch::mMessage is assigned when
454 // pre-handling touch events.
455 return aEvent
->mMessage
== eTouchStart
;
458 if (!info
.mConvertToPointer
) {
462 switch (aEvent
->mMessage
) {
464 // We don't want to fire duplicated pointerdown.
468 return !aTouch
->Equals(info
.mTouch
);
476 } // namespace mozilla