Bug 1717887 Part 2: Make RenderThread backed by nsIThread, with a hang monitor. r...
[gecko.git] / layout / base / TouchManager.cpp
blob8663a02e7417762a7c301c603868aabd2dfd772c
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 "mozilla/dom/Document.h"
11 #include "mozilla/dom/EventTarget.h"
12 #include "mozilla/PresShell.h"
13 #include "nsIFrame.h"
14 #include "nsLayoutUtils.h"
15 #include "nsView.h"
16 #include "PositionedEventTargeting.h"
18 using namespace mozilla::dom;
20 namespace mozilla {
22 nsTHashMap<nsUint32HashKey, TouchManager::TouchInfo>*
23 TouchManager::sCaptureTouchList;
24 layers::LayersId TouchManager::sCaptureTouchLayersId;
26 /*static*/
27 void TouchManager::InitializeStatics() {
28 NS_ASSERTION(!sCaptureTouchList, "InitializeStatics called multiple times!");
29 sCaptureTouchList = new nsTHashMap<nsUint32HashKey, TouchManager::TouchInfo>;
30 sCaptureTouchLayersId = layers::LayersId{0};
33 /*static*/
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);
47 mDocument = nullptr;
48 mPresShell = nullptr;
51 static nsIContent* GetNonAnonymousAncestor(EventTarget* aTarget) {
52 nsCOMPtr<nsIContent> content(do_QueryInterface(aTarget));
53 if (content && content->IsInNativeAnonymousSubtree()) {
54 content = content->FindFirstNonChromeOnlyAccessContent();
56 return content;
59 /*static*/
60 void TouchManager::EvictTouchPoint(RefPtr<Touch>& aTouch,
61 Document* aLimitToDocument) {
62 nsCOMPtr<nsINode> node(do_QueryInterface(aTouch->mOriginalTarget));
63 if (node) {
64 Document* doc = node->GetComposedDoc();
65 if (doc && (!aLimitToDocument || aLimitToDocument == doc)) {
66 PresShell* presShell = doc->GetPresShell();
67 if (presShell) {
68 nsIFrame* frame = presShell->GetRootFrame();
69 if (frame) {
70 nsPoint pt(aTouch->mRefPoint.x, aTouch->mRefPoint.y);
71 nsCOMPtr<nsIWidget> widget = frame->GetView()->GetNearestWidget(&pt);
72 if (widget) {
73 WidgetTouchEvent event(true, eTouchEnd, widget);
74 event.mTime = PR_IntervalNow();
75 event.mTouches.AppendElement(aTouch);
76 nsEventStatus status;
77 widget->DispatchEvent(&event, status);
83 if (!node || !aLimitToDocument || node->OwnerDoc() == aLimitToDocument) {
84 sCaptureTouchList->Remove(aTouch->Identifier());
88 /*static*/
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};
107 /* static */
108 nsIFrame* TouchManager::SetupTarget(WidgetTouchEvent* aEvent,
109 nsIFrame* aFrame) {
110 MOZ_ASSERT(aEvent);
112 if (!aEvent || aEvent->mMessage != eTouchStart) {
113 // All touch events except for touchstart use a captured target.
114 return aFrame;
117 nsIFrame* target = aFrame;
118 for (int32_t i = aEvent->mTouches.Length(); i;) {
119 --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);
129 if (target) {
130 nsCOMPtr<nsIContent> targetContent;
131 target->GetContentForEvent(aEvent, getter_AddRefs(targetContent));
132 touch->SetTouchTarget(targetContent
133 ? targetContent->GetAsElementOrParentElement()
134 : nullptr);
135 } else {
136 aEvent->mTouches.RemoveElementAt(i);
138 } else {
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);
143 if (oldTouch) {
144 touch->SetTouchTarget(oldTouch->mOriginalTarget);
148 return target;
151 /* static */
152 nsIFrame* TouchManager::SuppressInvalidPointsAndGetTargetedFrame(
153 WidgetTouchEvent* aEvent) {
154 MOZ_ASSERT(aEvent);
156 if (!aEvent || aEvent->mMessage != eTouchStart) {
157 // All touch events except for touchstart use a captured target.
158 return nullptr;
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
163 // the capture list
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;) {
171 --i;
172 dom::Touch* touch = aEvent->mTouches[i];
173 if (TouchManager::HasCapturedTouch(touch->Identifier())) {
174 continue;
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;
183 } else {
184 nsIFrame* newTargetFrame = nullptr;
185 for (nsIFrame* f = targetFrame; f;
186 f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
187 if (f->PresContext()->Document() == anyTarget->OwnerDoc()) {
188 newTargetFrame = f;
189 break;
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;
202 } else {
203 targetFrame = newTargetFrame;
204 targetFrame->GetContentForEvent(aEvent, getter_AddRefs(targetContent));
205 touch->SetTouchTarget(targetContent
206 ? targetContent->GetAsElementOrParentElement()
207 : nullptr);
210 if (targetFrame) {
211 frame = targetFrame;
214 return frame;
217 bool TouchManager::PreHandleEvent(WidgetEvent* aEvent, nsEventStatus* aStatus,
218 bool& aTouchIsNew,
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) {
225 case eTouchStart: {
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
229 // queue
230 if (touchEvent->mTouches.Length() == 1) {
231 EvictTouches();
232 // Per
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;
238 } else {
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;) {
244 --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;
252 TouchInfo info = {
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
257 // it is suppressed.
258 touches.RemoveElementAt(i);
259 continue;
262 break;
264 case eTouchMove: {
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;) {
271 --i;
272 Touch* touch = touches[i];
273 if (!touch) {
274 continue;
276 int32_t id = touch->Identifier();
277 touch->mMessage = aEvent->mMessage;
279 TouchInfo info;
280 if (!sCaptureTouchList->Get(id, &info)) {
281 touches.RemoveElementAt(i);
282 continue;
284 const RefPtr<Touch> oldTouch = info.mTouch;
285 if (!oldTouch->Equals(touch)) {
286 touch->mChanged = true;
287 haveChanged = true;
290 nsCOMPtr<EventTarget> targetPtr = oldTouch->mOriginalTarget;
291 if (!targetPtr) {
292 touches.RemoveElementAt(i);
293 continue;
295 nsCOMPtr<nsINode> targetNode(do_QueryInterface(targetPtr));
296 if (!targetNode->IsInComposedDoc()) {
297 targetPtr = info.mNonAnonymousTarget;
299 touch->SetTouchTarget(targetPtr);
301 info.mTouch = touch;
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) {
307 aTouchIsNew = true;
309 if (oldTouch->mIsTouchEventSuppressed) {
310 touch->mIsTouchEventSuppressed = true;
311 touches.RemoveElementAt(i);
312 continue;
315 // is nothing has changed, we should just return
316 if (!haveChanged) {
317 if (aTouchIsNew) {
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
323 // work.
324 for (uint32_t i = 0; i < touchEvent->mTouches.Length(); ++i) {
325 if (touchEvent->mTouches[i]) {
326 touchEvent->mTouches[i]->mChanged = true;
327 break;
330 } else {
331 return false;
334 break;
336 case eTouchEnd:
337 case eTouchCancel: {
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;) {
344 --i;
345 Touch* touch = touches[i];
346 if (!touch) {
347 continue;
349 touch->mMessage = aEvent->mMessage;
350 touch->mChanged = true;
352 int32_t id = touch->Identifier();
353 TouchInfo info;
354 if (!sCaptureTouchList->Get(id, &info)) {
355 continue;
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);
368 continue;
371 // add any touches left in the touch list, but ensure changed=false
372 AppendToTouchList(&touches);
373 break;
375 case eTouchPointerCancel: {
376 // Don't generate pointer events by touch events after eTouchPointerCancel
377 // is received.
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];
383 if (!touch) {
384 continue;
386 int32_t id = touch->Identifier();
387 TouchInfo info;
388 if (!sCaptureTouchList->Get(id, &info)) {
389 continue;
391 info.mConvertToPointer = false;
392 sCaptureTouchList->InsertOrUpdate(id, info);
394 break;
396 default:
397 break;
399 return true;
402 /*static*/
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;
410 if (touch) {
411 EventTarget* target = touch->GetTarget();
412 if (target) {
413 result = do_QueryInterface(target);
414 break;
418 return result.forget();
421 /*static*/
422 bool TouchManager::HasCapturedTouch(int32_t aId) {
423 return sCaptureTouchList->Contains(aId);
426 /*static*/
427 already_AddRefed<Touch> TouchManager::GetCapturedTouch(int32_t aId) {
428 RefPtr<Touch> touch;
429 TouchInfo info;
430 if (sCaptureTouchList->Get(aId, &info)) {
431 touch = info.mTouch;
433 return touch.forget();
436 /*static*/
437 bool TouchManager::ShouldConvertTouchToPointer(const Touch* aTouch,
438 const WidgetTouchEvent* aEvent) {
439 if (!aTouch || !aTouch->convertToPointer) {
440 return false;
442 TouchInfo info;
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
447 // registered.
448 // Check WidgetTouchEvent::mMessage because Touch::mMessage is assigned when
449 // pre-handling touch events.
450 return aEvent->mMessage == eTouchStart;
453 if (!info.mConvertToPointer) {
454 return false;
457 switch (aEvent->mMessage) {
458 case eTouchStart: {
459 // We don't want to fire duplicated pointerdown.
460 return false;
462 case eTouchMove: {
463 // Always fire first pointermove event.
464 return info.mTouch->mMessage != eTouchMove ||
465 !aTouch->Equals(info.mTouch);
467 default:
468 break;
470 return true;
473 } // namespace mozilla