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
62namespace WebCore {
63
64using namespace Inspector;
65
66static constexpr float elementDataSpacing = 2;
67static constexpr float elementDataArrowSize = 7;
68static constexpr float elementDataBorderSize = 1;
69
70static constexpr float rulerSize = 15;
71static constexpr float rulerLabelSize = 13;
72static constexpr float rulerStepIncrement = 50;
73static constexpr float rulerStepLength = 8;
74static constexpr float rulerSubStepIncrement = 5;
75static constexpr float rulerSubStepLength = 5;
76
77static 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
87static FloatPoint localPointToRootPoint(const FrameView* view, const FloatPoint& point)
88{
89 return view->contentsToRootView(roundedIntPoint(point));
90}
91
92static 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
103static 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
118static 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
185static 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
194static 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
201static 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
215static 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
228static 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
245static 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
278static 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
362InspectorOverlay::InspectorOverlay(Page& page, InspectorClient* client)
363 : m_page(page)
364 , m_client(client)
365 , m_paintRectUpdateTimer(*this, &InspectorOverlay::updatePaintRectsTimerFired)
366{
367}
368
369InspectorOverlay::~InspectorOverlay() = default;
370
371void 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
410void 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
431void InspectorOverlay::hideHighlight()
432{
433 m_highlightNode = nullptr;
434 m_highlightNodeList = nullptr;
435 m_highlightQuad = nullptr;
436 update();
437}
438
439void 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
447void InspectorOverlay::highlightNode(Node* node, const HighlightConfig& highlightConfig)
448{
449 m_nodeHighlightConfig = highlightConfig;
450 m_highlightNode = node;
451 m_highlightNodeList = nullptr;
452 update();
453}
454
455void 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
465Node* InspectorOverlay::highlightedNode() const
466{
467 return m_highlightNode.get();
468}
469
470void InspectorOverlay::didSetSearchingForNode(bool enabled)
471{
472 m_client->didSetSearchingForNode(enabled);
473}
474
475void InspectorOverlay::setIndicating(bool indicating)
476{
477 if (m_indicating == indicating)
478 return;
479
480 m_indicating = indicating;
481
482 update();
483}
484
485bool InspectorOverlay::shouldShowOverlay() const
486{
487 return m_highlightNode || m_highlightNodeList || m_highlightQuad || m_indicating || m_showPaintRects || m_showRulers;
488}
489
490void 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
504void 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
517void 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
537void InspectorOverlay::setShowRulers(bool showRulers)
538{
539 if (m_showRulers == showRulers)
540 return;
541
542 m_showRulers = showRulers;
543
544 update();
545}
546
547void 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
563void 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
580void 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
595void 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
606void 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
656void 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::numberToStringFixedPrecision(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::numberToStringFixedPrecision(y)), { 2, rulerLabelSize });
796 }
797 }
798 }
799}
800
801void 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
1003