1 | /* |
2 | * Copyright (C) 2014-2017 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 "DebugPageOverlays.h" |
28 | |
29 | #include "ColorHash.h" |
30 | #include "ElementIterator.h" |
31 | #include "FrameView.h" |
32 | #include "GraphicsContext.h" |
33 | #include "Page.h" |
34 | #include "PageOverlay.h" |
35 | #include "PageOverlayController.h" |
36 | #include "Region.h" |
37 | #include "ScrollingCoordinator.h" |
38 | #include "Settings.h" |
39 | |
40 | namespace WebCore { |
41 | |
42 | DebugPageOverlays* DebugPageOverlays::sharedDebugOverlays; |
43 | |
44 | class RegionOverlay : public RefCounted<RegionOverlay>, public PageOverlay::Client { |
45 | public: |
46 | static Ref<RegionOverlay> create(Page&, DebugPageOverlays::RegionType); |
47 | virtual ~RegionOverlay(); |
48 | |
49 | void recomputeRegion(); |
50 | PageOverlay& overlay() { return *m_overlay; } |
51 | |
52 | protected: |
53 | RegionOverlay(Page&, Color); |
54 | |
55 | private: |
56 | void willMoveToPage(PageOverlay&, Page*) final; |
57 | void didMoveToPage(PageOverlay&, Page*) final; |
58 | void drawRect(PageOverlay&, GraphicsContext&, const IntRect& dirtyRect) override; |
59 | bool mouseEvent(PageOverlay&, const PlatformMouseEvent&) final; |
60 | void didScrollFrame(PageOverlay&, Frame&) final; |
61 | |
62 | protected: |
63 | // Returns true if the region changed. |
64 | virtual bool updateRegion() = 0; |
65 | void drawRegion(GraphicsContext&, const Region&, const Color&, const IntRect& dirtyRect); |
66 | |
67 | Page& m_page; |
68 | RefPtr<PageOverlay> m_overlay; |
69 | std::unique_ptr<Region> m_region; |
70 | Color m_color; |
71 | }; |
72 | |
73 | class MouseWheelRegionOverlay final : public RegionOverlay { |
74 | public: |
75 | static Ref<MouseWheelRegionOverlay> create(Page& page) |
76 | { |
77 | return adoptRef(*new MouseWheelRegionOverlay(page)); |
78 | } |
79 | |
80 | private: |
81 | explicit MouseWheelRegionOverlay(Page& page) |
82 | : RegionOverlay(page, Color(0.5f, 0.0f, 0.0f, 0.4f)) |
83 | { |
84 | } |
85 | |
86 | bool updateRegion() override; |
87 | }; |
88 | |
89 | bool MouseWheelRegionOverlay::updateRegion() |
90 | { |
91 | auto region = std::make_unique<Region>(); |
92 | |
93 | for (const Frame* frame = &m_page.mainFrame(); frame; frame = frame->tree().traverseNext()) { |
94 | if (!frame->view() || !frame->document()) |
95 | continue; |
96 | |
97 | auto frameRegion = frame->document()->absoluteRegionForEventTargets(frame->document()->wheelEventTargets()); |
98 | frameRegion.first.translate(toIntSize(frame->view()->contentsToRootView(IntPoint()))); |
99 | region->unite(frameRegion.first); |
100 | } |
101 | |
102 | region->translate(m_overlay->viewToOverlayOffset()); |
103 | |
104 | bool regionChanged = !m_region || !(*m_region == *region); |
105 | m_region = WTFMove(region); |
106 | return regionChanged; |
107 | } |
108 | |
109 | class NonFastScrollableRegionOverlay final : public RegionOverlay { |
110 | public: |
111 | static Ref<NonFastScrollableRegionOverlay> create(Page& page) |
112 | { |
113 | return adoptRef(*new NonFastScrollableRegionOverlay(page)); |
114 | } |
115 | |
116 | private: |
117 | explicit NonFastScrollableRegionOverlay(Page& page) |
118 | : RegionOverlay(page, Color(1.0f, 0.5f, 0.0f, 0.4f)) |
119 | { |
120 | } |
121 | |
122 | bool updateRegion() override; |
123 | void drawRect(PageOverlay&, GraphicsContext&, const IntRect& dirtyRect) final; |
124 | |
125 | EventTrackingRegions m_eventTrackingRegions; |
126 | }; |
127 | |
128 | bool NonFastScrollableRegionOverlay::updateRegion() |
129 | { |
130 | bool regionChanged = false; |
131 | |
132 | if (ScrollingCoordinator* scrollingCoordinator = m_page.scrollingCoordinator()) { |
133 | EventTrackingRegions eventTrackingRegions = scrollingCoordinator->absoluteEventTrackingRegions(); |
134 | |
135 | if (eventTrackingRegions != m_eventTrackingRegions) { |
136 | m_eventTrackingRegions = eventTrackingRegions; |
137 | regionChanged = true; |
138 | } |
139 | } |
140 | |
141 | return regionChanged; |
142 | } |
143 | |
144 | static const HashMap<String, Color>& touchEventRegionColors() |
145 | { |
146 | static const auto regionColors = makeNeverDestroyed([] { |
147 | struct MapEntry { |
148 | ASCIILiteral name; |
149 | int r; |
150 | int g; |
151 | int b; |
152 | }; |
153 | static const MapEntry entries[] = { |
154 | { "touchstart"_s , 191, 191, 63 }, |
155 | { "touchmove"_s , 80, 204, 245 }, |
156 | { "touchend"_s , 191, 63, 127 }, |
157 | { "touchforcechange"_s , 63, 63, 191 }, |
158 | { "wheel"_s , 255, 128, 0 }, |
159 | { "mousedown"_s , 80, 245, 80 }, |
160 | { "mousemove"_s , 245, 245, 80 }, |
161 | { "mouseup"_s , 80, 245, 176 }, |
162 | }; |
163 | HashMap<String, Color> map; |
164 | for (auto& entry : entries) |
165 | map.add(entry.name, Color { entry.r, entry.g, entry.b, 80 }); |
166 | return map; |
167 | }()); |
168 | return regionColors; |
169 | } |
170 | |
171 | static void drawRightAlignedText(const String& text, GraphicsContext& context, const FontCascade& font, const FloatPoint& boxLocation) |
172 | { |
173 | float textGap = 10; |
174 | float textBaselineFromTop = 14; |
175 | |
176 | TextRun textRun = TextRun(text); |
177 | context.setFillColor(Color::transparent); |
178 | float textWidth = context.drawText(font, textRun, { }); |
179 | context.setFillColor(Color::black); |
180 | context.drawText(font, textRun, boxLocation + FloatSize(-(textWidth + textGap), textBaselineFromTop)); |
181 | } |
182 | |
183 | void NonFastScrollableRegionOverlay::drawRect(PageOverlay& pageOverlay, GraphicsContext& context, const IntRect&) |
184 | { |
185 | IntRect bounds = pageOverlay.bounds(); |
186 | |
187 | context.clearRect(bounds); |
188 | |
189 | FloatRect legendRect = { bounds.maxX() - 30.0f, 10, 20, 20 }; |
190 | |
191 | FontCascadeDescription fontDescription; |
192 | fontDescription.setOneFamily("Helvetica" ); |
193 | fontDescription.setSpecifiedSize(12); |
194 | fontDescription.setComputedSize(12); |
195 | fontDescription.setWeight(FontSelectionValue(500)); |
196 | FontCascade font(WTFMove(fontDescription), 0, 0); |
197 | font.update(nullptr); |
198 | |
199 | #if ENABLE(TOUCH_EVENTS) |
200 | context.setFillColor(touchEventRegionColors().get("touchstart" )); |
201 | context.fillRect(legendRect); |
202 | drawRightAlignedText("touchstart" , context, font, legendRect.location()); |
203 | |
204 | legendRect.move(0, 30); |
205 | context.setFillColor(touchEventRegionColors().get("touchmove" )); |
206 | context.fillRect(legendRect); |
207 | drawRightAlignedText("touchmove" , context, font, legendRect.location()); |
208 | |
209 | legendRect.move(0, 30); |
210 | context.setFillColor(touchEventRegionColors().get("touchend" )); |
211 | context.fillRect(legendRect); |
212 | drawRightAlignedText("touchend" , context, font, legendRect.location()); |
213 | |
214 | legendRect.move(0, 30); |
215 | context.setFillColor(touchEventRegionColors().get("touchforcechange" )); |
216 | context.fillRect(legendRect); |
217 | drawRightAlignedText("touchforcechange" , context, font, legendRect.location()); |
218 | |
219 | legendRect.move(0, 30); |
220 | context.setFillColor(m_color); |
221 | context.fillRect(legendRect); |
222 | drawRightAlignedText("passive listeners" , context, font, legendRect.location()); |
223 | |
224 | legendRect.move(0, 30); |
225 | context.setFillColor(touchEventRegionColors().get("mousedown" )); |
226 | context.fillRect(legendRect); |
227 | drawRightAlignedText("mousedown" , context, font, legendRect.location()); |
228 | |
229 | legendRect.move(0, 30); |
230 | context.setFillColor(touchEventRegionColors().get("mousemove" )); |
231 | context.fillRect(legendRect); |
232 | drawRightAlignedText("mousemove" , context, font, legendRect.location()); |
233 | |
234 | legendRect.move(0, 30); |
235 | context.setFillColor(touchEventRegionColors().get("mouseup" )); |
236 | context.fillRect(legendRect); |
237 | drawRightAlignedText("mouseup" , context, font, legendRect.location()); |
238 | #else |
239 | // On desktop platforms, the "wheel" region includes the non-fast scrollable region. |
240 | context.setFillColor(touchEventRegionColors().get("wheel" )); |
241 | context.fillRect(legendRect); |
242 | drawRightAlignedText("non-fast region" , context, font, legendRect.location()); |
243 | #endif |
244 | |
245 | for (const auto& synchronousEventRegion : m_eventTrackingRegions.eventSpecificSynchronousDispatchRegions) { |
246 | Color regionColor(0, 0, 0, 64); |
247 | auto it = touchEventRegionColors().find(synchronousEventRegion.key); |
248 | if (it != touchEventRegionColors().end()) |
249 | regionColor = it->value; |
250 | drawRegion(context, synchronousEventRegion.value, regionColor, bounds); |
251 | } |
252 | |
253 | drawRegion(context, m_eventTrackingRegions.asynchronousDispatchRegion, m_color, bounds); |
254 | } |
255 | |
256 | Ref<RegionOverlay> RegionOverlay::create(Page& page, DebugPageOverlays::RegionType regionType) |
257 | { |
258 | switch (regionType) { |
259 | case DebugPageOverlays::RegionType::WheelEventHandlers: |
260 | return MouseWheelRegionOverlay::create(page); |
261 | case DebugPageOverlays::RegionType::NonFastScrollableRegion: |
262 | return NonFastScrollableRegionOverlay::create(page); |
263 | } |
264 | ASSERT_NOT_REACHED(); |
265 | return MouseWheelRegionOverlay::create(page); |
266 | } |
267 | |
268 | RegionOverlay::RegionOverlay(Page& page, Color regionColor) |
269 | : m_page(page) |
270 | , m_overlay(PageOverlay::create(*this, PageOverlay::OverlayType::Document)) |
271 | , m_color(regionColor) |
272 | { |
273 | } |
274 | |
275 | RegionOverlay::~RegionOverlay() |
276 | { |
277 | if (m_overlay) |
278 | m_page.pageOverlayController().uninstallPageOverlay(*m_overlay, PageOverlay::FadeMode::DoNotFade); |
279 | } |
280 | |
281 | void RegionOverlay::willMoveToPage(PageOverlay&, Page* page) |
282 | { |
283 | if (!page) |
284 | m_overlay = nullptr; |
285 | } |
286 | |
287 | void RegionOverlay::didMoveToPage(PageOverlay&, Page* page) |
288 | { |
289 | if (page) |
290 | recomputeRegion(); |
291 | } |
292 | |
293 | void RegionOverlay::drawRect(PageOverlay&, GraphicsContext& context, const IntRect& dirtyRect) |
294 | { |
295 | context.clearRect(dirtyRect); |
296 | |
297 | if (!m_region) |
298 | return; |
299 | |
300 | drawRegion(context, *m_region, m_color, dirtyRect); |
301 | } |
302 | |
303 | void RegionOverlay::drawRegion(GraphicsContext& context, const Region& region, const Color& color, const IntRect& dirtyRect) |
304 | { |
305 | GraphicsContextStateSaver saver(context); |
306 | context.setFillColor(color); |
307 | for (auto rect : region.rects()) { |
308 | if (rect.intersects(dirtyRect)) |
309 | context.fillRect(rect); |
310 | } |
311 | } |
312 | |
313 | bool RegionOverlay::mouseEvent(PageOverlay&, const PlatformMouseEvent&) |
314 | { |
315 | return false; |
316 | } |
317 | |
318 | void RegionOverlay::didScrollFrame(PageOverlay&, Frame&) |
319 | { |
320 | } |
321 | |
322 | void RegionOverlay::recomputeRegion() |
323 | { |
324 | if (updateRegion()) |
325 | m_overlay->setNeedsDisplay(); |
326 | } |
327 | |
328 | DebugPageOverlays& DebugPageOverlays::singleton() |
329 | { |
330 | if (!sharedDebugOverlays) |
331 | sharedDebugOverlays = new DebugPageOverlays; |
332 | |
333 | return *sharedDebugOverlays; |
334 | } |
335 | |
336 | static inline size_t indexOf(DebugPageOverlays::RegionType regionType) |
337 | { |
338 | return static_cast<size_t>(regionType); |
339 | } |
340 | |
341 | RegionOverlay& DebugPageOverlays::ensureRegionOverlayForPage(Page& page, RegionType regionType) |
342 | { |
343 | auto it = m_pageRegionOverlays.find(&page); |
344 | if (it != m_pageRegionOverlays.end()) { |
345 | auto& visualizer = it->value[indexOf(regionType)]; |
346 | if (!visualizer) |
347 | visualizer = RegionOverlay::create(page, regionType); |
348 | return *visualizer; |
349 | } |
350 | |
351 | Vector<RefPtr<RegionOverlay>> visualizers(NumberOfRegionTypes); |
352 | auto visualizer = RegionOverlay::create(page, regionType); |
353 | visualizers[indexOf(regionType)] = visualizer.copyRef(); |
354 | m_pageRegionOverlays.add(&page, WTFMove(visualizers)); |
355 | return visualizer; |
356 | } |
357 | |
358 | void DebugPageOverlays::showRegionOverlay(Page& page, RegionType regionType) |
359 | { |
360 | auto& visualizer = ensureRegionOverlayForPage(page, regionType); |
361 | page.pageOverlayController().installPageOverlay(visualizer.overlay(), PageOverlay::FadeMode::DoNotFade); |
362 | } |
363 | |
364 | void DebugPageOverlays::hideRegionOverlay(Page& page, RegionType regionType) |
365 | { |
366 | auto it = m_pageRegionOverlays.find(&page); |
367 | if (it == m_pageRegionOverlays.end()) |
368 | return; |
369 | auto& visualizer = it->value[indexOf(regionType)]; |
370 | if (!visualizer) |
371 | return; |
372 | page.pageOverlayController().uninstallPageOverlay(visualizer->overlay(), PageOverlay::FadeMode::DoNotFade); |
373 | visualizer = nullptr; |
374 | } |
375 | |
376 | void DebugPageOverlays::regionChanged(Frame& frame, RegionType regionType) |
377 | { |
378 | auto* page = frame.page(); |
379 | if (!page) |
380 | return; |
381 | |
382 | if (auto* visualizer = regionOverlayForPage(*page, regionType)) |
383 | visualizer->recomputeRegion(); |
384 | } |
385 | |
386 | RegionOverlay* DebugPageOverlays::regionOverlayForPage(Page& page, RegionType regionType) const |
387 | { |
388 | auto it = m_pageRegionOverlays.find(&page); |
389 | if (it == m_pageRegionOverlays.end()) |
390 | return nullptr; |
391 | return it->value.at(indexOf(regionType)).get(); |
392 | } |
393 | |
394 | void DebugPageOverlays::updateOverlayRegionVisibility(Page& page, DebugOverlayRegions visibleRegions) |
395 | { |
396 | if (visibleRegions & NonFastScrollableRegion) |
397 | showRegionOverlay(page, RegionType::NonFastScrollableRegion); |
398 | else |
399 | hideRegionOverlay(page, RegionType::NonFastScrollableRegion); |
400 | |
401 | if (visibleRegions & WheelEventHandlerRegion) |
402 | showRegionOverlay(page, RegionType::WheelEventHandlers); |
403 | else |
404 | hideRegionOverlay(page, RegionType::WheelEventHandlers); |
405 | } |
406 | |
407 | void DebugPageOverlays::settingsChanged(Page& page) |
408 | { |
409 | DebugOverlayRegions activeOverlayRegions = page.settings().visibleDebugOverlayRegions(); |
410 | if (!activeOverlayRegions && !hasOverlays(page)) |
411 | return; |
412 | |
413 | DebugPageOverlays::singleton().updateOverlayRegionVisibility(page, activeOverlayRegions); |
414 | } |
415 | |
416 | } |
417 | |