PointerCaptureController.cpp [plain text]
#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
{
auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
if (iterator != m_activePointerIdsToCapturingData.end()) {
auto pointerCaptureElement = iterator->value.targetOverride;
if (pointerCaptureElement && &pointerCaptureElement->document() == document)
return pointerCaptureElement.get();
}
return nullptr;
}
ExceptionOr<void> PointerCaptureController::setPointerCapture(Element* capturingTarget, PointerID pointerId)
{
auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
if (iterator == m_activePointerIdsToCapturingData.end())
return Exception { NotFoundError };
if (!capturingTarget->isConnected())
return Exception { InvalidStateError };
#if ENABLE(POINTER_LOCK)
if (auto* page = capturingTarget->document().page()) {
if (page->pointerLockController().isLocked())
return Exception { InvalidStateError };
}
#endif
auto& capturingData = iterator->value;
if (capturingData.pointerIsPressed)
capturingData.pendingTargetOverride = capturingTarget;
updateHaveAnyCapturingElement();
return { };
}
ExceptionOr<void> PointerCaptureController::releasePointerCapture(Element* capturingTarget, PointerID pointerId)
{
auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
if (iterator == m_activePointerIdsToCapturingData.end())
return Exception { NotFoundError };
if (!hasPointerCapture(capturingTarget, pointerId))
return { };
iterator->value.pendingTargetOverride = nullptr;
updateHaveAnyCapturingElement();
return { };
}
bool PointerCaptureController::hasPointerCapture(Element* capturingTarget, PointerID pointerId)
{
if (!m_haveAnyCapturingElement)
return false;
auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
return iterator != m_activePointerIdsToCapturingData.end() && iterator->value.pendingTargetOverride == capturingTarget;
}
void PointerCaptureController::pointerLockWasApplied()
{
for (auto& capturingData : m_activePointerIdsToCapturingData.values()) {
capturingData.pendingTargetOverride = nullptr;
capturingData.targetOverride = nullptr;
}
updateHaveAnyCapturingElement();
}
void PointerCaptureController::elementWasRemoved(Element& element)
{
if (!m_haveAnyCapturingElement)
return;
for (auto& keyAndValue : m_activePointerIdsToCapturingData) {
auto& capturingData = keyAndValue.value;
if (capturingData.pendingTargetOverride == &element || capturingData.targetOverride == &element) {
ASSERT(isInBounds<PointerID>(keyAndValue.key));
auto pointerId = static_cast<PointerID>(keyAndValue.key);
auto pointerType = capturingData.pointerType;
releasePointerCapture(&element, pointerId);
element.document().queueTaskToDispatchEvent(TaskSource::UserInteraction, PointerEvent::create(eventNames().lostpointercaptureEvent, pointerId, pointerType));
return;
}
}
}
void PointerCaptureController::reset()
{
m_activePointerIdsToCapturingData.clear();
m_haveAnyCapturingElement = false;
CapturingData capturingData;
capturingData.pointerType = PointerEvent::mousePointerType();
m_activePointerIdsToCapturingData.add(mousePointerID, capturingData);
}
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
{
auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
return iterator != m_activePointerIdsToCapturingData.end() && iterator->value.cancelled;
}
bool PointerCaptureController::preventsCompatibilityMouseEventsForIdentifier(PointerID pointerId) const
{
auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
return iterator != m_activePointerIdsToCapturingData.end() && iterator->value.preventsCompatibilityMouseEvents;
}
#if ENABLE(TOUCH_EVENTS) && PLATFORM(IOS_FAMILY)
static bool hierarchyHasCapturingEventListeners(Element* target, const AtomString& eventName)
{
for (ContainerNode* curr = target; curr; curr = curr->parentInComposedTree()) {
if (curr->hasCapturingEventListeners(eventName))
return true;
}
return false;
}
void PointerCaptureController::dispatchEventForTouchAtIndex(EventTarget& target, const PlatformTouchEvent& platformTouchEvent, unsigned index, bool isPrimary, WindowProxy& view)
{
ASSERT(is<Element>(target));
auto dispatchOverOrOutEvent = [&](const String& type, EventTarget* target) {
dispatchEvent(PointerEvent::create(type, platformTouchEvent, index, isPrimary, view), target);
};
auto dispatchEnterOrLeaveEvent = [&](const String& type) {
auto* targetElement = &downcast<Element>(target);
bool hasCapturingListenerInHierarchy = false;
for (ContainerNode* curr = targetElement; curr; curr = curr->parentInComposedTree()) {
if (curr->hasCapturingEventListeners(type)) {
hasCapturingListenerInHierarchy = true;
break;
}
}
Vector<Ref<Element>, 32> targetChain;
for (Element* element = targetElement; element; element = element->parentElementInComposedTree()) {
if (hasCapturingListenerInHierarchy || element->hasEventListeners(type))
targetChain.append(*element);
}
if (type == eventNames().pointerenterEvent) {
for (auto& element : WTF::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);
auto& capturingData = ensureCapturingDataForPointerEvent(pointerEvent);
RefPtr<Element> previousTarget = capturingData.previousTarget;
RefPtr<Element> currentTarget = downcast<Element>(&target);
capturingData.previousTarget = currentTarget;
if (pointerEvent->type() == eventNames().pointermoveEvent && previousTarget != currentTarget) {
bool hasCapturingPointerEnterListener = hierarchyHasCapturingEventListeners(currentTarget.get(), eventNames().pointerenterEvent);
bool hasCapturingPointerLeaveListener = hierarchyHasCapturingEventListeners(previousTarget.get(), eventNames().pointerleaveEvent);
Vector<Ref<Element>, 32> leftElementsChain;
for (Element* element = previousTarget.get(); element; element = element->parentElementInComposedTree())
leftElementsChain.append(*element);
Vector<Ref<Element>, 32> enteredElementsChain;
for (Element* 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 : WTF::makeReversedRange(enteredElementsChain)) {
if (hasCapturingPointerEnterListener || chain->hasEventListeners(eventNames().pointerenterEvent))
dispatchEvent(PointerEvent::create(eventNames().pointerenterEvent, platformTouchEvent, index, isPrimary, view), chain.ptr());
}
}
if (pointerEvent->type() == eventNames().pointerdownEvent) {
dispatchOverOrOutEvent(eventNames().pointeroverEvent, currentTarget.get());
dispatchEnterOrLeaveEvent(eventNames().pointerenterEvent);
}
dispatchEvent(pointerEvent, &target);
if (pointerEvent->type() == eventNames().pointerupEvent) {
dispatchOverOrOutEvent(eventNames().pointeroutEvent, currentTarget.get());
dispatchEnterOrLeaveEvent(eventNames().pointerleaveEvent);
capturingData.previousTarget = nullptr;
touchWithIdentifierWasRemoved(pointerEvent->pointerId());
}
}
#endif
RefPtr<PointerEvent> PointerCaptureController::pointerEventForMouseEvent(const MouseEvent& mouseEvent, PointerID pointerId, const String& pointerType)
{
for (auto& capturingData : m_activePointerIdsToCapturingData.values()) {
if (capturingData.pointerType == PointerEvent::touchPointerType() && capturingData.pointerIsPressed && !capturingData.cancelled)
return nullptr;
}
const auto& type = mouseEvent.type();
const auto& names = eventNames();
auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
bool pointerIsPressed = iterator != m_activePointerIdsToCapturingData.end() ? iterator->value.pointerIsPressed : false;
short newButton = mouseEvent.button();
short previousMouseButton = iterator != m_activePointerIdsToCapturingData.end() ? iterator->value.previousMouseButton : -1;
short button = (type == names.mousemoveEvent && newButton == previousMouseButton) ? -1 : newButton;
if (type == names.mousedownEvent || type == names.mouseupEvent) {
if (type == names.mousedownEvent && pointerIsPressed)
return PointerEvent::create(names.pointermoveEvent, button, mouseEvent, pointerId, pointerType);
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 (iterator != m_activePointerIdsToCapturingData.end())
iterator->value.previousMouseButton = newButton;
else if (pointerEvent)
ensureCapturingDataForPointerEvent(*pointerEvent).previousMouseButton = newButton;
return pointerEvent;
}
void PointerCaptureController::dispatchEvent(PointerEvent& event, EventTarget* target)
{
if (!target || event.target())
return;
if (event.pointerType() != PointerEvent::mousePointerType())
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() != PointerEvent::touchPointerType()) {
auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
if (iterator != m_activePointerIdsToCapturingData.end())
iterator->value.pointerIsPressed = isPointerdown;
return;
}
if (!isPointerdown)
return;
auto& capturingData = ensureCapturingDataForPointerEvent(event);
capturingData.pointerIsPressed = true;
setPointerCapture(downcast<Element>(target), pointerId);
}
PointerCaptureController::CapturingData& PointerCaptureController::ensureCapturingDataForPointerEvent(const PointerEvent& event)
{
return m_activePointerIdsToCapturingData.ensure(event.pointerId(), [&event] {
CapturingData capturingData;
capturingData.pointerType = event.pointerType();
return capturingData;
}).iterator->value;
}
void PointerCaptureController::pointerEventWasDispatched(const PointerEvent& event)
{
auto iterator = m_activePointerIdsToCapturingData.find(event.pointerId());
if (iterator != m_activePointerIdsToCapturingData.end()) {
auto& capturingData = iterator->value;
capturingData.isPrimary = event.isPrimary();
if (event.type() == eventNames().pointerupEvent) {
capturingData.pendingTargetOverride = nullptr;
processPendingPointerCapture(event.pointerId());
}
if (event.type() == eventNames().pointermoveEvent && capturingData.pointerType == PointerEvent::mousePointerType() && !capturingData.pointerIsPressed)
capturingData.preventsCompatibilityMouseEvents = false;
if (event.type() == eventNames().pointerdownEvent)
capturingData.preventsCompatibilityMouseEvents = event.defaultPrevented();
}
}
void PointerCaptureController::cancelPointer(PointerID pointerId, const IntPoint& documentPoint)
{
auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
if (iterator == m_activePointerIdsToCapturingData.end())
return;
auto& capturingData = iterator->value;
if (capturingData.cancelled)
return;
capturingData.pendingTargetOverride = nullptr;
capturingData.cancelled = true;
#if ENABLE(TOUCH_EVENTS) && PLATFORM(IOS_FAMILY)
capturingData.previousTarget = nullptr;
#endif
auto target = [&]() -> RefPtr<Element> {
if (capturingData.targetOverride)
return capturingData.targetOverride;
constexpr OptionSet<HitTestRequest::RequestType> hitType { HitTestRequest::ReadOnly, HitTestRequest::Active, HitTestRequest::DisallowUserAgentShadowContent, HitTestRequest::AllowChildFrameContent };
return m_page.mainFrame().eventHandler().hitTestResultAtPoint(documentPoint, hitType).innerNonSharedElement();
}();
if (!target)
return;
auto isPrimary = capturingData.isPrimary ? PointerEvent::IsPrimary::Yes : PointerEvent::IsPrimary::No;
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)
{
auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
if (iterator == m_activePointerIdsToCapturingData.end())
return;
if (m_processingPendingPointerCapture)
return;
m_processingPendingPointerCapture = true;
auto& capturingData = iterator->value;
auto pendingTargetOverride = capturingData.pendingTargetOverride;
if (capturingData.targetOverride && capturingData.targetOverride != pendingTargetOverride) {
if (capturingData.targetOverride->isConnected())
capturingData.targetOverride->dispatchEvent(PointerEvent::createForPointerCapture(eventNames().lostpointercaptureEvent, pointerId, capturingData.isPrimary, capturingData.pointerType));
if (capturingData.pointerType == PointerEvent::mousePointerType()) {
if (auto* frame = capturingData.targetOverride->document().frame())
frame->eventHandler().pointerCaptureElementDidChange(nullptr);
}
}
if (capturingData.pendingTargetOverride && capturingData.targetOverride != pendingTargetOverride) {
if (capturingData.pointerType == PointerEvent::mousePointerType()) {
if (auto* frame = pendingTargetOverride->document().frame())
frame->eventHandler().pointerCaptureElementDidChange(pendingTargetOverride.get());
}
pendingTargetOverride->dispatchEvent(PointerEvent::createForPointerCapture(eventNames().gotpointercaptureEvent, pointerId, capturingData.isPrimary, capturingData.pointerType));
}
capturingData.targetOverride = pendingTargetOverride;
m_processingPendingPointerCapture = false;
}
}