// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "webkit/glue/resource_fetcher.h" #include "base/callback.h" #include "base/message_loop.h" #include "base/timer.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebURLResponse.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" #include "webkit/glue/unittest_test_server.h" #include "webkit/tools/test_shell/simple_resource_loader_bridge.h" #include "webkit/tools/test_shell/test_shell_test.h" using WebKit::WebFrame; using WebKit::WebURLRequest; using WebKit::WebURLResponse; using webkit_glue::ResourceFetcher; using webkit_glue::ResourceFetcherWithTimeout; namespace { class ResourceFetcherTests : public TestShellTest { protected: UnittestTestServer test_server_; }; static const int kMaxWaitTimeMs = 5000; class FetcherDelegate { public: FetcherDelegate() : completed_(false), timed_out_(false) { // Start a repeating timer waiting for the download to complete. The // callback has to be a static function, so we hold on to our instance. FetcherDelegate::instance_ = this; StartTimer(); } ResourceFetcher::Callback* NewCallback() { return ::NewCallback(this, &FetcherDelegate::OnURLFetchComplete); } virtual void OnURLFetchComplete(const WebURLResponse& response, const std::string& data) { response_ = response; data_ = data; completed_ = true; timer_.Stop(); MessageLoop::current()->Quit(); } bool completed() const { return completed_; } bool timed_out() const { return timed_out_; } std::string data() const { return data_; } const WebURLResponse& response() const { return response_; } // Wait for the request to complete or timeout. We use a loop here b/c the // testing infrastructure (test_shell) can generate spurious calls to the // MessageLoop's Quit method. void WaitForResponse() { while (!completed() && !timed_out()) MessageLoop::current()->Run(); } void StartTimer() { timer_.Start(base::TimeDelta::FromMilliseconds(kMaxWaitTimeMs), this, &FetcherDelegate::TimerFired); } void TimerFired() { ASSERT_FALSE(completed_); timed_out_ = true; MessageLoop::current()->Quit(); FAIL() << "fetch timed out"; } static FetcherDelegate* instance_; private: base::OneShotTimer timer_; bool completed_; bool timed_out_; WebURLResponse response_; std::string data_; }; FetcherDelegate* FetcherDelegate::instance_ = NULL; // Test a fetch from the test server. // Flaky, http://crbug.com/51622. TEST_F(ResourceFetcherTests, FLAKY_ResourceFetcherDownload) { ASSERT_TRUE(test_server_.Start()); WebFrame* frame = test_shell_->webView()->mainFrame(); GURL url(test_server_.GetURL("files/test_shell/index.html")); scoped_ptr delegate(new FetcherDelegate); scoped_ptr fetcher(new ResourceFetcher( url, frame, WebURLRequest::TargetIsMainFrame, delegate->NewCallback())); delegate->WaitForResponse(); ASSERT_TRUE(delegate->completed()); EXPECT_EQ(delegate->response().httpStatusCode(), 200); std::string text = delegate->data(); EXPECT_TRUE(text.find("What is this page?") != std::string::npos); // Test 404 response. url = test_server_.GetURL("files/thisfiledoesntexist.html"); delegate.reset(new FetcherDelegate); fetcher.reset(new ResourceFetcher(url, frame, WebURLRequest::TargetIsMainFrame, delegate->NewCallback())); delegate->WaitForResponse(); ASSERT_TRUE(delegate->completed()); EXPECT_EQ(delegate->response().httpStatusCode(), 404); EXPECT_TRUE(delegate->data().find("Not Found.") != std::string::npos); } // Flaky, http://crbug.com/51622. TEST_F(ResourceFetcherTests, FLAKY_ResourceFetcherDidFail) { ASSERT_TRUE(test_server_.Start()); WebFrame* frame = test_shell_->webView()->mainFrame(); // Try to fetch a page on a site that doesn't exist. GURL url("http://localhost:1339/doesnotexist"); scoped_ptr delegate(new FetcherDelegate); scoped_ptr fetcher(new ResourceFetcher( url, frame, WebURLRequest::TargetIsMainFrame, delegate->NewCallback())); delegate->WaitForResponse(); // When we fail, we still call the Delegate callback but we pass in empty // values. EXPECT_TRUE(delegate->completed()); EXPECT_TRUE(delegate->response().isNull()); EXPECT_EQ(delegate->data(), std::string()); EXPECT_FALSE(delegate->timed_out()); } TEST_F(ResourceFetcherTests, ResourceFetcherTimeout) { ASSERT_TRUE(test_server_.Start()); WebFrame* frame = test_shell_->webView()->mainFrame(); // Grab a page that takes at least 1 sec to respond, but set the fetcher to // timeout in 0 sec. GURL url(test_server_.GetURL("slow?1")); scoped_ptr delegate(new FetcherDelegate); scoped_ptr fetcher(new ResourceFetcherWithTimeout( url, frame, WebURLRequest::TargetIsMainFrame, 0, delegate->NewCallback())); delegate->WaitForResponse(); // When we timeout, we still call the Delegate callback but we pass in empty // values. EXPECT_TRUE(delegate->completed()); EXPECT_TRUE(delegate->response().isNull()); EXPECT_EQ(delegate->data(), std::string()); EXPECT_FALSE(delegate->timed_out()); } class EvilFetcherDelegate : public FetcherDelegate { public: void SetFetcher(ResourceFetcher* fetcher) { fetcher_.reset(fetcher); } void OnURLFetchComplete(const WebURLResponse& response, const std::string& data) { // Destroy the ResourceFetcher here. We are testing that upon returning // to the ResourceFetcher that it does not crash. fetcher_.reset(); FetcherDelegate::OnURLFetchComplete(response, data); } private: scoped_ptr fetcher_; }; TEST_F(ResourceFetcherTests, ResourceFetcherDeletedInCallback) { ASSERT_TRUE(test_server_.Start()); WebFrame* frame = test_shell_->webView()->mainFrame(); // Grab a page that takes at least 1 sec to respond, but set the fetcher to // timeout in 0 sec. GURL url(test_server_.GetURL("slow?1")); scoped_ptr delegate(new EvilFetcherDelegate); scoped_ptr fetcher(new ResourceFetcherWithTimeout( url, frame, WebURLRequest::TargetIsMainFrame, 0, delegate->NewCallback())); delegate->SetFetcher(fetcher.release()); delegate->WaitForResponse(); EXPECT_FALSE(delegate->timed_out()); } } // namespace