5575508509c24edff91e20d6665e364eb4846cc5
[WebKit-https.git] / Source / WebCore / rendering / EventRegion.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''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "EventRegion.h"
28
29 #include "Logging.h"
30 #include "RenderStyle.h"
31 #include <wtf/text/TextStream.h>
32
33 namespace WebCore {
34
35 EventRegionContext::EventRegionContext(EventRegion& eventRegion)
36     : m_eventRegion(eventRegion)
37 {
38 }
39
40 void EventRegionContext::pushTransform(const AffineTransform& transform)
41 {
42     if (m_transformStack.isEmpty())
43         m_transformStack.append(transform);
44     else
45         m_transformStack.append(m_transformStack.last() * transform);
46 }
47
48 void EventRegionContext::popTransform()
49 {
50     if (m_transformStack.isEmpty()) {
51         ASSERT_NOT_REACHED();
52         return;
53     }
54     m_transformStack.removeLast();
55 }
56
57 void EventRegionContext::pushClip(const IntRect& clipRect)
58 {
59     auto transformedClip = m_transformStack.isEmpty() ? clipRect : m_transformStack.last().mapRect(clipRect);
60
61     if (m_clipStack.isEmpty())
62         m_clipStack.append(transformedClip);
63     else
64         m_clipStack.append(intersection(m_clipStack.last(), transformedClip));
65 }
66
67 void EventRegionContext::popClip()
68 {
69     if (m_clipStack.isEmpty()) {
70         ASSERT_NOT_REACHED();
71         return;
72     }
73     m_clipStack.removeLast();
74 }
75
76 void EventRegionContext::unite(const Region& region, const RenderStyle& style, bool overrideUserModifyIsEditable)
77 {
78     if (m_transformStack.isEmpty() && m_clipStack.isEmpty()) {
79         m_eventRegion.unite(region, style, overrideUserModifyIsEditable);
80         return;
81     }
82
83     auto transformedAndClippedRegion = m_transformStack.isEmpty() ? region : m_transformStack.last().mapRegion(region);
84
85     if (!m_clipStack.isEmpty())
86         transformedAndClippedRegion.intersect(m_clipStack.last());
87
88     m_eventRegion.unite(transformedAndClippedRegion, style, overrideUserModifyIsEditable);
89 }
90
91 bool EventRegionContext::contains(const IntRect& rect) const
92 {
93     if (m_transformStack.isEmpty())
94         return m_eventRegion.contains(rect);
95
96     return m_eventRegion.contains(m_transformStack.last().mapRect(rect));
97 }
98
99 EventRegion::EventRegion() = default;
100
101 bool EventRegion::operator==(const EventRegion& other) const
102 {
103 #if ENABLE(TOUCH_ACTION_REGIONS)
104     if (m_touchActionRegions != other.m_touchActionRegions)
105         return false;
106 #endif
107
108 #if ENABLE(WHEEL_EVENT_REGIONS)
109     if (m_wheelEventListenerRegion != other.m_wheelEventListenerRegion)
110         return false;
111     if (m_nonPassiveWheelEventListenerRegion != other.m_nonPassiveWheelEventListenerRegion)
112         return false;
113 #endif
114
115 #if ENABLE(EDITABLE_REGION)
116     if (m_editableRegion != other.m_editableRegion)
117         return false;
118 #endif
119
120 #if ENABLE(INTERACTION_REGIONS_IN_EVENT_REGION)
121     if (m_interactionRegions != other.m_interactionRegions)
122         return false;
123 #endif
124
125     return m_region == other.m_region;
126 }
127
128 void EventRegion::unite(const Region& region, const RenderStyle& style, bool overrideUserModifyIsEditable)
129 {
130     m_region.unite(region);
131
132 #if ENABLE(TOUCH_ACTION_REGIONS)
133     uniteTouchActions(region, style.effectiveTouchActions());
134 #endif
135
136 #if ENABLE(WHEEL_EVENT_REGIONS)
137     uniteEventListeners(region, style.eventListenerRegionTypes());
138 #endif
139
140 #if ENABLE(EDITABLE_REGION)
141     if (m_editableRegion && (overrideUserModifyIsEditable || style.effectiveUserModify() != UserModify::ReadOnly)) {
142         m_editableRegion->unite(region);
143         LOG_WITH_STREAM(EventRegions, stream << " uniting editable region");
144     }
145 #else
146     UNUSED_PARAM(overrideUserModifyIsEditable);
147 #endif
148
149 #if !ENABLE(TOUCH_ACTION_REGIONS) && !ENABLE(WHEEL_EVENT_REGIONS) && !ENABLE(EDITABLE_REGION)
150     UNUSED_PARAM(style);
151 #endif
152 }
153
154 void EventRegion::translate(const IntSize& offset)
155 {
156     m_region.translate(offset);
157
158 #if ENABLE(TOUCH_ACTION_REGIONS)
159     for (auto& touchActionRegion : m_touchActionRegions)
160         touchActionRegion.translate(offset);
161 #endif
162
163 #if ENABLE(WHEEL_EVENT_REGIONS)
164     m_wheelEventListenerRegion.translate(offset);
165     m_nonPassiveWheelEventListenerRegion.translate(offset);
166 #endif
167
168 #if ENABLE(EDITABLE_REGION)
169     if (m_editableRegion)
170         m_editableRegion->translate(offset);
171 #endif
172
173 #if ENABLE(INTERACTION_REGIONS_IN_EVENT_REGION)
174     for (auto& region : m_interactionRegions) {
175         for (auto& rect : region.rectsInContentCoordinates)
176             rect.move(offset);
177     }
178 #endif
179 }
180
181 static inline unsigned toIndex(TouchAction touchAction)
182 {
183     switch (touchAction) {
184     case TouchAction::None:
185         return 0;
186     case TouchAction::Manipulation:
187         return 1;
188     case TouchAction::PanX:
189         return 2;
190     case TouchAction::PanY:
191         return 3;
192     case TouchAction::PinchZoom:
193         return 4;
194     case TouchAction::Auto:
195         break;
196     }
197     ASSERT_NOT_REACHED();
198     return 0;
199 }
200
201 static inline TouchAction toTouchAction(unsigned index)
202 {
203     switch (index) {
204     case 0:
205         return TouchAction::None;
206     case 1:
207         return TouchAction::Manipulation;
208     case 2:
209         return TouchAction::PanX;
210     case 3:
211         return TouchAction::PanY;
212     case 4:
213         return TouchAction::PinchZoom;
214     default:
215         break;
216     }
217     ASSERT_NOT_REACHED();
218     return TouchAction::Auto;
219 }
220
221 #if ENABLE(TOUCH_ACTION_REGIONS)
222 void EventRegion::uniteTouchActions(const Region& touchRegion, OptionSet<TouchAction> touchActions)
223 {
224     for (auto touchAction : touchActions) {
225         if (touchAction == TouchAction::Auto)
226             break;
227         auto index = toIndex(touchAction);
228         if (m_touchActionRegions.size() < index + 1)
229             m_touchActionRegions.grow(index + 1);
230     }
231
232     for (unsigned i = 0; i < m_touchActionRegions.size(); ++i) {
233         auto regionTouchAction = toTouchAction(i);
234         if (touchActions.contains(regionTouchAction)) {
235             m_touchActionRegions[i].unite(touchRegion);
236             LOG_WITH_STREAM(EventRegions, stream << " uniting for TouchAction " << regionTouchAction);
237         } else {
238             m_touchActionRegions[i].subtract(touchRegion);
239             LOG_WITH_STREAM(EventRegions, stream << " subtracting for TouchAction " << regionTouchAction);
240         }
241     }
242 }
243
244 const Region* EventRegion::regionForTouchAction(TouchAction action) const
245 {
246     unsigned actionIndex = toIndex(action);
247     if (actionIndex >= m_touchActionRegions.size())
248         return nullptr;
249
250     return &m_touchActionRegions[actionIndex];
251 }
252
253 OptionSet<TouchAction> EventRegion::touchActionsForPoint(const IntPoint& point) const
254 {
255     OptionSet<TouchAction> actions;
256
257     for (unsigned i = 0; i < m_touchActionRegions.size(); ++i) {
258         if (m_touchActionRegions[i].contains(point)) {
259             auto action = toTouchAction(i);
260             actions.add(action);
261             if (action == TouchAction::None || action == TouchAction::Manipulation)
262                 break;
263         }
264     }
265
266     if (actions.isEmpty())
267         return { TouchAction::Auto };
268
269     return actions;
270 }
271 #endif
272
273 #if ENABLE(WHEEL_EVENT_REGIONS)
274 void EventRegion::uniteEventListeners(const Region& region, OptionSet<EventListenerRegionType> eventListenerRegionTypes)
275 {
276     if (eventListenerRegionTypes.contains(EventListenerRegionType::Wheel)) {
277         m_wheelEventListenerRegion.unite(region);
278         LOG_WITH_STREAM(EventRegions, stream << " uniting for passive wheel event listener");
279     }
280     if (eventListenerRegionTypes.contains(EventListenerRegionType::NonPassiveWheel)) {
281         m_nonPassiveWheelEventListenerRegion.unite(region);
282         LOG_WITH_STREAM(EventRegions, stream << " uniting for active wheel event listener");
283     }
284 }
285
286 OptionSet<EventListenerRegionType> EventRegion::eventListenerRegionTypesForPoint(const IntPoint& point) const
287 {
288     OptionSet<EventListenerRegionType> regionTypes;
289     if (m_wheelEventListenerRegion.contains(point))
290         regionTypes.add(EventListenerRegionType::Wheel);
291     if (m_nonPassiveWheelEventListenerRegion.contains(point))
292         regionTypes.add(EventListenerRegionType::NonPassiveWheel);
293
294     return regionTypes;
295 }
296
297 const Region& EventRegion::eventListenerRegionForType(EventListenerRegionType type) const
298 {
299     switch (type) {
300     case EventListenerRegionType::Wheel:
301         return m_wheelEventListenerRegion;
302     case EventListenerRegionType::NonPassiveWheel:
303         return m_nonPassiveWheelEventListenerRegion;
304     }
305     ASSERT_NOT_REACHED();
306     return m_wheelEventListenerRegion;
307 }
308 #endif // ENABLE(WHEEL_EVENT_REGIONS)
309
310 #if ENABLE(EDITABLE_REGION)
311
312 bool EventRegion::containsEditableElementsInRect(const IntRect& rect) const
313 {
314     return m_editableRegion && m_editableRegion->intersects(rect);
315 }
316
317 #endif
318
319 #if ENABLE(INTERACTION_REGIONS_IN_EVENT_REGION)
320
321 void EventRegion::uniteInteractionRegions(const Vector<InteractionRegion>& interactionRegions)
322 {
323     m_interactionRegions.appendVector(interactionRegions);
324 }
325
326 void EventRegion::computeInteractionRegions(Page& page, IntRect rect)
327 {
328     // FIXME: Collect regions from `EventRegion::unite` instead of hit-testing, so that they are per-layer.
329     uniteInteractionRegions(WebCore::interactionRegions(page, rect));
330 }
331
332 #endif
333
334 void EventRegion::dump(TextStream& ts) const
335 {
336     ts << m_region;
337
338 #if ENABLE(TOUCH_ACTION_REGIONS)
339     if (!m_touchActionRegions.isEmpty()) {
340         TextStream::IndentScope indentScope(ts);
341         ts << indent << "(touch-action\n";
342         for (unsigned i = 0; i < m_touchActionRegions.size(); ++i) {
343             if (m_touchActionRegions[i].isEmpty())
344                 continue;
345             TextStream::IndentScope indentScope(ts);
346             ts << indent << "(" << toTouchAction(i);
347             ts << indent << m_touchActionRegions[i];
348             ts << indent << ")\n";
349         }
350         ts << indent << ")\n";
351     }
352 #endif
353
354 #if ENABLE(WHEEL_EVENT_REGIONS)
355     if (!m_wheelEventListenerRegion.isEmpty()) {
356         ts << indent << "(wheel event listener region" << m_wheelEventListenerRegion;
357         if (!m_nonPassiveWheelEventListenerRegion.isEmpty()) {
358             TextStream::IndentScope indentScope(ts);
359             ts << indent << "(non-passive" << m_nonPassiveWheelEventListenerRegion;
360             ts << indent << ")\n";
361         }
362         ts << indent << ")\n";
363     }
364 #endif
365
366 #if ENABLE(EDITABLE_REGION)
367     if (m_editableRegion && !m_editableRegion->isEmpty()) {
368         ts << indent << "(editable region" << *m_editableRegion;
369         ts << indent << ")\n";
370     }
371 #endif
372 }
373
374 TextStream& operator<<(TextStream& ts, TouchAction touchAction)
375 {
376     switch (touchAction) {
377     case TouchAction::None:
378         return ts << "none";
379     case TouchAction::Manipulation:
380         return ts << "manipulation";
381     case TouchAction::PanX:
382         return ts << "pan-x";
383     case TouchAction::PanY:
384         return ts << "pan-y";
385     case TouchAction::PinchZoom:
386         return ts << "pinch-zoom";
387     case TouchAction::Auto:
388         return ts << "auto";
389     }
390     ASSERT_NOT_REACHED();
391     return ts;
392 }
393
394 TextStream& operator<<(TextStream& ts, const EventRegion& eventRegion)
395 {
396     eventRegion.dump(ts);
397     return ts;
398 }
399
400 }