1/*
2 * Copyright (C) 2012 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 are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include "config.h"
32
33#include "core/testing/URLTestHelpers.h"
34#include "public/web/WebCache.h"
35#include "public/web/WebDocument.h"
36#include "public/web/WebElement.h"
37#include "public/web/WebFrame.h"
38#include "public/web/WebNode.h"
39#include "public/web/WebNodeList.h"
40#include "public/web/WebPrerendererClient.h"
41#include "public/web/WebScriptSource.h"
42#include "public/web/WebView.h"
43#include "public/web/WebViewClient.h"
44#include "web/tests/FrameTestHelpers.h"
45
46#include "public/platform/Platform.h"
47#include "public/platform/WebPrerender.h"
48#include "public/platform/WebPrerenderingSupport.h"
49#include "public/platform/WebString.h"
50#include "public/platform/WebUnitTestSupport.h"
51#include "wtf/OwnPtr.h"
52#include <functional>
53#include <gtest/gtest.h>
54#include <list>
55
56using namespace blink;
57using blink::URLTestHelpers::toKURL;
58
59namespace {
60
61WebURL toWebURL(const char* url)
62{
63    return WebURL(toKURL(url));
64}
65
66class TestPrerendererClient : public WebPrerendererClient {
67public:
68    TestPrerendererClient() { }
69    virtual ~TestPrerendererClient() { }
70
71    void setExtraDataForNextPrerender(WebPrerender::ExtraData* extraData)
72    {
73        ASSERT(!m_extraData);
74        m_extraData = adoptPtr(extraData);
75    }
76
77    WebPrerender releaseWebPrerender()
78    {
79        ASSERT(!m_webPrerenders.empty());
80        WebPrerender retval(m_webPrerenders.front());
81        m_webPrerenders.pop_front();
82        return retval;
83    }
84
85    bool empty() const
86    {
87        return m_webPrerenders.empty();
88    }
89
90    void clear()
91    {
92        m_webPrerenders.clear();
93    }
94
95private:
96    // From WebPrerendererClient:
97    virtual void willAddPrerender(WebPrerender* prerender) OVERRIDE
98    {
99        prerender->setExtraData(m_extraData.leakPtr());
100
101        ASSERT(!prerender->isNull());
102        m_webPrerenders.push_back(*prerender);
103    }
104
105    OwnPtr<WebPrerender::ExtraData> m_extraData;
106    std::list<WebPrerender> m_webPrerenders;
107};
108
109class TestPrerenderingSupport : public WebPrerenderingSupport {
110public:
111    TestPrerenderingSupport()
112    {
113        initialize(this);
114    }
115
116    virtual ~TestPrerenderingSupport()
117    {
118        shutdown();
119    }
120
121    void clear()
122    {
123        m_addedPrerenders.clear();
124        m_canceledPrerenders.clear();
125        m_abandonedPrerenders.clear();
126    }
127
128    size_t totalCount() const
129    {
130        return m_addedPrerenders.size() + m_canceledPrerenders.size() + m_abandonedPrerenders.size();
131    }
132
133    size_t addCount(const WebPrerender& prerender) const
134    {
135        return std::count_if(m_addedPrerenders.begin(), m_addedPrerenders.end(), std::bind1st(WebPrerenderEqual(), prerender));
136    }
137
138    size_t cancelCount(const WebPrerender& prerender) const
139    {
140        return std::count_if(m_canceledPrerenders.begin(), m_canceledPrerenders.end(), std::bind1st(WebPrerenderEqual(), prerender));
141    }
142
143    size_t abandonCount(const WebPrerender& prerender) const
144    {
145        return std::count_if(m_abandonedPrerenders.begin(), m_abandonedPrerenders.end(), std::bind1st(WebPrerenderEqual(), prerender));
146    }
147
148private:
149    class WebPrerenderEqual : public std::binary_function<WebPrerender, WebPrerender, bool> {
150    public:
151        bool operator()(const WebPrerender& first, const WebPrerender& second) const
152        {
153            return first.toPrerender() == second.toPrerender();
154        }
155    };
156
157    // From WebPrerenderingSupport:
158    virtual void add(const WebPrerender& prerender) OVERRIDE
159    {
160        m_addedPrerenders.push_back(prerender);
161    }
162
163    virtual void cancel(const WebPrerender& prerender) OVERRIDE
164    {
165        m_canceledPrerenders.push_back(prerender);
166    }
167
168    virtual void abandon(const WebPrerender& prerender) OVERRIDE
169    {
170        m_abandonedPrerenders.push_back(prerender);
171    }
172
173    std::vector<WebPrerender> m_addedPrerenders;
174    std::vector<WebPrerender> m_canceledPrerenders;
175    std::vector<WebPrerender> m_abandonedPrerenders;
176};
177
178class PrerenderingTest : public testing::Test {
179public:
180    ~PrerenderingTest()
181    {
182        Platform::current()->unitTestSupport()->unregisterAllMockedURLs();
183    }
184
185    void initialize(const char* baseURL, const char* fileName)
186    {
187        URLTestHelpers::registerMockedURLFromBaseURL(WebString::fromUTF8(baseURL), WebString::fromUTF8(fileName));
188        const bool RunJavascript = true;
189        m_webViewHelper.initialize(RunJavascript);
190        m_webViewHelper.webView()->setPrerendererClient(&m_prerendererClient);
191
192        FrameTestHelpers::loadFrame(m_webViewHelper.webView()->mainFrame(), std::string(baseURL) + fileName);
193    }
194
195    void navigateAway()
196    {
197        FrameTestHelpers::loadFrame(m_webViewHelper.webView()->mainFrame(), "about:blank");
198    }
199
200    void close()
201    {
202        m_webViewHelper.webView()->mainFrame()->collectGarbage();
203        m_webViewHelper.reset();
204
205        WebCache::clear();
206    }
207
208    WebElement console()
209    {
210        WebElement console = m_webViewHelper.webView()->mainFrame()->document().getElementById("console");
211        ASSERT(console.nodeName() == "UL");
212        return console;
213    }
214
215    unsigned consoleLength()
216    {
217        return console().childNodes().length() - 1;
218    }
219
220    std::string consoleAt(unsigned i)
221    {
222        ASSERT(consoleLength() > i);
223
224        WebNode consoleListItem = console().childNodes().item(1 + i);
225        ASSERT(consoleListItem.nodeName() == "LI");
226        ASSERT(consoleListItem.hasChildNodes());
227
228        WebNode textNode = consoleListItem.firstChild();
229        ASSERT(textNode.nodeName() == "#text");
230
231        return textNode.nodeValue().utf8().data();
232    }
233
234    void executeScript(const char* code)
235    {
236        m_webViewHelper.webView()->mainFrame()->executeScript(WebScriptSource(WebString::fromUTF8(code)));
237    }
238
239    TestPrerenderingSupport* prerenderingSupport()
240    {
241        return &m_prerenderingSupport;
242    }
243
244    TestPrerendererClient* prerendererClient()
245    {
246        return &m_prerendererClient;
247    }
248
249private:
250    TestPrerenderingSupport m_prerenderingSupport;
251    TestPrerendererClient m_prerendererClient;
252
253    FrameTestHelpers::WebViewHelper m_webViewHelper;
254};
255
256TEST_F(PrerenderingTest, SinglePrerender)
257{
258    initialize("http://www.foo.com/", "prerender/single_prerender.html");
259
260    WebPrerender webPrerender = prerendererClient()->releaseWebPrerender();
261    EXPECT_FALSE(webPrerender.isNull());
262    EXPECT_EQ(toWebURL("http://prerender.com/"), webPrerender.url());
263    EXPECT_EQ(PrerenderRelTypePrerender, webPrerender.relTypes());
264
265    EXPECT_EQ(1u, prerenderingSupport()->addCount(webPrerender));
266    EXPECT_EQ(1u, prerenderingSupport()->totalCount());
267
268    webPrerender.didStartPrerender();
269    EXPECT_EQ(1u, consoleLength());
270    EXPECT_EQ("webkitprerenderstart", consoleAt(0));
271
272    webPrerender.didSendDOMContentLoadedForPrerender();
273    EXPECT_EQ(2u, consoleLength());
274    EXPECT_EQ("webkitprerenderdomcontentloaded", consoleAt(1));
275
276    webPrerender.didSendLoadForPrerender();
277    EXPECT_EQ(3u, consoleLength());
278    EXPECT_EQ("webkitprerenderload", consoleAt(2));
279
280    webPrerender.didStopPrerender();
281    EXPECT_EQ(4u, consoleLength());
282    EXPECT_EQ("webkitprerenderstop", consoleAt(3));
283}
284
285TEST_F(PrerenderingTest, CancelPrerender)
286{
287    initialize("http://www.foo.com/", "prerender/single_prerender.html");
288
289    WebPrerender webPrerender = prerendererClient()->releaseWebPrerender();
290    EXPECT_FALSE(webPrerender.isNull());
291
292    EXPECT_EQ(1u, prerenderingSupport()->addCount(webPrerender));
293    EXPECT_EQ(1u, prerenderingSupport()->totalCount());
294
295    executeScript("removePrerender()");
296
297    EXPECT_EQ(1u, prerenderingSupport()->cancelCount(webPrerender));
298    EXPECT_EQ(2u, prerenderingSupport()->totalCount());
299}
300
301TEST_F(PrerenderingTest, AbandonPrerender)
302{
303    initialize("http://www.foo.com/", "prerender/single_prerender.html");
304
305    WebPrerender webPrerender = prerendererClient()->releaseWebPrerender();
306    EXPECT_FALSE(webPrerender.isNull());
307
308    EXPECT_EQ(1u, prerenderingSupport()->addCount(webPrerender));
309    EXPECT_EQ(1u, prerenderingSupport()->totalCount());
310
311    navigateAway();
312
313    EXPECT_EQ(1u, prerenderingSupport()->abandonCount(webPrerender));
314    EXPECT_EQ(2u, prerenderingSupport()->totalCount());
315
316    // Check that the prerender does not emit an extra cancel when garbage-collecting everything.
317    close();
318
319    EXPECT_EQ(2u, prerenderingSupport()->totalCount());
320}
321
322TEST_F(PrerenderingTest, ExtraData)
323{
324    class TestExtraData : public WebPrerender::ExtraData {
325    public:
326        explicit TestExtraData(bool* alive) : m_alive(alive)
327        {
328            *alive = true;
329        }
330
331        virtual ~TestExtraData() { *m_alive = false; }
332
333    private:
334        bool* m_alive;
335    };
336
337    bool alive = false;
338    {
339        prerendererClient()->setExtraDataForNextPrerender(new TestExtraData(&alive));
340        initialize("http://www.foo.com/", "prerender/single_prerender.html");
341        EXPECT_TRUE(alive);
342
343        WebPrerender webPrerender = prerendererClient()->releaseWebPrerender();
344
345        executeScript("removePrerender()");
346        close();
347        prerenderingSupport()->clear();
348    }
349    EXPECT_FALSE(alive);
350}
351
352TEST_F(PrerenderingTest, TwoPrerenders)
353{
354    initialize("http://www.foo.com/", "prerender/multiple_prerenders.html");
355
356    WebPrerender firstPrerender = prerendererClient()->releaseWebPrerender();
357    EXPECT_FALSE(firstPrerender.isNull());
358    EXPECT_EQ(toWebURL("http://first-prerender.com/"), firstPrerender.url());
359
360    WebPrerender secondPrerender = prerendererClient()->releaseWebPrerender();
361    EXPECT_FALSE(firstPrerender.isNull());
362    EXPECT_EQ(toWebURL("http://second-prerender.com/"), secondPrerender.url());
363
364    EXPECT_EQ(1u, prerenderingSupport()->addCount(firstPrerender));
365    EXPECT_EQ(1u, prerenderingSupport()->addCount(secondPrerender));
366    EXPECT_EQ(2u, prerenderingSupport()->totalCount());
367
368    firstPrerender.didStartPrerender();
369    EXPECT_EQ(1u, consoleLength());
370    EXPECT_EQ("first_webkitprerenderstart", consoleAt(0));
371
372    secondPrerender.didStartPrerender();
373    EXPECT_EQ(2u, consoleLength());
374    EXPECT_EQ("second_webkitprerenderstart", consoleAt(1));
375}
376
377TEST_F(PrerenderingTest, TwoPrerendersRemovingFirstThenNavigating)
378{
379    initialize("http://www.foo.com/", "prerender/multiple_prerenders.html");
380
381    WebPrerender firstPrerender = prerendererClient()->releaseWebPrerender();
382    WebPrerender secondPrerender = prerendererClient()->releaseWebPrerender();
383
384    EXPECT_EQ(1u, prerenderingSupport()->addCount(firstPrerender));
385    EXPECT_EQ(1u, prerenderingSupport()->addCount(secondPrerender));
386    EXPECT_EQ(2u, prerenderingSupport()->totalCount());
387
388    executeScript("removeFirstPrerender()");
389
390    EXPECT_EQ(1u, prerenderingSupport()->cancelCount(firstPrerender));
391    EXPECT_EQ(3u, prerenderingSupport()->totalCount());
392
393    navigateAway();
394
395    EXPECT_EQ(1u, prerenderingSupport()->abandonCount(secondPrerender));
396    EXPECT_EQ(4u, prerenderingSupport()->totalCount());
397}
398
399TEST_F(PrerenderingTest, TwoPrerendersAddingThird)
400{
401    initialize("http://www.foo.com/", "prerender/multiple_prerenders.html");
402
403    WebPrerender firstPrerender = prerendererClient()->releaseWebPrerender();
404    WebPrerender secondPrerender = prerendererClient()->releaseWebPrerender();
405
406    EXPECT_EQ(1u, prerenderingSupport()->addCount(firstPrerender));
407    EXPECT_EQ(1u, prerenderingSupport()->addCount(secondPrerender));
408    EXPECT_EQ(2u, prerenderingSupport()->totalCount());
409
410    executeScript("addThirdPrerender()");
411
412    WebPrerender thirdPrerender = prerendererClient()->releaseWebPrerender();
413    EXPECT_EQ(1u, prerenderingSupport()->addCount(thirdPrerender));
414    EXPECT_EQ(3u, prerenderingSupport()->totalCount());
415}
416
417TEST_F(PrerenderingTest, ShortLivedClient)
418{
419    initialize("http://www.foo.com/", "prerender/single_prerender.html");
420
421    WebPrerender webPrerender = prerendererClient()->releaseWebPrerender();
422    EXPECT_FALSE(webPrerender.isNull());
423
424    EXPECT_EQ(1u, prerenderingSupport()->addCount(webPrerender));
425    EXPECT_EQ(1u, prerenderingSupport()->totalCount());
426
427    navigateAway();
428    close();
429
430    // This test passes if this next line doesn't crash.
431    webPrerender.didStartPrerender();
432}
433
434TEST_F(PrerenderingTest, FastRemoveElement)
435{
436    initialize("http://www.foo.com/", "prerender/single_prerender.html");
437
438    WebPrerender webPrerender = prerendererClient()->releaseWebPrerender();
439    EXPECT_FALSE(webPrerender.isNull());
440
441    EXPECT_EQ(1u, prerenderingSupport()->addCount(webPrerender));
442    EXPECT_EQ(1u, prerenderingSupport()->totalCount());
443
444    // Race removing & starting the prerender against each other, as if the element was removed very quickly.
445    executeScript("removePrerender()");
446    EXPECT_FALSE(webPrerender.isNull());
447    webPrerender.didStartPrerender();
448
449    // The page should be totally disconnected from the Prerender at this point, so the console should not have updated.
450    EXPECT_EQ(0u, consoleLength());
451}
452
453TEST_F(PrerenderingTest, MutateTarget)
454{
455    initialize("http://www.foo.com/", "prerender/single_prerender.html");
456
457    WebPrerender webPrerender = prerendererClient()->releaseWebPrerender();
458    EXPECT_FALSE(webPrerender.isNull());
459    EXPECT_EQ(toWebURL("http://prerender.com/"), webPrerender.url());
460
461    EXPECT_EQ(1u, prerenderingSupport()->addCount(webPrerender));
462    EXPECT_EQ(0u, prerenderingSupport()->cancelCount(webPrerender));
463    EXPECT_EQ(1u, prerenderingSupport()->totalCount());
464
465    // Change the href of this prerender, make sure this is treated as a remove and add.
466    executeScript("mutateTarget()");
467    EXPECT_EQ(1u, prerenderingSupport()->cancelCount(webPrerender));
468
469    WebPrerender mutatedPrerender = prerendererClient()->releaseWebPrerender();
470    EXPECT_EQ(toWebURL("http://mutated.com/"), mutatedPrerender.url());
471    EXPECT_EQ(1u, prerenderingSupport()->addCount(webPrerender));
472    EXPECT_EQ(1u, prerenderingSupport()->addCount(mutatedPrerender));
473    EXPECT_EQ(3u, prerenderingSupport()->totalCount());
474}
475
476TEST_F(PrerenderingTest, MutateRel)
477{
478    initialize("http://www.foo.com/", "prerender/single_prerender.html");
479
480    WebPrerender webPrerender = prerendererClient()->releaseWebPrerender();
481    EXPECT_FALSE(webPrerender.isNull());
482    EXPECT_EQ(toWebURL("http://prerender.com/"), webPrerender.url());
483
484    EXPECT_EQ(1u, prerenderingSupport()->addCount(webPrerender));
485    EXPECT_EQ(0u, prerenderingSupport()->cancelCount(webPrerender));
486    EXPECT_EQ(1u, prerenderingSupport()->totalCount());
487
488    // Change the rel of this prerender, make sure this is treated as a remove.
489    executeScript("mutateRel()");
490    EXPECT_EQ(1u, prerenderingSupport()->cancelCount(webPrerender));
491    EXPECT_EQ(2u, prerenderingSupport()->totalCount());
492}
493
494TEST_F(PrerenderingTest, RelNext)
495{
496    initialize("http://www.foo.com/", "prerender/rel_next_prerender.html");
497
498    WebPrerender relNextOnly = prerendererClient()->releaseWebPrerender();
499    EXPECT_EQ(toWebURL("http://rel-next-only.com/"), relNextOnly.url());
500    EXPECT_EQ(PrerenderRelTypeNext, relNextOnly.relTypes());
501
502    WebPrerender relNextAndPrerender = prerendererClient()->releaseWebPrerender();
503    EXPECT_EQ(toWebURL("http://rel-next-and-prerender.com/"), relNextAndPrerender.url());
504    EXPECT_EQ(static_cast<unsigned>(PrerenderRelTypeNext | PrerenderRelTypePrerender), relNextAndPrerender.relTypes());
505}
506
507} // namespace
508