Web Inspector: Change the InspectorOverlay to use native rather than canvas
[WebKit.git] / Source / WebCore / inspector / InspectorOverlay.cpp
1 /*
2  * Copyright (C) 2011 Google Inc. All rights reserved.
3  * Copyright (C) 2019 Apple Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1.  Redistributions of source code must retain the above copyright
10  *     notice, this list of conditions and the following disclaimer.
11  * 2.  Redistributions in binary form must reproduce the above copyright
12  *     notice, this list of conditions and the following disclaimer in the
13  *     documentation and/or other materials provided with the distribution.
14  * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
15  *     its contributors may be used to endorse or promote products derived
16  *     from this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 #include "config.h"
31 #include "InspectorOverlay.h"
32
33 #include "AXObjectCache.h"
34 #include "AccessibilityObject.h"
35 #include "DOMCSSNamespace.h"
36 #include "DOMTokenList.h"
37 #include "Element.h"
38 #include "FloatPoint.h"
39 #include "FloatSize.h"
40 #include "FontCascade.h"
41 #include "FontCascadeDescription.h"
42 #include "Frame.h"
43 #include "FrameView.h"
44 #include "GraphicsContext.h"
45 #include "InspectorClient.h"
46 #include "IntPoint.h"
47 #include "IntRect.h"
48 #include "IntSize.h"
49 #include "Node.h"
50 #include "NodeList.h"
51 #include "Page.h"
52 #include "Path.h"
53 #include "PseudoElement.h"
54 #include "RenderBox.h"
55 #include "RenderBoxModelObject.h"
56 #include "RenderInline.h"
57 #include "RenderObject.h"
58 #include "Settings.h"
59 #include <wtf/MathExtras.h>
60 #include <wtf/text/StringBuilder.h>
61
62 namespace WebCore {
63
64 using namespace Inspector;
65
66 static constexpr float elementDataSpacing = 2;
67 static constexpr float elementDataArrowSize = 7;
68 static constexpr float elementDataBorderSize = 1;
69
70 static constexpr float rulerSize = 15;
71 static constexpr float rulerLabelSize = 13;
72 static constexpr float rulerStepIncrement = 50;
73 static constexpr float rulerStepLength = 8;
74 static constexpr float rulerSubStepIncrement = 5;
75 static constexpr float rulerSubStepLength = 5;
76
77 static void truncateWithEllipsis(String& string, size_t length)
78 {
79     const UChar ellipsisUChar[] = { 0x2026, 0 };
80
81     if (string.length() > length) {
82         string.truncate(length);
83         string.append(ellipsisUChar);
84     }
85 }
86
87 static FloatPoint localPointToRootPoint(const FrameView* view, const FloatPoint& point)
88 {
89     return view->contentsToRootView(roundedIntPoint(point));
90 }
91
92 static void contentsQuadToCoordinateSystem(const FrameView* mainView, const FrameView* view, FloatQuad& quad, InspectorOverlay::CoordinateSystem coordinateSystem)
93 {
94     quad.setP1(localPointToRootPoint(view, quad.p1()));
95     quad.setP2(localPointToRootPoint(view, quad.p2()));
96     quad.setP3(localPointToRootPoint(view, quad.p3()));
97     quad.setP4(localPointToRootPoint(view, quad.p4()));
98
99     if (coordinateSystem == InspectorOverlay::CoordinateSystem::View)
100         quad += toIntSize(mainView->scrollPosition());
101 }
102
103 static Element* effectiveElementForNode(Node& node)
104 {
105     if (!is<Element>(node) || !node.document().frame())
106         return nullptr;
107
108     Element* element = nullptr;
109     if (is<PseudoElement>(node)) {
110         if (Element* hostElement = downcast<PseudoElement>(node).hostElement())
111             element = hostElement;
112     } else
113         element = &downcast<Element>(node);
114
115     return element;
116 }
117
118 static void buildRendererHighlight(RenderObject* renderer, const HighlightConfig& highlightConfig, Highlight& highlight, InspectorOverlay::CoordinateSystem coordinateSystem)
119 {
120     Frame* containingFrame = renderer->document().frame();
121     if (!containingFrame)
122         return;
123
124     highlight.setDataFromConfig(highlightConfig);
125     FrameView* containingView = containingFrame->view();
126     FrameView* mainView = containingFrame->page()->mainFrame().view();
127
128     // RenderSVGRoot should be highlighted through the isBox() code path, all other SVG elements should just dump their absoluteQuads().
129     bool isSVGRenderer = renderer->node() && renderer->node()->isSVGElement() && !renderer->isSVGRoot();
130
131     if (isSVGRenderer) {
132         highlight.type = HighlightType::Rects;
133         renderer->absoluteQuads(highlight.quads);
134         for (auto& quad : highlight.quads)
135             contentsQuadToCoordinateSystem(mainView, containingView, quad, coordinateSystem);
136     } else if (is<RenderBox>(*renderer) || is<RenderInline>(*renderer)) {
137         LayoutRect contentBox;
138         LayoutRect paddingBox;
139         LayoutRect borderBox;
140         LayoutRect marginBox;
141
142         if (is<RenderBox>(*renderer)) {
143             auto& renderBox = downcast<RenderBox>(*renderer);
144
145             LayoutBoxExtent margins(renderBox.marginTop(), renderBox.marginRight(), renderBox.marginBottom(), renderBox.marginLeft());
146             paddingBox = renderBox.clientBoxRect();
147             contentBox = LayoutRect(paddingBox.x() + renderBox.paddingLeft(), paddingBox.y() + renderBox.paddingTop(),
148                 paddingBox.width() - renderBox.paddingLeft() - renderBox.paddingRight(), paddingBox.height() - renderBox.paddingTop() - renderBox.paddingBottom());
149             borderBox = LayoutRect(paddingBox.x() - renderBox.borderLeft(), paddingBox.y() - renderBox.borderTop(),
150                 paddingBox.width() + renderBox.borderLeft() + renderBox.borderRight(), paddingBox.height() + renderBox.borderTop() + renderBox.borderBottom());
151             marginBox = LayoutRect(borderBox.x() - margins.left(), borderBox.y() - margins.top(),
152                 borderBox.width() + margins.left() + margins.right(), borderBox.height() + margins.top() + margins.bottom());
153         } else {
154             auto& renderInline = downcast<RenderInline>(*renderer);
155
156             // RenderInline's bounding box includes paddings and borders, excludes margins.
157             borderBox = renderInline.linesBoundingBox();
158             paddingBox = LayoutRect(borderBox.x() + renderInline.borderLeft(), borderBox.y() + renderInline.borderTop(),
159                 borderBox.width() - renderInline.borderLeft() - renderInline.borderRight(), borderBox.height() - renderInline.borderTop() - renderInline.borderBottom());
160             contentBox = LayoutRect(paddingBox.x() + renderInline.paddingLeft(), paddingBox.y() + renderInline.paddingTop(),
161                 paddingBox.width() - renderInline.paddingLeft() - renderInline.paddingRight(), paddingBox.height() - renderInline.paddingTop() - renderInline.paddingBottom());
162             // Ignore marginTop and marginBottom for inlines.
163             marginBox = LayoutRect(borderBox.x() - renderInline.marginLeft(), borderBox.y(),
164                 borderBox.width() + renderInline.horizontalMarginExtent(), borderBox.height());
165         }
166
167         FloatQuad absContentQuad = renderer->localToAbsoluteQuad(FloatRect(contentBox));
168         FloatQuad absPaddingQuad = renderer->localToAbsoluteQuad(FloatRect(paddingBox));
169         FloatQuad absBorderQuad = renderer->localToAbsoluteQuad(FloatRect(borderBox));
170         FloatQuad absMarginQuad = renderer->localToAbsoluteQuad(FloatRect(marginBox));
171
172         contentsQuadToCoordinateSystem(mainView, containingView, absContentQuad, coordinateSystem);
173         contentsQuadToCoordinateSystem(mainView, containingView, absPaddingQuad, coordinateSystem);
174         contentsQuadToCoordinateSystem(mainView, containingView, absBorderQuad, coordinateSystem);
175         contentsQuadToCoordinateSystem(mainView, containingView, absMarginQuad, coordinateSystem);
176
177         highlight.type = HighlightType::Node;
178         highlight.quads.append(absMarginQuad);
179         highlight.quads.append(absBorderQuad);
180         highlight.quads.append(absPaddingQuad);
181         highlight.quads.append(absContentQuad);
182     }
183 }
184
185 static void buildNodeHighlight(Node& node, const HighlightConfig& highlightConfig, Highlight& highlight, InspectorOverlay::CoordinateSystem coordinateSystem)
186 {
187     RenderObject* renderer = node.renderer();
188     if (!renderer)
189         return;
190
191     buildRendererHighlight(renderer, highlightConfig, highlight, coordinateSystem);
192 }
193
194 static void buildQuadHighlight(const FloatQuad& quad, const HighlightConfig& highlightConfig, Highlight& highlight)
195 {
196     highlight.setDataFromConfig(highlightConfig);
197     highlight.type = HighlightType::Rects;
198     highlight.quads.append(quad);
199 }
200
201 static Path quadToPath(const FloatQuad& quad, FloatRect& bounds)
202 {
203     Path path;
204     path.moveTo(quad.p1());
205     path.addLineTo(quad.p2());
206     path.addLineTo(quad.p3());
207     path.addLineTo(quad.p4());
208     path.closeSubpath();
209
210     bounds.unite(path.boundingRect());
211
212     return path;
213 }
214
215 static void drawOutlinedQuadWithClip(GraphicsContext& context, const FloatQuad& quad, const FloatQuad& clipQuad, const Color& fillColor, FloatRect& bounds)
216 {
217     GraphicsContextStateSaver stateSaver(context);
218
219     context.setFillColor(fillColor);
220     context.setStrokeThickness(0);
221     context.fillPath(quadToPath(quad, bounds));
222
223     context.setCompositeOperation(CompositeDestinationOut);
224     context.setFillColor(Color::createUnchecked(255, 0, 0));
225     context.fillPath(quadToPath(clipQuad, bounds));
226 }
227
228 static void drawOutlinedQuad(GraphicsContext& context, const FloatQuad& quad, const Color& fillColor, const Color& outlineColor, FloatRect& bounds)
229 {
230     Path path = quadToPath(quad, bounds);
231
232     GraphicsContextStateSaver stateSaver(context);
233
234     context.setStrokeThickness(2);
235
236     context.clipPath(path);
237
238     context.setFillColor(fillColor);
239     context.fillPath(path);
240
241     context.setStrokeColor(outlineColor);
242     context.strokePath(path);
243 }
244
245 static void drawFragmentHighlight(GraphicsContext& context, Node& node, const HighlightConfig& highlightConfig, FloatRect& bounds)
246 {
247     Highlight highlight;
248     buildNodeHighlight(node, highlightConfig, highlight, InspectorOverlay::CoordinateSystem::Document);
249
250     FloatQuad marginQuad;
251     FloatQuad borderQuad;
252     FloatQuad paddingQuad;
253     FloatQuad contentQuad;
254
255     size_t size = highlight.quads.size();
256     if (size >= 1)
257         marginQuad = highlight.quads[0];
258     if (size >= 2)
259         borderQuad = highlight.quads[1];
260     if (size >= 3)
261         paddingQuad = highlight.quads[2];
262     if (size >= 4)
263         contentQuad = highlight.quads[3];
264
265     if (!marginQuad.isEmpty() && marginQuad != borderQuad && highlight.marginColor.isVisible())
266         drawOutlinedQuadWithClip(context, marginQuad, borderQuad, highlight.marginColor, bounds);
267
268     if (!borderQuad.isEmpty() && borderQuad != paddingQuad && highlight.borderColor.isVisible())
269         drawOutlinedQuadWithClip(context, borderQuad, paddingQuad, highlight.borderColor, bounds);
270
271     if (!paddingQuad.isEmpty() && paddingQuad != contentQuad && highlight.paddingColor.isVisible())
272         drawOutlinedQuadWithClip(context, paddingQuad, contentQuad, highlight.paddingColor, bounds);
273
274     if (!contentQuad.isEmpty() && (highlight.contentColor.isVisible() || highlight.contentOutlineColor.isVisible()))
275         drawOutlinedQuad(context, contentQuad, highlight.contentColor, highlight.contentOutlineColor, bounds);
276 }
277
278 static void drawShapeHighlight(GraphicsContext& context, Node& node, FloatRect& bounds)
279 {
280     Element* element = effectiveElementForNode(node);
281     if (!element)
282         return;
283
284     RenderObject* renderer = element->renderer();
285     if (!renderer || !is<RenderBox>(renderer))
286         return;
287
288     const ShapeOutsideInfo* shapeOutsideInfo = downcast<RenderBox>(renderer)->shapeOutsideInfo();
289     if (!shapeOutsideInfo)
290         return;
291
292     const Color shapeHighlightColor(96, 82, 127, 204);
293
294     Frame* containingFrame = element->document().frame();
295     FrameView* containingView = containingFrame->view();
296     FrameView* mainView = containingFrame->page()->mainFrame().view();
297
298     Shape::DisplayPaths paths;
299     shapeOutsideInfo->computedShape().buildDisplayPaths(paths);
300
301     if (paths.shape.isEmpty()) {
302         LayoutRect shapeBounds = shapeOutsideInfo->computedShapePhysicalBoundingBox();
303         FloatQuad shapeQuad = renderer->localToAbsoluteQuad(FloatRect(shapeBounds));
304         contentsQuadToCoordinateSystem(mainView, containingView, shapeQuad, InspectorOverlay::CoordinateSystem::Document);
305         drawOutlinedQuad(context, shapeQuad, shapeHighlightColor, Color::transparent, bounds);
306         return;
307     }
308
309     const auto mapPoints = [&] (const Path& path) {
310         Path newPath;
311         path.apply([&] (const PathElement& pathElement) {
312             const auto localToRoot = [&] (size_t index) {
313                 const FloatPoint& point = pathElement.points[index];
314                 return localPointToRootPoint(containingView, renderer->localToAbsolute(shapeOutsideInfo->shapeToRendererPoint(point)));
315             };
316
317             switch (pathElement.type) {
318             case PathElementMoveToPoint:
319                 newPath.moveTo(localToRoot(0));
320                 break;
321
322             case PathElementAddLineToPoint:
323                 newPath.addLineTo(localToRoot(0));
324                 break;
325
326             case PathElementAddCurveToPoint:
327                 newPath.addBezierCurveTo(localToRoot(0), localToRoot(1), localToRoot(2));
328                 break;
329
330             case PathElementAddQuadCurveToPoint:
331                 newPath.addQuadCurveTo(localToRoot(0), localToRoot(1));
332                 break;
333
334             case PathElementCloseSubpath:
335                 newPath.closeSubpath();
336                 break;
337             }
338         });
339         return newPath;
340     };
341
342     if (paths.marginShape.length()) {
343         Path marginPath = mapPoints(paths.marginShape);
344         bounds.unite(marginPath.boundingRect());
345
346         GraphicsContextStateSaver stateSaver(context);
347
348         const Color shapeMarginHighlightColor(96, 82, 127, 153);
349         context.setFillColor(shapeMarginHighlightColor);
350         context.fillPath(marginPath);
351     }
352
353     Path shapePath = mapPoints(paths.shape);
354     bounds.unite(shapePath.boundingRect());
355
356     GraphicsContextStateSaver stateSaver(context);
357
358     context.setFillColor(shapeHighlightColor);
359     context.fillPath(shapePath);
360 }
361
362 InspectorOverlay::InspectorOverlay(Page& page, InspectorClient* client)
363     : m_page(page)
364     , m_client(client)
365     , m_paintRectUpdateTimer(*this, &InspectorOverlay::updatePaintRectsTimerFired)
366 {
367 }
368
369 InspectorOverlay::~InspectorOverlay() = default;
370
371 void InspectorOverlay::paint(GraphicsContext& context)
372 {
373     if (!shouldShowOverlay())
374         return;
375
376     FloatSize viewportSize = m_page.mainFrame().view()->sizeForVisibleContent();
377
378     context.clearRect({ FloatPoint::zero(), viewportSize });
379
380     GraphicsContextStateSaver stateSaver(context);
381
382     if (m_indicating) {
383         GraphicsContextStateSaver stateSaver(context);
384
385         const Color indicatingColor(111, 168, 220, 168);
386         context.setFillColor(indicatingColor);
387         context.fillRect({ FloatPoint::zero(), viewportSize });
388     }
389
390     if (m_highlightQuad)
391         drawQuadHighlight(context, *m_highlightQuad);
392
393     if (m_highlightNodeList) {
394         for (unsigned i = 0; i < m_highlightNodeList->length(); ++i) {
395             if (Node* node = m_highlightNodeList->item(i))
396                 drawNodeHighlight(context, *node);
397         }
398     }
399
400     if (m_highlightNode)
401         drawNodeHighlight(context, *m_highlightNode);
402
403     if (!m_paintRects.isEmpty())
404         drawPaintRects(context, m_paintRects);
405
406     if (m_showRulers)
407         drawRulers(context);
408 }
409
410 void InspectorOverlay::getHighlight(Highlight& highlight, InspectorOverlay::CoordinateSystem coordinateSystem) const
411 {
412     if (!m_highlightNode && !m_highlightQuad && !m_highlightNodeList)
413         return;
414
415     highlight.type = HighlightType::Rects;
416     if (m_highlightNode)
417         buildNodeHighlight(*m_highlightNode, m_nodeHighlightConfig, highlight, coordinateSystem);
418     else if (m_highlightNodeList) {
419         highlight.setDataFromConfig(m_nodeHighlightConfig);
420         for (unsigned i = 0; i < m_highlightNodeList->length(); ++i) {
421             Highlight nodeHighlight;
422             buildNodeHighlight(*(m_highlightNodeList->item(i)), m_nodeHighlightConfig, nodeHighlight, coordinateSystem);
423             if (nodeHighlight.type == HighlightType::Node)
424                 highlight.quads.appendVector(nodeHighlight.quads);
425         }
426         highlight.type = HighlightType::NodeList;
427     } else
428         buildQuadHighlight(*m_highlightQuad, m_quadHighlightConfig, highlight);
429 }
430
431 void InspectorOverlay::hideHighlight()
432 {
433     m_highlightNode = nullptr;
434     m_highlightNodeList = nullptr;
435     m_highlightQuad = nullptr;
436     update();
437 }
438
439 void InspectorOverlay::highlightNodeList(RefPtr<NodeList>&& nodes, const HighlightConfig& highlightConfig)
440 {
441     m_nodeHighlightConfig = highlightConfig;
442     m_highlightNodeList = WTFMove(nodes);
443     m_highlightNode = nullptr;
444     update();
445 }
446
447 void InspectorOverlay::highlightNode(Node* node, const HighlightConfig& highlightConfig)
448 {
449     m_nodeHighlightConfig = highlightConfig;
450     m_highlightNode = node;
451     m_highlightNodeList = nullptr;
452     update();
453 }
454
455 void InspectorOverlay::highlightQuad(std::unique_ptr<FloatQuad> quad, const HighlightConfig& highlightConfig)
456 {
457     if (highlightConfig.usePageCoordinates)
458         *quad -= toIntSize(m_page.mainFrame().view()->scrollPosition());
459
460     m_quadHighlightConfig = highlightConfig;
461     m_highlightQuad = WTFMove(quad);
462     update();
463 }
464
465 Node* InspectorOverlay::highlightedNode() const
466 {
467     return m_highlightNode.get();
468 }
469
470 void InspectorOverlay::didSetSearchingForNode(bool enabled)
471 {
472     m_client->didSetSearchingForNode(enabled);
473 }
474
475 void InspectorOverlay::setIndicating(bool indicating)
476 {
477     if (m_indicating == indicating)
478         return;
479
480     m_indicating = indicating;
481
482     update();
483 }
484
485 bool InspectorOverlay::shouldShowOverlay() const
486 {
487     return m_highlightNode || m_highlightNodeList || m_highlightQuad || m_indicating || m_showPaintRects || m_showRulers;
488 }
489
490 void InspectorOverlay::update()
491 {
492     if (!shouldShowOverlay()) {
493         m_client->hideHighlight();
494         return;
495     }
496
497     FrameView* view = m_page.mainFrame().view();
498     if (!view)
499         return;
500
501     m_client->highlight();
502 }
503
504 void InspectorOverlay::setShowPaintRects(bool showPaintRects)
505 {
506     if (m_showPaintRects == showPaintRects)
507         return;
508
509     m_showPaintRects = showPaintRects;
510     if (!m_showPaintRects) {
511         m_paintRects.clear();
512         m_paintRectUpdateTimer.stop();
513         update();
514     }
515 }
516
517 void InspectorOverlay::showPaintRect(const FloatRect& rect)
518 {
519     if (!m_showPaintRects)
520         return;
521
522     IntRect rootRect = m_page.mainFrame().view()->contentsToRootView(enclosingIntRect(rect));
523
524     const auto removeDelay = 250_ms;
525
526     MonotonicTime removeTime = MonotonicTime::now() + removeDelay;
527     m_paintRects.append(TimeRectPair(removeTime, rootRect));
528
529     if (!m_paintRectUpdateTimer.isActive()) {
530         const Seconds paintRectsUpdateInterval { 32_ms };
531         m_paintRectUpdateTimer.startRepeating(paintRectsUpdateInterval);
532     }
533
534     update();
535 }
536
537 void InspectorOverlay::setShowRulers(bool showRulers)
538 {
539     if (m_showRulers == showRulers)
540         return;
541
542     m_showRulers = showRulers;
543
544     update();
545 }
546
547 void InspectorOverlay::updatePaintRectsTimerFired()
548 {
549     MonotonicTime now = MonotonicTime::now();
550     bool rectsChanged = false;
551     while (!m_paintRects.isEmpty() && m_paintRects.first().first < now) {
552         m_paintRects.removeFirst();
553         rectsChanged = true;
554     }
555
556     if (m_paintRects.isEmpty())
557         m_paintRectUpdateTimer.stop();
558
559     if (rectsChanged)
560         update();
561 }
562
563 void InspectorOverlay::drawNodeHighlight(GraphicsContext& context, Node& node)
564 {
565     FloatRect bounds;
566
567     drawFragmentHighlight(context, node, m_nodeHighlightConfig, bounds);
568
569     if (m_nodeHighlightConfig.showInfo)
570         drawShapeHighlight(context, node, bounds);
571
572     if (m_showRulers)
573         drawBounds(context, bounds);
574
575     // Ensure that the title information is drawn after the bounds.
576     if (m_nodeHighlightConfig.showInfo)
577         drawElementTitle(context, node, bounds);
578 }
579
580 void InspectorOverlay::drawQuadHighlight(GraphicsContext& context, const FloatQuad& quad)
581 {
582     Highlight highlight;
583     buildQuadHighlight(quad, m_quadHighlightConfig, highlight);
584
585     if (highlight.quads.size() >= 1) {
586         FloatRect bounds;
587
588         drawOutlinedQuad(context, highlight.quads[0], highlight.contentColor, highlight.contentOutlineColor, bounds);
589
590         if (m_showRulers)
591             drawBounds(context, bounds);
592     }
593 }
594
595 void InspectorOverlay::drawPaintRects(GraphicsContext& context, const Deque<TimeRectPair>& paintRects)
596 {
597     GraphicsContextStateSaver stateSaver(context);
598
599     const Color paintRectsColor(1.0f, 0.0f, 0.0f, 0.5f);
600     context.setFillColor(paintRectsColor);
601
602     for (const TimeRectPair& pair : paintRects)
603         context.fillRect(pair.second);
604 }
605
606 void InspectorOverlay::drawBounds(GraphicsContext& context, const FloatRect& bounds)
607 {
608     FrameView* pageView = m_page.mainFrame().view();
609     FloatSize viewportSize = pageView->sizeForVisibleContent();
610     FloatSize contentInset(0, pageView->topContentInset(ScrollView::TopContentInsetType::WebCoreOrPlatformContentInset));
611
612     Path path;
613
614     if (bounds.y() > contentInset.height()) {
615         path.moveTo({ bounds.x(), bounds.y() });
616         path.addLineTo({ bounds.x(), contentInset.height() });
617
618         path.moveTo({ bounds.maxX(), bounds.y() });
619         path.addLineTo({ bounds.maxX(), contentInset.height() });
620     }
621
622     if (bounds.maxY() < viewportSize.height()) {
623         path.moveTo({ bounds.x(), viewportSize.height() });
624         path.addLineTo({ bounds.x(), bounds.maxY() });
625
626         path.moveTo({ bounds.maxX(), viewportSize.height() });
627         path.addLineTo({ bounds.maxX(), bounds.maxY() });
628     }
629
630     if (bounds.x() > contentInset.width()) {
631         path.moveTo({ bounds.x(), bounds.y() });
632         path.addLineTo({ contentInset.width(), bounds.y() });
633
634         path.moveTo({ bounds.x(), bounds.maxY() });
635         path.addLineTo({ contentInset.width(), bounds.maxY() });
636     }
637
638     if (bounds.maxX() < viewportSize.width()) {
639         path.moveTo({ bounds.maxX(), bounds.y() });
640         path.addLineTo({ viewportSize.width(), bounds.y() });
641
642         path.moveTo({ bounds.maxX(), bounds.maxY() });
643         path.addLineTo({ viewportSize.width(), bounds.maxY() });
644     }
645
646     GraphicsContextStateSaver stateSaver(context);
647
648     context.setStrokeThickness(1);
649
650     const Color boundsColor(1.0f, 0.0f, 0.0f, 0.6f);
651     context.setStrokeColor(boundsColor);
652
653     context.strokePath(path);
654 }
655
656 void InspectorOverlay::drawRulers(GraphicsContext& context)
657 {
658     const Color rulerBackgroundColor(1.0f, 1.0f, 1.0f, 0.6f);
659     const Color lightRulerColor(0.0f, 0.0f, 0.0f, 0.2f);
660     const Color darkRulerColor(0.0f, 0.0f, 0.0f, 0.5f);
661
662     IntPoint scrollOffset;
663
664     FrameView* pageView = m_page.mainFrame().view();
665     if (!pageView->delegatesScrolling())
666         scrollOffset = pageView->visibleContentRect().location();
667
668     FloatSize viewportSize = pageView->sizeForVisibleContent();
669     FloatSize contentInset(0, pageView->topContentInset(ScrollView::TopContentInsetType::WebCoreOrPlatformContentInset));
670     float pageScaleFactor = m_page.pageScaleFactor();
671     float pageZoomFactor = m_page.mainFrame().pageZoomFactor();
672
673     float pageFactor = pageZoomFactor * pageScaleFactor;
674     float scrollX = scrollOffset.x() * pageScaleFactor;
675     float scrollY = scrollOffset.y() * pageScaleFactor;
676
677     const auto zoom = [&] (float value) -> float {
678         return value * pageFactor;
679     };
680
681     const auto unzoom = [&] (float value) -> float {
682         return value / pageFactor;
683     };
684
685     const auto multipleBelow = [&] (float value, float step) -> float {
686         return value - std::fmod(value, step);
687     };
688
689     float width = viewportSize.width() / pageFactor;
690     float height = viewportSize.height() / pageFactor;
691     float minX = unzoom(scrollX);
692     float minY = unzoom(scrollY);
693     float maxX = minX + width;
694     float maxY = minY + height;
695
696     // Draw backgrounds.
697     {
698         GraphicsContextStateSaver backgroundStateSaver(context);
699
700         float offsetX = contentInset.width() + rulerSize;
701         float offsetY = contentInset.height() + rulerSize;
702
703         context.setFillColor(rulerBackgroundColor);
704         context.fillRect({ contentInset.width(), contentInset.height(), rulerSize, rulerSize });
705         context.fillRect({ offsetX, contentInset.height(), zoom(width) - offsetX, rulerSize });
706         context.fillRect({ contentInset.width(), offsetY, rulerSize, zoom(height) - offsetY });
707     }
708
709     // Draw lines.
710     {
711         GraphicsContextStateSaver lineStateSaver(context);
712
713         context.setFillColor(darkRulerColor);
714         context.setStrokeThickness(1);
715
716         // Draw horizontal ruler.
717         {
718             GraphicsContextStateSaver horizontalRulerStateSaver(context);
719
720             context.translate(contentInset.width() - scrollX + 0.5f, contentInset.height() - scrollY);
721
722             for (float x = multipleBelow(minX, rulerSubStepIncrement); x < maxX; x += rulerSubStepIncrement) {
723                 if (!x && !scrollX)
724                     continue;
725
726                 Path path;
727                 path.moveTo({ zoom(x), scrollY });
728
729                 if (std::fmod(x, rulerStepIncrement)) {
730                     context.setStrokeColor(lightRulerColor);
731                     path.addLineTo({ zoom(x), scrollY + rulerSubStepLength });
732                 } else {
733                     context.setStrokeColor(darkRulerColor);
734                     path.addLineTo({ zoom(x), scrollY + (std::fmod(x, rulerStepIncrement * 2) ? rulerSubStepLength : rulerStepLength) });
735                 }
736
737                 context.strokePath(path);
738             }
739         }
740
741         // Draw vertical ruler.
742         {
743             GraphicsContextStateSaver veritcalRulerStateSaver(context);
744
745             context.translate(contentInset.width() - scrollX, contentInset.height() - scrollY + 0.5f);
746
747             for (float y = multipleBelow(minY, rulerSubStepIncrement); y < maxY; y += rulerSubStepIncrement) {
748                 if (!y && !scrollY)
749                     continue;
750
751                 Path path;
752                 path.moveTo({ scrollX, zoom(y) });
753
754                 if (std::fmod(y, rulerStepIncrement)) {
755                     context.setStrokeColor(lightRulerColor);
756                     path.addLineTo({ scrollX + rulerSubStepLength, zoom(y) });
757                 } else {
758                     context.setStrokeColor(darkRulerColor);
759                     path.addLineTo({ scrollX + (std::fmod(y, rulerStepIncrement * 2) ? rulerSubStepLength : rulerStepLength), zoom(y) });
760                 }
761
762                 context.strokePath(path);
763             }
764         }
765
766         // Draw labels.
767         {
768             GraphicsContextStateSaver labelStateSaver(context);
769
770             FontCascadeDescription fontDescription;
771             fontDescription.setOneFamily(m_page.settings().sansSerifFontFamily());
772             fontDescription.setComputedSize(10);
773
774             FontCascade font(WTFMove(fontDescription), 0, 0);
775             font.update(nullptr);
776
777             context.translate(contentInset.width() - scrollX, contentInset.height() - scrollY);
778
779             for (float x = multipleBelow(minX, rulerStepIncrement * 2); x < maxX; x += rulerStepIncrement * 2) {
780                 if (!x && !scrollX)
781                     continue;
782
783                 GraphicsContextStateSaver verticalLabelStateSaver(context);
784                 context.translate(zoom(x) + 0.5f, scrollY);
785                 context.drawText(font, TextRun(String::number(x)), { 2, rulerLabelSize });
786             }
787
788             for (float y = multipleBelow(minY, rulerStepIncrement * 2); y < maxY; y += rulerStepIncrement * 2) {
789                 if (!y && !scrollY)
790                     continue;
791
792                 GraphicsContextStateSaver horizontalLabelStateSaver(context);
793                 context.translate(scrollX, zoom(y) + 0.5f);
794                 context.rotate(-piOverTwoFloat);
795                 context.drawText(font, TextRun(String::number(y)), { 2, rulerLabelSize });
796             }
797         }
798     }
799 }
800
801 void InspectorOverlay::drawElementTitle(GraphicsContext& context, Node& node, const FloatRect& bounds)
802 {
803     if (bounds.isEmpty())
804         return;
805
806     Element* element = effectiveElementForNode(node);
807     if (!element)
808         return;
809
810     RenderObject* renderer = element->renderer();
811     if (!renderer)
812         return;
813
814     const UChar multiplicationSignUChar[] = { 0x00D7, 0 };
815
816     String elementTagName = element->nodeName();
817     if (!element->document().isXHTMLDocument())
818         elementTagName = elementTagName.convertToASCIILowercase();
819
820     String elementIDValue;
821     if (element->hasID())
822         elementIDValue = makeString('#', DOMCSSNamespace::escape(element->getIdAttribute()));
823
824     String elementClassValue;
825     if (element->hasClass()) {
826         StringBuilder builder;
827         DOMTokenList& classList = element->classList();
828         for (size_t i = 0; i < classList.length(); ++i) {
829             builder.append('.');
830             builder.append(DOMCSSNamespace::escape(classList.item(i)));
831         }
832
833         elementClassValue = builder.toString();
834         truncateWithEllipsis(elementClassValue, 50);
835     }
836
837     String elementPseudoType;
838     if (element->isBeforePseudoElement())
839         elementPseudoType = "::before"_s;
840     else if (element->isAfterPseudoElement())
841         elementPseudoType = "::after"_s;
842
843     String elementWidth;
844     String elementHeight;
845     if (is<RenderBoxModelObject>(renderer)) {
846         RenderBoxModelObject* modelObject = downcast<RenderBoxModelObject>(renderer);
847         elementWidth = String::number(adjustForAbsoluteZoom(roundToInt(modelObject->offsetWidth()), *modelObject));
848         elementHeight = String::number(adjustForAbsoluteZoom(roundToInt(modelObject->offsetHeight()), *modelObject));
849     } else {
850         FrameView* containingView = element->document().frame()->view();
851         IntRect boundingBox = snappedIntRect(containingView->contentsToRootView(renderer->absoluteBoundingBoxRect()));
852         elementWidth = String::number(boundingBox.width());
853         elementHeight = String::number(boundingBox.height());
854     }
855
856     // Need to enable AX to get the computed role.
857     if (!WebCore::AXObjectCache::accessibilityEnabled())
858         WebCore::AXObjectCache::enableAccessibility();
859
860     String elementRole;
861     if (AXObjectCache* axObjectCache = element->document().axObjectCache()) {
862         if (AccessibilityObject* axObject = axObjectCache->getOrCreate(element))
863             elementRole = axObject->computedRoleString();
864     }
865
866     FontCascadeDescription fontDescription;
867     fontDescription.setFamilies({ "Menlo", m_page.settings().fixedFontFamily() });
868     fontDescription.setComputedSize(11);
869
870     FontCascade font(WTFMove(fontDescription), 0, 0);
871     font.update(nullptr);
872
873     int fontHeight = font.fontMetrics().height();
874
875     float elementDataWidth;
876     float elementDataHeight = fontHeight;
877     bool hasSecondLine = !elementRole.isEmpty();
878
879     {
880         String firstLine = makeString(elementTagName, elementIDValue, elementClassValue, elementPseudoType, ' ', elementWidth, "px", ' ', multiplicationSignUChar, ' ', elementHeight, "px");
881         String secondLine = makeString("Role ", elementRole);
882
883         float firstLineWidth = font.width(TextRun(firstLine));
884         float secondLineWidth = font.width(TextRun(secondLine));
885
886         elementDataWidth = std::fmax(firstLineWidth, secondLineWidth);
887         if (hasSecondLine)
888             elementDataHeight += fontHeight;
889     }
890
891     FrameView* pageView = m_page.mainFrame().view();
892
893     FloatSize viewportSize = pageView->sizeForVisibleContent();
894     viewportSize.expand(-elementDataSpacing, -elementDataSpacing);
895
896     FloatSize contentInset(0, pageView->topContentInset(ScrollView::TopContentInsetType::WebCoreOrPlatformContentInset));
897     contentInset.expand(elementDataSpacing, elementDataSpacing);
898     if (m_showRulers)
899         contentInset.expand(rulerSize, rulerSize);
900
901     float anchorTop = bounds.y();
902     float anchorBottom = bounds.maxY();
903
904     bool renderArrowUp = false;
905     bool renderArrowDown = false;
906
907     float boxWidth = elementDataWidth + (elementDataSpacing * 2);
908     float boxHeight = elementDataArrowSize + elementDataHeight + (elementDataSpacing * 2);
909
910     float boxX = bounds.x();
911     if (boxX < contentInset.width()) {
912         boxX = contentInset.width();
913     } else if (boxX > viewportSize.width() - boxWidth)
914         boxX = viewportSize.width() - boxWidth;
915     else
916         boxX += elementDataSpacing;
917
918     float boxY;
919     if (anchorTop > viewportSize.height()) {
920         boxY = viewportSize.height() - boxHeight;
921         renderArrowDown = true;
922     } else if (anchorBottom < contentInset.height()) {
923         boxY = contentInset.height() + elementDataArrowSize;
924         renderArrowUp = true;
925     } else if (anchorTop - boxHeight - elementDataSpacing > contentInset.height()) {
926         boxY = anchorTop - boxHeight - elementDataSpacing;
927         renderArrowDown = true;
928     } else if (anchorBottom + boxHeight + elementDataSpacing < viewportSize.height()) {
929         boxY = anchorBottom + elementDataArrowSize + elementDataSpacing;
930         renderArrowUp = true;
931     } else {
932         boxY = contentInset.height();
933         renderArrowDown = true;
934     }
935
936     Path path;
937     path.moveTo({ boxX, boxY });
938     if (renderArrowUp) {
939         path.addLineTo({ boxX + (elementDataArrowSize * 2), boxY });
940         path.addLineTo({ boxX + (elementDataArrowSize * 3), boxY - elementDataArrowSize });
941         path.addLineTo({ boxX + (elementDataArrowSize * 4), boxY });
942     }
943     path.addLineTo({ boxX + elementDataWidth + (elementDataSpacing * 2), boxY });
944     path.addLineTo({ boxX + elementDataWidth + (elementDataSpacing * 2), boxY + elementDataHeight + (elementDataSpacing * 2) });
945     if (renderArrowDown) {
946         path.addLineTo({ boxX + (elementDataArrowSize * 4), boxY + elementDataHeight + (elementDataSpacing * 2) });
947         path.addLineTo({ boxX + (elementDataArrowSize * 3), boxY + elementDataHeight + (elementDataSpacing * 2) + elementDataArrowSize });
948         path.addLineTo({ boxX + (elementDataArrowSize * 2), boxY + elementDataHeight + (elementDataSpacing * 2) });
949     }
950     path.addLineTo({ boxX, boxY + elementDataHeight + (elementDataSpacing * 2) });
951     path.closeSubpath();
952
953     GraphicsContextStateSaver stateSaver(context);
954
955     context.translate(elementDataBorderSize / 2.0f, elementDataBorderSize / 2.0f);
956
957     const Color elementTitleBackgroundColor(255, 255, 194);
958     context.setFillColor(elementTitleBackgroundColor);
959
960     context.fillPath(path);
961
962     context.setStrokeThickness(elementDataBorderSize);
963
964     const Color elementTitleBorderColor(128, 128, 128);
965     context.setStrokeColor(elementTitleBorderColor);
966
967     context.strokePath(path);
968
969     float textPositionX = boxX + elementDataSpacing;
970     float textPositionY = boxY - (elementDataSpacing / 2.0f) + fontHeight;
971     const auto drawText = [&] (const String& text, const Color& color) {
972         if (text.isEmpty())
973             return;
974
975         context.setFillColor(color);
976         textPositionX += context.drawText(font, TextRun(text), { textPositionX, textPositionY });
977     };
978
979     drawText(elementTagName, Color(136, 18, 128)); // Keep this in sync with XMLViewer.css (.tag)
980     drawText(elementIDValue, Color(26, 26, 166)); // Keep this in sync with XMLViewer.css (.attribute-value)
981     drawText(elementClassValue, Color(153, 69, 0)); // Keep this in sync with XMLViewer.css (.attribute-name)
982     drawText(elementPseudoType, Color(136, 18, 128)); // Keep this in sync with XMLViewer.css (.tag)
983     drawText(" "_s, Color::black);
984     drawText(elementWidth, Color::black);
985     drawText("px"_s, Color::darkGray);
986     drawText(" "_s, Color::darkGray);
987     drawText(multiplicationSignUChar, Color::darkGray);
988     drawText(" "_s, Color::darkGray);
989     drawText(elementHeight, Color::black);
990     drawText("px"_s, Color::darkGray);
991
992     if (hasSecondLine) {
993         textPositionX = boxX + elementDataSpacing;
994         textPositionY += fontHeight;
995
996         drawText("Role"_s, Color(170, 13, 145));
997         drawText(" "_s, Color::black);
998         drawText(elementRole, Color::black);
999     }
1000 }
1001
1002 } // namespace WebCore