9aa69efe69cf2800dbb068c9b7c9147cfdbba116
[WebKit-https.git] / Source / WebCore / inspector / DOMNodeHighlighter.cpp
1 /*
2  * Copyright (C) 2011 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer.
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution.
13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include "config.h"
30 #include "DOMNodeHighlighter.h"
31
32 #if ENABLE(INSPECTOR)
33
34 #include "Element.h"
35 #include "FontCache.h"
36 #include "Frame.h"
37 #include "FrameView.h"
38 #include "GraphicsContext.h"
39 #include "Page.h"
40 #include "Range.h"
41 #include "RenderInline.h"
42 #include "Settings.h"
43 #include "StyledElement.h"
44 #include "TextRun.h"
45
46 namespace WebCore {
47
48 namespace {
49
50 Path quadToPath(const FloatQuad& quad)
51 {
52     Path quadPath;
53     quadPath.moveTo(quad.p1());
54     quadPath.addLineTo(quad.p2());
55     quadPath.addLineTo(quad.p3());
56     quadPath.addLineTo(quad.p4());
57     quadPath.closeSubpath();
58     return quadPath;
59 }
60
61 void drawOutlinedQuad(GraphicsContext& context, const FloatQuad& quad, const Color& fillColor)
62 {
63     static const int outlineThickness = 2;
64     static const Color outlineColor(62, 86, 180, 228);
65
66     Path quadPath = quadToPath(quad);
67
68     // Clip out the quad, then draw with a 2px stroke to get a pixel
69     // of outline (because inflating a quad is hard)
70     {
71         context.save();
72         context.clipOut(quadPath);
73
74         context.setStrokeThickness(outlineThickness);
75         context.setStrokeColor(outlineColor, ColorSpaceDeviceRGB);
76         context.strokePath(quadPath);
77
78         context.restore();
79     }
80
81     // Now do the fill
82     context.setFillColor(fillColor, ColorSpaceDeviceRGB);
83     context.fillPath(quadPath);
84 }
85
86 void drawOutlinedQuadWithClip(GraphicsContext& context, const FloatQuad& quad, const FloatQuad& clipQuad, const Color& fillColor)
87 {
88     context.save();
89     Path clipQuadPath = quadToPath(clipQuad);
90     context.clipOut(clipQuadPath);
91     drawOutlinedQuad(context, quad, fillColor);
92     context.restore();
93 }
94
95 void drawHighlightForBox(GraphicsContext& context, const FloatQuad& contentQuad, const FloatQuad& paddingQuad, const FloatQuad& borderQuad, const FloatQuad& marginQuad, DOMNodeHighlighter::HighlightMode mode)
96 {
97     static const Color contentBoxColor(125, 173, 217, 128);
98     static const Color paddingBoxColor(125, 173, 217, 160);
99     static const Color borderBoxColor(125, 173, 217, 192);
100     static const Color marginBoxColor(125, 173, 217, 228);
101
102     FloatQuad clipQuad;
103     if (mode == DOMNodeHighlighter::HighlightMargin || (mode == DOMNodeHighlighter::HighlightAll && marginQuad != borderQuad)) {
104         drawOutlinedQuadWithClip(context, marginQuad, borderQuad, marginBoxColor);
105         clipQuad = borderQuad;
106     }
107     if (mode == DOMNodeHighlighter::HighlightBorder || (mode == DOMNodeHighlighter::HighlightAll && borderQuad != paddingQuad)) {
108         drawOutlinedQuadWithClip(context, borderQuad, paddingQuad, borderBoxColor);
109         clipQuad = paddingQuad;
110     }
111     if (mode == DOMNodeHighlighter::HighlightPadding || (mode == DOMNodeHighlighter::HighlightAll && paddingQuad != contentQuad)) {
112         drawOutlinedQuadWithClip(context, paddingQuad, contentQuad, paddingBoxColor);
113         clipQuad = contentQuad;
114     }
115     if (mode == DOMNodeHighlighter::HighlightContent || mode == DOMNodeHighlighter::HighlightAll)
116         drawOutlinedQuad(context, contentQuad, contentBoxColor);
117     else
118         drawOutlinedQuadWithClip(context, clipQuad, clipQuad, contentBoxColor);
119 }
120
121 void drawHighlightForLineBoxesOrSVGRenderer(GraphicsContext& context, const Vector<FloatQuad>& lineBoxQuads)
122 {
123     static const Color lineBoxColor(125, 173, 217, 128);
124
125     for (size_t i = 0; i < lineBoxQuads.size(); ++i)
126         drawOutlinedQuad(context, lineBoxQuads[i], lineBoxColor);
127 }
128
129 inline IntSize frameToMainFrameOffset(Frame* frame)
130 {
131     IntPoint mainFramePoint = frame->page()->mainFrame()->view()->windowToContents(frame->view()->contentsToWindow(IntPoint()));
132     return mainFramePoint - IntPoint();
133 }
134
135 void drawElementTitle(GraphicsContext& context, Node* node, const IntRect& boundingBox, const IntRect& anchorBox, const FloatRect& overlayRect, WebCore::Settings* settings)
136 {
137     static const int rectInflatePx = 4;
138     static const int fontHeightPx = 12;
139     static const int borderWidthPx = 1;
140     static const Color tooltipBackgroundColor(255, 255, 194, 255);
141     static const Color tooltipBorderColor(Color::black);
142     static const Color tooltipFontColor(Color::black);
143     FontCachePurgePreventer fontCachePurgePreventer;
144
145     Element* element = static_cast<Element*>(node);
146     bool isXHTML = element->document()->isXHTMLDocument();
147     String nodeTitle = isXHTML ? element->nodeName() : element->nodeName().lower();
148     const AtomicString& idValue = element->getIdAttribute();
149     if (!idValue.isNull() && !idValue.isEmpty()) {
150         nodeTitle += "#";
151         nodeTitle += idValue;
152     }
153     if (element->hasClass() && element->isStyledElement()) {
154         const SpaceSplitString& classNamesString = static_cast<StyledElement*>(element)->classNames();
155         size_t classNameCount = classNamesString.size();
156         if (classNameCount) {
157             HashSet<AtomicString> usedClassNames;
158             for (size_t i = 0; i < classNameCount; ++i) {
159                 const AtomicString& className = classNamesString[i];
160                 if (usedClassNames.contains(className))
161                     continue;
162                 usedClassNames.add(className);
163                 nodeTitle += ".";
164                 nodeTitle += className;
165             }
166         }
167     }
168
169     nodeTitle += " [";
170     nodeTitle += String::number(boundingBox.width());
171     nodeTitle.append(static_cast<UChar>(0x00D7)); // &times;
172     nodeTitle += String::number(boundingBox.height());
173     nodeTitle += "]";
174
175     FontDescription desc;
176     FontFamily family;
177     family.setFamily(settings->fixedFontFamily());
178     desc.setFamily(family);
179     desc.setComputedSize(fontHeightPx);
180     Font font = Font(desc, 0, 0);
181     font.update(0);
182
183     TextRun nodeTitleRun(nodeTitle);
184     IntPoint titleBasePoint = IntPoint(anchorBox.x(), anchorBox.maxY() - 1);
185     titleBasePoint.move(rectInflatePx, rectInflatePx);
186     IntRect titleRect = enclosingIntRect(font.selectionRectForText(nodeTitleRun, titleBasePoint, fontHeightPx));
187     titleRect.inflate(rectInflatePx);
188
189     // The initial offsets needed to compensate for a 1px-thick border stroke (which is not a part of the rectangle).
190     int dx = -borderWidthPx;
191     int dy = borderWidthPx;
192
193     // If the tip sticks beyond the right of overlayRect, right-align the tip with the said boundary.
194     if (titleRect.maxX() > overlayRect.maxX())
195         dx = overlayRect.maxX() - titleRect.maxX();
196
197     // If the tip sticks beyond the left of overlayRect, left-align the tip with the said boundary.
198     if (titleRect.x() + dx < overlayRect.x())
199         dx = overlayRect.x() - titleRect.x() - borderWidthPx;
200
201     // If the tip sticks beyond the bottom of overlayRect, show the tip at top of bounding box.
202     if (titleRect.maxY() > overlayRect.maxY()) {
203         dy = anchorBox.y() - titleRect.maxY() - borderWidthPx;
204         // If the tip still sticks beyond the bottom of overlayRect, bottom-align the tip with the said boundary.
205         if (titleRect.maxY() + dy > overlayRect.maxY())
206             dy = overlayRect.maxY() - titleRect.maxY();
207     }
208
209     // If the tip sticks beyond the top of overlayRect, show the tip at top of overlayRect.
210     if (titleRect.y() + dy < overlayRect.y())
211         dy = overlayRect.y() - titleRect.y() + borderWidthPx;
212
213     titleRect.move(dx, dy);
214     context.setStrokeColor(tooltipBorderColor, ColorSpaceDeviceRGB);
215     context.setStrokeThickness(borderWidthPx);
216     context.setFillColor(tooltipBackgroundColor, ColorSpaceDeviceRGB);
217     context.drawRect(titleRect);
218     context.setFillColor(tooltipFontColor, ColorSpaceDeviceRGB);
219     context.drawText(font, nodeTitleRun, IntPoint(titleRect.x() + rectInflatePx, titleRect.y() + font.fontMetrics().height()));
220 }
221
222 } // anonymous namespace
223
224 namespace DOMNodeHighlighter {
225
226 void DrawNodeHighlight(GraphicsContext& context, Node* node, HighlightMode mode)
227 {
228     node->document()->updateLayoutIgnorePendingStylesheets();
229     RenderObject* renderer = node->renderer();
230     Frame* containingFrame = node->document()->frame();
231
232     if (!renderer || !containingFrame)
233         return;
234
235     IntSize mainFrameOffset = frameToMainFrameOffset(containingFrame);
236     IntRect boundingBox = renderer->absoluteBoundingBoxRect(true);
237
238     boundingBox.move(mainFrameOffset);
239
240     IntRect titleAnchorBox = boundingBox;
241
242     FrameView* view = containingFrame->page()->mainFrame()->view();
243     FloatRect overlayRect = view->visibleContentRect();
244     if (!overlayRect.contains(boundingBox) && !boundingBox.contains(enclosingIntRect(overlayRect)))
245         overlayRect = view->visibleContentRect();
246     context.translate(-overlayRect.x(), -overlayRect.y());
247
248     // RenderSVGRoot should be highlighted through the isBox() code path, all other SVG elements should just dump their absoluteQuads().
249 #if ENABLE(SVG)
250     bool isSVGRenderer = renderer->node() && renderer->node()->isSVGElement() && !renderer->isSVGRoot();
251 #else
252     bool isSVGRenderer = false;
253 #endif
254
255     if (renderer->isBox() && !isSVGRenderer) {
256         RenderBox* renderBox = toRenderBox(renderer);
257
258         // RenderBox returns the "pure" content area box, exclusive of the scrollbars (if present), which also count towards the content area in CSS.
259         IntRect contentBox = renderBox->contentBoxRect();
260         contentBox.setWidth(contentBox.width() + renderBox->verticalScrollbarWidth());
261         contentBox.setHeight(contentBox.height() + renderBox->horizontalScrollbarHeight());
262
263         IntRect paddingBox(contentBox.x() - renderBox->paddingLeft(), contentBox.y() - renderBox->paddingTop(),
264                            contentBox.width() + renderBox->paddingLeft() + renderBox->paddingRight(), contentBox.height() + renderBox->paddingTop() + renderBox->paddingBottom());
265         IntRect borderBox(paddingBox.x() - renderBox->borderLeft(), paddingBox.y() - renderBox->borderTop(),
266                           paddingBox.width() + renderBox->borderLeft() + renderBox->borderRight(), paddingBox.height() + renderBox->borderTop() + renderBox->borderBottom());
267         IntRect marginBox(borderBox.x() - renderBox->marginLeft(), borderBox.y() - renderBox->marginTop(),
268                           borderBox.width() + renderBox->marginLeft() + renderBox->marginRight(), borderBox.height() + renderBox->marginTop() + renderBox->marginBottom());
269
270
271         FloatQuad absContentQuad = renderBox->localToAbsoluteQuad(FloatRect(contentBox));
272         FloatQuad absPaddingQuad = renderBox->localToAbsoluteQuad(FloatRect(paddingBox));
273         FloatQuad absBorderQuad = renderBox->localToAbsoluteQuad(FloatRect(borderBox));
274         FloatQuad absMarginQuad = renderBox->localToAbsoluteQuad(FloatRect(marginBox));
275
276         absContentQuad.move(mainFrameOffset);
277         absPaddingQuad.move(mainFrameOffset);
278         absBorderQuad.move(mainFrameOffset);
279         absMarginQuad.move(mainFrameOffset);
280
281         titleAnchorBox = absMarginQuad.enclosingBoundingBox();
282
283         drawHighlightForBox(context, absContentQuad, absPaddingQuad, absBorderQuad, absMarginQuad, mode);
284     } else if (renderer->isRenderInline() || isSVGRenderer) {
285         // FIXME: We should show margins/padding/border for inlines.
286         Vector<FloatQuad> lineBoxQuads;
287         renderer->absoluteQuads(lineBoxQuads);
288         for (unsigned i = 0; i < lineBoxQuads.size(); ++i)
289             lineBoxQuads[i] += mainFrameOffset;
290
291         drawHighlightForLineBoxesOrSVGRenderer(context, lineBoxQuads);
292     }
293
294     // Draw node title if necessary.
295
296     if (!node->isElementNode())
297         return;
298
299     WebCore::Settings* settings = containingFrame->settings();
300     if (mode == DOMNodeHighlighter::HighlightAll)
301         drawElementTitle(context, node, boundingBox, titleAnchorBox, overlayRect, settings);
302 }
303
304 } // namespace DOMNodeHighlighter
305
306 } // namespace WebCore
307
308 #endif // ENABLE(INSPECTOR)