1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "config.h"
6#include "core/page/PrintContext.h"
7
8#include "core/dom/Document.h"
9#include "core/html/HTMLElement.h"
10#include "core/testing/DummyPageHolder.h"
11#include "platform/graphics/GraphicsContext.h"
12#include "platform/testing/SkiaForCoreTesting.h"
13#include "platform/text/TextStream.h"
14#include <gtest/gtest.h>
15
16namespace blink {
17
18const int kPageWidth = 800;
19const int kPageHeight = 600;
20
21class MockPrintContext : public PrintContext {
22public:
23    MockPrintContext(LocalFrame* frame) : PrintContext(frame) { }
24
25    void outputLinkAndLinkedDestinations(GraphicsContext& context, Node* node, const IntRect& pageRect)
26    {
27        PrintContext::outputLinkAndLinkedDestinations(context, node, pageRect);
28    }
29};
30
31class MockCanvas : public SkCanvas {
32public:
33    enum OperationType {
34        DrawRect,
35        DrawPoint
36    };
37
38    struct Operation {
39        OperationType type;
40        SkRect rect;
41    };
42
43    MockCanvas() : SkCanvas(kPageWidth, kPageHeight) { }
44
45    virtual void drawRect(const SkRect& rect, const SkPaint& paint) OVERRIDE
46    {
47        ASSERT_TRUE(paint.getAnnotation());
48        Operation operation = { DrawRect, rect };
49        m_recordedOperations.append(operation);
50    }
51
52    virtual void drawPoints(PointMode mode, size_t count, const SkPoint pts[], const SkPaint& paint) OVERRIDE
53    {
54        ASSERT_EQ(1u, count); // Only called from drawPoint().
55        ASSERT_TRUE(paint.getAnnotation());
56        Operation operation = { DrawPoint, SkRect::MakeXYWH(pts[0].x(), pts[0].y(), 0, 0) };
57        m_recordedOperations.append(operation);
58    }
59
60    const Vector<Operation>& recordedOperations() const { return m_recordedOperations; }
61
62private:
63    Vector<Operation> m_recordedOperations;
64};
65
66class PrintContextTest : public testing::Test {
67protected:
68    PrintContextTest()
69        : m_pageHolder(DummyPageHolder::create(IntSize(kPageWidth, kPageHeight)))
70        , m_printContext(adoptPtrWillBeNoop(new MockPrintContext(document().frame()))) { }
71
72    Document& document() const { return m_pageHolder->document(); }
73    MockPrintContext& printContext() { return *m_printContext.get(); }
74
75    void setBodyInnerHTML(String bodyContent)
76    {
77        document().body()->setInnerHTML(bodyContent, ASSERT_NO_EXCEPTION);
78    }
79
80    void printSinglePage(SkCanvas& canvas)
81    {
82        IntRect pageRect(0, 0, kPageWidth, kPageHeight);
83        GraphicsContext context(&canvas);
84        printContext().begin(kPageWidth, kPageHeight);
85        printContext().outputLinkAndLinkedDestinations(context, &document(), pageRect);
86        printContext().end();
87    }
88
89    static String htmlForLink(int x, int y, int width, int height, const char* url)
90    {
91        TextStream ts;
92        ts << "<a style='position: absolute; left: " << x << "px; top: " << y << "px; width: " << width << "px; height: " << height
93            << "px' href='" << url << "'>" << url << "</a>";
94        return ts.release();
95    }
96
97    static String htmlForAnchor(int x, int y, const char* name)
98    {
99        TextStream ts;
100        ts << "<a name='" << name << "' style='position: absolute; left: " << x << "px; top: " << y << "px'>" << name << "</a>";
101        return ts.release();
102    }
103
104private:
105    OwnPtr<DummyPageHolder> m_pageHolder;
106    OwnPtrWillBePersistent<MockPrintContext> m_printContext;
107};
108
109#define EXPECT_SKRECT_EQ(expectedX, expectedY, expectedWidth, expectedHeight, actualRect) \
110    EXPECT_EQ(expectedX, actualRect.x()); \
111    EXPECT_EQ(expectedY, actualRect.y()); \
112    EXPECT_EQ(expectedWidth, actualRect.width()); \
113    EXPECT_EQ(expectedHeight, actualRect.height());
114
115TEST_F(PrintContextTest, LinkTarget)
116{
117    MockCanvas canvas;
118    setBodyInnerHTML(htmlForLink(50, 60, 70, 80, "http://www.google.com")
119        + htmlForLink(150, 160, 170, 180, "http://www.google.com#fragment"));
120    printSinglePage(canvas);
121
122    const Vector<MockCanvas::Operation>& operations = canvas.recordedOperations();
123    ASSERT_EQ(2u, operations.size());
124
125    // The items in the result can be in any sequence.
126    size_t firstIndex = operations[0].rect.x() == 50 ? 0 : 1;
127    EXPECT_EQ(MockCanvas::DrawRect, operations[firstIndex].type);
128    EXPECT_SKRECT_EQ(50, 60, 70, 80, operations[firstIndex].rect);
129    // We should also check if the annotation is correct but Skia doesn't export
130    // SkAnnotation API.
131
132    size_t secondIndex = firstIndex == 0 ? 1 : 0;
133    EXPECT_EQ(MockCanvas::DrawRect, operations[secondIndex].type);
134    EXPECT_SKRECT_EQ(150, 160, 170, 180, operations[secondIndex].rect);
135}
136
137TEST_F(PrintContextTest, LinkedTarget)
138{
139    MockCanvas canvas;
140    document().setBaseURLOverride(KURL(ParsedURLString, "http://a.com/"));
141    setBodyInnerHTML(htmlForLink(50, 60, 70, 80, "#fragment") // Generates a Link_Named_Dest_Key annotation
142        + htmlForLink(150, 160, 170, 180, "#not-found") // Generates no annotation
143        + htmlForAnchor(250, 260, "fragment") // Generates a Define_Named_Dest_Key annotation
144        + htmlForAnchor(350, 360, "fragment-not-used")); // Generates no annotation
145    printSinglePage(canvas);
146
147    const Vector<MockCanvas::Operation>& operations = canvas.recordedOperations();
148    ASSERT_EQ(2u, operations.size());
149
150    size_t firstIndex = operations[0].rect.x() == 50 ? 0 : 1;
151    EXPECT_EQ(MockCanvas::DrawRect, operations[firstIndex].type);
152    EXPECT_SKRECT_EQ(50, 60, 70, 80, operations[firstIndex].rect);
153
154    size_t secondIndex = firstIndex == 0 ? 1 : 0;
155    EXPECT_EQ(MockCanvas::DrawPoint, operations[secondIndex].type);
156    EXPECT_SKRECT_EQ(250, 260, 0, 0, operations[secondIndex].rect);
157}
158
159} // namespace blink
160