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
40namespace WebCore {
41
42DebugPageOverlays* DebugPageOverlays::sharedDebugOverlays;
43
44class RegionOverlay : public RefCounted<RegionOverlay>, public PageOverlay::Client {
45public:
46 static Ref<RegionOverlay> create(Page&, DebugPageOverlays::RegionType);
47 virtual ~RegionOverlay();
48
49 void recomputeRegion();
50 PageOverlay& overlay() { return *m_overlay; }
51
52protected:
53 RegionOverlay(Page&, Color);
54
55private:
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
62protected:
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
73class MouseWheelRegionOverlay final : public RegionOverlay {
74public:
75 static Ref<MouseWheelRegionOverlay> create(Page& page)
76 {
77 return adoptRef(*new MouseWheelRegionOverlay(page));
78 }
79
80private:
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
89bool 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
109class NonFastScrollableRegionOverlay final : public RegionOverlay {
110public:
111 static Ref<NonFastScrollableRegionOverlay> create(Page& page)
112 {
113 return adoptRef(*new NonFastScrollableRegionOverlay(page));
114 }
115
116private:
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
128bool 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
144static 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, 50 });
166 return map;
167 }());
168 return regionColors;
169}
170
171static 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
183void 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
256Ref<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
268RegionOverlay::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
275RegionOverlay::~RegionOverlay()
276{
277 if (m_overlay)
278 m_page.pageOverlayController().uninstallPageOverlay(*m_overlay, PageOverlay::FadeMode::DoNotFade);
279}
280
281void RegionOverlay::willMoveToPage(PageOverlay&, Page* page)
282{
283 if (!page)
284 m_overlay = nullptr;
285}
286
287void RegionOverlay::didMoveToPage(PageOverlay&, Page* page)
288{
289 if (page)
290 recomputeRegion();
291}
292
293void 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
303void 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
313bool RegionOverlay::mouseEvent(PageOverlay&, const PlatformMouseEvent&)
314{
315 return false;
316}
317
318void RegionOverlay::didScrollFrame(PageOverlay&, Frame&)
319{
320}
321
322void RegionOverlay::recomputeRegion()
323{
324 if (updateRegion())
325 m_overlay->setNeedsDisplay();
326}
327
328DebugPageOverlays& DebugPageOverlays::singleton()
329{
330 if (!sharedDebugOverlays)
331 sharedDebugOverlays = new DebugPageOverlays;
332
333 return *sharedDebugOverlays;
334}
335
336static inline size_t indexOf(DebugPageOverlays::RegionType regionType)
337{
338 return static_cast<size_t>(regionType);
339}
340
341RegionOverlay& 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
358void DebugPageOverlays::showRegionOverlay(Page& page, RegionType regionType)
359{
360 auto& visualizer = ensureRegionOverlayForPage(page, regionType);
361 page.pageOverlayController().installPageOverlay(visualizer.overlay(), PageOverlay::FadeMode::DoNotFade);
362}
363
364void 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
376void 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
386RegionOverlay* 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
394void 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
407void 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