summaryrefslogtreecommitdiffstats
path: root/Source/WebCore/inspector/DOMNodeHighlighter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/inspector/DOMNodeHighlighter.cpp')
-rw-r--r--Source/WebCore/inspector/DOMNodeHighlighter.cpp292
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)); // &times;
+ 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)