2 * Copyright (C) 2019 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
26 #include "PointerCaptureController.h"
28 #if ENABLE(POINTER_EVENTS)
32 #include "EventNames.h"
33 #include "EventTarget.h"
35 #include "PointerEvent.h"
39 PointerCaptureController::PointerCaptureController() = default;
41 ExceptionOr<void> PointerCaptureController::setPointerCapture(Element* capturingTarget, int32_t pointerId)
43 // https://w3c.github.io/pointerevents/#setting-pointer-capture
45 // 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.
46 auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
47 if (iterator == m_activePointerIdsToCapturingData.end())
48 return Exception { NotFoundError };
50 // 2. If the Element on which this method is invoked is not connected, throw an exception with the name InvalidStateError.
51 if (!capturingTarget->isConnected())
52 return Exception { InvalidStateError };
54 #if ENABLE(POINTER_LOCK)
55 // 3. If this method is invoked while the document has a locked element, throw an exception with the name InvalidStateError.
56 if (auto* page = capturingTarget->document().page()) {
57 if (page->pointerLockController().isLocked())
58 return Exception { InvalidStateError };
62 // 4. If the pointer is not in the active buttons state, then terminate these steps.
63 // FIXME: implement when we support mouse events.
65 // 5. For the specified pointerId, set the pending pointer capture target override to the Element on which this method was invoked.
66 iterator->value.pendingTargetOverride = capturingTarget;
71 ExceptionOr<void> PointerCaptureController::releasePointerCapture(Element* capturingTarget, int32_t pointerId, ImplicitCapture implicit)
73 // https://w3c.github.io/pointerevents/#releasing-pointer-capture
75 // Pointer capture is released on an element explicitly by calling the element.releasePointerCapture(pointerId) method.
76 // When this method is called, a user agent MUST run the following steps:
78 // 1. If the pointerId provided as the method's argument does not match any of the active pointers and these steps are not
79 // being invoked as a result of the implicit release of pointer capture, then throw a DOMException with the name NotFoundError.
80 auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
81 if (implicit == ImplicitCapture::No && iterator == m_activePointerIdsToCapturingData.end())
82 return Exception { NotFoundError };
84 // 2. If hasPointerCapture is false for the Element with the specified pointerId, then terminate these steps.
85 if (!hasPointerCapture(capturingTarget, pointerId))
88 // 3. For the specified pointerId, clear the pending pointer capture target override, if set.
89 iterator->value.pendingTargetOverride = nullptr;
94 bool PointerCaptureController::hasPointerCapture(Element* capturingTarget, int32_t pointerId)
96 // https://w3c.github.io/pointerevents/#dom-element-haspointercapture
98 // Indicates whether the element on which this method is invoked has pointer capture for the pointer identified by the argument pointerId.
99 // In particular, returns true if the pending pointer capture target override for pointerId is set to the element on which this method is
100 // invoked, and false otherwise.
102 auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
103 if (iterator == m_activePointerIdsToCapturingData.end())
106 auto& capturingData = iterator->value;
107 return capturingData.pendingTargetOverride == capturingTarget || capturingData.targetOverride == capturingTarget;
110 void PointerCaptureController::pointerLockWasApplied()
112 // https://w3c.github.io/pointerevents/#implicit-release-of-pointer-capture
114 // When a pointer lock is successfully applied on an element, a user agent MUST run the steps as if the releasePointerCapture()
115 // method has been called if any element is set to be captured or pending to be captured.
116 for (auto& capturingData : m_activePointerIdsToCapturingData.values()) {
117 capturingData.pendingTargetOverride = nullptr;
118 capturingData.targetOverride = nullptr;
122 void PointerCaptureController::touchEndedOrWasCancelledForIdentifier(int32_t pointerId)
124 m_activePointerIdsToCapturingData.remove(pointerId);
127 void PointerCaptureController::pointerEventWillBeDispatched(const PointerEvent& event, EventTarget* target)
129 // https://w3c.github.io/pointerevents/#implicit-pointer-capture
131 // Some input devices (such as touchscreens) implement a "direct manipulation" metaphor where a pointer is intended to act primarily on the UI
132 // element it became active upon (providing a physical illusion of direct contact, instead of indirect contact via a cursor that conceptually
133 // floats above the UI). Such devices are identified by the InputDeviceCapabilities.pointerMovementScrolls property and should have "implicit
134 // pointer capture" behavior as follows.
136 // Direct manipulation devices should behave exactly as if setPointerCapture was called on the target element just before the invocation of any
137 // pointerdown listeners. The hasPointerCapture API may be used (eg. within any pointerdown listener) to determine whether this has occurred. If
138 // releasePointerCapture is not called for the pointer before the next pointer event is fired, then a gotpointercapture event will be dispatched
139 // to the target (as normal) indicating that capture is active.
141 if (!is<Element>(target) || event.type() != eventNames().pointerdownEvent)
144 auto pointerId = event.pointerId();
145 m_activePointerIdsToCapturingData.set(pointerId, CapturingData { });
146 setPointerCapture(downcast<Element>(target), pointerId);
149 void PointerCaptureController::pointerEventWasDispatched(const PointerEvent& event)
151 // https://w3c.github.io/pointerevents/#implicit-release-of-pointer-capture
153 auto iterator = m_activePointerIdsToCapturingData.find(event.pointerId());
154 if (iterator != m_activePointerIdsToCapturingData.end()) {
155 auto& capturingData = iterator->value;
156 // Immediately after firing the pointerup or pointercancel events, a user agent MUST clear the pending pointer capture target
157 // override for the pointerId of the pointerup or pointercancel event that was just dispatched, and then run Process Pending
158 // Pointer Capture steps to fire lostpointercapture if necessary.
159 if (event.type() == eventNames().pointerupEvent)
160 capturingData.pendingTargetOverride = nullptr;
162 // When the pointer capture target override is no longer connected, the pending pointer capture target override and pointer
163 // capture target override nodes SHOULD be cleared and also a PointerEvent named lostpointercapture corresponding to the captured
164 // pointer SHOULD be fired at the document.
165 if (capturingData.targetOverride && !capturingData.targetOverride->isConnected()) {
166 capturingData.pendingTargetOverride = nullptr;
167 capturingData.targetOverride = nullptr;
171 processPendingPointerCapture(event);
174 void PointerCaptureController::processPendingPointerCapture(const PointerEvent& event)
176 // https://w3c.github.io/pointerevents/#process-pending-pointer-capture
178 auto iterator = m_activePointerIdsToCapturingData.find(event.pointerId());
179 if (iterator == m_activePointerIdsToCapturingData.end())
182 auto& capturingData = iterator->value;
184 // 1. If the pointer capture target override for this pointer is set and is not equal to the pending pointer capture target override,
185 // then fire a pointer event named lostpointercapture at the pointer capture target override node.
186 if (capturingData.targetOverride && capturingData.targetOverride != capturingData.pendingTargetOverride)
187 capturingData.targetOverride->dispatchEvent(PointerEvent::createForPointerCapture(eventNames().lostpointercaptureEvent, event));
189 // 2. If the pending pointer capture target override for this pointer is set and is not equal to the pointer capture target override,
190 // then fire a pointer event named gotpointercapture at the pending pointer capture target override.
191 if (capturingData.pendingTargetOverride && capturingData.targetOverride != capturingData.pendingTargetOverride)
192 capturingData.pendingTargetOverride->dispatchEvent(PointerEvent::createForPointerCapture(eventNames().gotpointercaptureEvent, event));
194 // 3. Set the pointer capture target override to the pending pointer capture target override, if set. Otherwise, clear the pointer
195 // capture target override.
196 capturingData.targetOverride = capturingData.pendingTargetOverride;
199 } // namespace WebCore
201 #endif // ENABLE(POINTER_EVENTS)