1// Copyright 2013 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 "chrome/browser/search/iframe_source.h"
6
7#include "base/bind.h"
8#include "base/memory/ref_counted_memory.h"
9#include "base/memory/scoped_ptr.h"
10#include "base/message_loop/message_loop.h"
11#include "chrome/browser/search/instant_io_context.h"
12#include "content/public/browser/browser_thread.h"
13#include "content/public/browser/resource_request_info.h"
14#include "content/public/test/mock_resource_context.h"
15#include "content/public/test/test_browser_thread_bundle.h"
16#include "grit/browser_resources.h"
17#include "ipc/ipc_message.h"
18#include "net/base/request_priority.h"
19#include "net/url_request/url_request.h"
20#include "net/url_request/url_request_context.h"
21#include "net/url_request/url_request_test_util.h"
22#include "testing/gtest/include/gtest/gtest.h"
23#include "url/gurl.h"
24
25using content::ResourceType;
26
27const int kNonInstantRendererPID = 0;
28const char kNonInstantOrigin[] = "http://evil";
29const int kInstantRendererPID = 1;
30const char kInstantOrigin[] = "chrome-search://instant";
31const int kInvalidRendererPID = 42;
32
33class TestIframeSource : public IframeSource {
34 public:
35  using IframeSource::GetMimeType;
36  using IframeSource::ShouldServiceRequest;
37  using IframeSource::SendResource;
38  using IframeSource::SendJSWithOrigin;
39
40 protected:
41  virtual std::string GetSource() const OVERRIDE {
42    return "test";
43  }
44
45  virtual bool ServesPath(const std::string& path) const OVERRIDE {
46    return path == "/valid.html" || path == "/valid.js";
47  }
48
49  virtual void StartDataRequest(
50      const std::string& path,
51      int render_process_id,
52      int render_frame_id,
53      const content::URLDataSource::GotDataCallback& callback) OVERRIDE {
54  }
55
56  // RenderFrameHost is hard to mock in concert with everything else, so stub
57  // this method out for testing.
58  virtual bool GetOrigin(
59      int process_id,
60      int render_frame_id,
61      std::string* origin) const OVERRIDE {
62    if (process_id == kInstantRendererPID) {
63      *origin = kInstantOrigin;
64      return true;
65    }
66    if (process_id == kNonInstantRendererPID) {
67      *origin = kNonInstantOrigin;
68      return true;
69    }
70    return false;
71  }
72};
73
74class IframeSourceTest : public testing::Test {
75 public:
76  // net::URLRequest wants to be executed with a message loop that has TYPE_IO.
77  // InstantIOContext needs to be created on the UI thread and have everything
78  // else happen on the IO thread. This setup is a hacky way to satisfy all
79  // those constraints.
80  IframeSourceTest()
81      : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP),
82        resource_context_(&test_url_request_context_),
83        instant_io_context_(NULL),
84        response_(NULL) {
85  }
86
87  TestIframeSource* source() { return source_.get(); }
88
89  std::string response_string() {
90    if (response_.get()) {
91      return std::string(response_->front_as<char>(), response_->size());
92    }
93    return "";
94  }
95
96  scoped_ptr<net::URLRequest> MockRequest(
97      const std::string& url,
98      bool allocate_info,
99      int render_process_id,
100      int render_frame_id) {
101    scoped_ptr<net::URLRequest> request(
102        resource_context_.GetRequestContext()->CreateRequest(
103            GURL(url),
104            net::DEFAULT_PRIORITY,
105            NULL,
106            NULL));
107    if (allocate_info) {
108      content::ResourceRequestInfo::AllocateForTesting(
109          request.get(),
110          content::RESOURCE_TYPE_SUB_FRAME,
111          &resource_context_,
112          render_process_id,
113          render_frame_id,
114          MSG_ROUTING_NONE,
115          false);
116    }
117    return request.Pass();
118  }
119
120  void SendResource(int resource_id) {
121    source()->SendResource(resource_id, callback_);
122  }
123
124  void SendJSWithOrigin(
125      int resource_id,
126      int render_process_id,
127      int render_frame_id) {
128    source()->SendJSWithOrigin(resource_id, render_process_id, render_frame_id,
129                               callback_);
130  }
131
132 private:
133  virtual void SetUp() OVERRIDE {
134    source_.reset(new TestIframeSource());
135    callback_ = base::Bind(&IframeSourceTest::SaveResponse,
136                           base::Unretained(this));
137    instant_io_context_ = new InstantIOContext;
138    InstantIOContext::SetUserDataOnIO(&resource_context_, instant_io_context_);
139    InstantIOContext::AddInstantProcessOnIO(instant_io_context_,
140                                            kInstantRendererPID);
141    response_ = NULL;
142  }
143
144  virtual void TearDown() {
145    source_.reset();
146  }
147
148  void SaveResponse(base::RefCountedMemory* data) {
149    response_ = data;
150  }
151
152  content::TestBrowserThreadBundle thread_bundle_;
153
154  net::TestURLRequestContext test_url_request_context_;
155  content::MockResourceContext resource_context_;
156  scoped_ptr<TestIframeSource> source_;
157  content::URLDataSource::GotDataCallback callback_;
158  scoped_refptr<InstantIOContext> instant_io_context_;
159  scoped_refptr<base::RefCountedMemory> response_;
160};
161
162TEST_F(IframeSourceTest, ShouldServiceRequest) {
163  scoped_ptr<net::URLRequest> request;
164  request = MockRequest("http://test/loader.js", true,
165                        kNonInstantRendererPID, 0);
166  EXPECT_FALSE(source()->ShouldServiceRequest(request.get()));
167  request = MockRequest("chrome-search://bogus/valid.js", true,
168                        kInstantRendererPID, 0);
169  EXPECT_FALSE(source()->ShouldServiceRequest(request.get()));
170  request = MockRequest("chrome-search://test/bogus.js", true,
171                        kInstantRendererPID, 0);
172  EXPECT_FALSE(source()->ShouldServiceRequest(request.get()));
173  request = MockRequest("chrome-search://test/valid.js", true,
174                        kInstantRendererPID, 0);
175  EXPECT_TRUE(source()->ShouldServiceRequest(request.get()));
176  request = MockRequest("chrome-search://test/valid.js", true,
177                        kNonInstantRendererPID, 0);
178  EXPECT_FALSE(source()->ShouldServiceRequest(request.get()));
179  request = MockRequest("chrome-search://test/valid.js", true,
180                        kInvalidRendererPID, 0);
181  EXPECT_FALSE(source()->ShouldServiceRequest(request.get()));
182}
183
184TEST_F(IframeSourceTest, GetMimeType) {
185  // URLDataManagerBackend does not include / in path_and_query.
186  EXPECT_EQ("text/html", source()->GetMimeType("foo.html"));
187  EXPECT_EQ("application/javascript", source()->GetMimeType("foo.js"));
188  EXPECT_EQ("text/css", source()->GetMimeType("foo.css"));
189  EXPECT_EQ("image/png", source()->GetMimeType("foo.png"));
190  EXPECT_EQ("", source()->GetMimeType("bogus"));
191}
192
193TEST_F(IframeSourceTest, SendResource) {
194  SendResource(IDR_MOST_VISITED_TITLE_HTML);
195  EXPECT_FALSE(response_string().empty());
196}
197
198TEST_F(IframeSourceTest, SendJSWithOrigin) {
199  SendJSWithOrigin(IDR_MOST_VISITED_TITLE_JS, kInstantRendererPID, 0);
200  EXPECT_FALSE(response_string().empty());
201  SendJSWithOrigin(IDR_MOST_VISITED_TITLE_JS, kNonInstantRendererPID, 0);
202  EXPECT_FALSE(response_string().empty());
203  SendJSWithOrigin(IDR_MOST_VISITED_TITLE_JS, kInvalidRendererPID, 0);
204  EXPECT_TRUE(response_string().empty());
205}
206