blob: bb068c38476fb72c639c40c1b4eb860594e3c3b7 [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 "EventRegion.h"
#include "EventTrackingRegions.h"
#include "HTMLFormControlElement.h"
#include "Logging.h"
#include "Path.h"
#include "PathUtilities.h"
#include "RenderAncestorIterator.h"
#include "RenderBox.h"
#include "RenderObjectInlines.h"
#include "RenderStyle+GettersInlines.h"
#include "SimpleRange.h"
#include "WindRule.h"
#include <wtf/TZoneMallocInlines.h>
#include <wtf/text/TextStream.h>
namespace WebCore {
WTF_MAKE_TZONE_ALLOCATED_IMPL(EventRegionContext);
EventRegionContext::EventRegionContext(EventRegion& eventRegion)
: m_eventRegion(eventRegion)
{
}
EventRegionContext::~EventRegionContext() = default;
void EventRegionContext::unite(const FloatRoundedRect& roundedRect, const RenderObject& renderer, const RenderStyle& style, bool overrideUserModifyIsEditable)
{
auto transformAndClipIfNeeded = [&](auto input, auto transform) {
if (m_transformStack.isEmpty() && m_clipStack.isEmpty())
return input;
auto transformedAndClippedInput = m_transformStack.isEmpty() ? input : transform(m_transformStack.last(), input);
if (!m_clipStack.isEmpty())
transformedAndClippedInput.intersect(m_clipStack.last());
return transformedAndClippedInput;
};
auto region = transformAndClipIfNeeded(approximateAsRegion(roundedRect), [](auto affineTransform, auto region) {
return affineTransform.mapRegion(region);
});
m_eventRegion.unite(region, renderer, style, overrideUserModifyIsEditable);
#if ENABLE(INTERACTION_REGIONS_IN_EVENT_REGION)
auto rect = roundedRect.rect();
if (auto* modelObject = dynamicDowncast<RenderLayerModelObject>(renderer))
rect = snapRectToDevicePixelsIfNeeded(rect, *modelObject);
auto layerBounds = transformAndClipIfNeeded(rect, [](auto affineTransform, auto rect) {
return affineTransform.mapRect(rect);
});
// Same transform as `transformAndClipIfNeeded`.
std::optional<AffineTransform> transform;
if (!m_transformStack.isEmpty()) {
transform = m_transformStack.last();
rect = transform->mapRect(rect);
}
// The paths we generate to match shapes are complete and relative to the bounds.
// But the layerBounds we pass are already clipped.
// Keep track of the offset so we can adjust the paths location if needed.
auto clipOffset = rect.location() - layerBounds.location();
uniteInteractionRegions(renderer, layerBounds, clipOffset, transform);
#else
UNUSED_PARAM(renderer);
#endif
}
bool EventRegionContext::contains(const IntRect& rect) const
{
if (m_transformStack.isEmpty())
return m_eventRegion.contains(rect);
return m_eventRegion.contains(m_transformStack.last().mapRect(rect));
}
#if ENABLE(INTERACTION_REGIONS_IN_EVENT_REGION)
static std::optional<FloatRect> guardRectForRegionBounds(const InteractionRegion& region)
{
constexpr int minimumSize = 20;
constexpr int occlusionMargin = 10;
constexpr int complexSegmentsCount = 20;
bool isSmallRect = false;
bool isComplexShape = region.clipPath
&& region.clipPath->segmentsIfExists()
&& region.clipPath->segmentsIfExists()->size() > complexSegmentsCount;
auto guardRect = region.rectInLayerCoordinates;
if (guardRect.width() < minimumSize) {
guardRect.inflateX(occlusionMargin);
isSmallRect = true;
}
if (guardRect.height() < minimumSize) {
guardRect.inflateY(occlusionMargin);
isSmallRect = true;
}
if (isSmallRect || isComplexShape)
return guardRect;
return std::nullopt;
}
void EventRegionContext::uniteInteractionRegions(const RenderObject& renderer, const FloatRect& layerBounds, const FloatSize& clipOffset, const std::optional<AffineTransform>& transform)
{
if (!renderer.page().shouldBuildInteractionRegions())
return;
if (auto interactionRegion = interactionRegionForRenderedRegion(renderer, layerBounds, clipOffset, transform)) {
auto rectForTracking = enclosingIntRect(interactionRegion->rectInLayerCoordinates);
if (interactionRegion->type == InteractionRegion::Type::Occlusion) {
auto result = m_occlusionRects.add(rectForTracking);
if (!result.isNewEntry)
return;
m_interactionRegions.append(*interactionRegion);
return;
}
if (interactionRegion->type == InteractionRegion::Type::Guard) {
auto result = m_guardRects.add(rectForTracking, Inflated::No);
if (!result.isNewEntry)
return;
m_interactionRegions.append(*interactionRegion);
return;
}
auto result = m_interactionRectsAndContentHints.set(rectForTracking, interactionRegion->contentHint);
if (!result.isNewEntry)
return;
bool defaultContentHint = interactionRegion->contentHint == InteractionRegion::ContentHint::Default;
if (defaultContentHint && shouldConsolidateInteractionRegion(renderer, rectForTracking, interactionRegion->nodeIdentifier))
return;
// This region might be a container we can remove later.
bool hasNoVisualBorders = !renderer.hasVisibleBoxDecorations();
if (hasNoVisualBorders) {
if (auto* renderElement = dynamicDowncast<RenderElement>(renderer))
m_containerRemovalCandidates.add(renderElement->element()->nodeIdentifier());
}
auto discoveredAddResult = m_discoveredRegionsByElement.add(interactionRegion->nodeIdentifier, Vector<InteractionRegion>());
discoveredAddResult.iterator->value.append(*interactionRegion);
if (!discoveredAddResult.isNewEntry)
return;
auto guardRect = guardRectForRegionBounds(*interactionRegion);
if (guardRect) {
auto result = m_guardRects.add(enclosingIntRect(guardRect.value()), Inflated::Yes);
if (result.isNewEntry) {
m_interactionRegions.append({
InteractionRegion::Type::Guard,
interactionRegion->nodeIdentifier,
guardRect.value()
});
}
}
m_interactionRegions.append(*interactionRegion);
}
}
bool EventRegionContext::shouldConsolidateInteractionRegion(const RenderObject& renderer, const IntRect& bounds, const NodeIdentifier& nodeIdentifier)
{
for (auto& ancestor : ancestorsOfType<RenderElement>(renderer)) {
if (!ancestor.element())
continue;
auto ancestorElementIdentifier = ancestor.element()->nodeIdentifier();
auto discoveredIterator = m_discoveredRegionsByElement.find(ancestorElementIdentifier);
// The ancestor has no known InteractionRegion, we can skip it.
if (discoveredIterator == m_discoveredRegionsByElement.end()) {
// If it has a border / background, stop the search.
if (ancestor.hasVisibleBoxDecorations())
return false;
continue;
}
// The ancestor has multiple known rects (e.g. multi-line links), we can skip it.
if (discoveredIterator->value.size() > 1)
continue;
auto& ancestorBounds = discoveredIterator->value.first().rectInLayerCoordinates;
constexpr float looseContainmentMargin = 3.0;
FloatRect ancestorBoundsForLooseContainmentCheck = ancestorBounds;
ancestorBoundsForLooseContainmentCheck.inflate(looseContainmentMargin);
// The ancestor's InteractionRegion does not contain ours, we don't consolidate and stop the search.
if (!ancestorBoundsForLooseContainmentCheck.contains(bounds))
return false;
constexpr auto maxMargin = 50;
float marginLeft = bounds.x() - ancestorBounds.x();
float marginRight = ancestorBounds.maxX() - bounds.maxX();
float marginTop = bounds.y() - ancestorBounds.y();
float marginBottom = ancestorBounds.maxY() - bounds.maxY();
bool majorOverlap = marginLeft <= maxMargin
&& marginRight <= maxMargin
&& marginTop <= maxMargin
&& marginBottom <= maxMargin;
bool hasNoVisualBorders = !renderer.hasVisibleBoxDecorations();
bool canConsolidate = hasNoVisualBorders
&& (majorOverlap || nodeIdentifier == ancestorElementIdentifier);
// We're consolidating the region based on this ancestor, it shouldn't be removed or candidate for removal.
if (canConsolidate) {
m_containerRemovalCandidates.remove(ancestorElementIdentifier);
m_containersToRemove.remove(ancestorElementIdentifier);
return true;
}
// We found a region nested inside a container candidate for removal, flag it for removal.
if (m_containerRemovalCandidates.remove(ancestorElementIdentifier))
m_containersToRemove.add(ancestorElementIdentifier);
return false;
}
return false;
}
void EventRegionContext::convertGuardContainersToInterationIfNeeded(float minimumCornerRadius)
{
for (auto& region : m_interactionRegions) {
if (region.type != InteractionRegion::Type::Guard)
continue;
if (!m_discoveredRegionsByElement.contains(region.nodeIdentifier)) {
auto rectForTracking = enclosingIntRect(region.rectInLayerCoordinates);
auto result = m_interactionRectsAndContentHints.add(rectForTracking, region.contentHint);
if (result.isNewEntry) {
region.type = InteractionRegion::Type::Interaction;
region.cornerRadius = minimumCornerRadius;
m_discoveredRegionsByElement.add(region.nodeIdentifier, Vector<InteractionRegion>({ region }));
}
}
}
}
void EventRegionContext::shrinkWrapInteractionRegions()
{
for (size_t i = 0; i < m_interactionRegions.size(); ++i) {
auto& region = m_interactionRegions[i];
if (region.type != InteractionRegion::Type::Interaction)
continue;
auto discoveredIterator = m_discoveredRegionsByElement.find(region.nodeIdentifier);
if (discoveredIterator == m_discoveredRegionsByElement.end())
continue;
auto discoveredRegions = discoveredIterator->value;
if (discoveredRegions.size() == 1) {
auto rectForTracking = enclosingIntRect(region.rectInLayerCoordinates);
region.contentHint = m_interactionRectsAndContentHints.get(rectForTracking);
continue;
}
FloatRect layerBounds;
bool canUseSingleRect = true;
Vector<InteractionRegion> toAddAfterMerge;
Vector<FloatRect> discoveredRects;
Vector<Path> discoveredClipPaths;
discoveredRects.reserveInitialCapacity(discoveredRegions.size());
discoveredClipPaths.reserveInitialCapacity(discoveredRegions.size());
for (const auto& discoveredRegion : discoveredRegions) {
auto previousArea = layerBounds.area();
auto rect = discoveredRegion.rectInLayerCoordinates;
auto overlap = rect;
overlap.intersect(layerBounds);
layerBounds.unite(rect);
auto growth = layerBounds.area() - previousArea;
if (growth > rect.area() - overlap.area() + std::numeric_limits<float>::epsilon())
canUseSingleRect = false;
auto rectForTracking = enclosingIntRect(rect);
auto hint = m_interactionRectsAndContentHints.get(rectForTracking);
if (hint != region.contentHint)
toAddAfterMerge.append(discoveredRegion);
else if (growth > std::numeric_limits<float>::epsilon()) {
// If the discovered region's shape should not be a rounded-rect
// with uniform corner radii, its clipPath will be non-empty.
if (auto clipPath = discoveredRegion.clipPath) {
AffineTransform transform;
transform.translate(discoveredRegion.rectInLayerCoordinates.location());
Path foundPath = *clipPath;
foundPath.transform(transform);
discoveredClipPaths.append(foundPath);
} else if (discoveredRegion.useContinuousCorners) {
// If this region has continuous corners, we won't be able to
// shrink wrap it. Instead, find it's path so that it can be
// included in the final clip.
Path path;
path.addContinuousRoundedRect(discoveredRegion.rectInLayerCoordinates, discoveredRegion.cornerRadius);
discoveredClipPaths.append(path);
} else
discoveredRects.append(rect);
}
}
if (canUseSingleRect)
region.rectInLayerCoordinates = layerBounds;
else {
Path shrinkWrappedRects = PathUtilities::pathWithShrinkWrappedRects(discoveredRects, region.cornerRadius);
Path path;
path.addPath(shrinkWrappedRects, { });
for (Path clipPath : discoveredClipPaths)
path.addPath(clipPath, { });
path.translate(-toFloatSize(layerBounds.location()));
region.clipPath = path;
region.cornerRadius = 0;
region.rectInLayerCoordinates = layerBounds;
}
auto finalRegionRectForTracking = enclosingIntRect(region.rectInLayerCoordinates);
for (auto& extraRegion : toAddAfterMerge) {
auto extraRectForTracking = enclosingIntRect(extraRegion.rectInLayerCoordinates);
// Do not insert a new region if it creates a duplicated Interaction Rect.
if (finalRegionRectForTracking == extraRectForTracking) {
region.contentHint = m_interactionRectsAndContentHints.get(extraRectForTracking);
continue;
}
extraRegion.contentHint = m_interactionRectsAndContentHints.get(extraRectForTracking);
m_interactionRegions.insert(++i, WTF::move(extraRegion));
}
}
}
void EventRegionContext::removeSuperfluousInteractionRegions()
{
m_interactionRegions.removeAllMatching([&] (auto& region) {
if (region.type != InteractionRegion::Type::Guard)
return m_containersToRemove.contains(region.nodeIdentifier);
auto guardRect = enclosingIntRect(region.rectInLayerCoordinates);
auto guardIterator = m_guardRects.find(guardRect);
if (guardIterator != m_guardRects.end() && guardIterator->value == Inflated::No)
return false;
for (const auto& interactionRect : m_interactionRectsAndContentHints.keys()) {
auto intersection = interactionRect;
intersection.intersect(guardRect);
if (intersection.isEmpty())
continue;
// This is an interactive container of the guarded region.
if (intersection.contains(guardRect))
continue;
// This is probably the element being guarded.
if (intersection.contains(interactionRect) && guardRect.center() == interactionRect.center())
continue;
bool tooMuchOverlap = interactionRect.width() / 2 < intersection.width()
|| interactionRect.height() / 2 < intersection.height();
if (tooMuchOverlap)
return true;
}
return false;
});
}
void EventRegionContext::copyInteractionRegionsToEventRegion(float minimumCornerRadius)
{
convertGuardContainersToInterationIfNeeded(minimumCornerRadius);
removeSuperfluousInteractionRegions();
shrinkWrapInteractionRegions();
m_eventRegion.appendInteractionRegions(m_interactionRegions);
}
void EventRegionContext::reserveCapacityForInteractionRegions(size_t previousSize)
{
m_interactionRegions.reserveCapacity(previousSize);
}
#endif
EventRegion::EventRegion() = default;
EventRegion::EventRegion(Region&& region
#if ENABLE(TOUCH_ACTION_REGIONS)
, Vector<WebCore::Region> touchActionRegions
#endif
#if ENABLE(WHEEL_EVENT_REGIONS)
, WebCore::Region wheelEventListenerRegion
, WebCore::Region nonPassiveWheelEventListenerRegion
#endif
#if ENABLE(TOUCH_EVENT_REGIONS)
, EventTrackingRegions touchEventListenerRegion
#endif
#if ENABLE(EDITABLE_REGION)
, std::optional<WebCore::Region> editableRegion
#endif
#if ENABLE(INTERACTION_REGIONS_IN_EVENT_REGION)
, Vector<WebCore::InteractionRegion> interactionRegions
#endif
)
: m_region(WTF::move(region))
#if ENABLE(TOUCH_ACTION_REGIONS)
, m_touchActionRegions(WTF::move(touchActionRegions))
#endif
#if ENABLE(WHEEL_EVENT_REGIONS)
, m_wheelEventListenerRegion(WTF::move(wheelEventListenerRegion))
, m_nonPassiveWheelEventListenerRegion(WTF::move(nonPassiveWheelEventListenerRegion))
#endif
#if ENABLE(TOUCH_EVENT_REGIONS)
, m_touchEventListenerRegion(WTF::move(touchEventListenerRegion))
#endif
#if ENABLE(EDITABLE_REGION)
, m_editableRegion(WTF::move(editableRegion))
#endif
#if ENABLE(INTERACTION_REGIONS_IN_EVENT_REGION)
, m_interactionRegions(WTF::move(interactionRegions))
#endif
{
}
void EventRegion::unite(const Region& region, const RenderObject& renderer, const RenderStyle& style, bool overrideUserModifyIsEditable)
{
if (renderer.usedPointerEvents() == PointerEvents::None)
return;
m_region.unite(region);
#if ENABLE(TOUCH_ACTION_REGIONS)
uniteTouchActions(region, Style::toPlatform(style.usedTouchAction()));
#endif
uniteEventListeners(region, style.eventListenerRegionTypes());
#if ENABLE(EDITABLE_REGION)
if (m_editableRegion && (overrideUserModifyIsEditable || style.usedUserModify() != UserModify::ReadOnly)) {
m_editableRegion->unite(region);
LOG_WITH_STREAM(EventRegions, stream << " uniting editable region");
}
#else
UNUSED_PARAM(overrideUserModifyIsEditable);
#endif
#if !ENABLE(TOUCH_ACTION_REGIONS) && !ENABLE(WHEEL_EVENT_REGIONS) && !ENABLE(EDITABLE_REGION)
UNUSED_PARAM(style);
#endif
}
void EventRegion::translate(const IntSize& offset)
{
m_region.translate(offset);
#if ENABLE(TOUCH_ACTION_REGIONS)
for (auto& touchActionRegion : m_touchActionRegions)
touchActionRegion.translate(offset);
#endif
#if ENABLE(WHEEL_EVENT_REGIONS)
m_wheelEventListenerRegion.translate(offset);
m_nonPassiveWheelEventListenerRegion.translate(offset);
#endif
#if ENABLE(TOUCH_EVENT_REGIONS)
m_touchEventListenerRegion.translate(offset);
#endif
#if ENABLE(EDITABLE_REGION)
if (m_editableRegion)
m_editableRegion->translate(offset);
#endif
#if ENABLE(INTERACTION_REGIONS_IN_EVENT_REGION)
for (auto& region : m_interactionRegions)
region.rectInLayerCoordinates.move(offset);
#endif
}
#if ENABLE(TOUCH_ACTION_REGIONS)
static inline unsigned toIndex(TouchAction touchAction)
{
switch (touchAction) {
case TouchAction::None:
return 0;
case TouchAction::Manipulation:
return 1;
case TouchAction::PanX:
return 2;
case TouchAction::PanY:
return 3;
case TouchAction::PinchZoom:
return 4;
case TouchAction::Auto:
break;
}
ASSERT_NOT_REACHED();
return 0;
}
static inline TouchAction toTouchAction(unsigned index)
{
switch (index) {
case 0:
return TouchAction::None;
case 1:
return TouchAction::Manipulation;
case 2:
return TouchAction::PanX;
case 3:
return TouchAction::PanY;
case 4:
return TouchAction::PinchZoom;
default:
break;
}
ASSERT_NOT_REACHED();
return TouchAction::Auto;
}
void EventRegion::uniteTouchActions(const Region& touchRegion, OptionSet<TouchAction> touchActions)
{
for (auto touchAction : touchActions) {
if (touchAction == TouchAction::Auto)
break;
auto index = toIndex(touchAction);
if (m_touchActionRegions.size() < index + 1)
m_touchActionRegions.grow(index + 1);
}
for (unsigned i = 0; i < m_touchActionRegions.size(); ++i) {
auto regionTouchAction = toTouchAction(i);
if (touchActions.contains(regionTouchAction)) {
m_touchActionRegions[i].unite(touchRegion);
LOG_WITH_STREAM(EventRegions, stream << " uniting for TouchAction " << regionTouchAction);
} else {
m_touchActionRegions[i].subtract(touchRegion);
LOG_WITH_STREAM(EventRegions, stream << " subtracting for TouchAction " << regionTouchAction);
}
}
}
const Region* EventRegion::regionForTouchAction(TouchAction action) const
{
unsigned actionIndex = toIndex(action);
if (actionIndex >= m_touchActionRegions.size())
return nullptr;
return &m_touchActionRegions[actionIndex];
}
OptionSet<TouchAction> EventRegion::touchActionsForPoint(const IntPoint& point) const
{
OptionSet<TouchAction> actions;
for (unsigned i = 0; i < m_touchActionRegions.size(); ++i) {
if (m_touchActionRegions[i].contains(point)) {
auto action = toTouchAction(i);
actions.add(action);
if (action == TouchAction::None || action == TouchAction::Manipulation)
break;
}
}
if (actions.isEmpty())
return { TouchAction::Auto };
return actions;
}
#endif
#if ENABLE(TOUCH_EVENT_REGIONS)
OptionSet<EventListenerRegionType> touchEventTypes =
{
EventListenerRegionType::TouchStart, EventListenerRegionType::NonPassiveTouchStart
, EventListenerRegionType::TouchEnd, EventListenerRegionType::NonPassiveTouchEnd
, EventListenerRegionType::TouchMove, EventListenerRegionType::NonPassiveTouchMove
, EventListenerRegionType::TouchCancel
, EventListenerRegionType::TouchForceChange, EventListenerRegionType::NonPassiveTouchForceChange
, EventListenerRegionType::PointerDown, EventListenerRegionType::NonPassivePointerDown
, EventListenerRegionType::PointerEnter, EventListenerRegionType::NonPassivePointerEnter
, EventListenerRegionType::PointerLeave, EventListenerRegionType::NonPassivePointerLeave
, EventListenerRegionType::PointerMove, EventListenerRegionType::NonPassivePointerMove
, EventListenerRegionType::PointerOut, EventListenerRegionType::NonPassivePointerOut
, EventListenerRegionType::PointerOver, EventListenerRegionType::NonPassivePointerOver
, EventListenerRegionType::PointerUp, EventListenerRegionType::NonPassivePointerUp
, EventListenerRegionType::MouseMove, EventListenerRegionType::NonPassiveMouseMove
, EventListenerRegionType::MouseDown, EventListenerRegionType::NonPassiveMouseDown
, EventListenerRegionType::MouseMove, EventListenerRegionType::NonPassiveMouseMove
, EventListenerRegionType::GestureChange, EventListenerRegionType::NonPassiveGestureChange
, EventListenerRegionType::GestureEnd, EventListenerRegionType::NonPassiveGestureEnd
, EventListenerRegionType::GestureStart, EventListenerRegionType::NonPassiveGestureStart
};
OptionSet<EventListenerRegionType> touchEventNonPassiveTypes =
{
EventListenerRegionType::NonPassiveTouchStart
, EventListenerRegionType::NonPassiveTouchEnd
, EventListenerRegionType::NonPassiveTouchMove
, EventListenerRegionType::NonPassiveTouchForceChange
, EventListenerRegionType::NonPassivePointerDown
, EventListenerRegionType::NonPassivePointerEnter
, EventListenerRegionType::NonPassivePointerLeave
, EventListenerRegionType::NonPassivePointerMove
, EventListenerRegionType::NonPassivePointerOut
, EventListenerRegionType::NonPassivePointerOver
, EventListenerRegionType::NonPassivePointerUp
, EventListenerRegionType::NonPassiveMouseDown
, EventListenerRegionType::NonPassiveMouseUp
, EventListenerRegionType::NonPassiveMouseMove
, EventListenerRegionType::NonPassiveGestureChange
, EventListenerRegionType::NonPassiveGestureEnd
, EventListenerRegionType::NonPassiveGestureStart
};
static bool isNonPassiveTouchEventType(EventListenerRegionType eventListenerRegionType)
{
return touchEventNonPassiveTypes.contains(eventListenerRegionType);
}
static bool containsTouchEventType(OptionSet<EventListenerRegionType> eventListenerRegionTypes)
{
return eventListenerRegionTypes.containsAny(touchEventTypes);
}
static EventTrackingRegionsEventType eventTypeForEventListenerType(EventListenerRegionType eventType)
{
switch (eventType) {
case EventListenerRegionType::NonPassiveTouchStart:
return EventTrackingRegionsEventType::Touchstart;
case EventListenerRegionType::NonPassiveTouchEnd:
return EventTrackingRegionsEventType::Touchend;
case EventListenerRegionType::NonPassiveTouchMove:
return EventTrackingRegionsEventType::Touchmove;
case EventListenerRegionType::NonPassiveTouchForceChange:
return EventTrackingRegionsEventType::Touchforcechange;
case EventListenerRegionType::NonPassivePointerDown:
return EventTrackingRegionsEventType::Pointerdown;
case EventListenerRegionType::NonPassivePointerEnter:
return EventTrackingRegionsEventType::Pointerenter;
case EventListenerRegionType::NonPassivePointerLeave:
return EventTrackingRegionsEventType::Pointerleave;
case EventListenerRegionType::NonPassivePointerMove:
return EventTrackingRegionsEventType::Pointermove;
case EventListenerRegionType::NonPassivePointerOut:
return EventTrackingRegionsEventType::Pointerout;
case EventListenerRegionType::NonPassivePointerOver:
return EventTrackingRegionsEventType::Pointerover;
case EventListenerRegionType::NonPassivePointerUp:
return EventTrackingRegionsEventType::Pointerup;
case EventListenerRegionType::NonPassiveMouseDown:
return EventTrackingRegionsEventType::Mousedown;
case EventListenerRegionType::NonPassiveMouseUp:
return EventTrackingRegionsEventType::Mousemove;
case EventListenerRegionType::NonPassiveMouseMove:
return EventTrackingRegionsEventType::Mouseup;
case EventListenerRegionType::NonPassiveGestureChange:
return EventTrackingRegionsEventType::Gesturechange;
case EventListenerRegionType::NonPassiveGestureEnd:
return EventTrackingRegionsEventType::Gestureend;
case EventListenerRegionType::NonPassiveGestureStart:
return EventTrackingRegionsEventType::Gesturestart;
default:
break;
}
ASSERT_NOT_REACHED();
return EventTrackingRegionsEventType::Touchend;
}
#endif
void EventRegion::uniteEventListeners(const Region& region, OptionSet<EventListenerRegionType> eventListenerRegionTypes)
{
#if ENABLE(WHEEL_EVENT_REGIONS)
if (eventListenerRegionTypes.contains(EventListenerRegionType::Wheel)) {
m_wheelEventListenerRegion.unite(region);
LOG_WITH_STREAM(EventRegions, stream << " uniting for passive wheel event listener");
}
if (eventListenerRegionTypes.contains(EventListenerRegionType::NonPassiveWheel)) {
m_nonPassiveWheelEventListenerRegion.unite(region);
LOG_WITH_STREAM(EventRegions, stream << " uniting for active wheel event listener");
}
#endif // ENABLE(WHEEL_EVENT_REGIONS)
#if ENABLE(TOUCH_EVENT_REGIONS)
if (containsTouchEventType(eventListenerRegionTypes)) {
m_touchEventListenerRegion.asynchronousDispatchRegion.unite(region);
for (auto eventType : eventListenerRegionTypes) {
if (!isNonPassiveTouchEventType(eventType))
continue;
m_touchEventListenerRegion.uniteSynchronousRegion(eventTypeForEventListenerType(eventType), region);
}
LOG_WITH_STREAM(EventRegions, stream << " uniting for touch event listener");
}
#endif
#if !ENABLE(TOUCH_EVENT_REGIONS) && !ENABLE(WHEEL_EVENT_REGIONS)
UNUSED_PARAM(region);
UNUSED_PARAM(eventListenerRegionTypes);
#endif
}
#if ENABLE(TOUCH_EVENT_REGIONS)
TrackingType EventRegion::eventTrackingTypeForPoint(EventTrackingRegionsEventType event, const IntPoint& point) const
{
return m_touchEventListenerRegion.trackingTypeForPoint(event, point);
}
#endif
#if ENABLE(WHEEL_EVENT_REGIONS)
OptionSet<EventListenerRegionType> EventRegion::eventListenerRegionTypesForPoint(const IntPoint& point) const
{
OptionSet<EventListenerRegionType> regionTypes;
if (m_wheelEventListenerRegion.contains(point))
regionTypes.add(EventListenerRegionType::Wheel);
if (m_nonPassiveWheelEventListenerRegion.contains(point))
regionTypes.add(EventListenerRegionType::NonPassiveWheel);
return regionTypes;
}
const Region& EventRegion::eventListenerRegionForType(EventListenerRegionType type) const
{
switch (type) {
case EventListenerRegionType::Wheel:
return m_wheelEventListenerRegion;
case EventListenerRegionType::NonPassiveWheel:
return m_nonPassiveWheelEventListenerRegion;
default:
break;
}
ASSERT_NOT_REACHED();
return m_wheelEventListenerRegion;
}
#endif
#if ENABLE(EDITABLE_REGION)
bool EventRegion::containsEditableElementsInRect(const IntRect& rect) const
{
return m_editableRegion && m_editableRegion->intersects(rect);
}
#endif
#if ENABLE(INTERACTION_REGIONS_IN_EVENT_REGION)
void EventRegion::appendInteractionRegions(const Vector<InteractionRegion>& interactionRegions)
{
m_interactionRegions.appendVector(interactionRegions);
}
void EventRegion::clearInteractionRegions()
{
m_interactionRegions.clear();
}
#endif
void EventRegion::dump(TextStream& ts) const
{
ts << m_region;
#if ENABLE(TOUCH_ACTION_REGIONS)
if (!m_touchActionRegions.isEmpty()) {
TextStream::IndentScope indentScope(ts);
ts << indent << "(touch-action\n"_s;
for (unsigned i = 0; i < m_touchActionRegions.size(); ++i) {
if (m_touchActionRegions[i].isEmpty())
continue;
TextStream::IndentScope indentScope(ts);
ts << indent << '(' << toTouchAction(i);
ts << indent << m_touchActionRegions[i];
ts << indent << ")\n"_s;
}
ts << indent << ")\n"_s;
}
#endif
#if ENABLE(WHEEL_EVENT_REGIONS)
if (!m_wheelEventListenerRegion.isEmpty()) {
ts << indent << "(wheel event listener region"_s << m_wheelEventListenerRegion;
if (!m_nonPassiveWheelEventListenerRegion.isEmpty()) {
TextStream::IndentScope indentScope(ts);
ts << indent << "(non-passive"_s << m_nonPassiveWheelEventListenerRegion;
ts << indent << ")\n"_s;
}
ts << indent << ")\n"_s;
}
#endif
#if ENABLE(TOUCH_EVENT_REGIONS)
if (!m_touchEventListenerRegion.isEmpty())
ts << indent << "(touch event listener region:"_s << m_touchEventListenerRegion << '\n';
#endif
#if ENABLE(EDITABLE_REGION)
if (m_editableRegion && !m_editableRegion->isEmpty()) {
ts << indent << "(editable region"_s << *m_editableRegion;
ts << indent << ")\n"_s;
}
#endif
#if ENABLE(INTERACTION_REGIONS_IN_EVENT_REGION)
if (!m_interactionRegions.isEmpty()) {
ts.dumpProperty("interaction regions"_s, m_interactionRegions);
ts << '\n';
}
#endif
}
#if ENABLE(TOUCH_EVENT_REGIONS)
TextStream& operator<<(TextStream& ts, const TouchEventListenerRegion& region)
{
if (!region.start.isEmpty())
ts << " touchStart: "_s << region.start;
if (!region.end.isEmpty())
ts << " touchEnd: "_s << region.end;
if (!region.cancel.isEmpty())
ts << " touchCancel: "_s << region.cancel;
if (!region.move.isEmpty())
ts << " touchMove: "_s << region.move;
return ts;
}
#endif
TextStream& operator<<(TextStream& ts, TouchAction touchAction)
{
switch (touchAction) {
case TouchAction::None:
return ts << "none"_s;
case TouchAction::Manipulation:
return ts << "manipulation"_s;
case TouchAction::PanX:
return ts << "pan-x"_s;
case TouchAction::PanY:
return ts << "pan-y"_s;
case TouchAction::PinchZoom:
return ts << "pinch-zoom"_s;
case TouchAction::Auto:
return ts << "auto"_s;
}
ASSERT_NOT_REACHED();
return ts;
}
TextStream& operator<<(TextStream& ts, const EventRegion& eventRegion)
{
eventRegion.dump(ts);
return ts;
}
}