Move comment explaining our Options to OptionsList.h
[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 #include <wtf/CheckedArithmetic.h>
38
39 #if ENABLE(POINTER_LOCK)
40 #include "PointerLockController.h"
41 #endif
42
43 namespace WebCore {
44
45 PointerCaptureController::PointerCaptureController(Page& page)
46     : m_page(page)
47 {
48     reset();
49 }
50
51 Element* PointerCaptureController::pointerCaptureElement(Document* document, PointerID pointerId)
52 {
53     auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
54     if (iterator != m_activePointerIdsToCapturingData.end()) {
55         auto pointerCaptureElement = iterator->value.targetOverride;
56         if (pointerCaptureElement && &pointerCaptureElement->document() == document)
57             return pointerCaptureElement.get();
58     }
59     return nullptr;
60 }
61
62 ExceptionOr<void> PointerCaptureController::setPointerCapture(Element* capturingTarget, PointerID pointerId)
63 {
64     // https://w3c.github.io/pointerevents/#setting-pointer-capture
65
66     // 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.
67     auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
68     if (iterator == m_activePointerIdsToCapturingData.end())
69         return Exception { NotFoundError };
70
71     // 2. If the Element on which this method is invoked is not connected, throw an exception with the name InvalidStateError.
72     if (!capturingTarget->isConnected())
73         return Exception { InvalidStateError };
74
75 #if ENABLE(POINTER_LOCK)
76     // 3. If this method is invoked while the document has a locked element, throw an exception with the name InvalidStateError.
77     if (auto* page = capturingTarget->document().page()) {
78         if (page->pointerLockController().isLocked())
79             return Exception { InvalidStateError };
80     }
81 #endif
82
83     // 4. If the pointer is not in the active buttons state, then terminate these steps.
84     // 5. For the specified pointerId, set the pending pointer capture target override to the Element on which this method was invoked.
85     auto& capturingData = iterator->value;
86     if (capturingData.pointerIsPressed)
87         capturingData.pendingTargetOverride = capturingTarget;
88
89     return { };
90 }
91
92 ExceptionOr<void> PointerCaptureController::releasePointerCapture(Element* capturingTarget, PointerID pointerId)
93 {
94     // https://w3c.github.io/pointerevents/#releasing-pointer-capture
95
96     // Pointer capture is released on an element explicitly by calling the element.releasePointerCapture(pointerId) method.
97     // When this method is called, a user agent MUST run the following steps:
98
99     // 1. If the pointerId provided as the method's argument does not match any of the active pointers and these steps are not
100     // being invoked as a result of the implicit release of pointer capture, then throw a DOMException with the name NotFoundError.
101     auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
102     if (iterator == m_activePointerIdsToCapturingData.end())
103         return Exception { NotFoundError };
104
105     // 2. If hasPointerCapture is false for the Element with the specified pointerId, then terminate these steps.
106     if (!hasPointerCapture(capturingTarget, pointerId))
107         return { };
108
109     // 3. For the specified pointerId, clear the pending pointer capture target override, if set.
110     iterator->value.pendingTargetOverride = nullptr;
111
112     return { };
113 }
114
115 bool PointerCaptureController::hasPointerCapture(Element* capturingTarget, PointerID pointerId)
116 {
117     // https://w3c.github.io/pointerevents/#dom-element-haspointercapture
118
119     // Indicates whether the element on which this method is invoked has pointer capture for the pointer identified by the argument pointerId.
120     // In particular, returns true if the pending pointer capture target override for pointerId is set to the element on which this method is
121     // invoked, and false otherwise.
122
123     auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
124     return iterator != m_activePointerIdsToCapturingData.end() && iterator->value.pendingTargetOverride == capturingTarget;
125 }
126
127 void PointerCaptureController::pointerLockWasApplied()
128 {
129     // https://w3c.github.io/pointerevents/#implicit-release-of-pointer-capture
130
131     // When a pointer lock is successfully applied on an element, a user agent MUST run the steps as if the releasePointerCapture()
132     // method has been called if any element is set to be captured or pending to be captured.
133     for (auto& capturingData : m_activePointerIdsToCapturingData.values()) {
134         capturingData.pendingTargetOverride = nullptr;
135         capturingData.targetOverride = nullptr;
136     }
137 }
138
139 void PointerCaptureController::elementWasRemoved(Element& element)
140 {
141     for (auto& keyAndValue : m_activePointerIdsToCapturingData) {
142         auto& capturingData = keyAndValue.value;
143         if (capturingData.pendingTargetOverride == &element || capturingData.targetOverride == &element) {
144             // https://w3c.github.io/pointerevents/#implicit-release-of-pointer-capture
145             // When the pointer capture target override is no longer connected, the pending pointer capture target override and pointer capture target
146             // override nodes SHOULD be cleared and also a PointerEvent named lostpointercapture corresponding to the captured pointer SHOULD be fired
147             // at the document.
148             ASSERT(WTF::isInBounds<PointerID>(keyAndValue.key));
149             auto pointerId = static_cast<PointerID>(keyAndValue.key);
150             auto pointerType = capturingData.pointerType;
151             releasePointerCapture(&element, pointerId);
152             element.document().enqueueDocumentEvent(PointerEvent::create(eventNames().lostpointercaptureEvent, pointerId, pointerType));
153             return;
154         }
155     }
156 }
157
158 void PointerCaptureController::reset()
159 {
160     m_activePointerIdsToCapturingData.clear();
161 #if !ENABLE(TOUCH_EVENTS)
162     CapturingData capturingData;
163     capturingData.pointerType = PointerEvent::mousePointerType();
164     m_activePointerIdsToCapturingData.add(mousePointerID, capturingData);
165 #endif
166 }
167
168 void PointerCaptureController::touchWithIdentifierWasRemoved(PointerID pointerId)
169 {
170     m_activePointerIdsToCapturingData.remove(pointerId);
171 }
172
173 bool PointerCaptureController::hasCancelledPointerEventForIdentifier(PointerID pointerId)
174 {
175     auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
176     return iterator != m_activePointerIdsToCapturingData.end() && iterator->value.cancelled;
177 }
178
179 bool PointerCaptureController::preventsCompatibilityMouseEventsForIdentifier(PointerID pointerId)
180 {
181     auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
182     return iterator != m_activePointerIdsToCapturingData.end() && iterator->value.preventsCompatibilityMouseEvents;
183 }
184
185 #if ENABLE(TOUCH_EVENTS) && PLATFORM(IOS_FAMILY)
186 void PointerCaptureController::dispatchEventForTouchAtIndex(EventTarget& target, const PlatformTouchEvent& platformTouchEvent, unsigned index, bool isPrimary, WindowProxy& view)
187 {
188     auto dispatchOverOrOutEvent = [&](const String& type) {
189         dispatchEvent(PointerEvent::create(type, platformTouchEvent, index, isPrimary, view), &target);
190     };
191
192     auto dispatchEnterOrLeaveEvent = [&](const String& type) {
193         if (!is<Element>(&target))
194             return;
195
196         auto* targetElement = &downcast<Element>(target);
197
198         bool hasCapturingListenerInHierarchy = false;
199         for (ContainerNode* curr = targetElement; curr; curr = curr->parentInComposedTree()) {
200             if (curr->hasCapturingEventListeners(type)) {
201                 hasCapturingListenerInHierarchy = true;
202                 break;
203             }
204         }
205
206         Vector<Ref<Element>, 32> targetChain;
207         for (Element* element = targetElement; element; element = element->parentElementInComposedTree()) {
208             if (hasCapturingListenerInHierarchy || element->hasEventListeners(type))
209                 targetChain.append(*element);
210         }
211
212         if (type == eventNames().pointerenterEvent) {
213             for (auto& element : WTF::makeReversedRange(targetChain))
214                 dispatchEvent(PointerEvent::create(type, platformTouchEvent, index, isPrimary, view), element.ptr());
215         } else {
216             for (auto& element : targetChain)
217                 dispatchEvent(PointerEvent::create(type, platformTouchEvent, index, isPrimary, view), element.ptr());
218         }
219     };
220
221     auto pointerEvent = PointerEvent::create(platformTouchEvent, index, isPrimary, view);
222
223     if (pointerEvent->type() == eventNames().pointerdownEvent) {
224         // https://w3c.github.io/pointerevents/#the-pointerdown-event
225         // 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
226         // pointerenter prior to dispatching the pointerdown event.
227         dispatchOverOrOutEvent(eventNames().pointeroverEvent);
228         dispatchEnterOrLeaveEvent(eventNames().pointerenterEvent);
229     }
230
231     dispatchEvent(pointerEvent, &target);
232
233     if (pointerEvent->type() == eventNames().pointerupEvent) {
234         // https://w3c.github.io/pointerevents/#the-pointerup-event
235         // For input devices that do not support hover, a user agent MUST also fire a pointer event named pointerout followed by a
236         // pointer event named pointerleave after dispatching the pointerup event.
237         dispatchOverOrOutEvent(eventNames().pointeroutEvent);
238         dispatchEnterOrLeaveEvent(eventNames().pointerleaveEvent);
239     }
240 }
241 #endif
242
243 RefPtr<PointerEvent> PointerCaptureController::pointerEventForMouseEvent(const MouseEvent& mouseEvent)
244 {
245     const auto& type = mouseEvent.type();
246     const auto& names = eventNames();
247
248     auto iterator = m_activePointerIdsToCapturingData.find(mousePointerID);
249     ASSERT(iterator != m_activePointerIdsToCapturingData.end());
250     auto& capturingData = iterator->value;
251
252     short newButton = mouseEvent.button();
253     short button = (type == names.mousemoveEvent && newButton == capturingData.previousMouseButton) ? -1 : newButton;
254
255     // https://w3c.github.io/pointerevents/#chorded-button-interactions
256     // Some pointer devices, such as mouse or pen, support multiple buttons. In the Mouse Event model, each button
257     // press produces a mousedown and mouseup event. To better abstract this hardware difference and simplify
258     // cross-device input authoring, Pointer Events do not fire overlapping pointerdown and pointerup events
259     // for chorded button presses (depressing an additional button while another button on the pointer device is
260     // already depressed).
261     if (type == names.mousedownEvent || type == names.mouseupEvent) {
262         // We're already active and getting another mousedown, this means that we should dispatch
263         // a pointermove event and let the button state show the newly depressed button.
264         if (type == names.mousedownEvent && capturingData.pointerIsPressed)
265             return PointerEvent::create(names.pointermoveEvent, button, mouseEvent);
266
267         // We're active and the mouseup still has some pressed button, this means we should dispatch
268         // a pointermove event.
269         if (type == names.mouseupEvent && capturingData.pointerIsPressed && mouseEvent.buttons() > 0)
270             return PointerEvent::create(names.pointermoveEvent, button, mouseEvent);
271     }
272
273     capturingData.previousMouseButton = newButton;
274
275     return PointerEvent::create(button, mouseEvent);
276 }
277
278 void PointerCaptureController::dispatchEvent(PointerEvent& event, EventTarget* target)
279 {
280     if (!target || event.target())
281         return;
282
283     // https://w3c.github.io/pointerevents/#firing-events-using-the-pointerevent-interface
284     // If the event is not gotpointercapture or lostpointercapture, run Process Pending Pointer Capture steps for this PointerEvent.
285     // We only need to do this for non-mouse type since for mouse events this method will be called in Document::prepareMouseEvent().
286     if (event.pointerType() != PointerEvent::mousePointerType())
287         processPendingPointerCapture(event.pointerId());
288
289     pointerEventWillBeDispatched(event, target);
290     target->dispatchEvent(event);
291     pointerEventWasDispatched(event);
292 }
293
294 void PointerCaptureController::pointerEventWillBeDispatched(const PointerEvent& event, EventTarget* target)
295 {
296     if (!is<Element>(target))
297         return;
298
299     bool isPointerdown = event.type() == eventNames().pointerdownEvent;
300     bool isPointerup = event.type() == eventNames().pointerupEvent;
301     if (!isPointerdown && !isPointerup)
302         return;
303
304     auto pointerId = event.pointerId();
305
306     if (event.pointerType() == PointerEvent::mousePointerType()) {
307         auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
308         if (iterator != m_activePointerIdsToCapturingData.end())
309             iterator->value.pointerIsPressed = isPointerdown;
310         return;
311     }
312
313     if (!isPointerdown)
314         return;
315
316     // https://w3c.github.io/pointerevents/#implicit-pointer-capture
317
318     // Some input devices (such as touchscreens) implement a "direct manipulation" metaphor where a pointer is intended to act primarily on the UI
319     // element it became active upon (providing a physical illusion of direct contact, instead of indirect contact via a cursor that conceptually
320     // floats above the UI). Such devices are identified by the InputDeviceCapabilities.pointerMovementScrolls property and should have "implicit
321     // pointer capture" behavior as follows.
322
323     // Direct manipulation devices should behave exactly as if setPointerCapture was called on the target element just before the invocation of any
324     // pointerdown listeners. The hasPointerCapture API may be used (eg. within any pointerdown listener) to determine whether this has occurred. If
325     // releasePointerCapture is not called for the pointer before the next pointer event is fired, then a gotpointercapture event will be dispatched
326     // to the target (as normal) indicating that capture is active.
327
328     CapturingData capturingData;
329     capturingData.pointerType = event.pointerType();
330     capturingData.pointerIsPressed = true;
331     m_activePointerIdsToCapturingData.set(pointerId, capturingData);
332     setPointerCapture(downcast<Element>(target), pointerId);
333 }
334
335 void PointerCaptureController::pointerEventWasDispatched(const PointerEvent& event)
336 {
337     auto iterator = m_activePointerIdsToCapturingData.find(event.pointerId());
338     if (iterator != m_activePointerIdsToCapturingData.end()) {
339         auto& capturingData = iterator->value;
340         capturingData.isPrimary = event.isPrimary();
341
342         // Immediately after firing the pointerup or pointercancel events, a user agent MUST clear the pending pointer capture target
343         // override for the pointerId of the pointerup or pointercancel event that was just dispatched, and then run Process Pending
344         // Pointer Capture steps to fire lostpointercapture if necessary.
345         // https://w3c.github.io/pointerevents/#implicit-release-of-pointer-capture
346         if (event.type() == eventNames().pointerupEvent) {
347             capturingData.pendingTargetOverride = nullptr;
348             processPendingPointerCapture(event.pointerId());
349         }
350
351         // If a mouse pointer has moved while it isn't pressed, make sure we reset the preventsCompatibilityMouseEvents flag since
352         // we could otherwise prevent compatibility mouse events while those are only supposed to be prevented while the pointer is pressed.
353         if (event.type() == eventNames().pointermoveEvent && capturingData.pointerType == PointerEvent::mousePointerType() && !capturingData.pointerIsPressed)
354             capturingData.preventsCompatibilityMouseEvents = false;
355
356         // If the pointer event dispatched was pointerdown and the event was canceled, then set the PREVENT MOUSE EVENT flag for this pointerType.
357         // https://www.w3.org/TR/pointerevents/#mapping-for-devices-that-support-hover
358         if (event.type() == eventNames().pointerdownEvent)
359             capturingData.preventsCompatibilityMouseEvents = event.defaultPrevented();
360     }
361 }
362
363 void PointerCaptureController::cancelPointer(PointerID pointerId, const IntPoint& documentPoint)
364 {
365     // https://w3c.github.io/pointerevents/#the-pointercancel-event
366
367     // A user agent MUST fire a pointer event named pointercancel in the following circumstances:
368     //
369     // The user agent has determined that a pointer is unlikely to continue to produce events (for example, because of a hardware event).
370     // After having fired the pointerdown event, if the pointer is subsequently used to manipulate the page viewport (e.g. panning or zooming).
371     // Immediately before drag operation starts [HTML], for the pointer that caused the drag operation.
372     // After firing the pointercancel event, a user agent MUST also fire a pointer event named pointerout followed by firing a pointer event named pointerleave.
373
374     // https://w3c.github.io/pointerevents/#implicit-release-of-pointer-capture
375
376     // Immediately after firing the pointerup or pointercancel events, a user agent MUST clear the pending pointer capture target
377     // override for the pointerId of the pointerup or pointercancel event that was just dispatched, and then run Process Pending
378     // Pointer Capture steps to fire lostpointercapture if necessary. After running Process Pending Pointer Capture steps, if the
379     // pointer supports hover, user agent MUST also send corresponding boundary events necessary to reflect the current position of
380     // the pointer with no capture.
381
382     auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
383     if (iterator == m_activePointerIdsToCapturingData.end())
384         return;
385
386     auto& capturingData = iterator->value;
387     if (capturingData.cancelled)
388         return;
389
390     capturingData.pendingTargetOverride = nullptr;
391     capturingData.cancelled = true;
392
393     auto& target = capturingData.targetOverride;
394     if (!target)
395         target = m_page.mainFrame().eventHandler().hitTestResultAtPoint(documentPoint, HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::DisallowUserAgentShadowContent | HitTestRequest::AllowChildFrameContent).innerNonSharedElement();
396
397     if (!target)
398         return;
399
400     // After firing the pointercancel event, a user agent MUST also fire a pointer event named pointerout
401     // followed by firing a pointer event named pointerleave.
402     auto isPrimary = capturingData.isPrimary ? PointerEvent::IsPrimary::Yes : PointerEvent::IsPrimary::No;
403     auto cancelEvent = PointerEvent::create(eventNames().pointercancelEvent, pointerId, capturingData.pointerType, isPrimary);
404     target->dispatchEvent(cancelEvent);
405     target->dispatchEvent(PointerEvent::create(eventNames().pointeroutEvent, pointerId, capturingData.pointerType, isPrimary));
406     target->dispatchEvent(PointerEvent::create(eventNames().pointerleaveEvent, pointerId, capturingData.pointerType, isPrimary));
407     processPendingPointerCapture(pointerId);
408 }
409
410 void PointerCaptureController::processPendingPointerCapture(PointerID pointerId)
411 {
412     auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
413     if (iterator == m_activePointerIdsToCapturingData.end())
414         return;
415
416     if (m_processingPendingPointerCapture)
417         return;
418
419     m_processingPendingPointerCapture = true;
420
421     auto& capturingData = iterator->value;
422
423     // Cache the pending target override since it could be modified during the dispatch of events in this function.
424     auto pendingTargetOverride = capturingData.pendingTargetOverride;
425
426     // https://w3c.github.io/pointerevents/#process-pending-pointer-capture
427     // 1. If the pointer capture target override for this pointer is set and is not equal to the pending pointer capture target override,
428     // then fire a pointer event named lostpointercapture at the pointer capture target override node.
429     if (capturingData.targetOverride && capturingData.targetOverride->isConnected() && capturingData.targetOverride != pendingTargetOverride) {
430         capturingData.targetOverride->dispatchEvent(PointerEvent::createForPointerCapture(eventNames().lostpointercaptureEvent, pointerId, capturingData.isPrimary, capturingData.pointerType));
431         if (capturingData.pointerType == PointerEvent::mousePointerType()) {
432             if (auto* frame = capturingData.targetOverride->document().frame())
433                 frame->eventHandler().pointerCaptureElementDidChange(nullptr);
434         }
435     }
436
437     // 2. If the pending pointer capture target override for this pointer is set and is not equal to the pointer capture target override,
438     // then fire a pointer event named gotpointercapture at the pending pointer capture target override.
439     if (capturingData.pendingTargetOverride && capturingData.targetOverride != pendingTargetOverride) {
440         if (capturingData.pointerType == PointerEvent::mousePointerType()) {
441             if (auto* frame = pendingTargetOverride->document().frame())
442                 frame->eventHandler().pointerCaptureElementDidChange(pendingTargetOverride.get());
443         }
444         pendingTargetOverride->dispatchEvent(PointerEvent::createForPointerCapture(eventNames().gotpointercaptureEvent, pointerId, capturingData.isPrimary, capturingData.pointerType));
445     }
446
447     // 3. Set the pointer capture target override to the pending pointer capture target override, if set. Otherwise, clear the pointer
448     // capture target override.
449     capturingData.targetOverride = pendingTargetOverride;
450
451     m_processingPendingPointerCapture = false;
452 }
453
454 } // namespace WebCore
455
456 #endif // ENABLE(POINTER_EVENTS)