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 31#if ENABLE(INSPECTOR) 32 33#include "InspectorOverlay.h" 34 35#include "DocumentLoader.h" 36#include "Element.h" 37#include "EmptyClients.h" 38#include "Frame.h" 39#include "FrameView.h" 40#include "GraphicsContext.h" 41#include "InspectorClient.h" 42#include "InspectorOverlayPage.h" 43#include "InspectorValues.h" 44#include "Node.h" 45#include "Page.h" 46#include "RenderBoxModelObject.h" 47#include "RenderInline.h" 48#include "RenderObject.h" 49#include "ScriptController.h" 50#include "ScriptSourceCode.h" 51#include "ScriptValue.h" 52#include "Settings.h" 53#include "StyledElement.h" 54#include <wtf/text/StringBuilder.h> 55 56namespace WebCore { 57 58namespace { 59 60Path quadToPath(const FloatQuad& quad) 61{ 62 Path quadPath; 63 quadPath.moveTo(quad.p1()); 64 quadPath.addLineTo(quad.p2()); 65 quadPath.addLineTo(quad.p3()); 66 quadPath.addLineTo(quad.p4()); 67 quadPath.closeSubpath(); 68 return quadPath; 69} 70 71void drawOutlinedQuad(GraphicsContext* context, const FloatQuad& quad, const Color& fillColor, const Color& outlineColor) 72{ 73 static const int outlineThickness = 2; 74 75 Path quadPath = quadToPath(quad); 76 77 // Clip out the quad, then draw with a 2px stroke to get a pixel 78 // of outline (because inflating a quad is hard) 79 { 80 context->save(); 81 context->clipOut(quadPath); 82 83 context->setStrokeThickness(outlineThickness); 84 context->setStrokeColor(outlineColor, ColorSpaceDeviceRGB); 85 context->strokePath(quadPath); 86 87 context->restore(); 88 } 89 90 // Now do the fill 91 context->setFillColor(fillColor, ColorSpaceDeviceRGB); 92 context->fillPath(quadPath); 93} 94 95static void contentsQuadToPage(const FrameView* mainView, const FrameView* view, FloatQuad& quad) 96{ 97 quad.setP1(view->contentsToRootView(roundedIntPoint(quad.p1()))); 98 quad.setP2(view->contentsToRootView(roundedIntPoint(quad.p2()))); 99 quad.setP3(view->contentsToRootView(roundedIntPoint(quad.p3()))); 100 quad.setP4(view->contentsToRootView(roundedIntPoint(quad.p4()))); 101 quad += mainView->scrollOffset(); 102} 103 104static void buildNodeHighlight(Node* node, const HighlightConfig& highlightConfig, Highlight* highlight) 105{ 106 RenderObject* renderer = node->renderer(); 107 Frame* containingFrame = node->document()->frame(); 108 109 if (!renderer || !containingFrame) 110 return; 111 112 highlight->setDataFromConfig(highlightConfig); 113 FrameView* containingView = containingFrame->view(); 114 FrameView* mainView = containingFrame->page()->mainFrame()->view(); 115 IntRect boundingBox = pixelSnappedIntRect(containingView->contentsToRootView(renderer->absoluteBoundingBoxRect())); 116 boundingBox.move(mainView->scrollOffset()); 117 IntRect titleAnchorBox = boundingBox; 118 119 // RenderSVGRoot should be highlighted through the isBox() code path, all other SVG elements should just dump their absoluteQuads(). 120#if ENABLE(SVG) 121 bool isSVGRenderer = renderer->node() && renderer->node()->isSVGElement() && !renderer->isSVGRoot(); 122#else 123 bool isSVGRenderer = false; 124#endif 125 126 if (isSVGRenderer) { 127 highlight->type = HighlightTypeRects; 128 renderer->absoluteQuads(highlight->quads); 129 for (size_t i = 0; i < highlight->quads.size(); ++i) 130 contentsQuadToPage(mainView, containingView, highlight->quads[i]); 131 } else if (renderer->isBox() || renderer->isRenderInline()) { 132 LayoutRect contentBox; 133 LayoutRect paddingBox; 134 LayoutRect borderBox; 135 LayoutRect marginBox; 136 137 if (renderer->isBox()) { 138 RenderBox* renderBox = toRenderBox(renderer); 139 140 // RenderBox returns the "pure" content area box, exclusive of the scrollbars (if present), which also count towards the content area in CSS. 141 contentBox = renderBox->contentBoxRect(); 142 contentBox.setWidth(contentBox.width() + renderBox->verticalScrollbarWidth()); 143 contentBox.setHeight(contentBox.height() + renderBox->horizontalScrollbarHeight()); 144 145 paddingBox = LayoutRect(contentBox.x() - renderBox->paddingLeft(), contentBox.y() - renderBox->paddingTop(), 146 contentBox.width() + renderBox->paddingLeft() + renderBox->paddingRight(), contentBox.height() + renderBox->paddingTop() + renderBox->paddingBottom()); 147 borderBox = LayoutRect(paddingBox.x() - renderBox->borderLeft(), paddingBox.y() - renderBox->borderTop(), 148 paddingBox.width() + renderBox->borderLeft() + renderBox->borderRight(), paddingBox.height() + renderBox->borderTop() + renderBox->borderBottom()); 149 marginBox = LayoutRect(borderBox.x() - renderBox->marginLeft(), borderBox.y() - renderBox->marginTop(), 150 borderBox.width() + renderBox->marginWidth(), borderBox.height() + renderBox->marginHeight()); 151 } else { 152 RenderInline* renderInline = toRenderInline(renderer); 153 154 // RenderInline's bounding box includes paddings and borders, excludes margins. 155 borderBox = renderInline->linesBoundingBox(); 156 paddingBox = LayoutRect(borderBox.x() + renderInline->borderLeft(), borderBox.y() + renderInline->borderTop(), 157 borderBox.width() - renderInline->borderLeft() - renderInline->borderRight(), borderBox.height() - renderInline->borderTop() - renderInline->borderBottom()); 158 contentBox = LayoutRect(paddingBox.x() + renderInline->paddingLeft(), paddingBox.y() + renderInline->paddingTop(), 159 paddingBox.width() - renderInline->paddingLeft() - renderInline->paddingRight(), paddingBox.height() - renderInline->paddingTop() - renderInline->paddingBottom()); 160 // Ignore marginTop and marginBottom for inlines. 161 marginBox = LayoutRect(borderBox.x() - renderInline->marginLeft(), borderBox.y(), 162 borderBox.width() + renderInline->marginWidth(), borderBox.height()); 163 } 164 165 FloatQuad absContentQuad = renderer->localToAbsoluteQuad(FloatRect(contentBox)); 166 FloatQuad absPaddingQuad = renderer->localToAbsoluteQuad(FloatRect(paddingBox)); 167 FloatQuad absBorderQuad = renderer->localToAbsoluteQuad(FloatRect(borderBox)); 168 FloatQuad absMarginQuad = renderer->localToAbsoluteQuad(FloatRect(marginBox)); 169 170 contentsQuadToPage(mainView, containingView, absContentQuad); 171 contentsQuadToPage(mainView, containingView, absPaddingQuad); 172 contentsQuadToPage(mainView, containingView, absBorderQuad); 173 contentsQuadToPage(mainView, containingView, absMarginQuad); 174 175 titleAnchorBox = absMarginQuad.enclosingBoundingBox(); 176 177 highlight->type = HighlightTypeNode; 178 highlight->quads.append(absMarginQuad); 179 highlight->quads.append(absBorderQuad); 180 highlight->quads.append(absPaddingQuad); 181 highlight->quads.append(absContentQuad); 182 } 183} 184 185static void buildQuadHighlight(Page* page, const FloatQuad& quad, const HighlightConfig& highlightConfig, Highlight *highlight) 186{ 187 if (!page) 188 return; 189 highlight->setDataFromConfig(highlightConfig); 190 highlight->type = HighlightTypeRects; 191 highlight->quads.append(quad); 192} 193 194} // anonymous namespace 195 196InspectorOverlay::InspectorOverlay(Page* page, InspectorClient* client) 197 : m_page(page) 198 , m_client(client) 199{ 200} 201 202InspectorOverlay::~InspectorOverlay() 203{ 204} 205 206void InspectorOverlay::paint(GraphicsContext& context) 207{ 208 if (m_pausedInDebuggerMessage.isNull() && !m_highlightNode && !m_highlightQuad && m_size.isEmpty()) 209 return; 210 GraphicsContextStateSaver stateSaver(context); 211 FrameView* view = overlayPage()->mainFrame()->view(); 212 ASSERT(!view->needsLayout()); 213 view->paint(&context, IntRect(0, 0, view->width(), view->height())); 214} 215 216void InspectorOverlay::drawOutline(GraphicsContext* context, const LayoutRect& rect, const Color& color) 217{ 218 FloatRect outlineRect = rect; 219 drawOutlinedQuad(context, outlineRect, Color(), color); 220} 221 222void InspectorOverlay::getHighlight(Highlight* highlight) const 223{ 224 if (!m_highlightNode && !m_highlightQuad) 225 return; 226 227 highlight->type = HighlightTypeRects; 228 if (m_highlightNode) 229 buildNodeHighlight(m_highlightNode.get(), m_nodeHighlightConfig, highlight); 230 else 231 buildQuadHighlight(m_page, *m_highlightQuad, m_quadHighlightConfig, highlight); 232} 233 234void InspectorOverlay::resize(const IntSize& size) 235{ 236 m_size = size; 237 update(); 238} 239 240void InspectorOverlay::setPausedInDebuggerMessage(const String* message) 241{ 242 m_pausedInDebuggerMessage = message ? *message : String(); 243 update(); 244} 245 246void InspectorOverlay::hideHighlight() 247{ 248 m_highlightNode.clear(); 249 m_highlightQuad.clear(); 250 update(); 251} 252 253void InspectorOverlay::highlightNode(Node* node, const HighlightConfig& highlightConfig) 254{ 255 m_nodeHighlightConfig = highlightConfig; 256 m_highlightNode = node; 257 update(); 258} 259 260void InspectorOverlay::highlightQuad(PassOwnPtr<FloatQuad> quad, const HighlightConfig& highlightConfig) 261{ 262 if (m_quadHighlightConfig.usePageCoordinates) 263 *quad -= m_page->mainFrame()->view()->scrollOffset(); 264 265 m_quadHighlightConfig = highlightConfig; 266 m_highlightQuad = quad; 267 update(); 268} 269 270Node* InspectorOverlay::highlightedNode() const 271{ 272 return m_highlightNode.get(); 273} 274 275void InspectorOverlay::update() 276{ 277 if (!m_highlightNode && !m_highlightQuad && m_pausedInDebuggerMessage.isNull() && m_size.isEmpty()) { 278 m_client->hideHighlight(); 279 return; 280 } 281 282 FrameView* view = m_page->mainFrame()->view(); 283 if (!view) 284 return; 285 286 FrameView* overlayView = overlayPage()->mainFrame()->view(); 287 IntSize viewportSize = view->visibleContentRect().size(); 288 IntSize frameViewFullSize = view->visibleContentRect(ScrollableArea::IncludeScrollbars).size(); 289 IntSize size = m_size.isEmpty() ? frameViewFullSize : m_size; 290 overlayPage()->setPageScaleFactor(m_page->pageScaleFactor(), IntPoint()); 291 size.scale(m_page->pageScaleFactor()); 292 overlayView->resize(size); 293 294 // Clear canvas and paint things. 295 reset(viewportSize, m_size.isEmpty() ? IntSize() : frameViewFullSize); 296 297 // Include scrollbars to avoid masking them by the gutter. 298 drawGutter(); 299 drawNodeHighlight(); 300 drawQuadHighlight(); 301 drawPausedInDebuggerMessage(); 302 303 // Position DOM elements. 304 overlayPage()->mainFrame()->document()->recalcStyle(Node::Force); 305 if (overlayView->needsLayout()) 306 overlayView->layout(); 307 308 // Kick paint. 309 m_client->highlight(); 310} 311 312static PassRefPtr<InspectorObject> buildObjectForPoint(const FloatPoint& point) 313{ 314 RefPtr<InspectorObject> object = InspectorObject::create(); 315 object->setNumber("x", point.x()); 316 object->setNumber("y", point.y()); 317 return object.release(); 318} 319 320static PassRefPtr<InspectorArray> buildArrayForQuad(const FloatQuad& quad) 321{ 322 RefPtr<InspectorArray> array = InspectorArray::create(); 323 array->pushObject(buildObjectForPoint(quad.p1())); 324 array->pushObject(buildObjectForPoint(quad.p2())); 325 array->pushObject(buildObjectForPoint(quad.p3())); 326 array->pushObject(buildObjectForPoint(quad.p4())); 327 return array.release(); 328} 329 330static PassRefPtr<InspectorObject> buildObjectForHighlight(FrameView* mainView, const Highlight& highlight) 331{ 332 RefPtr<InspectorObject> object = InspectorObject::create(); 333 RefPtr<InspectorArray> array = InspectorArray::create(); 334 for (size_t i = 0; i < highlight.quads.size(); ++i) 335 array->pushArray(buildArrayForQuad(highlight.quads[i])); 336 object->setArray("quads", array.release()); 337 object->setBoolean("showRulers", highlight.showRulers); 338 object->setString("contentColor", highlight.contentColor.serialized()); 339 object->setString("contentOutlineColor", highlight.contentOutlineColor.serialized()); 340 object->setString("paddingColor", highlight.paddingColor.serialized()); 341 object->setString("borderColor", highlight.borderColor.serialized()); 342 object->setString("marginColor", highlight.marginColor.serialized()); 343 344 FloatRect visibleRect = mainView->visibleContentRect(); 345 if (!mainView->delegatesScrolling()) { 346 object->setNumber("scrollX", visibleRect.x()); 347 object->setNumber("scrollY", visibleRect.y()); 348 } else { 349 object->setNumber("scrollX", 0); 350 object->setNumber("scrollY", 0); 351 } 352 353 return object.release(); 354} 355 356static PassRefPtr<InspectorObject> buildObjectForSize(const IntSize& size) 357{ 358 RefPtr<InspectorObject> result = InspectorObject::create(); 359 result->setNumber("width", size.width()); 360 result->setNumber("height", size.height()); 361 return result.release(); 362} 363 364void InspectorOverlay::drawGutter() 365{ 366 evaluateInOverlay("drawGutter", ""); 367} 368 369void InspectorOverlay::drawNodeHighlight() 370{ 371 if (!m_highlightNode) 372 return; 373 374 Highlight highlight; 375 buildNodeHighlight(m_highlightNode.get(), m_nodeHighlightConfig, &highlight); 376 RefPtr<InspectorObject> highlightObject = buildObjectForHighlight(m_page->mainFrame()->view(), highlight); 377 378 Node* node = m_highlightNode.get(); 379 if (node->isElementNode() && m_nodeHighlightConfig.showInfo && node->renderer() && node->document()->frame()) { 380 RefPtr<InspectorObject> elementInfo = InspectorObject::create(); 381 Element* element = toElement(node); 382 bool isXHTML = element->document()->isXHTMLDocument(); 383 elementInfo->setString("tagName", isXHTML ? element->nodeName() : element->nodeName().lower()); 384 elementInfo->setString("idValue", element->getIdAttribute()); 385 HashSet<AtomicString> usedClassNames; 386 if (element->hasClass() && element->isStyledElement()) { 387 StringBuilder classNames; 388 const SpaceSplitString& classNamesString = static_cast<StyledElement*>(element)->classNames(); 389 size_t classNameCount = classNamesString.size(); 390 for (size_t i = 0; i < classNameCount; ++i) { 391 const AtomicString& className = classNamesString[i]; 392 if (usedClassNames.contains(className)) 393 continue; 394 usedClassNames.add(className); 395 classNames.append('.'); 396 classNames.append(className); 397 } 398 elementInfo->setString("className", classNames.toString()); 399 } 400 401 RenderObject* renderer = node->renderer(); 402 Frame* containingFrame = node->document()->frame(); 403 FrameView* containingView = containingFrame->view(); 404 IntRect boundingBox = pixelSnappedIntRect(containingView->contentsToRootView(renderer->absoluteBoundingBoxRect())); 405 RenderBoxModelObject* modelObject = renderer->isBoxModelObject() ? toRenderBoxModelObject(renderer) : 0; 406 elementInfo->setString("nodeWidth", String::number(modelObject ? adjustForAbsoluteZoom(modelObject->pixelSnappedOffsetWidth(), modelObject) : boundingBox.width())); 407 elementInfo->setString("nodeHeight", String::number(modelObject ? adjustForAbsoluteZoom(modelObject->pixelSnappedOffsetHeight(), modelObject) : boundingBox.height())); 408 highlightObject->setObject("elementInfo", elementInfo.release()); 409 } 410 evaluateInOverlay("drawNodeHighlight", highlightObject); 411} 412 413void InspectorOverlay::drawQuadHighlight() 414{ 415 if (!m_highlightQuad) 416 return; 417 418 Highlight highlight; 419 buildQuadHighlight(m_page, *m_highlightQuad, m_quadHighlightConfig, &highlight); 420 evaluateInOverlay("drawQuadHighlight", buildObjectForHighlight(m_page->mainFrame()->view(), highlight)); 421} 422 423void InspectorOverlay::drawPausedInDebuggerMessage() 424{ 425 if (!m_pausedInDebuggerMessage.isNull()) 426 evaluateInOverlay("drawPausedInDebuggerMessage", m_pausedInDebuggerMessage); 427} 428 429Page* InspectorOverlay::overlayPage() 430{ 431 if (m_overlayPage) 432 return m_overlayPage.get(); 433 434 static FrameLoaderClient* dummyFrameLoaderClient = new EmptyFrameLoaderClient; 435 Page::PageClients pageClients; 436 fillWithEmptyClients(pageClients); 437 m_overlayPage = adoptPtr(new Page(pageClients)); 438 439 Settings* settings = m_page->settings(); 440 Settings* overlaySettings = m_overlayPage->settings(); 441 442 overlaySettings->setStandardFontFamily(settings->standardFontFamily()); 443 overlaySettings->setSerifFontFamily(settings->serifFontFamily()); 444 overlaySettings->setSansSerifFontFamily(settings->sansSerifFontFamily()); 445 overlaySettings->setCursiveFontFamily(settings->cursiveFontFamily()); 446 overlaySettings->setFantasyFontFamily(settings->fantasyFontFamily()); 447 overlaySettings->setPictographFontFamily(settings->pictographFontFamily()); 448 overlaySettings->setMinimumFontSize(settings->minimumFontSize()); 449 overlaySettings->setMinimumLogicalFontSize(settings->minimumLogicalFontSize()); 450 overlaySettings->setMediaEnabled(false); 451 overlaySettings->setScriptEnabled(true); 452 overlaySettings->setPluginsEnabled(false); 453 454 RefPtr<Frame> frame = Frame::create(m_overlayPage.get(), 0, dummyFrameLoaderClient); 455 frame->setView(FrameView::create(frame.get())); 456 frame->init(); 457 FrameLoader* loader = frame->loader(); 458 frame->view()->setCanHaveScrollbars(false); 459 frame->view()->setTransparent(true); 460 ASSERT(loader->activeDocumentLoader()); 461 loader->activeDocumentLoader()->writer()->setMIMEType("text/html"); 462 loader->activeDocumentLoader()->writer()->begin(); 463 loader->activeDocumentLoader()->writer()->addData(reinterpret_cast<const char*>(InspectorOverlayPage_html), sizeof(InspectorOverlayPage_html)); 464 loader->activeDocumentLoader()->writer()->end(); 465 466#if OS(WINDOWS) 467 evaluateInOverlay("setPlatform", "windows"); 468#elif OS(MAC_OS_X) 469 evaluateInOverlay("setPlatform", "mac"); 470#elif OS(UNIX) 471 evaluateInOverlay("setPlatform", "linux"); 472#endif 473 474 return m_overlayPage.get(); 475} 476 477void InspectorOverlay::reset(const IntSize& viewportSize, const IntSize& frameViewFullSize) 478{ 479 RefPtr<InspectorObject> resetData = InspectorObject::create(); 480 resetData->setNumber("deviceScaleFactor", m_page->deviceScaleFactor()); 481 resetData->setObject("viewportSize", buildObjectForSize(viewportSize)); 482 resetData->setObject("frameViewFullSize", buildObjectForSize(frameViewFullSize)); 483 evaluateInOverlay("reset", resetData.release()); 484} 485 486void InspectorOverlay::evaluateInOverlay(const String& method, const String& argument) 487{ 488 RefPtr<InspectorArray> command = InspectorArray::create(); 489 command->pushString(method); 490 command->pushString(argument); 491 overlayPage()->mainFrame()->script()->evaluate(ScriptSourceCode(makeString("dispatch(", command->toJSONString(), ")"))); 492} 493 494void InspectorOverlay::evaluateInOverlay(const String& method, PassRefPtr<InspectorValue> argument) 495{ 496 RefPtr<InspectorArray> command = InspectorArray::create(); 497 command->pushString(method); 498 command->pushValue(argument); 499 overlayPage()->mainFrame()->script()->evaluate(ScriptSourceCode(makeString("dispatch(", command->toJSONString(), ")"))); 500} 501 502void InspectorOverlay::freePage() 503{ 504 m_overlayPage.clear(); 505} 506 507} // namespace WebCore 508 509#endif // ENABLE(INSPECTOR) 510