Home | History | Annotate | Download | only in inspector
      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)); // &times;
    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