/* * 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& 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(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(element)->classNames(); size_t classNameCount = classNamesString.size(); if (classNameCount) { HashSet 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(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); // RenderBox returns the "pure" content area box, exclusive of the scrollbars (if present), which also count towards the content area in CSS. IntRect contentBox = renderBox->contentBoxRect(); contentBox.setWidth(contentBox.width() + renderBox->verticalScrollbarWidth()); contentBox.setHeight(contentBox.height() + renderBox->horizontalScrollbarHeight()); 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 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)