Home | History | Annotate | Download | only in renderer
      1 // Copyright (c) 2012 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 "content/public/renderer/resource_fetcher.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/bind_helpers.h"
      9 #include "base/command_line.h"
     10 #include "base/message_loop/message_loop.h"
     11 #include "base/time/time.h"
     12 #include "base/timer/timer.h"
     13 #include "content/public/common/content_switches.h"
     14 #include "content/public/common/url_constants.h"
     15 #include "content/public/renderer/render_view.h"
     16 #include "content/public/test/test_utils.h"
     17 #include "content/shell/browser/shell.h"
     18 #include "content/test/content_browser_test.h"
     19 #include "content/test/content_browser_test_utils.h"
     20 #include "third_party/WebKit/public/platform/WebURLResponse.h"
     21 #include "third_party/WebKit/public/web/WebFrame.h"
     22 #include "third_party/WebKit/public/web/WebView.h"
     23 
     24 using blink::WebFrame;
     25 using blink::WebURLRequest;
     26 using blink::WebURLResponse;
     27 
     28 namespace content {
     29 
     30 static const int kMaxWaitTimeMs = 5000;
     31 
     32 class FetcherDelegate {
     33  public:
     34   FetcherDelegate()
     35       : completed_(false),
     36         timed_out_(false) {
     37     // Start a repeating timer waiting for the download to complete.  The
     38     // callback has to be a static function, so we hold on to our instance.
     39     FetcherDelegate::instance_ = this;
     40     StartTimer();
     41   }
     42 
     43   virtual ~FetcherDelegate() {}
     44 
     45   ResourceFetcher::Callback NewCallback() {
     46     return base::Bind(&FetcherDelegate::OnURLFetchComplete,
     47                       base::Unretained(this));
     48   }
     49 
     50   virtual void OnURLFetchComplete(const WebURLResponse& response,
     51                                   const std::string& data) {
     52     response_ = response;
     53     data_ = data;
     54     completed_ = true;
     55     timer_.Stop();
     56     if (!timed_out_)
     57       quit_task_.Run();
     58   }
     59 
     60   bool completed() const { return completed_; }
     61   bool timed_out() const { return timed_out_; }
     62 
     63   std::string data() const { return data_; }
     64   const WebURLResponse& response() const { return response_; }
     65 
     66   // Wait for the request to complete or timeout.
     67   void WaitForResponse() {
     68     scoped_refptr<MessageLoopRunner> runner = new MessageLoopRunner;
     69     quit_task_ = runner->QuitClosure();
     70     runner->Run();
     71   }
     72 
     73   void StartTimer() {
     74     timer_.Start(FROM_HERE,
     75                  base::TimeDelta::FromMilliseconds(kMaxWaitTimeMs),
     76                  this,
     77                  &FetcherDelegate::TimerFired);
     78   }
     79 
     80   void TimerFired() {
     81     ASSERT_FALSE(completed_);
     82 
     83     timed_out_ = true;
     84     if (!completed_)
     85       quit_task_.Run();
     86     FAIL() << "fetch timed out";
     87   }
     88 
     89   static FetcherDelegate* instance_;
     90 
     91  private:
     92   base::OneShotTimer<FetcherDelegate> timer_;
     93   bool completed_;
     94   bool timed_out_;
     95   WebURLResponse response_;
     96   std::string data_;
     97   base::Closure quit_task_;
     98 };
     99 
    100 FetcherDelegate* FetcherDelegate::instance_ = NULL;
    101 
    102 class EvilFetcherDelegate : public FetcherDelegate {
    103  public:
    104   virtual ~EvilFetcherDelegate() {}
    105 
    106   void SetFetcher(ResourceFetcher* fetcher) {
    107     fetcher_.reset(fetcher);
    108   }
    109 
    110   virtual void OnURLFetchComplete(const WebURLResponse& response,
    111                                   const std::string& data) OVERRIDE {
    112     FetcherDelegate::OnURLFetchComplete(response, data);
    113 
    114     // Destroy the ResourceFetcher here.  We are testing that upon returning
    115     // to the ResourceFetcher that it does not crash.  This must be done after
    116     // calling FetcherDelegate::OnURLFetchComplete, since deleting the fetcher
    117     // invalidates |response| and |data|.
    118     fetcher_.reset();
    119   }
    120 
    121  private:
    122   scoped_ptr<ResourceFetcher> fetcher_;
    123 };
    124 
    125 class ResourceFetcherTests : public ContentBrowserTest {
    126  public:
    127   virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
    128     command_line->AppendSwitch(switches::kSingleProcess);
    129 #if defined(OS_WIN) && defined(USE_AURA)
    130     // Don't want to try to create a GPU process.
    131     command_line->AppendSwitch(switches::kDisableAcceleratedCompositing);
    132 #endif
    133   }
    134 
    135   RenderView* GetRenderView() {
    136     // We could have the test on the UI thread get the WebContent's routing ID,
    137     // but we know this will be the first RV so skip that and just hardcode it.
    138     return RenderView::FromRoutingID(1);
    139   }
    140 
    141   void ResourceFetcherDownloadOnRenderer(const GURL& url) {
    142     WebFrame* frame = GetRenderView()->GetWebView()->mainFrame();
    143 
    144     scoped_ptr<FetcherDelegate> delegate(new FetcherDelegate);
    145     scoped_ptr<ResourceFetcher> fetcher(ResourceFetcher::Create(
    146         url, frame, WebURLRequest::TargetIsMainFrame, delegate->NewCallback()));
    147 
    148     delegate->WaitForResponse();
    149 
    150     ASSERT_TRUE(delegate->completed());
    151     EXPECT_EQ(delegate->response().httpStatusCode(), 200);
    152     std::string text = delegate->data();
    153     EXPECT_TRUE(text.find("Basic html test.") != std::string::npos);
    154   }
    155 
    156   void ResourceFetcher404OnRenderer(const GURL& url) {
    157     WebFrame* frame = GetRenderView()->GetWebView()->mainFrame();
    158 
    159     scoped_ptr<FetcherDelegate> delegate(new FetcherDelegate);
    160     scoped_ptr<ResourceFetcher> fetcher(ResourceFetcher::Create(
    161         url, frame, WebURLRequest::TargetIsMainFrame, delegate->NewCallback()));
    162 
    163     delegate->WaitForResponse();
    164 
    165     ASSERT_TRUE(delegate->completed());
    166     EXPECT_EQ(delegate->response().httpStatusCode(), 404);
    167     EXPECT_TRUE(delegate->data().find("Not Found.") != std::string::npos);
    168   }
    169 
    170   void ResourceFetcherDidFailOnRenderer() {
    171     WebFrame* frame = GetRenderView()->GetWebView()->mainFrame();
    172 
    173     // Try to fetch a page on a site that doesn't exist.
    174     GURL url("http://localhost:1339/doesnotexist");
    175     scoped_ptr<FetcherDelegate> delegate(new FetcherDelegate);
    176     scoped_ptr<ResourceFetcher> fetcher(ResourceFetcher::Create(
    177         url, frame, WebURLRequest::TargetIsMainFrame, delegate->NewCallback()));
    178 
    179     delegate->WaitForResponse();
    180 
    181     // When we fail, we still call the Delegate callback but we pass in empty
    182     // values.
    183     EXPECT_TRUE(delegate->completed());
    184     EXPECT_TRUE(delegate->response().isNull());
    185     EXPECT_EQ(delegate->data(), std::string());
    186     EXPECT_FALSE(delegate->timed_out());
    187   }
    188 
    189   void ResourceFetcherTimeoutOnRenderer(const GURL& url) {
    190     WebFrame* frame = GetRenderView()->GetWebView()->mainFrame();
    191 
    192     scoped_ptr<FetcherDelegate> delegate(new FetcherDelegate);
    193     scoped_ptr<ResourceFetcher> fetcher(ResourceFetcher::Create(
    194         url, frame, WebURLRequest::TargetIsMainFrame,
    195         delegate->NewCallback()));
    196     fetcher->SetTimeout(base::TimeDelta());
    197 
    198     delegate->WaitForResponse();
    199 
    200     // When we timeout, we still call the Delegate callback but we pass in empty
    201     // values.
    202     EXPECT_TRUE(delegate->completed());
    203     EXPECT_TRUE(delegate->response().isNull());
    204     EXPECT_EQ(delegate->data(), std::string());
    205     EXPECT_FALSE(delegate->timed_out());
    206   }
    207 
    208   void ResourceFetcherDeletedInCallbackOnRenderer(const GURL& url) {
    209     WebFrame* frame = GetRenderView()->GetWebView()->mainFrame();
    210 
    211     scoped_ptr<EvilFetcherDelegate> delegate(new EvilFetcherDelegate);
    212     scoped_ptr<ResourceFetcher> fetcher(ResourceFetcher::Create(
    213         url, frame, WebURLRequest::TargetIsMainFrame,
    214         delegate->NewCallback()));
    215     fetcher->SetTimeout(base::TimeDelta());
    216     delegate->SetFetcher(fetcher.release());
    217 
    218     delegate->WaitForResponse();
    219     EXPECT_FALSE(delegate->timed_out());
    220   }
    221 };
    222 
    223 // Test a fetch from the test server.
    224 // If this flakes, use http://crbug.com/51622.
    225 IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, ResourceFetcherDownload) {
    226   // Need to spin up the renderer.
    227   NavigateToURL(shell(), GURL(kAboutBlankURL));
    228 
    229   ASSERT_TRUE(test_server()->Start());
    230   GURL url(test_server()->GetURL("files/simple_page.html"));
    231 
    232   PostTaskToInProcessRendererAndWait(
    233         base::Bind(&ResourceFetcherTests::ResourceFetcherDownloadOnRenderer,
    234                    base::Unretained(this), url));
    235 }
    236 
    237 IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, ResourceFetcher404) {
    238   // Need to spin up the renderer.
    239   NavigateToURL(shell(), GURL(kAboutBlankURL));
    240 
    241   // Test 404 response.
    242   ASSERT_TRUE(test_server()->Start());
    243   GURL url = test_server()->GetURL("files/thisfiledoesntexist.html");
    244 
    245   PostTaskToInProcessRendererAndWait(
    246         base::Bind(&ResourceFetcherTests::ResourceFetcher404OnRenderer,
    247                    base::Unretained(this), url));
    248 }
    249 
    250 // If this flakes, use http://crbug.com/51622.
    251 IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, ResourceFetcherDidFail) {
    252   // Need to spin up the renderer.
    253   NavigateToURL(shell(), GURL(kAboutBlankURL));
    254 
    255   PostTaskToInProcessRendererAndWait(
    256         base::Bind(&ResourceFetcherTests::ResourceFetcherDidFailOnRenderer,
    257                    base::Unretained(this)));
    258 }
    259 
    260 IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, ResourceFetcherTimeout) {
    261   // Need to spin up the renderer.
    262   NavigateToURL(shell(), GURL(kAboutBlankURL));
    263 
    264   // Grab a page that takes at least 1 sec to respond, but set the fetcher to
    265   // timeout in 0 sec.
    266   ASSERT_TRUE(test_server()->Start());
    267   GURL url(test_server()->GetURL("slow?1"));
    268 
    269   PostTaskToInProcessRendererAndWait(
    270         base::Bind(&ResourceFetcherTests::ResourceFetcherTimeoutOnRenderer,
    271                    base::Unretained(this), url));
    272 }
    273 
    274 IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, ResourceFetcherDeletedInCallback) {
    275   // Need to spin up the renderer.
    276   NavigateToURL(shell(), GURL(kAboutBlankURL));
    277 
    278   // Grab a page that takes at least 1 sec to respond, but set the fetcher to
    279   // timeout in 0 sec.
    280   ASSERT_TRUE(test_server()->Start());
    281   GURL url(test_server()->GetURL("slow?1"));
    282 
    283   PostTaskToInProcessRendererAndWait(
    284         base::Bind(
    285             &ResourceFetcherTests::ResourceFetcherDeletedInCallbackOnRenderer,
    286             base::Unretained(this), url));
    287 }
    288 
    289 }  // namespace content
    290