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"
14 #include "nsLayoutUtils.h"
16 #include "PositionedEventTargeting.h"
18 using namespace mozilla::dom
;
22 nsTHashMap
<nsUint32HashKey
, TouchManager::TouchInfo
>*
23 TouchManager::sCaptureTouchList
;
24 layers::LayersId
TouchManager::sCaptureTouchLayersId
;
27 void TouchManager::InitializeStatics() {
28 NS_ASSERTION(!sCaptureTouchList
, "InitializeStatics called multiple times!");
29 sCaptureTouchList
= new nsTHashMap
<nsUint32HashKey
, TouchManager::TouchInfo
>;
30 sCaptureTouchLayersId
= layers::LayersId
{0};
34 void TouchManager::ReleaseStatics() {
35 NS_ASSERTION(sCaptureTouchList
, "ReleaseStatics called without Initialize!");
36 delete sCaptureTouchList
;
37 sCaptureTouchList
= nullptr;
40 void TouchManager::Init(PresShell
* aPresShell
, Document
* aDocument
) {
41 mPresShell
= aPresShell
;
42 mDocument
= aDocument
;
45 void TouchManager::Destroy() {
46 EvictTouches(mDocument
);
51 static nsIContent
* GetNonAnonymousAncestor(EventTarget
* aTarget
) {
52 nsCOMPtr
<nsIContent
> content(do_QueryInterface(aTarget
));
53 if (content
&& content
->IsInNativeAnonymousSubtree()) {
54 content
= content
->FindFirstNonChromeOnlyAccessContent();
60 void TouchManager::EvictTouchPoint(RefPtr
<Touch
>& aTouch
,
61 Document
* aLimitToDocument
) {
62 nsCOMPtr
<nsINode
> node(do_QueryInterface(aTouch
->mOriginalTarget
));
64 Document
* doc
= node
->GetComposedDoc();
65 if (doc
&& (!aLimitToDocument
|| aLimitToDocument
== doc
)) {
66 PresShell
* presShell
= doc
->GetPresShell();
68 nsIFrame
* frame
= presShell
->GetRootFrame();
70 nsPoint
pt(aTouch
->mRefPoint
.x
, aTouch
->mRefPoint
.y
);
71 nsCOMPtr
<nsIWidget
> widget
= frame
->GetView()->GetNearestWidget(&pt
);
73 WidgetTouchEvent
event(true, eTouchEnd
, widget
);
74 event
.mTime
= PR_IntervalNow();
75 event
.mTouches
.AppendElement(aTouch
);
77 widget
->DispatchEvent(&event
, status
);
83 if (!node
|| !aLimitToDocument
|| node
->OwnerDoc() == aLimitToDocument
) {
84 sCaptureTouchList
->Remove(aTouch
->Identifier());
89 void TouchManager::AppendToTouchList(
90 WidgetTouchEvent::TouchArrayBase
* aTouchList
) {
91 for (const auto& data
: sCaptureTouchList
->Values()) {
92 const RefPtr
<Touch
>& touch
= data
.mTouch
;
93 touch
->mChanged
= false;
94 aTouchList
->AppendElement(touch
);
98 void TouchManager::EvictTouches(Document
* aLimitToDocument
) {
99 WidgetTouchEvent::AutoTouchArray touches
;
100 AppendToTouchList(&touches
);
101 for (uint32_t i
= 0; i
< touches
.Length(); ++i
) {
102 EvictTouchPoint(touches
[i
], aLimitToDocument
);
104 sCaptureTouchLayersId
= layers::LayersId
{0};
108 nsIFrame
* TouchManager::SetupTarget(WidgetTouchEvent
* aEvent
,
112 if (!aEvent
|| aEvent
->mMessage
!= eTouchStart
) {
113 // All touch events except for touchstart use a captured target.
117 nsIFrame
* target
= aFrame
;
118 for (int32_t i
= aEvent
->mTouches
.Length(); i
;) {
120 dom::Touch
* touch
= aEvent
->mTouches
[i
];
122 int32_t id
= touch
->Identifier();
123 if (!TouchManager::HasCapturedTouch(id
)) {
124 // find the target for this touch
125 RelativeTo relativeTo
{aFrame
};
126 nsPoint eventPoint
= nsLayoutUtils::GetEventCoordinatesRelativeTo(
127 aEvent
, touch
->mRefPoint
, relativeTo
);
128 target
= FindFrameTargetedByInputEvent(aEvent
, relativeTo
, eventPoint
);
130 nsCOMPtr
<nsIContent
> targetContent
;
131 target
->GetContentForEvent(aEvent
, getter_AddRefs(targetContent
));
132 touch
->SetTouchTarget(targetContent
133 ? targetContent
->GetAsElementOrParentElement()
136 aEvent
->mTouches
.RemoveElementAt(i
);
139 // This touch is an old touch, we need to ensure that is not
140 // marked as changed and set its target correctly
141 touch
->mChanged
= false;
142 RefPtr
<dom::Touch
> oldTouch
= TouchManager::GetCapturedTouch(id
);
144 touch
->SetTouchTarget(oldTouch
->mOriginalTarget
);
152 nsIFrame
* TouchManager::SuppressInvalidPointsAndGetTargetedFrame(
153 WidgetTouchEvent
* aEvent
) {
156 if (!aEvent
|| aEvent
->mMessage
!= eTouchStart
) {
157 // All touch events except for touchstart use a captured target.
161 // if this is a continuing session, ensure that all these events are
162 // in the same document by taking the target of the events already in
164 nsCOMPtr
<nsIContent
> anyTarget
;
165 if (aEvent
->mTouches
.Length() > 1) {
166 anyTarget
= TouchManager::GetAnyCapturedTouchTarget();
169 nsIFrame
* frame
= nullptr;
170 for (int32_t i
= aEvent
->mTouches
.Length(); i
;) {
172 dom::Touch
* touch
= aEvent
->mTouches
[i
];
173 if (TouchManager::HasCapturedTouch(touch
->Identifier())) {
177 MOZ_ASSERT(touch
->mOriginalTarget
);
178 nsCOMPtr
<nsIContent
> targetContent
= do_QueryInterface(touch
->GetTarget());
179 nsIFrame
* targetFrame
=
180 targetContent
? targetContent
->GetPrimaryFrame() : nullptr;
181 if (targetFrame
&& !anyTarget
) {
182 anyTarget
= targetContent
;
184 nsIFrame
* newTargetFrame
= nullptr;
185 for (nsIFrame
* f
= targetFrame
; f
;
186 f
= nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f
)) {
187 if (f
->PresContext()->Document() == anyTarget
->OwnerDoc()) {
191 // We must be in a subdocument so jump directly to the root frame.
192 // GetParentOrPlaceholderForCrossDoc gets called immediately to
193 // jump up to the containing document.
194 f
= f
->PresShell()->GetRootFrame();
196 // if we couldn't find a target frame in the same document as
197 // anyTarget, remove the touch from the capture touch list, as
198 // well as the event->mTouches array. touchmove events that aren't
199 // in the captured touch list will be discarded
200 if (!newTargetFrame
) {
201 touch
->mIsTouchEventSuppressed
= true;
203 targetFrame
= newTargetFrame
;
204 targetFrame
->GetContentForEvent(aEvent
, getter_AddRefs(targetContent
));
205 touch
->SetTouchTarget(targetContent
206 ? targetContent
->GetAsElementOrParentElement()
217 bool TouchManager::PreHandleEvent(WidgetEvent
* aEvent
, nsEventStatus
* aStatus
,
219 nsCOMPtr
<nsIContent
>& aCurrentEventContent
) {
220 MOZ_DIAGNOSTIC_ASSERT(aEvent
->IsTrusted());
222 // NOTE: If you need to handle new event messages here, you need to add new
223 // cases in PresShell::EventHandler::PrepareToDispatchEvent().
224 switch (aEvent
->mMessage
) {
226 WidgetTouchEvent
* touchEvent
= aEvent
->AsTouchEvent();
227 // if there is only one touch in this touchstart event, assume that it is
228 // the start of a new touch session and evict any old touches in the
230 if (touchEvent
->mTouches
.Length() == 1) {
233 // https://w3c.github.io/touch-events/#touchevent-implementer-s-note,
234 // all touch event should be dispatched to the same document that first
235 // touch event associated to. We cache layers id of the first touchstart
236 // event, all subsequent touch events will use the same layers id.
237 sCaptureTouchLayersId
= aEvent
->mLayersId
;
239 touchEvent
->mLayersId
= sCaptureTouchLayersId
;
241 // Add any new touches to the queue
242 WidgetTouchEvent::TouchArray
& touches
= touchEvent
->mTouches
;
243 for (int32_t i
= touches
.Length(); i
;) {
245 Touch
* touch
= touches
[i
];
246 int32_t id
= touch
->Identifier();
247 if (!sCaptureTouchList
->Get(id
, nullptr)) {
248 // If it is not already in the queue, it is a new touch
249 touch
->mChanged
= true;
251 touch
->mMessage
= aEvent
->mMessage
;
253 touch
, GetNonAnonymousAncestor(touch
->mOriginalTarget
), true};
254 sCaptureTouchList
->InsertOrUpdate(id
, info
);
255 if (touch
->mIsTouchEventSuppressed
) {
256 // We're going to dispatch touch event. Remove this touch instance if
258 touches
.RemoveElementAt(i
);
265 // Check for touches that changed. Mark them add to queue
266 WidgetTouchEvent
* touchEvent
= aEvent
->AsTouchEvent();
267 WidgetTouchEvent::TouchArray
& touches
= touchEvent
->mTouches
;
268 touchEvent
->mLayersId
= sCaptureTouchLayersId
;
269 bool haveChanged
= false;
270 for (int32_t i
= touches
.Length(); i
;) {
272 Touch
* touch
= touches
[i
];
276 int32_t id
= touch
->Identifier();
277 touch
->mMessage
= aEvent
->mMessage
;
280 if (!sCaptureTouchList
->Get(id
, &info
)) {
281 touches
.RemoveElementAt(i
);
284 const RefPtr
<Touch
> oldTouch
= info
.mTouch
;
285 if (!oldTouch
->Equals(touch
)) {
286 touch
->mChanged
= true;
290 nsCOMPtr
<EventTarget
> targetPtr
= oldTouch
->mOriginalTarget
;
292 touches
.RemoveElementAt(i
);
295 nsCOMPtr
<nsINode
> targetNode(do_QueryInterface(targetPtr
));
296 if (!targetNode
->IsInComposedDoc()) {
297 targetPtr
= info
.mNonAnonymousTarget
;
299 touch
->SetTouchTarget(targetPtr
);
302 // info.mNonAnonymousTarget is still valid from above
303 sCaptureTouchList
->InsertOrUpdate(id
, info
);
304 // if we're moving from touchstart to touchmove for this touch
305 // we allow preventDefault to prevent mouse events
306 if (oldTouch
->mMessage
!= touch
->mMessage
) {
309 if (oldTouch
->mIsTouchEventSuppressed
) {
310 touch
->mIsTouchEventSuppressed
= true;
311 touches
.RemoveElementAt(i
);
315 // is nothing has changed, we should just return
318 // however, if this is the first touchmove after a touchstart,
319 // it is special in that preventDefault is allowed on it, so
320 // we must dispatch it to content even if nothing changed. we
321 // arbitrarily pick the first touch point to be the "changed"
322 // touch because firing an event with no changed events doesn't
324 for (uint32_t i
= 0; i
< touchEvent
->mTouches
.Length(); ++i
) {
325 if (touchEvent
->mTouches
[i
]) {
326 touchEvent
->mTouches
[i
]->mChanged
= true;
338 // Remove the changed touches
339 // need to make sure we only remove touches that are ending here
340 WidgetTouchEvent
* touchEvent
= aEvent
->AsTouchEvent();
341 WidgetTouchEvent::TouchArray
& touches
= touchEvent
->mTouches
;
342 touchEvent
->mLayersId
= sCaptureTouchLayersId
;
343 for (int32_t i
= touches
.Length(); i
;) {
345 Touch
* touch
= touches
[i
];
349 touch
->mMessage
= aEvent
->mMessage
;
350 touch
->mChanged
= true;
352 int32_t id
= touch
->Identifier();
354 if (!sCaptureTouchList
->Get(id
, &info
)) {
357 nsCOMPtr
<EventTarget
> targetPtr
= info
.mTouch
->mOriginalTarget
;
358 nsCOMPtr
<nsINode
> targetNode(do_QueryInterface(targetPtr
));
359 if (targetNode
&& !targetNode
->IsInComposedDoc()) {
360 targetPtr
= info
.mNonAnonymousTarget
;
363 aCurrentEventContent
= do_QueryInterface(targetPtr
);
364 touch
->SetTouchTarget(targetPtr
);
365 sCaptureTouchList
->Remove(id
);
366 if (info
.mTouch
->mIsTouchEventSuppressed
) {
367 touches
.RemoveElementAt(i
);
371 // add any touches left in the touch list, but ensure changed=false
372 AppendToTouchList(&touches
);
375 case eTouchPointerCancel
: {
376 // Don't generate pointer events by touch events after eTouchPointerCancel
378 WidgetTouchEvent
* touchEvent
= aEvent
->AsTouchEvent();
379 WidgetTouchEvent::TouchArray
& touches
= touchEvent
->mTouches
;
380 touchEvent
->mLayersId
= sCaptureTouchLayersId
;
381 for (uint32_t i
= 0; i
< touches
.Length(); ++i
) {
382 Touch
* touch
= touches
[i
];
386 int32_t id
= touch
->Identifier();
388 if (!sCaptureTouchList
->Get(id
, &info
)) {
391 info
.mConvertToPointer
= false;
392 sCaptureTouchList
->InsertOrUpdate(id
, info
);
403 already_AddRefed
<nsIContent
> TouchManager::GetAnyCapturedTouchTarget() {
404 nsCOMPtr
<nsIContent
> result
= nullptr;
405 if (sCaptureTouchList
->Count() == 0) {
406 return result
.forget();
408 for (const auto& data
: sCaptureTouchList
->Values()) {
409 const RefPtr
<Touch
>& touch
= data
.mTouch
;
411 EventTarget
* target
= touch
->GetTarget();
413 result
= do_QueryInterface(target
);
418 return result
.forget();
422 bool TouchManager::HasCapturedTouch(int32_t aId
) {
423 return sCaptureTouchList
->Contains(aId
);
427 already_AddRefed
<Touch
> TouchManager::GetCapturedTouch(int32_t aId
) {
430 if (sCaptureTouchList
->Get(aId
, &info
)) {
433 return touch
.forget();
437 bool TouchManager::ShouldConvertTouchToPointer(const Touch
* aTouch
,
438 const WidgetTouchEvent
* aEvent
) {
439 if (!aTouch
|| !aTouch
->convertToPointer
) {
443 if (!sCaptureTouchList
->Get(aTouch
->Identifier(), &info
)) {
444 // This check runs before the TouchManager has the touch registered in its
445 // touch list. It's because we dispatching pointer events before handling
446 // touch events. So we convert eTouchStart to pointerdown even it's not
448 // Check WidgetTouchEvent::mMessage because Touch::mMessage is assigned when
449 // pre-handling touch events.
450 return aEvent
->mMessage
== eTouchStart
;
453 if (!info
.mConvertToPointer
) {
457 switch (aEvent
->mMessage
) {
459 // We don't want to fire duplicated pointerdown.
463 // Always fire first pointermove event.
464 return info
.mTouch
->mMessage
!= eTouchMove
||
465 !aTouch
->Equals(info
.mTouch
);
473 } // namespace mozilla