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