[GTK][WPE] WebKitWebContext should identify web views by their WebPageProxy identifier
[WebKit-https.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 "FloatRoundedRect.h"
40 #include "FloatSize.h"
41 #include "FontCascade.h"
42 #include "FontCascadeDescription.h"
43 #include "Frame.h"
44 #include "FrameView.h"
45 #include "GraphicsContext.h"
46 #include "InspectorClient.h"
47 #include "IntPoint.h"
48 #include "IntRect.h"
49 #include "IntSize.h"
50 #include "Node.h"
51 #include "NodeList.h"
52 #include "Page.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 constexpr UChar ellipsis = 0x2026;
78 static constexpr UChar multiplicationSign = 0x00D7;
79
80 static void truncateWithEllipsis(String& string, size_t length)
81 {
82     if (string.length() > length) {
83         string.truncate(length);
84         string.append(ellipsis);
85     }
86 }
87
88 static FloatPoint localPointToRootPoint(const FrameView* view, const FloatPoint& point)
89 {
90     return view->contentsToRootView(roundedIntPoint(point));
91 }
92
93 static void contentsQuadToCoordinateSystem(const FrameView* mainView, const FrameView* view, FloatQuad& quad, InspectorOverlay::CoordinateSystem coordinateSystem)
94 {
95     quad.setP1(localPointToRootPoint(view, quad.p1()));
96     quad.setP2(localPointToRootPoint(view, quad.p2()));
97     quad.setP3(localPointToRootPoint(view, quad.p3()));
98     quad.setP4(localPointToRootPoint(view, quad.p4()));
99
100     if (coordinateSystem == InspectorOverlay::CoordinateSystem::View)
101         quad += toIntSize(mainView->scrollPosition());
102 }
103
104 static Element* effectiveElementForNode(Node& node)
105 {
106     if (!is<Element>(node) || !node.document().frame())
107         return nullptr;
108
109     Element* element = nullptr;
110     if (is<PseudoElement>(node)) {
111         if (Element* hostElement = downcast<PseudoElement>(node).hostElement())
112             element = hostElement;
113     } else
114         element = &downcast<Element>(node);
115
116     return element;
117 }
118
119 static void buildRendererHighlight(RenderObject* renderer, const HighlightConfig& highlightConfig, Highlight& highlight, InspectorOverlay::CoordinateSystem coordinateSystem)
120 {
121     Frame* containingFrame = renderer->document().frame();
122     if (!containingFrame)
123         return;
124
125     highlight.setDataFromConfig(highlightConfig);
126     FrameView* containingView = containingFrame->view();
127     FrameView* mainView = containingFrame->page()->mainFrame().view();
128
129     // RenderSVGRoot should be highlighted through the isBox() code path, all other SVG elements should just dump their absoluteQuads().
130     bool isSVGRenderer = renderer->node() && renderer->node()->isSVGElement() && !renderer->isSVGRoot();
131
132     if (isSVGRenderer) {
133         highlight.type = HighlightType::Rects;
134         renderer->absoluteQuads(highlight.quads);
135         for (auto& quad : highlight.quads)
136             contentsQuadToCoordinateSystem(mainView, containingView, quad, coordinateSystem);
137     } else if (is<RenderBox>(*renderer) || is<RenderInline>(*renderer)) {
138         LayoutRect contentBox;
139         LayoutRect paddingBox;
140         LayoutRect borderBox;
141         LayoutRect marginBox;
142
143         if (is<RenderBox>(*renderer)) {
144             auto& renderBox = downcast<RenderBox>(*renderer);
145
146             LayoutBoxExtent margins(renderBox.marginTop(), renderBox.marginRight(), renderBox.marginBottom(), renderBox.marginLeft());
147             paddingBox = renderBox.clientBoxRect();
148             contentBox = LayoutRect(paddingBox.x() + renderBox.paddingLeft(), paddingBox.y() + renderBox.paddingTop(),
149                 paddingBox.width() - renderBox.paddingLeft() - renderBox.paddingRight(), paddingBox.height() - renderBox.paddingTop() - renderBox.paddingBottom());
150             borderBox = LayoutRect(paddingBox.x() - renderBox.borderLeft(), paddingBox.y() - renderBox.borderTop(),
151                 paddingBox.width() + renderBox.borderLeft() + renderBox.borderRight(), paddingBox.height() + renderBox.borderTop() + renderBox.borderBottom());
152             marginBox = LayoutRect(borderBox.x() - margins.left(), borderBox.y() - margins.top(),
153                 borderBox.width() + margins.left() + margins.right(), borderBox.height() + margins.top() + margins.bottom());
154         } else {
155             auto& renderInline = downcast<RenderInline>(*renderer);
156
157             // RenderInline's bounding box includes paddings and borders, excludes margins.
158             borderBox = renderInline.linesBoundingBox();
159             paddingBox = LayoutRect(borderBox.x() + renderInline.borderLeft(), borderBox.y() + renderInline.borderTop(),
160                 borderBox.width() - renderInline.borderLeft() - renderInline.borderRight(), borderBox.height() - renderInline.borderTop() - renderInline.borderBottom());
161             contentBox = LayoutRect(paddingBox.x() + renderInline.paddingLeft(), paddingBox.y() + renderInline.paddingTop(),
162                 paddingBox.width() - renderInline.paddingLeft() - renderInline.paddingRight(), paddingBox.height() - renderInline.paddingTop() - renderInline.paddingBottom());
163             // Ignore marginTop and marginBottom for inlines.
164             marginBox = LayoutRect(borderBox.x() - renderInline.marginLeft(), borderBox.y(),
165                 borderBox.width() + renderInline.horizontalMarginExtent(), borderBox.height());
166         }
167
168         FloatQuad absContentQuad = renderer->localToAbsoluteQuad(FloatRect(contentBox));
169         FloatQuad absPaddingQuad = renderer->localToAbsoluteQuad(FloatRect(paddingBox));
170         FloatQuad absBorderQuad = renderer->localToAbsoluteQuad(FloatRect(borderBox));
171         FloatQuad absMarginQuad = renderer->localToAbsoluteQuad(FloatRect(marginBox));
172
173         contentsQuadToCoordinateSystem(mainView, containingView, absContentQuad, coordinateSystem);
174         contentsQuadToCoordinateSystem(mainView, containingView, absPaddingQuad, coordinateSystem);
175         contentsQuadToCoordinateSystem(mainView, containingView, absBorderQuad, coordinateSystem);
176         contentsQuadToCoordinateSystem(mainView, containingView, absMarginQuad, coordinateSystem);
177
178         highlight.type = HighlightType::Node;
179         highlight.quads.append(absMarginQuad);
180         highlight.quads.append(absBorderQuad);
181         highlight.quads.append(absPaddingQuad);
182         highlight.quads.append(absContentQuad);
183     }
184 }
185
186 static void buildNodeHighlight(Node& node, const HighlightConfig& highlightConfig, Highlight& highlight, InspectorOverlay::CoordinateSystem coordinateSystem)
187 {
188     RenderObject* renderer = node.renderer();
189     if (!renderer)
190         return;
191
192     buildRendererHighlight(renderer, highlightConfig, highlight, coordinateSystem);
193 }
194
195 static void buildQuadHighlight(const FloatQuad& quad, const HighlightConfig& highlightConfig, Highlight& highlight)
196 {
197     highlight.setDataFromConfig(highlightConfig);
198     highlight.type = HighlightType::Rects;
199     highlight.quads.append(quad);
200 }
201
202 static Path quadToPath(const FloatQuad& quad, Highlight::Bounds& bounds)
203 {
204     Path path;
205     path.moveTo(quad.p1());
206     path.addLineTo(quad.p2());
207     path.addLineTo(quad.p3());
208     path.addLineTo(quad.p4());
209     path.closeSubpath();
210
211     bounds.unite(path.boundingRect());
212
213     return path;
214 }
215
216 static void drawOutlinedQuadWithClip(GraphicsContext& context, const FloatQuad& quad, const FloatQuad& clipQuad, const Color& fillColor, Highlight::Bounds& bounds)
217 {
218     GraphicsContextStateSaver stateSaver(context);
219
220     context.setFillColor(fillColor);
221     context.setStrokeThickness(0);
222     context.fillPath(quadToPath(quad, bounds));
223
224     context.setCompositeOperation(CompositeDestinationOut);
225     context.setFillColor(Color::createUnchecked(255, 0, 0));
226     context.fillPath(quadToPath(clipQuad, bounds));
227 }
228
229 static void drawOutlinedQuad(GraphicsContext& context, const FloatQuad& quad, const Color& fillColor, const Color& outlineColor, Highlight::Bounds& bounds)
230 {
231     Path path = quadToPath(quad, bounds);
232
233     GraphicsContextStateSaver stateSaver(context);
234
235     context.setStrokeThickness(2);
236
237     context.clipPath(path);
238
239     context.setFillColor(fillColor);
240     context.fillPath(path);
241
242     context.setStrokeColor(outlineColor);
243     context.strokePath(path);
244 }
245
246 static void drawFragmentHighlight(GraphicsContext& context, Node& node, const HighlightConfig& highlightConfig, Highlight::Bounds& bounds)
247 {
248     Highlight highlight;
249     buildNodeHighlight(node, highlightConfig, highlight, InspectorOverlay::CoordinateSystem::Document);
250
251     FloatQuad marginQuad;
252     FloatQuad borderQuad;
253     FloatQuad paddingQuad;
254     FloatQuad contentQuad;
255
256     size_t size = highlight.quads.size();
257     if (size >= 1)
258         marginQuad = highlight.quads[0];
259     if (size >= 2)
260         borderQuad = highlight.quads[1];
261     if (size >= 3)
262         paddingQuad = highlight.quads[2];
263     if (size >= 4)
264         contentQuad = highlight.quads[3];
265
266     if (!marginQuad.isEmpty() && marginQuad != borderQuad && highlight.marginColor.isVisible())
267         drawOutlinedQuadWithClip(context, marginQuad, borderQuad, highlight.marginColor, bounds);
268
269     if (!borderQuad.isEmpty() && borderQuad != paddingQuad && highlight.borderColor.isVisible())
270         drawOutlinedQuadWithClip(context, borderQuad, paddingQuad, highlight.borderColor, bounds);
271
272     if (!paddingQuad.isEmpty() && paddingQuad != contentQuad && highlight.paddingColor.isVisible())
273         drawOutlinedQuadWithClip(context, paddingQuad, contentQuad, highlight.paddingColor, bounds);
274
275     if (!contentQuad.isEmpty() && (highlight.contentColor.isVisible() || highlight.contentOutlineColor.isVisible()))
276         drawOutlinedQuad(context, contentQuad, highlight.contentColor, highlight.contentOutlineColor, bounds);
277 }
278
279 static void drawShapeHighlight(GraphicsContext& context, Node& node, Highlight::Bounds& bounds)
280 {
281     RenderObject* renderer = node.renderer();
282     if (!renderer || !is<RenderBox>(renderer))
283         return;
284
285     const ShapeOutsideInfo* shapeOutsideInfo = downcast<RenderBox>(renderer)->shapeOutsideInfo();
286     if (!shapeOutsideInfo)
287         return;
288
289     Frame* containingFrame = node.document().frame();
290     if (!containingFrame)
291         return;
292
293     FrameView* containingView = containingFrame->view();
294     FrameView* mainView = containingFrame->page()->mainFrame().view();
295
296     const Color shapeHighlightColor(96, 82, 127, 204);
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     RulerExclusion rulerExclusion;
391
392     if (m_highlightQuad) {
393         auto quadRulerExclusion = drawQuadHighlight(context, *m_highlightQuad);
394         rulerExclusion.bounds.unite(quadRulerExclusion.bounds);
395     }
396
397     if (m_highlightNodeList) {
398         for (unsigned i = 0; i < m_highlightNodeList->length(); ++i) {
399             if (auto* node = m_highlightNodeList->item(i)) {
400                 auto nodeRulerExclusion = drawNodeHighlight(context, *node);
401                 rulerExclusion.bounds.unite(nodeRulerExclusion.bounds);
402             }
403         }
404     }
405
406     if (m_highlightNode) {
407         auto nodeRulerExclusion = drawNodeHighlight(context, *m_highlightNode);
408         rulerExclusion.bounds.unite(nodeRulerExclusion.bounds);
409         rulerExclusion.titlePath = nodeRulerExclusion.titlePath;
410     }
411
412     if (!m_paintRects.isEmpty())
413         drawPaintRects(context, m_paintRects);
414
415     if (m_showRulers || m_showRulersDuringElementSelection)
416         drawRulers(context, rulerExclusion);
417 }
418
419 void InspectorOverlay::getHighlight(Highlight& highlight, InspectorOverlay::CoordinateSystem coordinateSystem) const
420 {
421     if (!m_highlightNode && !m_highlightQuad && !m_highlightNodeList)
422         return;
423
424     highlight.type = HighlightType::Rects;
425     if (m_highlightNode)
426         buildNodeHighlight(*m_highlightNode, m_nodeHighlightConfig, highlight, coordinateSystem);
427     else if (m_highlightNodeList) {
428         highlight.setDataFromConfig(m_nodeHighlightConfig);
429         for (unsigned i = 0; i < m_highlightNodeList->length(); ++i) {
430             Highlight nodeHighlight;
431             buildNodeHighlight(*(m_highlightNodeList->item(i)), m_nodeHighlightConfig, nodeHighlight, coordinateSystem);
432             if (nodeHighlight.type == HighlightType::Node)
433                 highlight.quads.appendVector(nodeHighlight.quads);
434         }
435         highlight.type = HighlightType::NodeList;
436     } else
437         buildQuadHighlight(*m_highlightQuad, m_quadHighlightConfig, highlight);
438 }
439
440 void InspectorOverlay::hideHighlight()
441 {
442     m_highlightNode = nullptr;
443     m_highlightNodeList = nullptr;
444     m_highlightQuad = nullptr;
445     update();
446 }
447
448 void InspectorOverlay::highlightNodeList(RefPtr<NodeList>&& nodes, const HighlightConfig& highlightConfig)
449 {
450     m_nodeHighlightConfig = highlightConfig;
451     m_highlightNodeList = WTFMove(nodes);
452     m_highlightNode = nullptr;
453     update();
454 }
455
456 void InspectorOverlay::highlightNode(Node* node, const HighlightConfig& highlightConfig)
457 {
458     m_nodeHighlightConfig = highlightConfig;
459     m_highlightNode = node;
460     m_highlightNodeList = nullptr;
461     update();
462 }
463
464 void InspectorOverlay::highlightQuad(std::unique_ptr<FloatQuad> quad, const HighlightConfig& highlightConfig)
465 {
466     if (highlightConfig.usePageCoordinates)
467         *quad -= toIntSize(m_page.mainFrame().view()->scrollPosition());
468
469     m_quadHighlightConfig = highlightConfig;
470     m_highlightQuad = WTFMove(quad);
471     update();
472 }
473
474 Node* InspectorOverlay::highlightedNode() const
475 {
476     return m_highlightNode.get();
477 }
478
479 void InspectorOverlay::didSetSearchingForNode(bool enabled)
480 {
481     m_client->didSetSearchingForNode(enabled);
482 }
483
484 void InspectorOverlay::setIndicating(bool indicating)
485 {
486     if (m_indicating == indicating)
487         return;
488
489     m_indicating = indicating;
490
491     update();
492 }
493
494 bool InspectorOverlay::shouldShowOverlay() const
495 {
496     // Don't show the overlay when m_showRulersDuringElementSelection is true, as it's only supposed
497     // to have an effect when element selection is active (e.g. a node is hovered).
498     return m_highlightNode || m_highlightNodeList || m_highlightQuad || m_indicating || m_showPaintRects || m_showRulers;
499 }
500
501 void InspectorOverlay::update()
502 {
503     if (!shouldShowOverlay()) {
504         m_client->hideHighlight();
505         return;
506     }
507
508     FrameView* view = m_page.mainFrame().view();
509     if (!view)
510         return;
511
512     m_client->highlight();
513 }
514
515 void InspectorOverlay::setShowPaintRects(bool showPaintRects)
516 {
517     if (m_showPaintRects == showPaintRects)
518         return;
519
520     m_showPaintRects = showPaintRects;
521     if (!m_showPaintRects) {
522         m_paintRects.clear();
523         m_paintRectUpdateTimer.stop();
524         update();
525     }
526 }
527
528 void InspectorOverlay::showPaintRect(const FloatRect& rect)
529 {
530     if (!m_showPaintRects)
531         return;
532
533     IntRect rootRect = m_page.mainFrame().view()->contentsToRootView(enclosingIntRect(rect));
534
535     const auto removeDelay = 250_ms;
536
537     MonotonicTime removeTime = MonotonicTime::now() + removeDelay;
538     m_paintRects.append(TimeRectPair(removeTime, rootRect));
539
540     if (!m_paintRectUpdateTimer.isActive()) {
541         const Seconds paintRectsUpdateInterval { 32_ms };
542         m_paintRectUpdateTimer.startRepeating(paintRectsUpdateInterval);
543     }
544
545     update();
546 }
547
548 void InspectorOverlay::setShowRulers(bool showRulers)
549 {
550     if (m_showRulers == showRulers)
551         return;
552
553     m_showRulers = showRulers;
554
555     update();
556 }
557
558 void InspectorOverlay::updatePaintRectsTimerFired()
559 {
560     MonotonicTime now = MonotonicTime::now();
561     bool rectsChanged = false;
562     while (!m_paintRects.isEmpty() && m_paintRects.first().first < now) {
563         m_paintRects.removeFirst();
564         rectsChanged = true;
565     }
566
567     if (m_paintRects.isEmpty())
568         m_paintRectUpdateTimer.stop();
569
570     if (rectsChanged)
571         update();
572 }
573
574 InspectorOverlay::RulerExclusion InspectorOverlay::drawNodeHighlight(GraphicsContext& context, Node& node)
575 {
576     RulerExclusion rulerExclusion;
577
578     drawFragmentHighlight(context, node, m_nodeHighlightConfig, rulerExclusion.bounds);
579
580     if (m_nodeHighlightConfig.showInfo)
581         drawShapeHighlight(context, node, rulerExclusion.bounds);
582
583     if (m_showRulers || m_showRulersDuringElementSelection)
584         drawBounds(context, rulerExclusion.bounds);
585
586     // Ensure that the title information is drawn after the bounds.
587     if (m_nodeHighlightConfig.showInfo)
588         rulerExclusion.titlePath = drawElementTitle(context, node, rulerExclusion.bounds);
589
590     return rulerExclusion;
591 }
592
593 InspectorOverlay::RulerExclusion InspectorOverlay::drawQuadHighlight(GraphicsContext& context, const FloatQuad& quad)
594 {
595     RulerExclusion rulerExclusion;
596
597     Highlight highlight;
598     buildQuadHighlight(quad, m_quadHighlightConfig, highlight);
599
600     if (highlight.quads.size() >= 1) {
601         drawOutlinedQuad(context, highlight.quads[0], highlight.contentColor, highlight.contentOutlineColor, rulerExclusion.bounds);
602
603         if (m_showRulers || m_showRulersDuringElementSelection)
604             drawBounds(context, rulerExclusion.bounds);
605     }
606
607     return rulerExclusion;
608 }
609
610 void InspectorOverlay::drawPaintRects(GraphicsContext& context, const Deque<TimeRectPair>& paintRects)
611 {
612     GraphicsContextStateSaver stateSaver(context);
613
614     const Color paintRectsColor(1.0f, 0.0f, 0.0f, 0.5f);
615     context.setFillColor(paintRectsColor);
616
617     for (const TimeRectPair& pair : paintRects)
618         context.fillRect(pair.second);
619 }
620
621 void InspectorOverlay::drawBounds(GraphicsContext& context, const Highlight::Bounds& bounds)
622 {
623     FrameView* pageView = m_page.mainFrame().view();
624     FloatSize viewportSize = pageView->sizeForVisibleContent();
625     FloatSize contentInset(0, pageView->topContentInset(ScrollView::TopContentInsetType::WebCoreOrPlatformContentInset));
626
627     Path path;
628
629     if (bounds.y() > contentInset.height()) {
630         path.moveTo({ bounds.x(), bounds.y() });
631         path.addLineTo({ bounds.x(), contentInset.height() });
632
633         path.moveTo({ bounds.maxX(), bounds.y() });
634         path.addLineTo({ bounds.maxX(), contentInset.height() });
635     }
636
637     if (bounds.maxY() < viewportSize.height()) {
638         path.moveTo({ bounds.x(), viewportSize.height() });
639         path.addLineTo({ bounds.x(), bounds.maxY() });
640
641         path.moveTo({ bounds.maxX(), viewportSize.height() });
642         path.addLineTo({ bounds.maxX(), bounds.maxY() });
643     }
644
645     if (bounds.x() > contentInset.width()) {
646         path.moveTo({ bounds.x(), bounds.y() });
647         path.addLineTo({ contentInset.width(), bounds.y() });
648
649         path.moveTo({ bounds.x(), bounds.maxY() });
650         path.addLineTo({ contentInset.width(), bounds.maxY() });
651     }
652
653     if (bounds.maxX() < viewportSize.width()) {
654         path.moveTo({ bounds.maxX(), bounds.y() });
655         path.addLineTo({ viewportSize.width(), bounds.y() });
656
657         path.moveTo({ bounds.maxX(), bounds.maxY() });
658         path.addLineTo({ viewportSize.width(), bounds.maxY() });
659     }
660
661     GraphicsContextStateSaver stateSaver(context);
662
663     context.setStrokeThickness(1);
664
665     const Color boundsColor(1.0f, 0.0f, 0.0f, 0.6f);
666     context.setStrokeColor(boundsColor);
667
668     context.strokePath(path);
669 }
670
671 void InspectorOverlay::drawRulers(GraphicsContext& context, const InspectorOverlay::RulerExclusion& rulerExclusion)
672 {
673     const Color rulerBackgroundColor(1.0f, 1.0f, 1.0f, 0.6f);
674     const Color lightRulerColor(0.0f, 0.0f, 0.0f, 0.2f);
675     const Color darkRulerColor(0.0f, 0.0f, 0.0f, 0.5f);
676
677     IntPoint scrollOffset;
678
679     FrameView* pageView = m_page.mainFrame().view();
680     if (!pageView->delegatesScrolling())
681         scrollOffset = pageView->visibleContentRect().location();
682
683     FloatSize viewportSize = pageView->sizeForVisibleContent();
684     FloatSize contentInset(0, pageView->topContentInset(ScrollView::TopContentInsetType::WebCoreOrPlatformContentInset));
685     float pageScaleFactor = m_page.pageScaleFactor();
686     float pageZoomFactor = m_page.mainFrame().pageZoomFactor();
687
688     float pageFactor = pageZoomFactor * pageScaleFactor;
689     float scrollX = scrollOffset.x() * pageScaleFactor;
690     float scrollY = scrollOffset.y() * pageScaleFactor;
691
692     const auto zoom = [&] (float value) -> float {
693         return value * pageFactor;
694     };
695
696     const auto unzoom = [&] (float value) -> float {
697         return value / pageFactor;
698     };
699
700     const auto multipleBelow = [&] (float value, float step) -> float {
701         return value - std::fmod(value, step);
702     };
703
704     float width = viewportSize.width() / pageFactor;
705     float height = viewportSize.height() / pageFactor;
706     float minX = unzoom(scrollX);
707     float minY = unzoom(scrollY);
708     float maxX = minX + width;
709     float maxY = minY + height;
710
711     bool drawTopEdge = true;
712     bool drawLeftEdge = true;
713
714     // Determine which side (top/bottom and left/right) to draw the rulers.
715     {
716         FloatRect topEdge(contentInset.width(), contentInset.height(), zoom(width) - contentInset.width(), rulerSize);
717         FloatRect bottomEdge(contentInset.width(), zoom(height) - rulerSize, zoom(width) - contentInset.width(), rulerSize);
718         drawTopEdge = !rulerExclusion.bounds.intersects(topEdge) || rulerExclusion.bounds.intersects(bottomEdge);
719
720         FloatRect rightEdge(zoom(width) - rulerSize, contentInset.height(), rulerSize, zoom(height) - contentInset.height());
721         FloatRect leftEdge(contentInset.width(), contentInset.height(), rulerSize, zoom(height) - contentInset.height());
722         drawLeftEdge = !rulerExclusion.bounds.intersects(leftEdge) || rulerExclusion.bounds.intersects(rightEdge);
723     }
724
725     float cornerX = drawLeftEdge ? contentInset.width() : zoom(width) - rulerSize;
726     float cornerY = drawTopEdge ? contentInset.height() : zoom(height) - rulerSize;
727
728     // Draw backgrounds.
729     {
730         GraphicsContextStateSaver backgroundStateSaver(context);
731
732         context.setFillColor(rulerBackgroundColor);
733
734         context.fillRect({ cornerX, cornerY, rulerSize, rulerSize });
735
736         if (drawLeftEdge)
737             context.fillRect({ cornerX + rulerSize, cornerY, zoom(width) - cornerX - rulerSize, rulerSize });
738         else
739             context.fillRect({ contentInset.width(), cornerY, cornerX - contentInset.width(), rulerSize });
740
741         if (drawTopEdge)
742             context.fillRect({ cornerX, cornerY + rulerSize, rulerSize, zoom(height) - cornerY - rulerSize });  
743         else
744             context.fillRect({ cornerX, contentInset.height(), rulerSize, cornerY - contentInset.height() });
745     }
746
747     // Draw lines.
748     {
749         FontCascadeDescription fontDescription;
750         fontDescription.setOneFamily(m_page.settings().sansSerifFontFamily());
751         fontDescription.setComputedSize(10);
752
753         FontCascade font(WTFMove(fontDescription), 0, 0);
754         font.update(nullptr);
755
756         GraphicsContextStateSaver lineStateSaver(context);
757
758         context.setFillColor(darkRulerColor);
759         context.setStrokeThickness(1);
760
761         // Draw horizontal ruler.
762         {
763             GraphicsContextStateSaver horizontalRulerStateSaver(context);
764
765             context.translate(contentInset.width() - scrollX + 0.5f, cornerY - scrollY);
766
767             for (float x = multipleBelow(minX, rulerSubStepIncrement); x < maxX; x += rulerSubStepIncrement) {
768                 if (!x && !scrollX)
769                     continue;
770
771                 Path path;
772                 path.moveTo({ zoom(x), drawTopEdge ? scrollY : scrollY + rulerSize });
773
774                 float lineLength = 0.0f;
775                 if (std::fmod(x, rulerStepIncrement)) {
776                     lineLength = rulerSubStepLength;
777                     context.setStrokeColor(lightRulerColor);
778                 } else {
779                     lineLength = std::fmod(x, rulerStepIncrement * 2) ? rulerSubStepLength : rulerStepLength;
780                     context.setStrokeColor(darkRulerColor);
781                 }
782                 path.addLineTo({ zoom(x), scrollY + (drawTopEdge ? lineLength : rulerSize - lineLength) });
783
784                 context.strokePath(path);
785             }
786
787             // Draw labels.
788             for (float x = multipleBelow(minX, rulerStepIncrement * 2); x < maxX; x += rulerStepIncrement * 2) {
789                 if (!x && !scrollX)
790                     continue;
791
792                 GraphicsContextStateSaver verticalLabelStateSaver(context);
793                 context.translate(zoom(x) + 0.5f, scrollY);
794                 context.drawText(font, TextRun(String::numberToStringFixedPrecision(x)), { 2, drawTopEdge ? rulerLabelSize : rulerLabelSize - rulerSize + font.fontMetrics().height() - 1.0f });
795             }
796         }
797
798         // Draw vertical ruler.
799         {
800             GraphicsContextStateSaver veritcalRulerStateSaver(context);
801
802             context.translate(cornerX - scrollX, contentInset.height() - scrollY + 0.5f);
803
804             for (float y = multipleBelow(minY, rulerSubStepIncrement); y < maxY; y += rulerSubStepIncrement) {
805                 if (!y && !scrollY)
806                     continue;
807
808                 Path path;
809                 path.moveTo({ drawLeftEdge ? scrollX : scrollX + rulerSize, zoom(y) });
810
811                 float lineLength = 0.0f;
812                 if (std::fmod(y, rulerStepIncrement)) {
813                     lineLength = rulerSubStepLength;
814                     context.setStrokeColor(lightRulerColor);
815                 } else {
816                     lineLength = std::fmod(y, rulerStepIncrement * 2) ? rulerSubStepLength : rulerStepLength;
817                     context.setStrokeColor(darkRulerColor);
818                 }
819                 path.addLineTo({ scrollX + (drawLeftEdge ? lineLength : rulerSize - lineLength), zoom(y) });
820
821                 context.strokePath(path);
822             }
823
824             // Draw labels.
825             for (float y = multipleBelow(minY, rulerStepIncrement * 2); y < maxY; y += rulerStepIncrement * 2) {
826                 if (!y && !scrollY)
827                     continue;
828
829                 GraphicsContextStateSaver horizontalLabelStateSaver(context);
830                 context.translate(scrollX, zoom(y) + 0.5f);
831                 context.rotate(drawLeftEdge ? -piOverTwoFloat : piOverTwoFloat);
832                 context.drawText(font, TextRun(String::numberToStringFixedPrecision(y)), { 2, drawLeftEdge ? rulerLabelSize : rulerLabelSize - rulerSize });
833             }
834         }
835     }
836
837     // Draw viewport size.
838     {
839         FontCascadeDescription fontDescription;
840         fontDescription.setOneFamily(m_page.settings().sansSerifFontFamily());
841         fontDescription.setComputedSize(12);
842
843         FontCascade font(WTFMove(fontDescription), 0, 0);
844         font.update(nullptr);
845
846         auto viewportRect = pageView->visualViewportRect();
847         auto viewportWidthText = String::numberToStringFixedPrecision(viewportRect.width() / pageZoomFactor);
848         auto viewportHeightText = String::numberToStringFixedPrecision(viewportRect.height() / pageZoomFactor);
849         TextRun viewportTextRun(makeString(viewportWidthText, "px", ' ', multiplicationSign, ' ', viewportHeightText, "px"));
850
851         const float margin = 4;
852         const float padding = 2;
853         const float radius = 4;
854         float fontWidth = font.width(viewportTextRun);
855         float fontHeight = font.fontMetrics().floatHeight();
856         FloatRect viewportTextRect(margin, margin, (padding * 2.0f) + fontWidth, (padding * 2.0f) + fontHeight);
857         const auto viewportTextRectCenter = viewportTextRect.center();
858
859         GraphicsContextStateSaver viewportSizeStateSaver(context);
860
861         float leftTranslateX = rulerSize;
862         float rightTranslateX = 0.0f - (margin * 2.0f) - (padding * 2.0f) - fontWidth;
863         float translateX = cornerX + (drawLeftEdge ? leftTranslateX : rightTranslateX);
864
865         float topTranslateY = rulerSize;
866         float bottomTranslateY = 0.0f - (margin * 2.0f) - (padding * 2.0f) - fontHeight;
867         float translateY = cornerY + (drawTopEdge ? topTranslateY : bottomTranslateY);
868
869         FloatPoint translate(translateX, translateY);
870         if (rulerExclusion.titlePath.contains(viewportTextRectCenter + translate)) {
871             // Try the opposite horizontal side.
872             float oppositeTranslateX = drawLeftEdge ? zoom(width) + rightTranslateX : contentInset.width() + leftTranslateX;
873             translate.setX(oppositeTranslateX);
874
875             if (rulerExclusion.titlePath.contains(viewportTextRectCenter + translate)) {
876                 translate.setX(translateX);
877
878                 // Try the opposite vertical side.
879                 float oppositeTranslateY = drawTopEdge ? zoom(height) + bottomTranslateY : contentInset.height() + topTranslateY;
880                 translate.setY(oppositeTranslateY);
881
882                 if (rulerExclusion.titlePath.contains(viewportTextRectCenter + translate)) {
883                     // Try the opposite corner.
884                     translate.setX(oppositeTranslateX);
885                 }
886             }
887         }
888         context.translate(translate);
889
890         context.fillRoundedRect(FloatRoundedRect(viewportTextRect, FloatRoundedRect::Radii(radius)), rulerBackgroundColor);
891
892         context.setFillColor(Color::black);
893         context.drawText(font, viewportTextRun, {margin +  padding, margin + padding + fontHeight - font.fontMetrics().descent() });
894     }
895 }
896
897 Path InspectorOverlay::drawElementTitle(GraphicsContext& context, Node& node, const Highlight::Bounds& bounds)
898 {
899     if (bounds.isEmpty())
900         return { };
901
902     Element* element = effectiveElementForNode(node);
903     if (!element)
904         return { };
905
906     RenderObject* renderer = node.renderer();
907     if (!renderer)
908         return { };
909
910     String elementTagName = element->nodeName();
911     if (!element->document().isXHTMLDocument())
912         elementTagName = elementTagName.convertToASCIILowercase();
913
914     String elementIDValue;
915     if (element->hasID())
916         elementIDValue = makeString('#', DOMCSSNamespace::escape(element->getIdAttribute()));
917
918     String elementClassValue;
919     if (element->hasClass()) {
920         StringBuilder builder;
921         DOMTokenList& classList = element->classList();
922         for (size_t i = 0; i < classList.length(); ++i) {
923             builder.append('.');
924             builder.append(DOMCSSNamespace::escape(classList.item(i)));
925         }
926
927         elementClassValue = builder.toString();
928         truncateWithEllipsis(elementClassValue, 50);
929     }
930
931     String elementPseudoType;
932     if (node.isBeforePseudoElement())
933         elementPseudoType = "::before"_s;
934     else if (node.isAfterPseudoElement())
935         elementPseudoType = "::after"_s;
936
937     String elementWidth;
938     String elementHeight;
939     if (is<RenderBoxModelObject>(renderer)) {
940         RenderBoxModelObject* modelObject = downcast<RenderBoxModelObject>(renderer);
941         elementWidth = String::number(adjustForAbsoluteZoom(roundToInt(modelObject->offsetWidth()), *modelObject));
942         elementHeight = String::number(adjustForAbsoluteZoom(roundToInt(modelObject->offsetHeight()), *modelObject));
943     } else {
944         FrameView* containingView = node.document().frame()->view();
945         IntRect boundingBox = snappedIntRect(containingView->contentsToRootView(renderer->absoluteBoundingBoxRect()));
946         elementWidth = String::number(boundingBox.width());
947         elementHeight = String::number(boundingBox.height());
948     }
949
950     // Need to enable AX to get the computed role.
951     if (!WebCore::AXObjectCache::accessibilityEnabled())
952         WebCore::AXObjectCache::enableAccessibility();
953
954     String elementRole;
955     if (AXObjectCache* axObjectCache = node.document().axObjectCache()) {
956         if (AccessibilityObject* axObject = axObjectCache->getOrCreate(&node))
957             elementRole = axObject->computedRoleString();
958     }
959
960     FontCascadeDescription fontDescription;
961     fontDescription.setFamilies({ "Menlo", m_page.settings().fixedFontFamily() });
962     fontDescription.setComputedSize(11);
963
964     FontCascade font(WTFMove(fontDescription), 0, 0);
965     font.update(nullptr);
966
967     int fontHeight = font.fontMetrics().height();
968
969     float elementDataWidth;
970     float elementDataHeight = fontHeight;
971     bool hasSecondLine = !elementRole.isEmpty();
972
973     {
974         String firstLine = makeString(elementTagName, elementIDValue, elementClassValue, elementPseudoType, ' ', elementWidth, "px", ' ', multiplicationSign, ' ', elementHeight, "px");
975         String secondLine = makeString("Role ", elementRole);
976
977         float firstLineWidth = font.width(TextRun(firstLine));
978         float secondLineWidth = font.width(TextRun(secondLine));
979
980         elementDataWidth = std::fmax(firstLineWidth, secondLineWidth);
981         if (hasSecondLine)
982             elementDataHeight += fontHeight;
983     }
984
985     FrameView* pageView = m_page.mainFrame().view();
986
987     FloatSize viewportSize = pageView->sizeForVisibleContent();
988     viewportSize.expand(-elementDataSpacing, -elementDataSpacing);
989
990     FloatSize contentInset(0, pageView->topContentInset(ScrollView::TopContentInsetType::WebCoreOrPlatformContentInset));
991     contentInset.expand(elementDataSpacing, elementDataSpacing);
992     if (m_showRulers || m_showRulersDuringElementSelection)
993         contentInset.expand(rulerSize, rulerSize);
994
995     float anchorTop = bounds.y();
996     float anchorBottom = bounds.maxY();
997
998     bool renderArrowUp = false;
999     bool renderArrowDown = false;
1000
1001     float boxWidth = elementDataWidth + (elementDataSpacing * 2);
1002     float boxHeight = elementDataArrowSize + elementDataHeight + (elementDataSpacing * 2);
1003
1004     float boxX = bounds.x();
1005     if (boxX < contentInset.width())
1006         boxX = contentInset.width();
1007     else if (boxX > viewportSize.width() - boxWidth)
1008         boxX = viewportSize.width() - boxWidth;
1009     else
1010         boxX += elementDataSpacing;
1011
1012     float boxY;
1013     if (anchorTop > viewportSize.height()) {
1014         boxY = viewportSize.height() - boxHeight;
1015         renderArrowDown = true;
1016     } else if (anchorBottom < contentInset.height()) {
1017         boxY = contentInset.height() + elementDataArrowSize;
1018         renderArrowUp = true;
1019     } else if (anchorTop - boxHeight - elementDataSpacing > contentInset.height()) {
1020         boxY = anchorTop - boxHeight - elementDataSpacing;
1021         renderArrowDown = true;
1022     } else if (anchorBottom + boxHeight + elementDataSpacing < viewportSize.height()) {
1023         boxY = anchorBottom + elementDataArrowSize + elementDataSpacing;
1024         renderArrowUp = true;
1025     } else {
1026         boxY = contentInset.height();
1027         renderArrowDown = true;
1028     }
1029
1030     Path path;
1031     path.moveTo({ boxX, boxY });
1032     if (renderArrowUp) {
1033         path.addLineTo({ boxX + (elementDataArrowSize * 2), boxY });
1034         path.addLineTo({ boxX + (elementDataArrowSize * 3), boxY - elementDataArrowSize });
1035         path.addLineTo({ boxX + (elementDataArrowSize * 4), boxY });
1036     }
1037     path.addLineTo({ boxX + elementDataWidth + (elementDataSpacing * 2), boxY });
1038     path.addLineTo({ boxX + elementDataWidth + (elementDataSpacing * 2), boxY + elementDataHeight + (elementDataSpacing * 2) });
1039     if (renderArrowDown) {
1040         path.addLineTo({ boxX + (elementDataArrowSize * 4), boxY + elementDataHeight + (elementDataSpacing * 2) });
1041         path.addLineTo({ boxX + (elementDataArrowSize * 3), boxY + elementDataHeight + (elementDataSpacing * 2) + elementDataArrowSize });
1042         path.addLineTo({ boxX + (elementDataArrowSize * 2), boxY + elementDataHeight + (elementDataSpacing * 2) });
1043     }
1044     path.addLineTo({ boxX, boxY + elementDataHeight + (elementDataSpacing * 2) });
1045     path.closeSubpath();
1046
1047     GraphicsContextStateSaver stateSaver(context);
1048
1049     context.translate(elementDataBorderSize / 2.0f, elementDataBorderSize / 2.0f);
1050
1051     const Color elementTitleBackgroundColor(255, 255, 194);
1052     context.setFillColor(elementTitleBackgroundColor);
1053
1054     context.fillPath(path);
1055
1056     context.setStrokeThickness(elementDataBorderSize);
1057
1058     const Color elementTitleBorderColor(128, 128, 128);
1059     context.setStrokeColor(elementTitleBorderColor);
1060
1061     context.strokePath(path);
1062
1063     float textPositionX = boxX + elementDataSpacing;
1064     float textPositionY = boxY - (elementDataSpacing / 2.0f) + fontHeight;
1065     const auto drawText = [&] (const String& text, const Color& color) {
1066         if (text.isEmpty())
1067             return;
1068
1069         context.setFillColor(color);
1070         textPositionX += context.drawText(font, TextRun(text), { textPositionX, textPositionY });
1071     };
1072
1073     drawText(elementTagName, Color(136, 18, 128)); // Keep this in sync with XMLViewer.css (.tag)
1074     drawText(elementIDValue, Color(26, 26, 166)); // Keep this in sync with XMLViewer.css (.attribute-value)
1075     drawText(elementClassValue, Color(153, 69, 0)); // Keep this in sync with XMLViewer.css (.attribute-name)
1076     drawText(elementPseudoType, Color(136, 18, 128)); // Keep this in sync with XMLViewer.css (.tag)
1077     drawText(" "_s, Color::black);
1078     drawText(elementWidth, Color::black);
1079     drawText("px"_s, Color::darkGray);
1080     drawText(" "_s, Color::darkGray);
1081     drawText(makeString(multiplicationSign), Color::darkGray);
1082     drawText(" "_s, Color::darkGray);
1083     drawText(elementHeight, Color::black);
1084     drawText("px"_s, Color::darkGray);
1085
1086     if (hasSecondLine) {
1087         textPositionX = boxX + elementDataSpacing;
1088         textPositionY += fontHeight;
1089
1090         drawText("Role"_s, Color(170, 13, 145));
1091         drawText(" "_s, Color::black);
1092         drawText(elementRole, Color::black);
1093     }
1094
1095     return path;
1096 }
1097
1098 } // namespace WebCore