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 "Frame.h" 36 #include "FrameView.h" 37 #include "GraphicsContext.h" 38 #include "Page.h" 39 #include "Range.h" 40 #include "RenderInline.h" 41 #include "Settings.h" 42 #include "StyledElement.h" 43 #include "TextRun.h" 44 45 namespace WebCore { 46 47 namespace { 48 49 Path quadToPath(const FloatQuad& quad) 50 { 51 Path quadPath; 52 quadPath.moveTo(quad.p1()); 53 quadPath.addLineTo(quad.p2()); 54 quadPath.addLineTo(quad.p3()); 55 quadPath.addLineTo(quad.p4()); 56 quadPath.closeSubpath(); 57 return quadPath; 58 } 59 60 void drawOutlinedQuad(GraphicsContext& context, const FloatQuad& quad, const Color& fillColor) 61 { 62 static const int outlineThickness = 2; 63 static const Color outlineColor(62, 86, 180, 228); 64 65 Path quadPath = quadToPath(quad); 66 67 // Clip out the quad, then draw with a 2px stroke to get a pixel 68 // of outline (because inflating a quad is hard) 69 { 70 context.save(); 71 context.clipOut(quadPath); 72 73 context.setStrokeThickness(outlineThickness); 74 context.setStrokeColor(outlineColor, ColorSpaceDeviceRGB); 75 context.strokePath(quadPath); 76 77 context.restore(); 78 } 79 80 // Now do the fill 81 context.setFillColor(fillColor, ColorSpaceDeviceRGB); 82 context.fillPath(quadPath); 83 } 84 85 void drawOutlinedQuadWithClip(GraphicsContext& context, const FloatQuad& quad, const FloatQuad& clipQuad, const Color& fillColor) 86 { 87 context.save(); 88 Path clipQuadPath = quadToPath(clipQuad); 89 context.clipOut(clipQuadPath); 90 drawOutlinedQuad(context, quad, fillColor); 91 context.restore(); 92 } 93 94 void drawHighlightForBox(GraphicsContext& context, const FloatQuad& contentQuad, const FloatQuad& paddingQuad, const FloatQuad& borderQuad, const FloatQuad& marginQuad) 95 { 96 static const Color contentBoxColor(125, 173, 217, 128); 97 static const Color paddingBoxColor(125, 173, 217, 160); 98 static const Color borderBoxColor(125, 173, 217, 192); 99 static const Color marginBoxColor(125, 173, 217, 228); 100 101 if (marginQuad != borderQuad) 102 drawOutlinedQuadWithClip(context, marginQuad, borderQuad, marginBoxColor); 103 if (borderQuad != paddingQuad) 104 drawOutlinedQuadWithClip(context, borderQuad, paddingQuad, borderBoxColor); 105 if (paddingQuad != contentQuad) 106 drawOutlinedQuadWithClip(context, paddingQuad, contentQuad, paddingBoxColor); 107 108 drawOutlinedQuad(context, contentQuad, contentBoxColor); 109 } 110 111 void drawHighlightForLineBoxesOrSVGRenderer(GraphicsContext& context, const Vector<FloatQuad>& lineBoxQuads) 112 { 113 static const Color lineBoxColor(125, 173, 217, 128); 114 115 for (size_t i = 0; i < lineBoxQuads.size(); ++i) 116 drawOutlinedQuad(context, lineBoxQuads[i], lineBoxColor); 117 } 118 119 inline IntSize frameToMainFrameOffset(Frame* frame) 120 { 121 IntPoint mainFramePoint = frame->page()->mainFrame()->view()->windowToContents(frame->view()->contentsToWindow(IntPoint())); 122 return mainFramePoint - IntPoint(); 123 } 124 125 void drawElementTitle(GraphicsContext& context, Node* node, const IntRect& boundingBox, const IntRect& anchorBox, const FloatRect& overlayRect, WebCore::Settings* settings) 126 { 127 static const int rectInflatePx = 4; 128 static const int fontHeightPx = 12; 129 static const int borderWidthPx = 1; 130 static const Color tooltipBackgroundColor(255, 255, 194, 255); 131 static const Color tooltipBorderColor(Color::black); 132 static const Color tooltipFontColor(Color::black); 133 134 Element* element = static_cast<Element*>(node); 135 bool isXHTML = element->document()->isXHTMLDocument(); 136 String nodeTitle = isXHTML ? element->nodeName() : element->nodeName().lower(); 137 const AtomicString& idValue = element->getIdAttribute(); 138 if (!idValue.isNull() && !idValue.isEmpty()) { 139 nodeTitle += "#"; 140 nodeTitle += idValue; 141 } 142 if (element->hasClass() && element->isStyledElement()) { 143 const SpaceSplitString& classNamesString = static_cast<StyledElement*>(element)->classNames(); 144 size_t classNameCount = classNamesString.size(); 145 if (classNameCount) { 146 HashSet<AtomicString> usedClassNames; 147 for (size_t i = 0; i < classNameCount; ++i) { 148 const AtomicString& className = classNamesString[i]; 149 if (usedClassNames.contains(className)) 150 continue; 151 usedClassNames.add(className); 152 nodeTitle += "."; 153 nodeTitle += className; 154 } 155 } 156 } 157 158 nodeTitle += " ["; 159 nodeTitle += String::number(boundingBox.width()); 160 nodeTitle.append(static_cast<UChar>(0x00D7)); // × 161 nodeTitle += String::number(boundingBox.height()); 162 nodeTitle += "]"; 163 164 FontDescription desc; 165 FontFamily family; 166 family.setFamily(settings->fixedFontFamily()); 167 desc.setFamily(family); 168 desc.setComputedSize(fontHeightPx); 169 Font font = Font(desc, 0, 0); 170 font.update(0); 171 172 TextRun nodeTitleRun(nodeTitle); 173 IntPoint titleBasePoint = IntPoint(anchorBox.x(), anchorBox.maxY() - 1); 174 titleBasePoint.move(rectInflatePx, rectInflatePx); 175 IntRect titleRect = enclosingIntRect(font.selectionRectForText(nodeTitleRun, titleBasePoint, fontHeightPx)); 176 titleRect.inflate(rectInflatePx); 177 178 // The initial offsets needed to compensate for a 1px-thick border stroke (which is not a part of the rectangle). 179 int dx = -borderWidthPx; 180 int dy = borderWidthPx; 181 182 // If the tip sticks beyond the right of overlayRect, right-align the tip with the said boundary. 183 if (titleRect.maxX() > overlayRect.maxX()) 184 dx = overlayRect.maxX() - titleRect.maxX(); 185 186 // If the tip sticks beyond the left of overlayRect, left-align the tip with the said boundary. 187 if (titleRect.x() + dx < overlayRect.x()) 188 dx = overlayRect.x() - titleRect.x() - borderWidthPx; 189 190 // If the tip sticks beyond the bottom of overlayRect, show the tip at top of bounding box. 191 if (titleRect.maxY() > overlayRect.maxY()) { 192 dy = anchorBox.y() - titleRect.maxY() - borderWidthPx; 193 // If the tip still sticks beyond the bottom of overlayRect, bottom-align the tip with the said boundary. 194 if (titleRect.maxY() + dy > overlayRect.maxY()) 195 dy = overlayRect.maxY() - titleRect.maxY(); 196 } 197 198 // If the tip sticks beyond the top of overlayRect, show the tip at top of overlayRect. 199 if (titleRect.y() + dy < overlayRect.y()) 200 dy = overlayRect.y() - titleRect.y() + borderWidthPx; 201 202 titleRect.move(dx, dy); 203 context.setStrokeColor(tooltipBorderColor, ColorSpaceDeviceRGB); 204 context.setStrokeThickness(borderWidthPx); 205 context.setFillColor(tooltipBackgroundColor, ColorSpaceDeviceRGB); 206 context.drawRect(titleRect); 207 context.setFillColor(tooltipFontColor, ColorSpaceDeviceRGB); 208 context.drawText(font, nodeTitleRun, IntPoint(titleRect.x() + rectInflatePx, titleRect.y() + font.fontMetrics().height())); 209 } 210 211 } // anonymous namespace 212 213 namespace DOMNodeHighlighter { 214 215 void DrawNodeHighlight(GraphicsContext& context, Node* node) 216 { 217 RenderObject* renderer = node->renderer(); 218 Frame* containingFrame = node->document()->frame(); 219 220 if (!renderer || !containingFrame) 221 return; 222 223 IntSize mainFrameOffset = frameToMainFrameOffset(containingFrame); 224 IntRect boundingBox = renderer->absoluteBoundingBoxRect(true); 225 226 boundingBox.move(mainFrameOffset); 227 228 IntRect titleAnchorBox = boundingBox; 229 230 FrameView* view = containingFrame->page()->mainFrame()->view(); 231 FloatRect overlayRect = view->visibleContentRect(); 232 if (!overlayRect.contains(boundingBox) && !boundingBox.contains(enclosingIntRect(overlayRect))) 233 overlayRect = view->visibleContentRect(); 234 context.translate(-overlayRect.x(), -overlayRect.y()); 235 236 // RenderSVGRoot should be highlighted through the isBox() code path, all other SVG elements should just dump their absoluteQuads(). 237 #if ENABLE(SVG) 238 bool isSVGRenderer = renderer->node() && renderer->node()->isSVGElement() && !renderer->isSVGRoot(); 239 #else 240 bool isSVGRenderer = false; 241 #endif 242 243 if (renderer->isBox() && !isSVGRenderer) { 244 RenderBox* renderBox = toRenderBox(renderer); 245 246 // RenderBox returns the "pure" content area box, exclusive of the scrollbars (if present), which also count towards the content area in CSS. 247 IntRect contentBox = renderBox->contentBoxRect(); 248 contentBox.setWidth(contentBox.width() + renderBox->verticalScrollbarWidth()); 249 contentBox.setHeight(contentBox.height() + renderBox->horizontalScrollbarHeight()); 250 251 IntRect paddingBox(contentBox.x() - renderBox->paddingLeft(), contentBox.y() - renderBox->paddingTop(), 252 contentBox.width() + renderBox->paddingLeft() + renderBox->paddingRight(), contentBox.height() + renderBox->paddingTop() + renderBox->paddingBottom()); 253 IntRect borderBox(paddingBox.x() - renderBox->borderLeft(), paddingBox.y() - renderBox->borderTop(), 254 paddingBox.width() + renderBox->borderLeft() + renderBox->borderRight(), paddingBox.height() + renderBox->borderTop() + renderBox->borderBottom()); 255 IntRect marginBox(borderBox.x() - renderBox->marginLeft(), borderBox.y() - renderBox->marginTop(), 256 borderBox.width() + renderBox->marginLeft() + renderBox->marginRight(), borderBox.height() + renderBox->marginTop() + renderBox->marginBottom()); 257 258 259 FloatQuad absContentQuad = renderBox->localToAbsoluteQuad(FloatRect(contentBox)); 260 FloatQuad absPaddingQuad = renderBox->localToAbsoluteQuad(FloatRect(paddingBox)); 261 FloatQuad absBorderQuad = renderBox->localToAbsoluteQuad(FloatRect(borderBox)); 262 FloatQuad absMarginQuad = renderBox->localToAbsoluteQuad(FloatRect(marginBox)); 263 264 absContentQuad.move(mainFrameOffset); 265 absPaddingQuad.move(mainFrameOffset); 266 absBorderQuad.move(mainFrameOffset); 267 absMarginQuad.move(mainFrameOffset); 268 269 titleAnchorBox = absMarginQuad.enclosingBoundingBox(); 270 271 drawHighlightForBox(context, absContentQuad, absPaddingQuad, absBorderQuad, absMarginQuad); 272 } else if (renderer->isRenderInline() || isSVGRenderer) { 273 // FIXME: We should show margins/padding/border for inlines. 274 Vector<FloatQuad> lineBoxQuads; 275 renderer->absoluteQuads(lineBoxQuads); 276 for (unsigned i = 0; i < lineBoxQuads.size(); ++i) 277 lineBoxQuads[i] += mainFrameOffset; 278 279 drawHighlightForLineBoxesOrSVGRenderer(context, lineBoxQuads); 280 } 281 282 // Draw node title if necessary. 283 284 if (!node->isElementNode()) 285 return; 286 287 WebCore::Settings* settings = containingFrame->settings(); 288 drawElementTitle(context, node, boundingBox, titleAnchorBox, overlayRect, settings); 289 } 290 291 } // namespace DOMNodeHighlighter 292 293 } // namespace WebCore 294 295 #endif // ENABLE(INSPECTOR) 296