19eede4c995a30a1caa273bec5de3b849534c9a9
[WebKit-https.git] / Source / WebCore / page / DebugPageOverlays.cpp
1 /*
2  * Copyright (C) 2014-2021 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 "Cursor.h"
31 #include "ElementIterator.h"
32 #include "FloatRoundedRect.h"
33 #include "FrameView.h"
34 #include "Gradient.h"
35 #include "GraphicsContext.h"
36 #include "InteractionRegion.h"
37 #include "Page.h"
38 #include "PageOverlay.h"
39 #include "PageOverlayController.h"
40 #include "PathUtilities.h"
41 #include "PlatformMouseEvent.h"
42 #include "Region.h"
43 #include "ScrollingCoordinator.h"
44 #include "Settings.h"
45 #include <wtf/SortedArrayMap.h>
46
47 namespace WebCore {
48
49 DebugPageOverlays* DebugPageOverlays::sharedDebugOverlays;
50
51 class RegionOverlay : public RefCounted<RegionOverlay>, public PageOverlay::Client {
52 public:
53     static Ref<RegionOverlay> create(Page&, DebugPageOverlays::RegionType);
54     virtual ~RegionOverlay();
55
56     void recomputeRegion();
57     PageOverlay& overlay() { return *m_overlay; }
58
59     void setRegionChanged() { m_regionChanged = true; }
60
61 protected:
62     RegionOverlay(Page&, Color);
63
64 private:
65     void willMoveToPage(PageOverlay&, Page*) final;
66     void didMoveToPage(PageOverlay&, Page*) final;
67     void drawRect(PageOverlay&, GraphicsContext&, const IntRect& dirtyRect) override;
68     bool mouseEvent(PageOverlay&, const PlatformMouseEvent&) override;
69     void didScrollFrame(PageOverlay&, Frame&) override;
70
71 protected:
72     // Returns true if the region changed.
73     virtual bool updateRegion() = 0;
74     void drawRegion(GraphicsContext&, const Region&, const Color&, const IntRect& dirtyRect);
75     
76     Page& m_page;
77     RefPtr<PageOverlay> m_overlay;
78     std::unique_ptr<Region> m_region;
79     Color m_color;
80     bool m_regionChanged { true };
81 };
82
83 #if COMPILER(CLANG)
84 #pragma mark - MouseWheelRegionOverlay
85 #endif
86
87 class MouseWheelRegionOverlay final : public RegionOverlay {
88 public:
89     static Ref<MouseWheelRegionOverlay> create(Page& page)
90     {
91         return adoptRef(*new MouseWheelRegionOverlay(page));
92     }
93
94 private:
95     explicit MouseWheelRegionOverlay(Page& page)
96         : RegionOverlay(page, SRGBA<uint8_t> { 128, 0, 0, 102 })
97     {
98     }
99
100     bool updateRegion() override;
101 };
102
103 bool MouseWheelRegionOverlay::updateRegion()
104 {
105 #if ENABLE(WHEEL_EVENT_REGIONS)
106     // Wheel event regions are painted via RenderLayerBacking::paintDebugOverlays().
107     return false;
108 #else
109     auto region = makeUnique<Region>();
110     
111     for (const Frame* frame = &m_page.mainFrame(); frame; frame = frame->tree().traverseNext()) {
112         if (!frame->view() || !frame->document())
113             continue;
114
115         auto frameRegion = frame->document()->absoluteRegionForEventTargets(frame->document()->wheelEventTargets());
116         frameRegion.first.translate(toIntSize(frame->view()->contentsToRootView(IntPoint())));
117         region->unite(frameRegion.first);
118     }
119     
120     region->translate(m_overlay->viewToOverlayOffset());
121
122     bool regionChanged = !m_region || !(*m_region == *region);
123     m_region = WTFMove(region);
124     return regionChanged;
125 #endif
126 }
127
128 #if COMPILER(CLANG)
129 #pragma mark - NonFastScrollableRegionOverlay
130 #endif
131
132 class NonFastScrollableRegionOverlay final : public RegionOverlay {
133 public:
134     static Ref<NonFastScrollableRegionOverlay> create(Page& page)
135     {
136         return adoptRef(*new NonFastScrollableRegionOverlay(page));
137     }
138
139 private:
140     explicit NonFastScrollableRegionOverlay(Page& page)
141         : RegionOverlay(page, Color::orange.colorWithAlphaByte(102))
142     {
143     }
144
145     bool updateRegion() override;
146     void drawRect(PageOverlay&, GraphicsContext&, const IntRect& dirtyRect) final;
147     
148     EventTrackingRegions m_eventTrackingRegions;
149 };
150
151 bool NonFastScrollableRegionOverlay::updateRegion()
152 {
153     bool regionChanged = false;
154
155     if (ScrollingCoordinator* scrollingCoordinator = m_page.scrollingCoordinator()) {
156         EventTrackingRegions eventTrackingRegions = scrollingCoordinator->absoluteEventTrackingRegions();
157
158         if (eventTrackingRegions != m_eventTrackingRegions) {
159             m_eventTrackingRegions = eventTrackingRegions;
160             regionChanged = true;
161         }
162     }
163
164     return regionChanged;
165 }
166
167 static void drawRightAlignedText(const String& text, GraphicsContext& context, const FontCascade& font, const FloatPoint& boxLocation)
168 {
169     float textGap = 10;
170     float textBaselineFromTop = 14;
171
172     TextRun textRun = TextRun(text);
173     float textWidth = font.width(textRun);
174     context.setFillColor(Color::black);
175     context.drawText(font, textRun, boxLocation + FloatSize(-(textWidth + textGap), textBaselineFromTop));
176 }
177
178 void NonFastScrollableRegionOverlay::drawRect(PageOverlay& pageOverlay, GraphicsContext& context, const IntRect&)
179 {
180     static constexpr std::pair<EventTrackingRegions::EventType, SRGBA<uint8_t>> colorMappings[] = {
181         { EventTrackingRegions::EventType::Mousedown, { 80, 245, 80, 50 } },
182         { EventTrackingRegions::EventType::Mousemove, { 245, 245, 80, 50 } },
183         { EventTrackingRegions::EventType::Mouseup, { 80, 245, 176, 50 } },
184         { EventTrackingRegions::EventType::Touchend, { 191, 63, 127, 50 } },
185         { EventTrackingRegions::EventType::Touchforcechange, { 63, 63, 191, 50 } },
186         { EventTrackingRegions::EventType::Touchmove, { 80, 204, 245, 50 } },
187         { EventTrackingRegions::EventType::Touchstart, { 191, 191, 63, 50 } },
188         { EventTrackingRegions::EventType::Wheel, { 255, 128, 0, 50 } },
189     };
190     constexpr SortedArrayMap colors { colorMappings };
191     constexpr auto defaultColor = Color::black.colorWithAlphaByte(64);
192
193     IntRect bounds = pageOverlay.bounds();
194     
195     context.clearRect(bounds);
196     
197     FloatRect legendRect = { bounds.maxX() - 30.0f, 10, 20, 20 };
198     
199     FontCascadeDescription fontDescription;
200     fontDescription.setOneFamily("Helvetica"_s);
201     fontDescription.setSpecifiedSize(12);
202     fontDescription.setComputedSize(12);
203     fontDescription.setWeight(FontSelectionValue(500));
204     FontCascade font(WTFMove(fontDescription), 0, 0);
205     font.update(nullptr);
206
207     auto drawLegend = [&] (const Color& color, ASCIILiteral text) {
208         context.setFillColor(color);
209         context.fillRect(legendRect);
210         drawRightAlignedText(text, context, font, legendRect.location());
211         legendRect.move(0, 30);
212     };
213
214 #if ENABLE(TOUCH_EVENTS)
215     auto drawEventLegend = [&](EventTrackingRegions::EventType eventType) {
216         drawLegend(colors.get(eventType), EventTrackingRegions::eventName(eventType));
217     };
218     drawEventLegend(EventTrackingRegions::EventType::Touchstart);
219     drawEventLegend(EventTrackingRegions::EventType::Touchmove);
220     drawEventLegend(EventTrackingRegions::EventType::Touchend);
221     drawEventLegend(EventTrackingRegions::EventType::Touchforcechange);
222     drawLegend(m_color, "passive listeners"_s);
223     drawEventLegend(EventTrackingRegions::EventType::Mousedown);
224     drawEventLegend(EventTrackingRegions::EventType::Mousemove);
225     drawEventLegend(EventTrackingRegions::EventType::Mouseup);
226 #else
227     // On desktop platforms, the "wheel" region includes the non-fast scrollable region.
228     drawLegend(colors.get(EventTrackingRegions::EventType::Wheel), "non-fast region"_s);
229 #endif
230
231     for (auto& region : m_eventTrackingRegions.eventSpecificSynchronousDispatchRegions)
232         drawRegion(context, region.value, colors.get(region.key, defaultColor), bounds);
233     drawRegion(context, m_eventTrackingRegions.asynchronousDispatchRegion, m_color, bounds);
234 }
235
236 #if COMPILER(CLANG)
237 #pragma mark - InteractionRegionOverlay
238 #endif
239
240 class InteractionRegionOverlay final : public RegionOverlay {
241 public:
242     static Ref<InteractionRegionOverlay> create(Page& page)
243     {
244         return adoptRef(*new InteractionRegionOverlay(page));
245     }
246
247 private:
248     explicit InteractionRegionOverlay(Page& page)
249         : RegionOverlay(page, Color::green.colorWithAlphaByte(102))
250     {
251     }
252
253     bool updateRegion() final;
254     void drawRect(PageOverlay&, GraphicsContext&, const IntRect& dirtyRect) final;
255
256     void drawSettings(GraphicsContext&);
257
258     bool mouseEvent(PageOverlay&, const PlatformMouseEvent&) final;
259
260     FloatRect rectForSettingAtIndex(unsigned);
261     bool valueForSetting(ASCIILiteral);
262         
263     std::optional<InteractionRegion> activeRegion();
264
265     struct Setting {
266         ASCIILiteral key;
267         ASCIILiteral name;
268         bool value { false };
269     };    
270
271     FixedVector<Setting> m_settings {
272         { "constrain"_s, "Constrain to Regions"_s, true },
273         { "clip"_s, "Clip to Regions"_s, true },
274         { "wash"_s, "Draw Wash"_s, false },
275         { "contextualColor"_s, "Contextual Color"_s, true },
276         { "contextualSize"_s, "Contextual Size"_s, true },
277         { "cursor"_s, "Show Cursor"_s, true },
278         { "hover"_s, "CSS Hover"_s, false },
279         { "regions"_s, "Show Regions"_s, false }
280     };
281     
282     Vector<InteractionRegion> m_regions;
283     IntPoint m_mouseLocationInContentCoordinates;
284 };
285
286 bool InteractionRegionOverlay::updateRegion()
287 {
288     m_overlay->setNeedsDisplay();
289     m_regions = interactionRegions(m_page, { { 0, 0 }, m_page.mainFrame().view()->contentsSize() });
290     
291     return true;
292 }
293
294 static Vector<Path> pathsForRegion(const InteractionRegion& region)
295 {
296     static constexpr float radius = 4;
297
298     Vector<FloatRect> rects;
299     for (auto rect : region.rectsInContentCoordinates)
300         rects.append(rect);
301     return PathUtilities::pathsWithShrinkWrappedRects(rects, std::max(region.borderRadius, radius));
302 }
303
304 std::optional<InteractionRegion> InteractionRegionOverlay::activeRegion()
305 {
306     std::optional<InteractionRegion> hitRegion;
307     float hitRegionArea = 0;
308     
309     for (const auto& region : m_regions) {
310         float area = 0;
311         FloatRect boundingRect;
312         for (const auto& rect : region.rectsInContentCoordinates) {
313             if (boundingRect.isEmpty())
314                 boundingRect = rect;
315             else
316                 boundingRect.unite(rect);
317             area += rect.area();
318         }
319
320         if (!boundingRect.contains(m_mouseLocationInContentCoordinates))
321             continue;
322
323         auto paths = pathsForRegion(region);
324         bool didHitRegion = false;
325         for (const auto& path : paths) {
326             if (path.contains(m_mouseLocationInContentCoordinates)) {
327                 didHitRegion = true;
328                 break;
329             }
330         }
331         
332         if (!didHitRegion)
333             continue;
334
335         if (area > m_page.mainFrame().view()->layoutSize().area() / 2)
336             continue;
337
338         if (didHitRegion && (!hitRegion || area < hitRegionArea)) {
339             hitRegion = region;
340             hitRegionArea = area;
341         }
342     }
343     
344     return hitRegion;
345 }
346
347 static void drawCheckbox(const String& text, GraphicsContext& context, const FontCascade& font, const FloatRect& box, bool state)
348 {
349     static constexpr float lineHeight = 14;
350     static constexpr float checkboxVerticalPadding = 2;
351     static constexpr float textHorizontalPadding = 4;
352
353     FloatRect checkboxRect { box.location() + FloatSize { 0, checkboxVerticalPadding }, FloatSize { lineHeight, lineHeight } };
354
355     TextRun textRun = TextRun(text);
356     context.setFillColor(Color::black);
357     context.drawText(font, textRun, box.location() + FloatSize { checkboxRect.width() + textHorizontalPadding, lineHeight });
358
359     Path checkboxPath;
360     checkboxPath.addRoundedRect(FloatRoundedRect { checkboxRect, FloatRoundedRect::Radii { 3 } });
361
362     if (state) {
363         context.setFillColor(Color::darkGray);
364         context.fillPath(checkboxPath);
365     }
366
367     context.setStrokeColor(Color::black.colorWithAlphaByte(127));
368     context.setStrokeThickness(1);
369     context.strokePath(checkboxPath);
370 }
371
372 FloatRect InteractionRegionOverlay::rectForSettingAtIndex(unsigned index)
373 {
374     auto viewSize = m_page.mainFrame().view()->layoutSize();
375     static constexpr float settingsWidth = 150;
376     static constexpr float rowHeight = 16;
377     return {
378         FloatPoint { viewSize.width() - settingsWidth - 14, 10 }
379             + FloatSize { 4, rowHeight * index + 2 },
380         FloatSize { settingsWidth, rowHeight }
381     };
382 }
383
384 bool InteractionRegionOverlay::valueForSetting(ASCIILiteral name)
385 {
386     for (const auto& setting : m_settings) {
387         if (name == setting.key)
388             return setting.value;
389     }
390
391     ASSERT_NOT_REACHED();
392     return false;
393 }
394
395 void InteractionRegionOverlay::drawSettings(GraphicsContext& context)
396 {
397     GraphicsContextStateSaver stateSaver(context);
398
399     FloatRect rect = rectForSettingAtIndex(0);
400     for (unsigned i = 1; i < m_settings.size(); i++)
401         rect.unite(rectForSettingAtIndex(i));
402
403     rect.expand(FloatBoxExtent { 4, 4, 4, 4 });
404
405     {
406         GraphicsContextStateSaver stateSaver(context);
407         context.setShadow({ }, 5, Color(Color::black).colorWithAlpha(0.5));
408         context.fillRoundedRect(FloatRoundedRect { rect, FloatRoundedRect::Radii { 6 } }, Color(Color::white).colorWithAlpha(0.85));
409     }
410
411     FontCascadeDescription fontDescription;
412     fontDescription.setOneFamily("Helvetica"_s);
413     fontDescription.setSpecifiedSize(12);
414     fontDescription.setComputedSize(12);
415     fontDescription.setWeight(FontSelectionValue(500));
416     FontCascade font(WTFMove(fontDescription), 0, 0);
417     font.update(nullptr);
418
419     for (unsigned i = 0; i < m_settings.size(); i++) {
420         const auto& setting = m_settings[i];
421         drawCheckbox(setting.name, context, font, rectForSettingAtIndex(i), setting.value);
422     }
423 }
424
425 void InteractionRegionOverlay::drawRect(PageOverlay&, GraphicsContext& context, const IntRect& dirtyRect)
426 {
427     GraphicsContextStateSaver stateSaver(context);
428     
429     context.clearRect(dirtyRect);
430
431     if (valueForSetting("regions"_s)) {
432         context.setStrokeThickness(2);
433         context.setStrokeColor(Color::green);
434
435         for (const auto& region : m_regions) {
436             for (const auto& rect : region.rectsInContentCoordinates)
437             context.strokeRect(rect, 2);
438         }
439     }
440
441     auto region = activeRegion();
442
443     if (region || !valueForSetting("constrain"_s)) {
444         auto gradientData = [&] (float radius) {
445             Gradient::RadialData gradientData;
446             gradientData.point0 = m_mouseLocationInContentCoordinates;
447             gradientData.point1 = m_mouseLocationInContentCoordinates;
448             gradientData.startRadius = 0;
449             gradientData.endRadius = radius;
450             gradientData.aspectRatio = 1;
451             return gradientData;
452         };
453
454         auto makeGradient = [&] (bool hasLightBackground, Gradient::RadialData gradientData) {
455             auto gradient = Gradient::create(WTFMove(gradientData), { ColorInterpolationMethod::SRGB { }, AlphaPremultiplication::Unpremultiplied });
456             if (region && valueForSetting("wash"_s) && valueForSetting("clip"_s)) {
457                 gradient->addColorStop({ 0.1, Color(Color::white).colorWithAlpha(0.5) });
458                 gradient->addColorStop({ 1, hasLightBackground ? Color(Color::black).colorWithAlpha(0.05) : Color(Color::white).colorWithAlpha(0.1) });
459             } else if (!valueForSetting("clip"_s) || !valueForSetting("constrain"_s)) {
460                 gradient->addColorStop({ 0.1, Color(Color::white).colorWithAlpha(0.2) });
461                 gradient->addColorStop({ 1, Color(Color::white).colorWithAlpha(0) });
462             } else {
463                 gradient->addColorStop({ 0.1, Color(Color::white).colorWithAlpha(0.5) });
464                 gradient->addColorStop({ 1, Color(Color::white).colorWithAlpha(0) });
465             }
466
467             return gradient;
468         };
469         
470         constexpr float defaultRadius = 50;
471         bool shouldClip = valueForSetting("clip"_s);
472         Vector<Path> clipPaths;
473
474         if (shouldClip)
475             clipPaths = pathsForRegion(*region);
476
477         bool shouldUseBackdropGradient = !shouldClip || !region || (!valueForSetting("wash"_s) && valueForSetting("clip"_s));
478
479         if (shouldUseBackdropGradient) {
480             if (shouldClip) {
481                 for (const auto& path : clipPaths) {
482                     float radius = valueForSetting("contextualSize"_s) ? 1.5 * path.boundingRect().size().minDimension() : defaultRadius;
483                     auto backdropGradient = Gradient::create(gradientData(radius * 1.5), { ColorInterpolationMethod::SRGB { }, AlphaPremultiplication::Unpremultiplied });
484                     backdropGradient->addColorStop({ 0.1, Color(Color::black).colorWithAlpha(0.2) });
485                     backdropGradient->addColorStop({ 1, Color(Color::black).colorWithAlpha(0) });
486
487                     context.setFillGradient(WTFMove(backdropGradient));
488                     context.fillPath(path);
489                 }
490             } else {
491                 auto backdropGradient = Gradient::create(gradientData(defaultRadius * 2), { ColorInterpolationMethod::SRGB { }, AlphaPremultiplication::Unpremultiplied });
492                 backdropGradient->addColorStop({ 0.1, Color(Color::black).colorWithAlpha(0.2) });
493                 backdropGradient->addColorStop({ 1, Color(Color::black).colorWithAlpha(0) });
494
495                 context.setFillGradient(WTFMove(backdropGradient));
496                 context.fillRect(dirtyRect);    
497             }
498         }
499
500         bool hasLightBackground = false;
501         if (!shouldUseBackdropGradient && valueForSetting("contextualColor"_s))
502             hasLightBackground = region->hasLightBackground;
503
504         if (shouldClip) {
505             for (const auto& path : clipPaths) {
506                 float radius = valueForSetting("contextualSize"_s) ? 1.5 * path.boundingRect().size().minDimension() : defaultRadius;
507                 context.setFillGradient(makeGradient(hasLightBackground, gradientData(radius)));
508                 context.fillPath(path);
509             }
510         } else {
511             context.setFillGradient(makeGradient(hasLightBackground, gradientData(defaultRadius)));
512             context.fillRect(dirtyRect);
513         }
514     }
515
516     stateSaver.restore();
517
518     drawSettings(context);
519 }
520
521 bool InteractionRegionOverlay::mouseEvent(PageOverlay& overlay, const PlatformMouseEvent& event)
522 {
523     auto mainFrameView = m_page.mainFrame().view();
524
525     std::optional<Cursor> cursorToSet;
526
527     if (!valueForSetting("cursor"_s))
528         cursorToSet = noneCursor();
529     else if (!valueForSetting("hover"_s))
530         cursorToSet = pointerCursor();
531
532     auto eventInContentsCoordinates = mainFrameView->windowToContents(event.position());
533     for (unsigned i = 0; i < m_settings.size(); i++) {
534         if (!rectForSettingAtIndex(i).contains(eventInContentsCoordinates))
535             continue;
536         cursorToSet = handCursor();
537         if (event.button() == LeftButton && event.type() == PlatformEvent::MousePressed) {
538             m_settings[i].value = !m_settings[i].value;
539             return true;
540         }
541     }
542
543     if (cursorToSet)
544         mainFrameView->setCursor(*cursorToSet);
545
546     m_mouseLocationInContentCoordinates = eventInContentsCoordinates;
547     overlay.setNeedsDisplay();
548
549     if (event.type() == PlatformEvent::MouseMoved && !event.buttons() && !valueForSetting("hover"_s))
550         return true;
551
552     return false;
553 }
554
555 #if COMPILER(CLANG)
556 #pragma mark - RegionOverlay
557 #endif
558
559 Ref<RegionOverlay> RegionOverlay::create(Page& page, DebugPageOverlays::RegionType regionType)
560 {
561     switch (regionType) {
562     case DebugPageOverlays::RegionType::WheelEventHandlers:
563         return MouseWheelRegionOverlay::create(page);
564     case DebugPageOverlays::RegionType::NonFastScrollableRegion:
565         return NonFastScrollableRegionOverlay::create(page);
566     case DebugPageOverlays::RegionType::InteractionRegion:
567         return InteractionRegionOverlay::create(page);
568     }
569     ASSERT_NOT_REACHED();
570     return MouseWheelRegionOverlay::create(page);
571 }
572
573 RegionOverlay::RegionOverlay(Page& page, Color regionColor)
574     : m_page(page)
575     , m_overlay(PageOverlay::create(*this, PageOverlay::OverlayType::Document))
576     , m_color(regionColor)
577 {
578 }
579
580 RegionOverlay::~RegionOverlay()
581 {
582     if (m_overlay)
583         m_page.pageOverlayController().uninstallPageOverlay(*m_overlay, PageOverlay::FadeMode::DoNotFade);
584 }
585
586 void RegionOverlay::willMoveToPage(PageOverlay&, Page* page)
587 {
588     if (!page)
589         m_overlay = nullptr;
590 }
591
592 void RegionOverlay::didMoveToPage(PageOverlay&, Page* page)
593 {
594     if (page)
595         setRegionChanged();
596 }
597
598 void RegionOverlay::drawRect(PageOverlay&, GraphicsContext& context, const IntRect& dirtyRect)
599 {
600     context.clearRect(dirtyRect);
601
602     if (!m_region)
603         return;
604
605     drawRegion(context, *m_region, m_color, dirtyRect);
606 }
607
608 void RegionOverlay::drawRegion(GraphicsContext& context, const Region& region, const Color& color, const IntRect& dirtyRect)
609 {
610     GraphicsContextStateSaver saver(context);
611     context.setFillColor(color);
612     for (auto rect : region.rects()) {
613         if (rect.intersects(dirtyRect))
614             context.fillRect(rect);
615     }
616 }
617
618 bool RegionOverlay::mouseEvent(PageOverlay&, const PlatformMouseEvent&)
619 {
620     return false;
621 }
622
623 void RegionOverlay::didScrollFrame(PageOverlay&, Frame&)
624 {
625 }
626
627 void RegionOverlay::recomputeRegion()
628 {
629     if (!m_regionChanged)
630         return;
631
632     if (updateRegion())
633         m_overlay->setNeedsDisplay();
634
635     m_regionChanged = false;
636 }
637
638 #if COMPILER(CLANG)
639 #pragma mark - DebugPageOverlays
640 #endif
641
642 DebugPageOverlays& DebugPageOverlays::singleton()
643 {
644     if (!sharedDebugOverlays)
645         sharedDebugOverlays = new DebugPageOverlays;
646
647     return *sharedDebugOverlays;
648 }
649
650 static inline size_t indexOf(DebugPageOverlays::RegionType regionType)
651 {
652     return static_cast<size_t>(regionType);
653 }
654
655 RegionOverlay& DebugPageOverlays::ensureRegionOverlayForPage(Page& page, RegionType regionType)
656 {
657     auto it = m_pageRegionOverlays.find(&page);
658     if (it != m_pageRegionOverlays.end()) {
659         auto& visualizer = it->value[indexOf(regionType)];
660         if (!visualizer)
661             visualizer = RegionOverlay::create(page, regionType);
662         return *visualizer;
663     }
664
665     Vector<RefPtr<RegionOverlay>> visualizers(NumberOfRegionTypes);
666     auto visualizer = RegionOverlay::create(page, regionType);
667     visualizers[indexOf(regionType)] = visualizer.copyRef();
668     m_pageRegionOverlays.add(&page, WTFMove(visualizers));
669     return visualizer;
670 }
671
672 void DebugPageOverlays::showRegionOverlay(Page& page, RegionType regionType)
673 {
674     auto& visualizer = ensureRegionOverlayForPage(page, regionType);
675     page.pageOverlayController().installPageOverlay(visualizer.overlay(), PageOverlay::FadeMode::DoNotFade);
676 }
677
678 void DebugPageOverlays::hideRegionOverlay(Page& page, RegionType regionType)
679 {
680     auto it = m_pageRegionOverlays.find(&page);
681     if (it == m_pageRegionOverlays.end())
682         return;
683     auto& visualizer = it->value[indexOf(regionType)];
684     if (!visualizer)
685         return;
686     page.pageOverlayController().uninstallPageOverlay(visualizer->overlay(), PageOverlay::FadeMode::DoNotFade);
687     visualizer = nullptr;
688 }
689
690 void DebugPageOverlays::regionChanged(Frame& frame, RegionType regionType)
691 {
692     auto* page = frame.page();
693     if (!page)
694         return;
695
696     if (auto* visualizer = regionOverlayForPage(*page, regionType))
697         visualizer->setRegionChanged();
698 }
699
700 void DebugPageOverlays::updateRegionIfNecessary(Page& page, RegionType regionType)
701 {
702     if (auto* visualizer = regionOverlayForPage(page, regionType))
703         visualizer->recomputeRegion();
704 }
705
706 RegionOverlay* DebugPageOverlays::regionOverlayForPage(Page& page, RegionType regionType) const
707 {
708     auto it = m_pageRegionOverlays.find(&page);
709     if (it == m_pageRegionOverlays.end())
710         return nullptr;
711     return it->value.at(indexOf(regionType)).get();
712 }
713
714 void DebugPageOverlays::updateOverlayRegionVisibility(Page& page, OptionSet<DebugOverlayRegions> visibleRegions)
715 {
716     if (visibleRegions.contains(DebugOverlayRegions::NonFastScrollableRegion))
717         showRegionOverlay(page, RegionType::NonFastScrollableRegion);
718     else
719         hideRegionOverlay(page, RegionType::NonFastScrollableRegion);
720
721     if (visibleRegions.contains(DebugOverlayRegions::WheelEventHandlerRegion))
722         showRegionOverlay(page, RegionType::WheelEventHandlers);
723     else
724         hideRegionOverlay(page, RegionType::WheelEventHandlers);
725     
726     if (visibleRegions.contains(DebugOverlayRegions::InteractionRegion))
727         showRegionOverlay(page, RegionType::InteractionRegion);
728     else
729         hideRegionOverlay(page, RegionType::InteractionRegion);
730 }
731
732 void DebugPageOverlays::settingsChanged(Page& page)
733 {
734     auto activeOverlayRegions = OptionSet<DebugOverlayRegions>::fromRaw(page.settings().visibleDebugOverlayRegions());
735     if (!activeOverlayRegions && !hasOverlays(page))
736         return;
737
738     DebugPageOverlays::singleton().updateOverlayRegionVisibility(page, activeOverlayRegions);
739 }
740
741 }