1/*
2 * Copyright (C) 2019 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
17 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24
25#include "config.h"
26#include "PointerCaptureController.h"
27
28#if ENABLE(POINTER_EVENTS)
29
30#include "Document.h"
31#include "Element.h"
32#include "EventHandler.h"
33#include "EventNames.h"
34#include "EventTarget.h"
35#include "Page.h"
36#include "PointerEvent.h"
37
38#if ENABLE(POINTER_LOCK)
39#include "PointerLockController.h"
40#endif
41
42namespace WebCore {
43
44PointerCaptureController::PointerCaptureController(Page& page)
45 : m_page(page)
46{
47#if !ENABLE(TOUCH_EVENTS)
48 CapturingData capturingData;
49 capturingData.pointerType = PointerEvent::mousePointerType();
50 m_activePointerIdsToCapturingData.set(PointerEvent::defaultMousePointerIdentifier(), capturingData);
51#endif
52}
53
54ExceptionOr<void> PointerCaptureController::setPointerCapture(Element* capturingTarget, PointerID pointerId)
55{
56 // https://w3c.github.io/pointerevents/#setting-pointer-capture
57
58 // 1. If the pointerId provided as the method's argument does not match any of the active pointers, then throw a DOMException with the name NotFoundError.
59 auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
60 if (iterator == m_activePointerIdsToCapturingData.end())
61 return Exception { NotFoundError };
62
63 // 2. If the Element on which this method is invoked is not connected, throw an exception with the name InvalidStateError.
64 if (!capturingTarget->isConnected())
65 return Exception { InvalidStateError };
66
67#if ENABLE(POINTER_LOCK)
68 // 3. If this method is invoked while the document has a locked element, throw an exception with the name InvalidStateError.
69 if (auto* page = capturingTarget->document().page()) {
70 if (page->pointerLockController().isLocked())
71 return Exception { InvalidStateError };
72 }
73#endif
74
75 // 4. If the pointer is not in the active buttons state, then terminate these steps.
76 // FIXME: implement when we support mouse events.
77
78 // 5. For the specified pointerId, set the pending pointer capture target override to the Element on which this method was invoked.
79 iterator->value.pendingTargetOverride = capturingTarget;
80
81 return { };
82}
83
84ExceptionOr<void> PointerCaptureController::releasePointerCapture(Element* capturingTarget, PointerID pointerId)
85{
86 // https://w3c.github.io/pointerevents/#releasing-pointer-capture
87
88 // Pointer capture is released on an element explicitly by calling the element.releasePointerCapture(pointerId) method.
89 // When this method is called, a user agent MUST run the following steps:
90
91 // 1. If the pointerId provided as the method's argument does not match any of the active pointers and these steps are not
92 // being invoked as a result of the implicit release of pointer capture, then throw a DOMException with the name NotFoundError.
93 auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
94 if (iterator == m_activePointerIdsToCapturingData.end())
95 return Exception { NotFoundError };
96
97 // 2. If hasPointerCapture is false for the Element with the specified pointerId, then terminate these steps.
98 if (!hasPointerCapture(capturingTarget, pointerId))
99 return { };
100
101 // 3. For the specified pointerId, clear the pending pointer capture target override, if set.
102 iterator->value.pendingTargetOverride = nullptr;
103
104 return { };
105}
106
107bool PointerCaptureController::hasPointerCapture(Element* capturingTarget, PointerID pointerId)
108{
109 // https://w3c.github.io/pointerevents/#dom-element-haspointercapture
110
111 // Indicates whether the element on which this method is invoked has pointer capture for the pointer identified by the argument pointerId.
112 // In particular, returns true if the pending pointer capture target override for pointerId is set to the element on which this method is
113 // invoked, and false otherwise.
114
115 auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
116 return iterator != m_activePointerIdsToCapturingData.end() && iterator->value.pendingTargetOverride == capturingTarget;
117}
118
119void PointerCaptureController::pointerLockWasApplied()
120{
121 // https://w3c.github.io/pointerevents/#implicit-release-of-pointer-capture
122
123 // When a pointer lock is successfully applied on an element, a user agent MUST run the steps as if the releasePointerCapture()
124 // method has been called if any element is set to be captured or pending to be captured.
125 for (auto& capturingData : m_activePointerIdsToCapturingData.values()) {
126 capturingData.pendingTargetOverride = nullptr;
127 capturingData.targetOverride = nullptr;
128 }
129}
130
131void PointerCaptureController::elementWasRemoved(Element& element)
132{
133 for (auto& keyAndValue : m_activePointerIdsToCapturingData) {
134 auto& capturingData = keyAndValue.value;
135 if (capturingData.pendingTargetOverride == &element || capturingData.targetOverride == &element) {
136 // https://w3c.github.io/pointerevents/#implicit-release-of-pointer-capture
137 // When the pointer capture target override is no longer connected, the pending pointer capture target override and pointer capture target
138 // override nodes SHOULD be cleared and also a PointerEvent named lostpointercapture corresponding to the captured pointer SHOULD be fired
139 // at the document.
140 auto pointerId = keyAndValue.key;
141 auto pointerType = capturingData.pointerType;
142 releasePointerCapture(&element, pointerId);
143 element.document().enqueueDocumentEvent(PointerEvent::create(eventNames().lostpointercaptureEvent, pointerId, pointerType));
144 return;
145 }
146 }
147}
148
149void PointerCaptureController::touchEndedOrWasCancelledForIdentifier(PointerID pointerId)
150{
151 m_activePointerIdsToCapturingData.remove(pointerId);
152}
153
154bool PointerCaptureController::hasCancelledPointerEventForIdentifier(PointerID pointerId)
155{
156 auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
157 return iterator != m_activePointerIdsToCapturingData.end() && iterator->value.cancelled;
158}
159
160#if ENABLE(TOUCH_EVENTS) && PLATFORM(IOS_FAMILY)
161void PointerCaptureController::dispatchEventForTouchAtIndex(EventTarget& target, const PlatformTouchEvent& platformTouchEvent, unsigned index, bool isPrimary, WindowProxy& view)
162{
163 auto dispatchEvent = [&](const String& type) {
164 target.dispatchEvent(PointerEvent::create(type, platformTouchEvent, index, isPrimary, view));
165 };
166
167 auto dispatchEnterOrLeaveEvent = [&](const String& type) {
168 if (!is<Element>(&target))
169 return;
170
171 auto* targetElement = &downcast<Element>(target);
172
173 bool hasCapturingListenerInHierarchy = false;
174 for (ContainerNode* curr = targetElement; curr; curr = curr->parentInComposedTree()) {
175 if (curr->hasCapturingEventListeners(type)) {
176 hasCapturingListenerInHierarchy = true;
177 break;
178 }
179 }
180
181 for (Element* element = &downcast<Element>(target); element; element = element->parentElementInComposedTree()) {
182 if (hasCapturingListenerInHierarchy || element->hasEventListeners(type))
183 element->dispatchEvent(PointerEvent::create(type, platformTouchEvent, index, isPrimary, view));
184 }
185 };
186
187 auto pointerEvent = PointerEvent::create(platformTouchEvent, index, isPrimary, view);
188
189 if (pointerEvent->type() == eventNames().pointerdownEvent) {
190 // https://w3c.github.io/pointerevents/#the-pointerdown-event
191 // For input devices that do not support hover, a user agent MUST also fire a pointer event named pointerover followed by a pointer event named
192 // pointerenter prior to dispatching the pointerdown event.
193 dispatchEvent(eventNames().pointeroverEvent);
194 dispatchEnterOrLeaveEvent(eventNames().pointerenterEvent);
195 }
196
197 pointerEventWillBeDispatched(pointerEvent, &target);
198 target.dispatchEvent(pointerEvent);
199 pointerEventWasDispatched(pointerEvent);
200
201 if (pointerEvent->type() == eventNames().pointerupEvent) {
202 // https://w3c.github.io/pointerevents/#the-pointerup-event
203 // For input devices that do not support hover, a user agent MUST also fire a pointer event named pointerout followed by a
204 // pointer event named pointerleave after dispatching the pointerup event.
205 dispatchEvent(eventNames().pointeroutEvent);
206 dispatchEnterOrLeaveEvent(eventNames().pointerleaveEvent);
207 }
208}
209#endif
210
211void PointerCaptureController::dispatchEvent(PointerEvent& event, EventTarget* target)
212{
213 auto iterator = m_activePointerIdsToCapturingData.find(event.pointerId());
214 if (iterator != m_activePointerIdsToCapturingData.end()) {
215 auto& capturingData = iterator->value;
216 if (capturingData.pendingTargetOverride && capturingData.targetOverride)
217 capturingData.targetOverride->dispatchEvent(event);
218 }
219
220 if (target && !event.target())
221 target->dispatchEvent(event);
222
223 pointerEventWasDispatched(event);
224}
225
226void PointerCaptureController::pointerEventWillBeDispatched(const PointerEvent& event, EventTarget* target)
227{
228 // https://w3c.github.io/pointerevents/#implicit-pointer-capture
229
230 // Some input devices (such as touchscreens) implement a "direct manipulation" metaphor where a pointer is intended to act primarily on the UI
231 // element it became active upon (providing a physical illusion of direct contact, instead of indirect contact via a cursor that conceptually
232 // floats above the UI). Such devices are identified by the InputDeviceCapabilities.pointerMovementScrolls property and should have "implicit
233 // pointer capture" behavior as follows.
234
235 // Direct manipulation devices should behave exactly as if setPointerCapture was called on the target element just before the invocation of any
236 // pointerdown listeners. The hasPointerCapture API may be used (eg. within any pointerdown listener) to determine whether this has occurred. If
237 // releasePointerCapture is not called for the pointer before the next pointer event is fired, then a gotpointercapture event will be dispatched
238 // to the target (as normal) indicating that capture is active.
239
240 if (!is<Element>(target) || event.type() != eventNames().pointerdownEvent)
241 return;
242
243 auto pointerId = event.pointerId();
244 CapturingData capturingData;
245 capturingData.pointerType = event.pointerType();
246 m_activePointerIdsToCapturingData.set(pointerId, capturingData);
247 setPointerCapture(downcast<Element>(target), pointerId);
248}
249
250void PointerCaptureController::pointerEventWasDispatched(const PointerEvent& event)
251{
252 // https://w3c.github.io/pointerevents/#implicit-release-of-pointer-capture
253
254 auto iterator = m_activePointerIdsToCapturingData.find(event.pointerId());
255 if (iterator != m_activePointerIdsToCapturingData.end()) {
256 auto& capturingData = iterator->value;
257 capturingData.isPrimary = event.isPrimary();
258
259 // Immediately after firing the pointerup or pointercancel events, a user agent MUST clear the pending pointer capture target
260 // override for the pointerId of the pointerup or pointercancel event that was just dispatched, and then run Process Pending
261 // Pointer Capture steps to fire lostpointercapture if necessary.
262 if (event.type() == eventNames().pointerupEvent)
263 capturingData.pendingTargetOverride = nullptr;
264 }
265
266 processPendingPointerCapture(event);
267}
268
269void PointerCaptureController::cancelPointer(PointerID pointerId, const IntPoint& documentPoint)
270{
271 // https://w3c.github.io/pointerevents/#the-pointercancel-event
272
273 // A user agent MUST fire a pointer event named pointercancel in the following circumstances:
274 //
275 // The user agent has determined that a pointer is unlikely to continue to produce events (for example, because of a hardware event).
276 // After having fired the pointerdown event, if the pointer is subsequently used to manipulate the page viewport (e.g. panning or zooming).
277 // Immediately before drag operation starts [HTML], for the pointer that caused the drag operation.
278 // After firing the pointercancel event, a user agent MUST also fire a pointer event named pointerout followed by firing a pointer event named pointerleave.
279
280 // https://w3c.github.io/pointerevents/#implicit-release-of-pointer-capture
281
282 // Immediately after firing the pointerup or pointercancel events, a user agent MUST clear the pending pointer capture target
283 // override for the pointerId of the pointerup or pointercancel event that was just dispatched, and then run Process Pending
284 // Pointer Capture steps to fire lostpointercapture if necessary. After running Process Pending Pointer Capture steps, if the
285 // pointer supports hover, user agent MUST also send corresponding boundary events necessary to reflect the current position of
286 // the pointer with no capture.
287
288 auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
289 if (iterator == m_activePointerIdsToCapturingData.end())
290 return;
291
292 auto& capturingData = iterator->value;
293 if (capturingData.cancelled)
294 return;
295
296 capturingData.pendingTargetOverride = nullptr;
297 capturingData.cancelled = true;
298
299 auto& target = capturingData.targetOverride;
300 if (!target)
301 target = m_page.mainFrame().eventHandler().hitTestResultAtPoint(documentPoint, HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::DisallowUserAgentShadowContent | HitTestRequest::AllowChildFrameContent).innerNonSharedElement();
302
303 if (!target)
304 return;
305
306 auto event = PointerEvent::create(eventNames().pointercancelEvent, pointerId, capturingData.pointerType, capturingData.isPrimary ? PointerEvent::IsPrimary::Yes : PointerEvent::IsPrimary::No);
307 target->dispatchEvent(event);
308 processPendingPointerCapture(WTFMove(event));
309}
310
311void PointerCaptureController::processPendingPointerCapture(const PointerEvent& event)
312{
313 // https://w3c.github.io/pointerevents/#process-pending-pointer-capture
314
315 auto iterator = m_activePointerIdsToCapturingData.find(event.pointerId());
316 if (iterator == m_activePointerIdsToCapturingData.end())
317 return;
318
319 auto& capturingData = iterator->value;
320
321 // 1. If the pointer capture target override for this pointer is set and is not equal to the pending pointer capture target override,
322 // then fire a pointer event named lostpointercapture at the pointer capture target override node.
323 if (capturingData.targetOverride && capturingData.targetOverride != capturingData.pendingTargetOverride)
324 capturingData.targetOverride->dispatchEvent(PointerEvent::createForPointerCapture(eventNames().lostpointercaptureEvent, event));
325
326 // 2. If the pending pointer capture target override for this pointer is set and is not equal to the pointer capture target override,
327 // then fire a pointer event named gotpointercapture at the pending pointer capture target override.
328 if (capturingData.pendingTargetOverride && capturingData.targetOverride != capturingData.pendingTargetOverride)
329 capturingData.pendingTargetOverride->dispatchEvent(PointerEvent::createForPointerCapture(eventNames().gotpointercaptureEvent, event));
330
331 // 3. Set the pointer capture target override to the pending pointer capture target override, if set. Otherwise, clear the pointer
332 // capture target override.
333 capturingData.targetOverride = capturingData.pendingTargetOverride;
334}
335
336} // namespace WebCore
337
338#endif // ENABLE(POINTER_EVENTS)
339