Bug 1839338 [wpt PR 40636] - Clone encoded WebRTC audio frame when deserializing...
[gecko.git] / layout / base / TouchManager.cpp
blob5dfbfab153717c7357bd8dc8e884db24ffab2db2
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 "mozilla/layers/InputAPZContext.h"
14 #include "nsIContent.h"
15 #include "nsIFrame.h"
16 #include "nsLayoutUtils.h"
17 #include "nsView.h"
18 #include "PositionedEventTargeting.h"
20 using namespace mozilla::dom;
22 namespace mozilla {
24 StaticAutoPtr<nsTHashMap<nsUint32HashKey, TouchManager::TouchInfo>>
25 TouchManager::sCaptureTouchList;
26 layers::LayersId TouchManager::sCaptureTouchLayersId;
28 /*static*/
29 void TouchManager::InitializeStatics() {
30 NS_ASSERTION(!sCaptureTouchList, "InitializeStatics called multiple times!");
31 sCaptureTouchList = new nsTHashMap<nsUint32HashKey, TouchManager::TouchInfo>;
32 sCaptureTouchLayersId = layers::LayersId{0};
35 /*static*/
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);
48 mDocument = nullptr;
49 mPresShell = nullptr;
52 static nsIContent* GetNonAnonymousAncestor(EventTarget* aTarget) {
53 nsIContent* content = nsIContent::FromEventTargetOrNull(aTarget);
54 if (content && content->IsInNativeAnonymousSubtree()) {
55 content = content->FindFirstNonChromeOnlyAccessContent();
57 return content;
60 /*static*/
61 void TouchManager::EvictTouchPoint(RefPtr<Touch>& aTouch,
62 Document* aLimitToDocument) {
63 nsCOMPtr<nsINode> node(
64 nsINode::FromEventTargetOrNull(aTouch->mOriginalTarget));
65 if (node) {
66 Document* doc = node->GetComposedDoc();
67 if (doc && (!aLimitToDocument || aLimitToDocument == doc)) {
68 PresShell* presShell = doc->GetPresShell();
69 if (presShell) {
70 nsIFrame* frame = presShell->GetRootFrame();
71 if (frame) {
72 nsPoint pt(aTouch->mRefPoint.x, aTouch->mRefPoint.y);
73 nsCOMPtr<nsIWidget> widget = frame->GetView()->GetNearestWidget(&pt);
74 if (widget) {
75 WidgetTouchEvent event(true, eTouchEnd, widget);
76 event.mTouches.AppendElement(aTouch);
77 nsEventStatus status;
78 widget->DispatchEvent(&event, status);
84 if (!node || !aLimitToDocument || node->OwnerDoc() == aLimitToDocument) {
85 sCaptureTouchList->Remove(aTouch->Identifier());
89 /*static*/
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};
108 /* static */
109 nsIFrame* TouchManager::SetupTarget(WidgetTouchEvent* aEvent,
110 nsIFrame* aFrame) {
111 MOZ_ASSERT(aEvent);
113 if (!aEvent || aEvent->mMessage != eTouchStart) {
114 // All touch events except for touchstart use a captured target.
115 return aFrame;
118 nsIFrame* target = aFrame;
119 for (int32_t i = aEvent->mTouches.Length(); i;) {
120 --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);
130 if (target) {
131 nsCOMPtr<nsIContent> targetContent;
132 target->GetContentForEvent(aEvent, getter_AddRefs(targetContent));
133 touch->SetTouchTarget(targetContent
134 ? targetContent->GetAsElementOrParentElement()
135 : nullptr);
136 } else {
137 aEvent->mTouches.RemoveElementAt(i);
139 } else {
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);
144 if (oldTouch) {
145 touch->SetTouchTarget(oldTouch->mOriginalTarget);
149 return target;
152 /* static */
153 nsIFrame* TouchManager::SuppressInvalidPointsAndGetTargetedFrame(
154 WidgetTouchEvent* aEvent) {
155 MOZ_ASSERT(aEvent);
157 if (!aEvent || aEvent->mMessage != eTouchStart) {
158 // All touch events except for touchstart use a captured target.
159 return nullptr;
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
164 // the capture list
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;) {
172 --i;
173 dom::Touch* touch = aEvent->mTouches[i];
174 if (TouchManager::HasCapturedTouch(touch->Identifier())) {
175 continue;
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;
184 } else {
185 nsIFrame* newTargetFrame = nullptr;
186 for (nsIFrame* f = targetFrame; f;
187 f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
188 if (f->PresContext()->Document() == anyTarget->OwnerDoc()) {
189 newTargetFrame = f;
190 break;
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;
203 } else {
204 targetFrame = newTargetFrame;
205 targetFrame->GetContentForEvent(aEvent, getter_AddRefs(targetContent));
206 touch->SetTouchTarget(targetContent
207 ? targetContent->GetAsElementOrParentElement()
208 : nullptr);
211 if (targetFrame) {
212 frame = targetFrame;
215 return frame;
218 bool TouchManager::PreHandleEvent(WidgetEvent* aEvent, nsEventStatus* aStatus,
219 bool& aTouchIsNew,
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) {
226 case eTouchStart: {
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
230 // queue
231 if (touchEvent->mTouches.Length() == 1) {
232 EvictTouches();
233 // Per
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;
239 } else {
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;) {
245 --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;
253 TouchInfo info = {
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
258 // it is suppressed.
259 touches.RemoveElementAt(i);
260 continue;
263 break;
265 case eTouchMove: {
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;) {
272 --i;
273 Touch* touch = touches[i];
274 if (!touch) {
275 continue;
277 int32_t id = touch->Identifier();
278 touch->mMessage = aEvent->mMessage;
280 TouchInfo info;
281 if (!sCaptureTouchList->Get(id, &info)) {
282 touches.RemoveElementAt(i);
283 continue;
285 const RefPtr<Touch> oldTouch = info.mTouch;
286 if (!oldTouch->Equals(touch)) {
287 touch->mChanged = true;
288 haveChanged = true;
291 nsCOMPtr<EventTarget> targetPtr = oldTouch->mOriginalTarget;
292 if (!targetPtr) {
293 touches.RemoveElementAt(i);
294 continue;
296 nsCOMPtr<nsINode> targetNode(do_QueryInterface(targetPtr));
297 if (!targetNode->IsInComposedDoc()) {
298 targetPtr = info.mNonAnonymousTarget;
300 touch->SetTouchTarget(targetPtr);
302 info.mTouch = touch;
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) {
308 aTouchIsNew = true;
310 if (oldTouch->mIsTouchEventSuppressed) {
311 touch->mIsTouchEventSuppressed = true;
312 touches.RemoveElementAt(i);
313 continue;
316 // is nothing has changed, we should just return
317 if (!haveChanged) {
318 if (aTouchIsNew) {
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
324 // work.
325 for (uint32_t i = 0; i < touchEvent->mTouches.Length(); ++i) {
326 if (touchEvent->mTouches[i]) {
327 touchEvent->mTouches[i]->mChanged = true;
328 break;
331 } else {
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();
336 return false;
339 break;
341 case eTouchEnd:
342 case eTouchCancel: {
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;) {
349 --i;
350 Touch* touch = touches[i];
351 if (!touch) {
352 continue;
354 touch->mMessage = aEvent->mMessage;
355 touch->mChanged = true;
357 int32_t id = touch->Identifier();
358 TouchInfo info;
359 if (!sCaptureTouchList->Get(id, &info)) {
360 continue;
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);
373 continue;
376 // add any touches left in the touch list, but ensure changed=false
377 AppendToTouchList(&touches);
378 break;
380 case eTouchPointerCancel: {
381 // Don't generate pointer events by touch events after eTouchPointerCancel
382 // is received.
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];
388 if (!touch) {
389 continue;
391 int32_t id = touch->Identifier();
392 TouchInfo info;
393 if (!sCaptureTouchList->Get(id, &info)) {
394 continue;
396 info.mConvertToPointer = false;
397 sCaptureTouchList->InsertOrUpdate(id, info);
399 break;
401 default:
402 break;
404 return true;
407 /*static*/
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;
415 if (touch) {
416 EventTarget* target = touch->GetTarget();
417 if (target) {
418 result = nsIContent::FromEventTargetOrNull(target);
419 break;
423 return result.forget();
426 /*static*/
427 bool TouchManager::HasCapturedTouch(int32_t aId) {
428 return sCaptureTouchList->Contains(aId);
431 /*static*/
432 already_AddRefed<Touch> TouchManager::GetCapturedTouch(int32_t aId) {
433 RefPtr<Touch> touch;
434 TouchInfo info;
435 if (sCaptureTouchList->Get(aId, &info)) {
436 touch = info.mTouch;
438 return touch.forget();
441 /*static*/
442 bool TouchManager::ShouldConvertTouchToPointer(const Touch* aTouch,
443 const WidgetTouchEvent* aEvent) {
444 if (!aTouch || !aTouch->convertToPointer) {
445 return false;
447 TouchInfo info;
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
452 // registered.
453 // Check WidgetTouchEvent::mMessage because Touch::mMessage is assigned when
454 // pre-handling touch events.
455 return aEvent->mMessage == eTouchStart;
458 if (!info.mConvertToPointer) {
459 return false;
462 switch (aEvent->mMessage) {
463 case eTouchStart: {
464 // We don't want to fire duplicated pointerdown.
465 return false;
467 case eTouchMove: {
468 return !aTouch->Equals(info.mTouch);
470 default:
471 break;
473 return true;
476 } // namespace mozilla