/*
* 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);
// 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<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)