diff options
Diffstat (limited to 'Source/WebCore/inspector/DOMNodeHighlighter.cpp')
-rw-r--r-- | Source/WebCore/inspector/DOMNodeHighlighter.cpp | 292 |
1 files changed, 292 insertions, 0 deletions
diff --git a/Source/WebCore/inspector/DOMNodeHighlighter.cpp b/Source/WebCore/inspector/DOMNodeHighlighter.cpp new file mode 100644 index 0000000..c87649b --- /dev/null +++ b/Source/WebCore/inspector/DOMNodeHighlighter.cpp @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "DOMNodeHighlighter.h" + +#if ENABLE(INSPECTOR) + +#include "Element.h" +#include "Frame.h" +#include "FrameView.h" +#include "GraphicsContext.h" +#include "Page.h" +#include "Range.h" +#include "RenderInline.h" +#include "Settings.h" +#include "StyledElement.h" +#include "TextRun.h" + +namespace WebCore { + +namespace { + +Path quadToPath(const FloatQuad& quad) +{ + Path quadPath; + quadPath.moveTo(quad.p1()); + quadPath.addLineTo(quad.p2()); + quadPath.addLineTo(quad.p3()); + quadPath.addLineTo(quad.p4()); + quadPath.closeSubpath(); + return quadPath; +} + +void drawOutlinedQuad(GraphicsContext& context, const FloatQuad& quad, const Color& fillColor) +{ + static const int outlineThickness = 2; + static const Color outlineColor(62, 86, 180, 228); + + Path quadPath = quadToPath(quad); + + // Clip out the quad, then draw with a 2px stroke to get a pixel + // of outline (because inflating a quad is hard) + { + context.save(); + context.clipOut(quadPath); + + context.setStrokeThickness(outlineThickness); + context.setStrokeColor(outlineColor, ColorSpaceDeviceRGB); + context.strokePath(quadPath); + + context.restore(); + } + + // Now do the fill + context.setFillColor(fillColor, ColorSpaceDeviceRGB); + context.fillPath(quadPath); +} + +void drawOutlinedQuadWithClip(GraphicsContext& context, const FloatQuad& quad, const FloatQuad& clipQuad, const Color& fillColor) +{ + context.save(); + Path clipQuadPath = quadToPath(clipQuad); + context.clipOut(clipQuadPath); + drawOutlinedQuad(context, quad, fillColor); + context.restore(); +} + +void drawHighlightForBox(GraphicsContext& context, const FloatQuad& contentQuad, const FloatQuad& paddingQuad, const FloatQuad& borderQuad, const FloatQuad& marginQuad) +{ + static const Color contentBoxColor(125, 173, 217, 128); + static const Color paddingBoxColor(125, 173, 217, 160); + static const Color borderBoxColor(125, 173, 217, 192); + static const Color marginBoxColor(125, 173, 217, 228); + + if (marginQuad != borderQuad) + drawOutlinedQuadWithClip(context, marginQuad, borderQuad, marginBoxColor); + if (borderQuad != paddingQuad) + drawOutlinedQuadWithClip(context, borderQuad, paddingQuad, borderBoxColor); + if (paddingQuad != contentQuad) + drawOutlinedQuadWithClip(context, paddingQuad, contentQuad, paddingBoxColor); + + drawOutlinedQuad(context, contentQuad, contentBoxColor); +} + +void drawHighlightForLineBoxesOrSVGRenderer(GraphicsContext& context, const Vector<FloatQuad>& lineBoxQuads) +{ + static const Color lineBoxColor(125, 173, 217, 128); + + for (size_t i = 0; i < lineBoxQuads.size(); ++i) + drawOutlinedQuad(context, lineBoxQuads[i], lineBoxColor); +} + +inline IntSize frameToMainFrameOffset(Frame* frame) +{ + IntPoint mainFramePoint = frame->page()->mainFrame()->view()->windowToContents(frame->view()->contentsToWindow(IntPoint())); + return mainFramePoint - IntPoint(); +} + +void drawElementTitle(GraphicsContext& context, Node* node, const IntRect& boundingBox, const IntRect& anchorBox, const FloatRect& overlayRect, WebCore::Settings* settings) +{ + static const int rectInflatePx = 4; + static const int fontHeightPx = 12; + static const int borderWidthPx = 1; + static const Color tooltipBackgroundColor(255, 255, 194, 255); + static const Color tooltipBorderColor(Color::black); + static const Color tooltipFontColor(Color::black); + + Element* element = static_cast<Element*>(node); + bool isXHTML = element->document()->isXHTMLDocument(); + String nodeTitle = isXHTML ? element->nodeName() : element->nodeName().lower(); + const AtomicString& idValue = element->getIdAttribute(); + if (!idValue.isNull() && !idValue.isEmpty()) { + nodeTitle += "#"; + nodeTitle += idValue; + } + if (element->hasClass() && element->isStyledElement()) { + const SpaceSplitString& classNamesString = static_cast<StyledElement*>(element)->classNames(); + size_t classNameCount = classNamesString.size(); + if (classNameCount) { + HashSet<AtomicString> usedClassNames; + for (size_t i = 0; i < classNameCount; ++i) { + const AtomicString& className = classNamesString[i]; + if (usedClassNames.contains(className)) + continue; + usedClassNames.add(className); + nodeTitle += "."; + nodeTitle += className; + } + } + } + + nodeTitle += " ["; + nodeTitle += String::number(boundingBox.width()); + nodeTitle.append(static_cast<UChar>(0x00D7)); // × + nodeTitle += String::number(boundingBox.height()); + nodeTitle += "]"; + + FontDescription desc; + FontFamily family; + family.setFamily(settings->fixedFontFamily()); + desc.setFamily(family); + desc.setComputedSize(fontHeightPx); + Font font = Font(desc, 0, 0); + font.update(0); + + TextRun nodeTitleRun(nodeTitle); + IntPoint titleBasePoint = IntPoint(anchorBox.x(), anchorBox.maxY() - 1); + titleBasePoint.move(rectInflatePx, rectInflatePx); + IntRect titleRect = enclosingIntRect(font.selectionRectForText(nodeTitleRun, titleBasePoint, fontHeightPx)); + titleRect.inflate(rectInflatePx); + + // The initial offsets needed to compensate for a 1px-thick border stroke (which is not a part of the rectangle). + int dx = -borderWidthPx; + int dy = borderWidthPx; + + // If the tip sticks beyond the right of overlayRect, right-align the tip with the said boundary. + if (titleRect.maxX() > overlayRect.maxX()) + dx = overlayRect.maxX() - titleRect.maxX(); + + // If the tip sticks beyond the left of overlayRect, left-align the tip with the said boundary. + if (titleRect.x() + dx < overlayRect.x()) + dx = overlayRect.x() - titleRect.x() - borderWidthPx; + + // If the tip sticks beyond the bottom of overlayRect, show the tip at top of bounding box. + if (titleRect.maxY() > overlayRect.maxY()) { + dy = anchorBox.y() - titleRect.maxY() - borderWidthPx; + // If the tip still sticks beyond the bottom of overlayRect, bottom-align the tip with the said boundary. + if (titleRect.maxY() + dy > overlayRect.maxY()) + dy = overlayRect.maxY() - titleRect.maxY(); + } + + // If the tip sticks beyond the top of overlayRect, show the tip at top of overlayRect. + if (titleRect.y() + dy < overlayRect.y()) + dy = overlayRect.y() - titleRect.y() + borderWidthPx; + + titleRect.move(dx, dy); + context.setStrokeColor(tooltipBorderColor, ColorSpaceDeviceRGB); + context.setStrokeThickness(borderWidthPx); + context.setFillColor(tooltipBackgroundColor, ColorSpaceDeviceRGB); + context.drawRect(titleRect); + context.setFillColor(tooltipFontColor, ColorSpaceDeviceRGB); + context.drawText(font, nodeTitleRun, IntPoint(titleRect.x() + rectInflatePx, titleRect.y() + font.fontMetrics().height())); +} + +} // anonymous namespace + +namespace DOMNodeHighlighter { + +void DrawNodeHighlight(GraphicsContext& context, Node* node) +{ + RenderObject* renderer = node->renderer(); + Frame* containingFrame = node->document()->frame(); + + if (!renderer || !containingFrame) + return; + + IntSize mainFrameOffset = frameToMainFrameOffset(containingFrame); + IntRect boundingBox = renderer->absoluteBoundingBoxRect(true); + + boundingBox.move(mainFrameOffset); + + IntRect titleAnchorBox = boundingBox; + + FrameView* view = containingFrame->page()->mainFrame()->view(); + FloatRect overlayRect = view->visibleContentRect(); + if (!overlayRect.contains(boundingBox) && !boundingBox.contains(enclosingIntRect(overlayRect))) + overlayRect = view->visibleContentRect(); + context.translate(-overlayRect.x(), -overlayRect.y()); + + // RenderSVGRoot should be highlighted through the isBox() code path, all other SVG elements should just dump their absoluteQuads(). +#if ENABLE(SVG) + bool isSVGRenderer = renderer->node() && renderer->node()->isSVGElement() && !renderer->isSVGRoot(); +#else + bool isSVGRenderer = false; +#endif + + if (renderer->isBox() && !isSVGRenderer) { + RenderBox* renderBox = toRenderBox(renderer); + + IntRect contentBox = renderBox->contentBoxRect(); + + IntRect paddingBox(contentBox.x() - renderBox->paddingLeft(), contentBox.y() - renderBox->paddingTop(), + contentBox.width() + renderBox->paddingLeft() + renderBox->paddingRight(), contentBox.height() + renderBox->paddingTop() + renderBox->paddingBottom()); + IntRect borderBox(paddingBox.x() - renderBox->borderLeft(), paddingBox.y() - renderBox->borderTop(), + paddingBox.width() + renderBox->borderLeft() + renderBox->borderRight(), paddingBox.height() + renderBox->borderTop() + renderBox->borderBottom()); + IntRect marginBox(borderBox.x() - renderBox->marginLeft(), borderBox.y() - renderBox->marginTop(), + borderBox.width() + renderBox->marginLeft() + renderBox->marginRight(), borderBox.height() + renderBox->marginTop() + renderBox->marginBottom()); + + + FloatQuad absContentQuad = renderBox->localToAbsoluteQuad(FloatRect(contentBox)); + FloatQuad absPaddingQuad = renderBox->localToAbsoluteQuad(FloatRect(paddingBox)); + FloatQuad absBorderQuad = renderBox->localToAbsoluteQuad(FloatRect(borderBox)); + FloatQuad absMarginQuad = renderBox->localToAbsoluteQuad(FloatRect(marginBox)); + + absContentQuad.move(mainFrameOffset); + absPaddingQuad.move(mainFrameOffset); + absBorderQuad.move(mainFrameOffset); + absMarginQuad.move(mainFrameOffset); + + titleAnchorBox = absMarginQuad.enclosingBoundingBox(); + + drawHighlightForBox(context, absContentQuad, absPaddingQuad, absBorderQuad, absMarginQuad); + } else if (renderer->isRenderInline() || isSVGRenderer) { + // FIXME: We should show margins/padding/border for inlines. + Vector<FloatQuad> lineBoxQuads; + renderer->absoluteQuads(lineBoxQuads); + for (unsigned i = 0; i < lineBoxQuads.size(); ++i) + lineBoxQuads[i] += mainFrameOffset; + + drawHighlightForLineBoxesOrSVGRenderer(context, lineBoxQuads); + } + + // Draw node title if necessary. + + if (!node->isElementNode()) + return; + + WebCore::Settings* settings = containingFrame->settings(); + drawElementTitle(context, node, boundingBox, titleAnchorBox, overlayRect, settings); +} + +} // namespace DOMNodeHighlighter + +} // namespace WebCore + +#endif // ENABLE(INSPECTOR) |