Dispatch pointercancel events when content is panned or zoomed on iOS
[WebKit-https.git] / Source / WebCore / page / PointerCaptureController.cpp
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 namespace WebCore {
39
40 PointerCaptureController::PointerCaptureController(Page& page)
41     : m_page(page)
42 {
43 }
44
45 ExceptionOr<void> PointerCaptureController::setPointerCapture(Element* capturingTarget, int32_t pointerId)
46 {
47     // https://w3c.github.io/pointerevents/#setting-pointer-capture
48
49     // 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.
50     auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
51     if (iterator == m_activePointerIdsToCapturingData.end())
52         return Exception { NotFoundError };
53
54     // 2. If the Element on which this method is invoked is not connected, throw an exception with the name InvalidStateError.
55     if (!capturingTarget->isConnected())
56         return Exception { InvalidStateError };
57
58 #if ENABLE(POINTER_LOCK)
59     // 3. If this method is invoked while the document has a locked element, throw an exception with the name InvalidStateError.
60     if (auto* page = capturingTarget->document().page()) {
61         if (page->pointerLockController().isLocked())
62             return Exception { InvalidStateError };
63     }
64 #endif
65
66     // 4. If the pointer is not in the active buttons state, then terminate these steps.
67     // FIXME: implement when we support mouse events.
68
69     // 5. For the specified pointerId, set the pending pointer capture target override to the Element on which this method was invoked.
70     iterator->value.pendingTargetOverride = capturingTarget;
71
72     return { };
73 }
74
75 ExceptionOr<void> PointerCaptureController::releasePointerCapture(Element* capturingTarget, int32_t pointerId)
76 {
77     // https://w3c.github.io/pointerevents/#releasing-pointer-capture
78
79     // Pointer capture is released on an element explicitly by calling the element.releasePointerCapture(pointerId) method.
80     // When this method is called, a user agent MUST run the following steps:
81
82     // 1. If the pointerId provided as the method's argument does not match any of the active pointers and these steps are not
83     // being invoked as a result of the implicit release of pointer capture, then throw a DOMException with the name NotFoundError.
84     auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
85     if (iterator == m_activePointerIdsToCapturingData.end())
86         return Exception { NotFoundError };
87
88     // 2. If hasPointerCapture is false for the Element with the specified pointerId, then terminate these steps.
89     if (!hasPointerCapture(capturingTarget, pointerId))
90         return { };
91
92     // 3. For the specified pointerId, clear the pending pointer capture target override, if set.
93     iterator->value.pendingTargetOverride = nullptr;
94
95     return { };
96 }
97
98 bool PointerCaptureController::hasPointerCapture(Element* capturingTarget, int32_t pointerId)
99 {
100     // https://w3c.github.io/pointerevents/#dom-element-haspointercapture
101
102     // Indicates whether the element on which this method is invoked has pointer capture for the pointer identified by the argument pointerId.
103     // In particular, returns true if the pending pointer capture target override for pointerId is set to the element on which this method is
104     // invoked, and false otherwise.
105
106     auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
107     if (iterator == m_activePointerIdsToCapturingData.end())
108         return false;
109
110     auto& capturingData = iterator->value;
111     return capturingData.pendingTargetOverride == capturingTarget || capturingData.targetOverride == capturingTarget;
112 }
113
114 void PointerCaptureController::pointerLockWasApplied()
115 {
116     // https://w3c.github.io/pointerevents/#implicit-release-of-pointer-capture
117
118     // When a pointer lock is successfully applied on an element, a user agent MUST run the steps as if the releasePointerCapture()
119     // method has been called if any element is set to be captured or pending to be captured.
120     for (auto& capturingData : m_activePointerIdsToCapturingData.values()) {
121         capturingData.pendingTargetOverride = nullptr;
122         capturingData.targetOverride = nullptr;
123     }
124 }
125
126 void PointerCaptureController::touchEndedOrWasCancelledForIdentifier(int32_t pointerId)
127 {
128     m_activePointerIdsToCapturingData.remove(pointerId);
129 }
130
131 bool PointerCaptureController::hasCancelledPointerEventForIdentifier(int32_t pointerId)
132 {
133     auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
134     return iterator != m_activePointerIdsToCapturingData.end() && iterator->value.cancelled;
135 }
136
137 void PointerCaptureController::pointerEventWillBeDispatched(const PointerEvent& event, EventTarget* target)
138 {
139     // https://w3c.github.io/pointerevents/#implicit-pointer-capture
140
141     // Some input devices (such as touchscreens) implement a "direct manipulation" metaphor where a pointer is intended to act primarily on the UI
142     // element it became active upon (providing a physical illusion of direct contact, instead of indirect contact via a cursor that conceptually
143     // floats above the UI). Such devices are identified by the InputDeviceCapabilities.pointerMovementScrolls property and should have "implicit
144     // pointer capture" behavior as follows.
145
146     // Direct manipulation devices should behave exactly as if setPointerCapture was called on the target element just before the invocation of any
147     // pointerdown listeners. The hasPointerCapture API may be used (eg. within any pointerdown listener) to determine whether this has occurred. If
148     // releasePointerCapture is not called for the pointer before the next pointer event is fired, then a gotpointercapture event will be dispatched
149     // to the target (as normal) indicating that capture is active.
150
151     if (!is<Element>(target) || event.type() != eventNames().pointerdownEvent)
152         return;
153
154     auto pointerId = event.pointerId();
155     CapturingData capturingData;
156     capturingData.pointerType = event.pointerType();
157     m_activePointerIdsToCapturingData.set(pointerId, capturingData);
158     setPointerCapture(downcast<Element>(target), pointerId);
159 }
160
161 void PointerCaptureController::pointerEventWasDispatched(const PointerEvent& event)
162 {
163     // https://w3c.github.io/pointerevents/#implicit-release-of-pointer-capture
164
165     auto iterator = m_activePointerIdsToCapturingData.find(event.pointerId());
166     if (iterator != m_activePointerIdsToCapturingData.end()) {
167         auto& capturingData = iterator->value;
168         // Immediately after firing the pointerup or pointercancel events, a user agent MUST clear the pending pointer capture target
169         // override for the pointerId of the pointerup or pointercancel event that was just dispatched, and then run Process Pending
170         // Pointer Capture steps to fire lostpointercapture if necessary.
171         if (event.type() == eventNames().pointerupEvent)
172             capturingData.pendingTargetOverride = nullptr;
173         
174         // When the pointer capture target override is no longer connected, the pending pointer capture target override and pointer
175         // capture target override nodes SHOULD be cleared and also a PointerEvent named lostpointercapture corresponding to the captured
176         // pointer SHOULD be fired at the document.
177         if (capturingData.targetOverride && !capturingData.targetOverride->isConnected()) {
178             capturingData.pendingTargetOverride = nullptr;
179             capturingData.targetOverride = nullptr;
180         }
181     }
182
183     processPendingPointerCapture(event);
184 }
185
186 void PointerCaptureController::cancelPointer(int32_t pointerId, const IntPoint& documentPoint)
187 {
188     // https://w3c.github.io/pointerevents/#the-pointercancel-event
189
190     // A user agent MUST fire a pointer event named pointercancel in the following circumstances:
191     //
192     // The user agent has determined that a pointer is unlikely to continue to produce events (for example, because of a hardware event).
193     // After having fired the pointerdown event, if the pointer is subsequently used to manipulate the page viewport (e.g. panning or zooming).
194     // Immediately before drag operation starts [HTML], for the pointer that caused the drag operation.
195     // After firing the pointercancel event, a user agent MUST also fire a pointer event named pointerout followed by firing a pointer event named pointerleave.
196
197     // https://w3c.github.io/pointerevents/#implicit-release-of-pointer-capture
198
199     // Immediately after firing the pointerup or pointercancel events, a user agent MUST clear the pending pointer capture target
200     // override for the pointerId of the pointerup or pointercancel event that was just dispatched, and then run Process Pending
201     // Pointer Capture steps to fire lostpointercapture if necessary. After running Process Pending Pointer Capture steps, if the
202     // pointer supports hover, user agent MUST also send corresponding boundary events necessary to reflect the current position of
203     // the pointer with no capture.
204
205     auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
206     if (iterator == m_activePointerIdsToCapturingData.end())
207         return;
208
209     auto& capturingData = iterator->value;
210     if (capturingData.cancelled)
211         return;
212
213     capturingData.pendingTargetOverride = nullptr;
214     capturingData.cancelled = true;
215
216     auto& target = capturingData.targetOverride;
217     if (!target)
218         target = m_page.mainFrame().eventHandler().hitTestResultAtPoint(documentPoint, HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::DisallowUserAgentShadowContent | HitTestRequest::AllowChildFrameContent).innerNonSharedElement();
219
220     if (!target)
221         return;
222
223     auto event = PointerEvent::create(eventNames().pointercancelEvent, pointerId, capturingData.pointerType);
224     target->dispatchEvent(event);
225     processPendingPointerCapture(WTFMove(event));
226 }
227
228 void PointerCaptureController::processPendingPointerCapture(const PointerEvent& event)
229 {
230     // https://w3c.github.io/pointerevents/#process-pending-pointer-capture
231
232     auto iterator = m_activePointerIdsToCapturingData.find(event.pointerId());
233     if (iterator == m_activePointerIdsToCapturingData.end())
234         return;
235
236     auto& capturingData = iterator->value;
237
238     // 1. If the pointer capture target override for this pointer is set and is not equal to the pending pointer capture target override,
239     // then fire a pointer event named lostpointercapture at the pointer capture target override node.
240     if (capturingData.targetOverride && capturingData.targetOverride != capturingData.pendingTargetOverride)
241         capturingData.targetOverride->dispatchEvent(PointerEvent::createForPointerCapture(eventNames().lostpointercaptureEvent, event));
242
243     // 2. If the pending pointer capture target override for this pointer is set and is not equal to the pointer capture target override,
244     // then fire a pointer event named gotpointercapture at the pending pointer capture target override.
245     if (capturingData.pendingTargetOverride && capturingData.targetOverride != capturingData.pendingTargetOverride)
246         capturingData.pendingTargetOverride->dispatchEvent(PointerEvent::createForPointerCapture(eventNames().gotpointercaptureEvent, event));
247
248     // 3. Set the pointer capture target override to the pending pointer capture target override, if set. Otherwise, clear the pointer
249     // capture target override.
250     capturingData.targetOverride = capturingData.pendingTargetOverride;
251 }
252
253 } // namespace WebCore
254
255 #endif // ENABLE(POINTER_EVENTS)