[WTF] Add makeUnique<T>, which ensures T is fast-allocated, makeUnique / makeUniqueWi...
[WebKit-https.git] / Source / WebCore / page / DebugPageOverlays.cpp
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 = makeUnique<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, 50 });
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 }