blob: 88c3ca9610ca27e2bfa8d548597170b990990897 [file] [log] [blame]
/*
* Copyright (C) 2019 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "PointerCaptureController.h"
#include "Document.h"
#include "Element.h"
#include "EventHandler.h"
#include "EventNames.h"
#include "EventTarget.h"
#include "HitTestResult.h"
#include "Page.h"
#include "PointerEvent.h"
#include <wtf/CheckedArithmetic.h>
#if ENABLE(POINTER_LOCK)
#include "PointerLockController.h"
#endif
namespace WebCore {
PointerCaptureController::PointerCaptureController(Page& page)
: m_page(page)
{
reset();
}
Element* PointerCaptureController::pointerCaptureElement(Document* document, PointerID pointerId) const
{
if (auto capturingData = m_activePointerIdsToCapturingData.get(pointerId)) {
auto pointerCaptureElement = capturingData->targetOverride;
if (pointerCaptureElement && &pointerCaptureElement->document() == document)
return pointerCaptureElement.get();
}
return nullptr;
}
ExceptionOr<void> PointerCaptureController::setPointerCapture(Element* capturingTarget, PointerID pointerId)
{
// https://w3c.github.io/pointerevents/#setting-pointer-capture
// 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.
RefPtr capturingData = m_activePointerIdsToCapturingData.get(pointerId);
if (!capturingData)
return Exception { NotFoundError };
// 2. If the Element on which this method is invoked is not connected, throw an exception with the name InvalidStateError.
if (!capturingTarget->isConnected())
return Exception { InvalidStateError };
#if ENABLE(POINTER_LOCK)
// 3. If this method is invoked while the document has a locked element, throw an exception with the name InvalidStateError.
if (auto* page = capturingTarget->document().page()) {
if (page->pointerLockController().isLocked())
return Exception { InvalidStateError };
}
#endif
// 4. If the pointer is not in the active buttons state, then terminate these steps.
// 5. For the specified pointerId, set the pending pointer capture target override to the Element on which this method was invoked.
if (capturingData->pointerIsPressed)
capturingData->pendingTargetOverride = capturingTarget;
updateHaveAnyCapturingElement();
return { };
}
ExceptionOr<void> PointerCaptureController::releasePointerCapture(Element* capturingTarget, PointerID pointerId)
{
// https://w3c.github.io/pointerevents/#releasing-pointer-capture
// Pointer capture is released on an element explicitly by calling the element.releasePointerCapture(pointerId) method.
// When this method is called, a user agent MUST run the following steps:
// 1. If the pointerId provided as the method's argument does not match any of the active pointers and these steps are not
// being invoked as a result of the implicit release of pointer capture, then throw a DOMException with the name NotFoundError.
RefPtr capturingData = m_activePointerIdsToCapturingData.get(pointerId);
if (!capturingData)
return Exception { NotFoundError };
// 2. If hasPointerCapture is false for the Element with the specified pointerId, then terminate these steps.
if (!hasPointerCapture(capturingTarget, pointerId))
return { };
// 3. For the specified pointerId, clear the pending pointer capture target override, if set.
capturingData->pendingTargetOverride = nullptr;
// FIXME: This leaves value.targetOverride set: webkit.org/b/221342.
updateHaveAnyCapturingElement();
return { };
}
bool PointerCaptureController::hasPointerCapture(Element* capturingTarget, PointerID pointerId)
{
// https://w3c.github.io/pointerevents/#dom-element-haspointercapture
// Indicates whether the element on which this method is invoked has pointer capture for the pointer identified by the argument pointerId.
// In particular, returns true if the pending pointer capture target override for pointerId is set to the element on which this method is
// invoked, and false otherwise.
if (!m_haveAnyCapturingElement)
return false;
auto capturingData = m_activePointerIdsToCapturingData.get(pointerId);
return capturingData && capturingData->pendingTargetOverride == capturingTarget;
}
void PointerCaptureController::pointerLockWasApplied()
{
// https://w3c.github.io/pointerevents/#implicit-release-of-pointer-capture
// When a pointer lock is successfully applied on an element, a user agent MUST run the steps as if the releasePointerCapture()
// method has been called if any element is set to be captured or pending to be captured.
for (auto& capturingData : m_activePointerIdsToCapturingData.values()) {
capturingData->pendingTargetOverride = nullptr;
capturingData->targetOverride = nullptr;
}
updateHaveAnyCapturingElement();
}
void PointerCaptureController::elementWasRemovedSlow(Element& element)
{
ASSERT(m_haveAnyCapturingElement);
for (auto [pointerId, capturingData] : m_activePointerIdsToCapturingData) {
if (capturingData->pendingTargetOverride == &element || capturingData->targetOverride == &element) {
// https://w3c.github.io/pointerevents/#implicit-release-of-pointer-capture
// When the pointer capture target override is no longer connected, the pending pointer capture target override and pointer capture target
// override nodes SHOULD be cleared and also a PointerEvent named lostpointercapture corresponding to the captured pointer SHOULD be fired
// at the document.
ASSERT(isInBounds<PointerID>(pointerId));
auto pointerType = capturingData->pointerType;
releasePointerCapture(&element, pointerId);
// FIXME: Spec doesn't specify which task source to use.
element.document().queueTaskToDispatchEvent(TaskSource::UserInteraction, PointerEvent::create(eventNames().lostpointercaptureEvent, pointerId, pointerType));
return;
}
}
}
void PointerCaptureController::reset()
{
m_activePointerIdsToCapturingData.clear();
m_haveAnyCapturingElement = false;
m_activePointerIdsToCapturingData.add(mousePointerID, CapturingData::create(mousePointerEventType()));
}
void PointerCaptureController::updateHaveAnyCapturingElement()
{
m_haveAnyCapturingElement = WTF::anyOf(m_activePointerIdsToCapturingData.values(), [&](auto& capturingData) {
return capturingData->hasAnyElement();
});
}
void PointerCaptureController::touchWithIdentifierWasRemoved(PointerID pointerId)
{
m_activePointerIdsToCapturingData.remove(pointerId);
updateHaveAnyCapturingElement();
}
bool PointerCaptureController::hasCancelledPointerEventForIdentifier(PointerID pointerId) const
{
RefPtr capturingData = m_activePointerIdsToCapturingData.get(pointerId);
return capturingData && capturingData->state == CapturingData::State::Cancelled;
}
bool PointerCaptureController::preventsCompatibilityMouseEventsForIdentifier(PointerID pointerId) const
{
RefPtr capturingData = m_activePointerIdsToCapturingData.get(pointerId);
return capturingData && capturingData->preventsCompatibilityMouseEvents;
}
#if ENABLE(TOUCH_EVENTS) && PLATFORM(IOS_FAMILY)
static bool hierarchyHasCapturingEventListeners(Element* target, const AtomString& eventName)
{
for (RefPtr<ContainerNode> currentNode = target; currentNode; currentNode = currentNode->parentInComposedTree()) {
if (currentNode->hasCapturingEventListeners(eventName))
return true;
}
return false;
}
void PointerCaptureController::dispatchEventForTouchAtIndex(EventTarget& target, const PlatformTouchEvent& platformTouchEvent, unsigned index, bool isPrimary, WindowProxy& view)
{
RELEASE_ASSERT(is<Element>(target));
auto dispatchOverOrOutEvent = [&](const AtomString& type, EventTarget* target) {
dispatchEvent(PointerEvent::create(type, platformTouchEvent, index, isPrimary, view), target);
};
auto dispatchEnterOrLeaveEvent = [&](const AtomString& type, Element& targetElement) {
bool hasCapturingListenerInHierarchy = false;
for (RefPtr<ContainerNode> currentNode = &targetElement; currentNode; currentNode = currentNode->parentInComposedTree()) {
if (currentNode->hasCapturingEventListeners(type)) {
hasCapturingListenerInHierarchy = true;
break;
}
}
Vector<Ref<Element>, 32> targetChain;
for (RefPtr element = &targetElement; element; element = element->parentElementInComposedTree()) {
if (hasCapturingListenerInHierarchy || element->hasEventListeners(type))
targetChain.append(*element);
}
if (type == eventNames().pointerenterEvent) {
for (auto& element : makeReversedRange(targetChain))
dispatchEvent(PointerEvent::create(type, platformTouchEvent, index, isPrimary, view), element.ptr());
} else {
for (auto& element : targetChain)
dispatchEvent(PointerEvent::create(type, platformTouchEvent, index, isPrimary, view), element.ptr());
}
};
auto pointerEvent = PointerEvent::create(platformTouchEvent, index, isPrimary, view);
Ref capturingData = ensureCapturingDataForPointerEvent(pointerEvent);
// Check if the target changed, which would require dispatching boundary events.
RefPtr<Element> previousTarget = capturingData->previousTarget;
RefPtr<Element> currentTarget = downcast<Element>(&target);
capturingData->previousTarget = currentTarget;
if (pointerEvent->type() == eventNames().pointermoveEvent && previousTarget != currentTarget) {
// The pointerenter and pointerleave events are only dispatched if there is a capturing event listener on an ancestor
// or a normal event listener on the element itself since those events do not bubble.
// This optimization is necessary since these events can cause O(n^2) capturing event-handler checks. This follows the
// code for similar mouse events in EventHandler::updateMouseEventTargetNode().
bool hasCapturingPointerEnterListener = hierarchyHasCapturingEventListeners(currentTarget.get(), eventNames().pointerenterEvent);
bool hasCapturingPointerLeaveListener = hierarchyHasCapturingEventListeners(previousTarget.get(), eventNames().pointerleaveEvent);
Vector<Ref<Element>, 32> leftElementsChain;
for (RefPtr element = previousTarget.get(); element; element = element->parentElementInComposedTree())
leftElementsChain.append(*element);
Vector<Ref<Element>, 32> enteredElementsChain;
for (RefPtr element = currentTarget.get(); element; element = element->parentElementInComposedTree())
enteredElementsChain.append(*element);
if (!leftElementsChain.isEmpty() && !enteredElementsChain.isEmpty() && leftElementsChain.last().ptr() == enteredElementsChain.last().ptr()) {
size_t minHeight = std::min(leftElementsChain.size(), enteredElementsChain.size());
size_t i;
for (i = 0; i < minHeight; ++i) {
if (leftElementsChain[leftElementsChain.size() - i - 1].ptr() != enteredElementsChain[enteredElementsChain.size() - i - 1].ptr())
break;
}
leftElementsChain.shrink(leftElementsChain.size() - i);
enteredElementsChain.shrink(enteredElementsChain.size() - i);
}
if (previousTarget)
dispatchOverOrOutEvent(eventNames().pointeroutEvent, previousTarget.get());
for (auto& chain : leftElementsChain) {
if (hasCapturingPointerLeaveListener || chain->hasEventListeners(eventNames().pointerleaveEvent))
dispatchEvent(PointerEvent::create(eventNames().pointerleaveEvent, platformTouchEvent, index, isPrimary, view), chain.ptr());
}
if (currentTarget)
dispatchOverOrOutEvent(eventNames().pointeroverEvent, currentTarget.get());
for (auto& chain : makeReversedRange(enteredElementsChain)) {
if (hasCapturingPointerEnterListener || chain->hasEventListeners(eventNames().pointerenterEvent))
dispatchEvent(PointerEvent::create(eventNames().pointerenterEvent, platformTouchEvent, index, isPrimary, view), chain.ptr());
}
}
if (pointerEvent->type() == eventNames().pointerdownEvent) {
// https://w3c.github.io/pointerevents/#the-pointerdown-event
// 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
// pointerenter prior to dispatching the pointerdown event.
dispatchOverOrOutEvent(eventNames().pointeroverEvent, currentTarget.get());
dispatchEnterOrLeaveEvent(eventNames().pointerenterEvent, *currentTarget);
}
dispatchEvent(pointerEvent, &target);
if (pointerEvent->type() == eventNames().pointerupEvent) {
// https://w3c.github.io/pointerevents/#the-pointerup-event
// For input devices that do not support hover, a user agent MUST also fire a pointer event named pointerout followed by a
// pointer event named pointerleave after dispatching the pointerup event.
dispatchOverOrOutEvent(eventNames().pointeroutEvent, currentTarget.get());
dispatchEnterOrLeaveEvent(eventNames().pointerleaveEvent, *currentTarget);
capturingData->previousTarget = nullptr;
capturingData->state = CapturingData::State::Finished;
}
}
#endif
RefPtr<PointerEvent> PointerCaptureController::pointerEventForMouseEvent(const MouseEvent& mouseEvent, PointerID pointerId, const String& pointerType)
{
// If we already have known touches then we cannot dispatch a mouse event,
// for instance in the case of a long press to initiate a system drag.
for (auto& capturingData : m_activePointerIdsToCapturingData.values()) {
if (capturingData->pointerType == touchPointerEventType() && capturingData->state == CapturingData::State::Ready)
return nullptr;
}
const auto& type = mouseEvent.type();
const auto& names = eventNames();
RefPtr capturingData = m_activePointerIdsToCapturingData.get(pointerId);
bool pointerIsPressed = capturingData ? capturingData->pointerIsPressed : false;
short newButton = mouseEvent.button();
short previousMouseButton = capturingData ? capturingData->previousMouseButton : -1;
short button = (type == names.mousemoveEvent && newButton == previousMouseButton) ? -1 : newButton;
// https://w3c.github.io/pointerevents/#chorded-button-interactions
// Some pointer devices, such as mouse or pen, support multiple buttons. In the Mouse Event model, each button
// press produces a mousedown and mouseup event. To better abstract this hardware difference and simplify
// cross-device input authoring, Pointer Events do not fire overlapping pointerdown and pointerup events
// for chorded button presses (depressing an additional button while another button on the pointer device is
// already depressed).
if (type == names.mousedownEvent || type == names.mouseupEvent) {
// We're already active and getting another mousedown, this means that we should dispatch
// a pointermove event and let the button state show the newly depressed button.
if (type == names.mousedownEvent && pointerIsPressed)
return PointerEvent::create(names.pointermoveEvent, button, mouseEvent, pointerId, pointerType);
// We're active and the mouseup still has some pressed button, this means we should dispatch
// a pointermove event.
if (type == names.mouseupEvent && pointerIsPressed && mouseEvent.buttons() > 0)
return PointerEvent::create(names.pointermoveEvent, button, mouseEvent, pointerId, pointerType);
}
auto pointerEvent = PointerEvent::create(button, mouseEvent, pointerId, pointerType);
if (capturingData)
capturingData->previousMouseButton = newButton;
else if (pointerEvent)
ensureCapturingDataForPointerEvent(*pointerEvent)->previousMouseButton = newButton;
return pointerEvent;
}
void PointerCaptureController::dispatchEvent(PointerEvent& event, EventTarget* target)
{
if (!target || event.target())
return;
// https://w3c.github.io/pointerevents/#firing-events-using-the-pointerevent-interface
// If the event is not gotpointercapture or lostpointercapture, run Process Pending Pointer Capture steps for this PointerEvent.
// We only need to do this for non-mouse type since for mouse events this method will be called in Document::prepareMouseEvent().
if (event.pointerType() != mousePointerEventType())
processPendingPointerCapture(event.pointerId());
pointerEventWillBeDispatched(event, target);
target->dispatchEvent(event);
pointerEventWasDispatched(event);
}
void PointerCaptureController::pointerEventWillBeDispatched(const PointerEvent& event, EventTarget* target)
{
if (!is<Element>(target))
return;
bool isPointerdown = event.type() == eventNames().pointerdownEvent;
bool isPointerup = event.type() == eventNames().pointerupEvent;
if (!isPointerdown && !isPointerup)
return;
auto pointerId = event.pointerId();
if (event.pointerType() != touchPointerEventType()) {
if (RefPtr capturingData = m_activePointerIdsToCapturingData.get(pointerId))
capturingData->pointerIsPressed = isPointerdown;
return;
}
if (!isPointerdown)
return;
// https://w3c.github.io/pointerevents/#implicit-pointer-capture
// Some input devices (such as touchscreens) implement a "direct manipulation" metaphor where a pointer is intended to act primarily on the UI
// element it became active upon (providing a physical illusion of direct contact, instead of indirect contact via a cursor that conceptually
// floats above the UI). Such devices are identified by the InputDeviceCapabilities.pointerMovementScrolls property and should have "implicit
// pointer capture" behavior as follows.
// Direct manipulation devices should behave exactly as if setPointerCapture was called on the target element just before the invocation of any
// pointerdown listeners. The hasPointerCapture API may be used (eg. within any pointerdown listener) to determine whether this has occurred. If
// releasePointerCapture is not called for the pointer before the next pointer event is fired, then a gotpointercapture event will be dispatched
// to the target (as normal) indicating that capture is active.
auto capturingData = ensureCapturingDataForPointerEvent(event);
capturingData->pointerIsPressed = true;
setPointerCapture(downcast<Element>(target), pointerId);
}
auto PointerCaptureController::ensureCapturingDataForPointerEvent(const PointerEvent& event) -> Ref<CapturingData>
{
return m_activePointerIdsToCapturingData.ensure(event.pointerId(), [pointerType = event.pointerType()] {
return CapturingData::create(pointerType);
}).iterator->value;
}
void PointerCaptureController::pointerEventWasDispatched(const PointerEvent& event)
{
RefPtr capturingData = m_activePointerIdsToCapturingData.get(event.pointerId());
if (!capturingData)
return;
capturingData->isPrimary = event.isPrimary();
// Immediately after firing the pointerup or pointercancel events, a user agent MUST clear the pending pointer capture target
// override for the pointerId of the pointerup or pointercancel event that was just dispatched, and then run Process Pending
// Pointer Capture steps to fire lostpointercapture if necessary.
// https://w3c.github.io/pointerevents/#implicit-release-of-pointer-capture
if (event.type() == eventNames().pointerupEvent) {
capturingData->pendingTargetOverride = nullptr;
processPendingPointerCapture(event.pointerId());
}
// If a mouse pointer has moved while it isn't pressed, make sure we reset the preventsCompatibilityMouseEvents flag since
// we could otherwise prevent compatibility mouse events while those are only supposed to be prevented while the pointer is pressed.
if (event.type() == eventNames().pointermoveEvent && capturingData->pointerType == mousePointerEventType() && !capturingData->pointerIsPressed)
capturingData->preventsCompatibilityMouseEvents = false;
// If the pointer event dispatched was pointerdown and the event was canceled, then set the PREVENT MOUSE EVENT flag for this pointerType.
// https://www.w3.org/TR/pointerevents/#mapping-for-devices-that-support-hover
if (event.type() == eventNames().pointerdownEvent)
capturingData->preventsCompatibilityMouseEvents = event.defaultPrevented();
}
void PointerCaptureController::cancelPointer(PointerID pointerId, const IntPoint& documentPoint)
{
// https://w3c.github.io/pointerevents/#the-pointercancel-event
// A user agent MUST fire a pointer event named pointercancel in the following circumstances:
//
// The user agent has determined that a pointer is unlikely to continue to produce events (for example, because of a hardware event).
// After having fired the pointerdown event, if the pointer is subsequently used to manipulate the page viewport (e.g. panning or zooming).
// Immediately before drag operation starts [HTML], for the pointer that caused the drag operation.
// After firing the pointercancel event, a user agent MUST also fire a pointer event named pointerout followed by firing a pointer event named pointerleave.
// https://w3c.github.io/pointerevents/#implicit-release-of-pointer-capture
// Immediately after firing the pointerup or pointercancel events, a user agent MUST clear the pending pointer capture target
// override for the pointerId of the pointerup or pointercancel event that was just dispatched, and then run Process Pending
// Pointer Capture steps to fire lostpointercapture if necessary. After running Process Pending Pointer Capture steps, if the
// pointer supports hover, user agent MUST also send corresponding boundary events necessary to reflect the current position of
// the pointer with no capture.
RefPtr capturingData = m_activePointerIdsToCapturingData.get(pointerId);
if (!capturingData)
return;
if (capturingData->state == CapturingData::State::Cancelled)
return;
capturingData->pendingTargetOverride = nullptr;
capturingData->state = CapturingData::State::Cancelled;
#if ENABLE(TOUCH_EVENTS) && PLATFORM(IOS_FAMILY)
capturingData->previousTarget = nullptr;
#endif
auto target = [&]() -> RefPtr<Element> {
if (capturingData->targetOverride)
return capturingData->targetOverride;
constexpr OptionSet<HitTestRequest::Type> hitType { HitTestRequest::Type::ReadOnly, HitTestRequest::Type::Active, HitTestRequest::Type::DisallowUserAgentShadowContent, HitTestRequest::Type::AllowChildFrameContent };
return Ref(m_page.mainFrame())->eventHandler().hitTestResultAtPoint(documentPoint, hitType).innerNonSharedElement();
}();
if (!target)
return;
// After firing the pointercancel event, a user agent MUST also fire a pointer event named pointerout
// followed by firing a pointer event named pointerleave.
auto isPrimary = capturingData->isPrimary ? PointerEvent::IsPrimary::Yes : PointerEvent::IsPrimary::No;
auto& eventNames = WebCore::eventNames();
auto cancelEvent = PointerEvent::create(eventNames.pointercancelEvent, pointerId, capturingData->pointerType, isPrimary);
target->dispatchEvent(cancelEvent);
target->dispatchEvent(PointerEvent::create(eventNames.pointeroutEvent, pointerId, capturingData->pointerType, isPrimary));
target->dispatchEvent(PointerEvent::create(eventNames.pointerleaveEvent, pointerId, capturingData->pointerType, isPrimary));
processPendingPointerCapture(pointerId);
}
void PointerCaptureController::processPendingPointerCapture(PointerID pointerId)
{
RefPtr capturingData = m_activePointerIdsToCapturingData.get(pointerId);
if (!capturingData)
return;
if (m_processingPendingPointerCapture)
return;
m_processingPendingPointerCapture = true;
// Cache the pending target override since it could be modified during the dispatch of events in this function.
auto pendingTargetOverride = capturingData->pendingTargetOverride;
// https://w3c.github.io/pointerevents/#process-pending-pointer-capture
// 1. If the pointer capture target override for this pointer is set and is not equal to the pending pointer capture target override,
// then fire a pointer event named lostpointercapture at the pointer capture target override node.
if (auto targetOverride = capturingData->targetOverride; targetOverride && targetOverride != pendingTargetOverride) {
if (capturingData->targetOverride->isConnected())
capturingData->targetOverride->dispatchEvent(PointerEvent::createForPointerCapture(eventNames().lostpointercaptureEvent, pointerId, capturingData->isPrimary, capturingData->pointerType));
if (capturingData->pointerType == mousePointerEventType()) {
if (RefPtr frame = capturingData->targetOverride->document().frame())
frame->eventHandler().pointerCaptureElementDidChange(nullptr);
}
}
// 2. If the pending pointer capture target override for this pointer is set and is not equal to the pointer capture target override,
// then fire a pointer event named gotpointercapture at the pending pointer capture target override.
if (capturingData->pendingTargetOverride && capturingData->targetOverride != pendingTargetOverride) {
if (capturingData->pointerType == mousePointerEventType()) {
if (RefPtr frame = pendingTargetOverride->document().frame())
frame->eventHandler().pointerCaptureElementDidChange(pendingTargetOverride.get());
}
pendingTargetOverride->dispatchEvent(PointerEvent::createForPointerCapture(eventNames().gotpointercaptureEvent, pointerId, capturingData->isPrimary, capturingData->pointerType));
}
// 3. Set the pointer capture target override to the pending pointer capture target override, if set. Otherwise, clear the pointer
// capture target override.
capturingData->targetOverride = pendingTargetOverride;
m_processingPendingPointerCapture = false;
}
} // namespace WebCore