Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 : * vim: set ts=2 sw=2 et tw=78:
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 : */
7 :
8 : #include "TouchManager.h"
9 :
10 : #include "mozilla/dom/EventTarget.h"
11 : #include "mozilla/PresShell.h"
12 : #include "nsIFrame.h"
13 : #include "nsView.h"
14 :
15 : using namespace mozilla::dom;
16 :
17 : namespace mozilla {
18 :
19 : nsDataHashtable<nsUint32HashKey, TouchManager::TouchInfo>* TouchManager::sCaptureTouchList;
20 :
21 : /*static*/ void
22 3 : TouchManager::InitializeStatics()
23 : {
24 3 : NS_ASSERTION(!sCaptureTouchList, "InitializeStatics called multiple times!");
25 3 : sCaptureTouchList = new nsDataHashtable<nsUint32HashKey, TouchManager::TouchInfo>;
26 3 : }
27 :
28 : /*static*/ void
29 0 : TouchManager::ReleaseStatics()
30 : {
31 0 : NS_ASSERTION(sCaptureTouchList, "ReleaseStatics called without Initialize!");
32 0 : delete sCaptureTouchList;
33 0 : sCaptureTouchList = nullptr;
34 0 : }
35 :
36 : void
37 28 : TouchManager::Init(PresShell* aPresShell, nsIDocument* aDocument)
38 : {
39 28 : mPresShell = aPresShell;
40 28 : mDocument = aDocument;
41 28 : }
42 :
43 : void
44 4 : TouchManager::Destroy()
45 : {
46 4 : EvictTouches();
47 4 : mDocument = nullptr;
48 4 : mPresShell = nullptr;
49 4 : }
50 :
51 : static nsIContent*
52 0 : GetNonAnonymousAncestor(EventTarget* aTarget)
53 : {
54 0 : nsCOMPtr<nsIContent> content(do_QueryInterface(aTarget));
55 0 : if (content && content->IsInNativeAnonymousSubtree()) {
56 0 : content = content->FindFirstNonChromeOnlyAccessContent();
57 : }
58 0 : return content;
59 : }
60 :
61 : /*static*/ void
62 0 : TouchManager::EvictTouchPoint(RefPtr<Touch>& aTouch,
63 : nsIDocument* aLimitToDocument)
64 : {
65 0 : nsCOMPtr<nsINode> node(do_QueryInterface(aTouch->mTarget));
66 0 : if (node) {
67 0 : nsIDocument* doc = node->GetUncomposedDoc();
68 0 : if (doc && (!aLimitToDocument || aLimitToDocument == doc)) {
69 0 : nsIPresShell* presShell = doc->GetShell();
70 0 : if (presShell) {
71 0 : nsIFrame* frame = presShell->GetRootFrame();
72 0 : if (frame) {
73 0 : nsPoint pt(aTouch->mRefPoint.x, aTouch->mRefPoint.y);
74 0 : nsCOMPtr<nsIWidget> widget = frame->GetView()->GetNearestWidget(&pt);
75 0 : if (widget) {
76 0 : WidgetTouchEvent event(true, eTouchEnd, widget);
77 0 : event.mTime = PR_IntervalNow();
78 0 : event.mTouches.AppendElement(aTouch);
79 : nsEventStatus status;
80 0 : widget->DispatchEvent(&event, status);
81 : }
82 : }
83 : }
84 : }
85 : }
86 0 : if (!node || !aLimitToDocument || node->OwnerDoc() == aLimitToDocument) {
87 0 : sCaptureTouchList->Remove(aTouch->Identifier());
88 : }
89 0 : }
90 :
91 : /*static*/ void
92 4 : TouchManager::AppendToTouchList(WidgetTouchEvent::TouchArray* aTouchList)
93 : {
94 8 : for (auto iter = sCaptureTouchList->Iter();
95 4 : !iter.Done();
96 0 : iter.Next()) {
97 0 : RefPtr<Touch>& touch = iter.Data().mTouch;
98 0 : touch->mChanged = false;
99 0 : aTouchList->AppendElement(touch);
100 : }
101 4 : }
102 :
103 : void
104 4 : TouchManager::EvictTouches()
105 : {
106 8 : WidgetTouchEvent::AutoTouchArray touches;
107 4 : AppendToTouchList(&touches);
108 4 : for (uint32_t i = 0; i < touches.Length(); ++i) {
109 0 : EvictTouchPoint(touches[i], mDocument);
110 : }
111 4 : }
112 :
113 : bool
114 10 : TouchManager::PreHandleEvent(WidgetEvent* aEvent,
115 : nsEventStatus* aStatus,
116 : bool& aTouchIsNew,
117 : bool& aIsHandlingUserInput,
118 : nsCOMPtr<nsIContent>& aCurrentEventContent)
119 : {
120 10 : switch (aEvent->mMessage) {
121 : case eTouchStart: {
122 0 : aIsHandlingUserInput = true;
123 0 : WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
124 : // if there is only one touch in this touchstart event, assume that it is
125 : // the start of a new touch session and evict any old touches in the
126 : // queue
127 0 : if (touchEvent->mTouches.Length() == 1) {
128 0 : WidgetTouchEvent::AutoTouchArray touches;
129 0 : AppendToTouchList(&touches);
130 0 : for (uint32_t i = 0; i < touches.Length(); ++i) {
131 0 : EvictTouchPoint(touches[i]);
132 : }
133 : }
134 : // Add any new touches to the queue
135 0 : for (uint32_t i = 0; i < touchEvent->mTouches.Length(); ++i) {
136 0 : Touch* touch = touchEvent->mTouches[i];
137 0 : int32_t id = touch->Identifier();
138 0 : if (!sCaptureTouchList->Get(id, nullptr)) {
139 : // If it is not already in the queue, it is a new touch
140 0 : touch->mChanged = true;
141 : }
142 0 : touch->mMessage = aEvent->mMessage;
143 : TouchInfo info = { touch, GetNonAnonymousAncestor(touch->mTarget),
144 0 : true };
145 0 : sCaptureTouchList->Put(id, info);
146 : }
147 0 : break;
148 : }
149 : case eTouchMove: {
150 : // Check for touches that changed. Mark them add to queue
151 0 : WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
152 0 : WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
153 0 : bool haveChanged = false;
154 0 : for (int32_t i = touches.Length(); i; ) {
155 0 : --i;
156 0 : Touch* touch = touches[i];
157 0 : if (!touch) {
158 0 : continue;
159 : }
160 0 : int32_t id = touch->Identifier();
161 0 : touch->mMessage = aEvent->mMessage;
162 :
163 0 : TouchInfo info;
164 0 : if (!sCaptureTouchList->Get(id, &info)) {
165 0 : touches.RemoveElementAt(i);
166 0 : continue;
167 : }
168 0 : RefPtr<Touch> oldTouch = info.mTouch;
169 0 : if (!touch->Equals(oldTouch)) {
170 0 : touch->mChanged = true;
171 0 : haveChanged = true;
172 : }
173 :
174 0 : nsCOMPtr<EventTarget> targetPtr = oldTouch->mTarget;
175 0 : if (!targetPtr) {
176 0 : touches.RemoveElementAt(i);
177 0 : continue;
178 : }
179 0 : nsCOMPtr<nsINode> targetNode(do_QueryInterface(targetPtr));
180 0 : if (!targetNode->IsInComposedDoc()) {
181 0 : targetPtr = do_QueryInterface(info.mNonAnonymousTarget);
182 : }
183 0 : touch->SetTarget(targetPtr);
184 :
185 0 : info.mTouch = touch;
186 : // info.mNonAnonymousTarget is still valid from above
187 0 : sCaptureTouchList->Put(id, info);
188 : // if we're moving from touchstart to touchmove for this touch
189 : // we allow preventDefault to prevent mouse events
190 0 : if (oldTouch->mMessage != touch->mMessage) {
191 0 : aTouchIsNew = true;
192 : }
193 : }
194 : // is nothing has changed, we should just return
195 0 : if (!haveChanged) {
196 0 : if (aTouchIsNew) {
197 : // however, if this is the first touchmove after a touchstart,
198 : // it is special in that preventDefault is allowed on it, so
199 : // we must dispatch it to content even if nothing changed. we
200 : // arbitrarily pick the first touch point to be the "changed"
201 : // touch because firing an event with no changed events doesn't
202 : // work.
203 0 : for (uint32_t i = 0; i < touchEvent->mTouches.Length(); ++i) {
204 0 : if (touchEvent->mTouches[i]) {
205 0 : touchEvent->mTouches[i]->mChanged = true;
206 0 : break;
207 : }
208 : }
209 : } else {
210 0 : return false;
211 : }
212 : }
213 0 : break;
214 : }
215 : case eTouchEnd:
216 0 : aIsHandlingUserInput = true;
217 : // Fall through to touchcancel code
218 : MOZ_FALLTHROUGH;
219 : case eTouchCancel: {
220 : // Remove the changed touches
221 : // need to make sure we only remove touches that are ending here
222 0 : WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
223 0 : WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
224 0 : for (uint32_t i = 0; i < touches.Length(); ++i) {
225 0 : Touch* touch = touches[i];
226 0 : if (!touch) {
227 0 : continue;
228 : }
229 0 : touch->mMessage = aEvent->mMessage;
230 0 : touch->mChanged = true;
231 :
232 0 : int32_t id = touch->Identifier();
233 0 : TouchInfo info;
234 0 : if (!sCaptureTouchList->Get(id, &info)) {
235 0 : continue;
236 : }
237 0 : nsCOMPtr<EventTarget> targetPtr = info.mTouch->mTarget;
238 0 : nsCOMPtr<nsINode> targetNode(do_QueryInterface(targetPtr));
239 0 : if (targetNode && !targetNode->IsInComposedDoc()) {
240 0 : targetPtr = do_QueryInterface(info.mNonAnonymousTarget);
241 : }
242 :
243 0 : aCurrentEventContent = do_QueryInterface(targetPtr);
244 0 : touch->SetTarget(targetPtr);
245 0 : sCaptureTouchList->Remove(id);
246 : }
247 : // add any touches left in the touch list, but ensure changed=false
248 0 : AppendToTouchList(&touches);
249 0 : break;
250 : }
251 : case eTouchPointerCancel: {
252 : // Don't generate pointer events by touch events after eTouchPointerCancel
253 : // is received.
254 0 : WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
255 0 : WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
256 0 : for (uint32_t i = 0; i < touches.Length(); ++i) {
257 0 : Touch* touch = touches[i];
258 0 : if (!touch) {
259 0 : continue;
260 : }
261 0 : int32_t id = touch->Identifier();
262 0 : TouchInfo info;
263 0 : if (!sCaptureTouchList->Get(id, &info)) {
264 0 : continue;
265 : }
266 0 : info.mConvertToPointer = false;
267 0 : sCaptureTouchList->Put(id, info);
268 : }
269 0 : break;
270 : }
271 : default:
272 10 : break;
273 : }
274 10 : return true;
275 : }
276 :
277 : /*static*/ already_AddRefed<nsIContent>
278 0 : TouchManager::GetAnyCapturedTouchTarget()
279 : {
280 0 : nsCOMPtr<nsIContent> result = nullptr;
281 0 : if (sCaptureTouchList->Count() == 0) {
282 0 : return result.forget();
283 : }
284 0 : for (auto iter = sCaptureTouchList->Iter(); !iter.Done(); iter.Next()) {
285 0 : RefPtr<Touch>& touch = iter.Data().mTouch;
286 0 : if (touch) {
287 0 : EventTarget* target = touch->GetTarget();
288 0 : if (target) {
289 0 : result = do_QueryInterface(target);
290 0 : break;
291 : }
292 : }
293 : }
294 0 : return result.forget();
295 : }
296 :
297 : /*static*/ bool
298 0 : TouchManager::HasCapturedTouch(int32_t aId)
299 : {
300 0 : return sCaptureTouchList->Contains(aId);
301 : }
302 :
303 : /*static*/ already_AddRefed<Touch>
304 0 : TouchManager::GetCapturedTouch(int32_t aId)
305 : {
306 0 : RefPtr<Touch> touch;
307 0 : TouchInfo info;
308 0 : if (sCaptureTouchList->Get(aId, &info)) {
309 0 : touch = info.mTouch;
310 : }
311 0 : return touch.forget();
312 : }
313 :
314 : /*static*/ bool
315 0 : TouchManager::ShouldConvertTouchToPointer(const Touch* aTouch,
316 : const WidgetTouchEvent* aEvent)
317 : {
318 0 : if (!aTouch || !aTouch->convertToPointer) {
319 0 : return false;
320 : }
321 0 : TouchInfo info;
322 0 : if (!sCaptureTouchList->Get(aTouch->Identifier(), &info)) {
323 : // This check runs before the TouchManager has the touch registered in its
324 : // touch list. It's because we dispatching pointer events before handling
325 : // touch events. So we convert eTouchStart to pointerdown even it's not
326 : // registered.
327 : // Check WidgetTouchEvent::mMessage because Touch::mMessage is assigned when
328 : // pre-handling touch events.
329 0 : return aEvent->mMessage == eTouchStart;
330 : }
331 0 : return info.mConvertToPointer;
332 : }
333 :
334 : } // namespace mozilla
|