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 "base/file_util.h"
6#include "base/json/json_file_value_serializer.h"
7#include "base/message_loop/message_loop.h"
8#include "base/threading/thread.h"
9#include "chrome/browser/chrome_notification_types.h"
10#include "chrome/browser/extensions/extension_service_unittest.h"
11#include "chrome/browser/extensions/unpacked_installer.h"
12#include "chrome/browser/extensions/user_script_listener.h"
13#include "chrome/common/chrome_paths.h"
14#include "chrome/common/extensions/extension_file_util.h"
15#include "chrome/test/base/testing_profile.h"
16#include "content/public/browser/notification_service.h"
17#include "content/public/browser/resource_controller.h"
18#include "content/public/browser/resource_throttle.h"
19#include "net/url_request/url_request.h"
20#include "net/url_request/url_request_filter.h"
21#include "net/url_request/url_request_test_job.h"
22#include "net/url_request/url_request_test_util.h"
23#include "testing/gtest/include/gtest/gtest.h"
24
25using content::ResourceController;
26using content::ResourceThrottle;
27
28namespace extensions {
29
30namespace {
31
32const char kMatchingUrl[] = "http://google.com/";
33const char kNotMatchingUrl[] = "http://example.com/";
34const char kTestData[] = "Hello, World!";
35
36class ThrottleController : public base::SupportsUserData::Data,
37                           public ResourceController {
38 public:
39  ThrottleController(net::URLRequest* request, ResourceThrottle* throttle)
40      : request_(request),
41        throttle_(throttle) {
42    throttle_->set_controller_for_testing(this);
43  }
44
45  // ResourceController implementation:
46  virtual void Resume() OVERRIDE {
47    request_->Start();
48  }
49  virtual void Cancel() OVERRIDE {
50    NOTREACHED();
51  }
52  virtual void CancelAndIgnore() OVERRIDE {
53    NOTREACHED();
54  }
55  virtual void CancelWithError(int error_code) OVERRIDE {
56    NOTREACHED();
57  }
58
59 private:
60  net::URLRequest* request_;
61  scoped_ptr<ResourceThrottle> throttle_;
62};
63
64// A simple test net::URLRequestJob. We don't care what it does, only that
65// whether it starts and finishes.
66class SimpleTestJob : public net::URLRequestTestJob {
67 public:
68  SimpleTestJob(net::URLRequest* request,
69                net::NetworkDelegate* network_delegate)
70      : net::URLRequestTestJob(request,
71                               network_delegate,
72                               test_headers(),
73                               kTestData,
74                               true) {}
75 private:
76  virtual ~SimpleTestJob() {}
77};
78
79// Yoinked from extension_manifest_unittest.cc.
80DictionaryValue* LoadManifestFile(const base::FilePath path,
81                                  std::string* error) {
82  EXPECT_TRUE(base::PathExists(path));
83  JSONFileValueSerializer serializer(path);
84  return static_cast<DictionaryValue*>(serializer.Deserialize(NULL, error));
85}
86
87scoped_refptr<Extension> LoadExtension(const std::string& filename,
88                                       std::string* error) {
89  base::FilePath path;
90  PathService::Get(chrome::DIR_TEST_DATA, &path);
91  path = path.
92      AppendASCII("extensions").
93      AppendASCII("manifest_tests").
94      AppendASCII(filename.c_str());
95  scoped_ptr<DictionaryValue> value(LoadManifestFile(path, error));
96  if (!value)
97    return NULL;
98  return Extension::Create(path.DirName(), Manifest::UNPACKED, *value,
99                           Extension::NO_FLAGS, error);
100}
101
102class SimpleTestJobProtocolHandler
103    : public net::URLRequestJobFactory::ProtocolHandler {
104 public:
105  SimpleTestJobProtocolHandler() {}
106  virtual ~SimpleTestJobProtocolHandler() {}
107
108  // net::URLRequestJobFactory::ProtocolHandler
109  virtual net::URLRequestJob* MaybeCreateJob(
110      net::URLRequest* request,
111      net::NetworkDelegate* network_delegate) const OVERRIDE {
112    return new SimpleTestJob(request, network_delegate);
113  }
114
115 private:
116  DISALLOW_COPY_AND_ASSIGN(SimpleTestJobProtocolHandler);
117};
118
119}  // namespace
120
121class UserScriptListenerTest : public ExtensionServiceTestBase {
122 public:
123  UserScriptListenerTest() {
124    net::URLRequestFilter::GetInstance()->AddHostnameProtocolHandler(
125        "http", "google.com",
126        scoped_ptr<net::URLRequestJobFactory::ProtocolHandler>(
127            new SimpleTestJobProtocolHandler()));
128    net::URLRequestFilter::GetInstance()->AddHostnameProtocolHandler(
129        "http", "example.com",
130        scoped_ptr<net::URLRequestJobFactory::ProtocolHandler>(
131            new SimpleTestJobProtocolHandler()));
132  }
133
134  virtual ~UserScriptListenerTest() {
135    net::URLRequestFilter::GetInstance()->RemoveHostnameHandler("http",
136                                                                "google.com");
137    net::URLRequestFilter::GetInstance()->RemoveHostnameHandler("http",
138                                                                "example.com");
139  }
140
141  virtual void SetUp() OVERRIDE {
142    ExtensionServiceTestBase::SetUp();
143
144    InitializeEmptyExtensionService();
145    service_->Init();
146    base::MessageLoop::current()->RunUntilIdle();
147
148    listener_ = new UserScriptListener();
149  }
150
151  virtual void TearDown() OVERRIDE {
152    listener_ = NULL;
153    base::MessageLoop::current()->RunUntilIdle();
154    ExtensionServiceTestBase::TearDown();
155  }
156
157 protected:
158  net::TestURLRequest* StartTestRequest(net::URLRequest::Delegate* delegate,
159                                        const std::string& url_string,
160                                        net::TestURLRequestContext* context) {
161    GURL url(url_string);
162    net::TestURLRequest* request =
163        new net::TestURLRequest(url, delegate, context, NULL);
164
165    ResourceThrottle* throttle =
166        listener_->CreateResourceThrottle(url, ResourceType::MAIN_FRAME);
167
168    bool defer = false;
169    if (throttle) {
170      request->SetUserData(NULL, new ThrottleController(request, throttle));
171
172      throttle->WillStartRequest(&defer);
173    }
174
175    if (!defer)
176      request->Start();
177
178    return request;
179  }
180
181  void LoadTestExtension() {
182    base::FilePath test_dir;
183    ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_dir));
184    base::FilePath extension_path = test_dir
185        .AppendASCII("extensions")
186        .AppendASCII("good")
187        .AppendASCII("Extensions")
188        .AppendASCII("behllobkkfkfnphdnhnkndlbkcpglgmj")
189        .AppendASCII("1.0.0.0");
190    UnpackedInstaller::Create(service_)->Load(extension_path);
191  }
192
193  void UnloadTestExtension() {
194    ASSERT_FALSE(service_->extensions()->is_empty());
195    service_->UnloadExtension((*service_->extensions()->begin())->id(),
196                              extension_misc::UNLOAD_REASON_DISABLE);
197  }
198
199  scoped_refptr<UserScriptListener> listener_;
200};
201
202namespace {
203
204TEST_F(UserScriptListenerTest, DelayAndUpdate) {
205  LoadTestExtension();
206  base::MessageLoop::current()->RunUntilIdle();
207
208  net::TestDelegate delegate;
209  net::TestURLRequestContext context;
210  scoped_ptr<net::TestURLRequest> request(
211      StartTestRequest(&delegate, kMatchingUrl, &context));
212  ASSERT_FALSE(request->is_pending());
213
214  content::NotificationService::current()->Notify(
215      chrome::NOTIFICATION_USER_SCRIPTS_UPDATED,
216      content::Source<Profile>(profile_.get()),
217      content::NotificationService::NoDetails());
218  base::MessageLoop::current()->RunUntilIdle();
219  EXPECT_EQ(kTestData, delegate.data_received());
220}
221
222TEST_F(UserScriptListenerTest, DelayAndUnload) {
223  LoadTestExtension();
224  base::MessageLoop::current()->RunUntilIdle();
225
226  net::TestDelegate delegate;
227  net::TestURLRequestContext context;
228  scoped_ptr<net::TestURLRequest> request(
229      StartTestRequest(&delegate, kMatchingUrl, &context));
230  ASSERT_FALSE(request->is_pending());
231
232  UnloadTestExtension();
233  base::MessageLoop::current()->RunUntilIdle();
234
235  // This is still not enough to start delayed requests. We have to notify the
236  // listener that the user scripts have been updated.
237  ASSERT_FALSE(request->is_pending());
238
239  content::NotificationService::current()->Notify(
240      chrome::NOTIFICATION_USER_SCRIPTS_UPDATED,
241      content::Source<Profile>(profile_.get()),
242      content::NotificationService::NoDetails());
243  base::MessageLoop::current()->RunUntilIdle();
244  EXPECT_EQ(kTestData, delegate.data_received());
245}
246
247TEST_F(UserScriptListenerTest, NoDelayNoExtension) {
248  net::TestDelegate delegate;
249  net::TestURLRequestContext context;
250  scoped_ptr<net::TestURLRequest> request(
251      StartTestRequest(&delegate, kMatchingUrl, &context));
252
253  // The request should be started immediately.
254  ASSERT_TRUE(request->is_pending());
255
256  base::MessageLoop::current()->RunUntilIdle();
257  EXPECT_EQ(kTestData, delegate.data_received());
258}
259
260TEST_F(UserScriptListenerTest, NoDelayNotMatching) {
261  LoadTestExtension();
262  base::MessageLoop::current()->RunUntilIdle();
263
264  net::TestDelegate delegate;
265  net::TestURLRequestContext context;
266  scoped_ptr<net::TestURLRequest> request(StartTestRequest(&delegate,
267                                                           kNotMatchingUrl,
268                                                           &context));
269
270  // The request should be started immediately.
271  ASSERT_TRUE(request->is_pending());
272
273  base::MessageLoop::current()->RunUntilIdle();
274  EXPECT_EQ(kTestData, delegate.data_received());
275}
276
277TEST_F(UserScriptListenerTest, MultiProfile) {
278  LoadTestExtension();
279  base::MessageLoop::current()->RunUntilIdle();
280
281  // Fire up a second profile and have it load and extension with a content
282  // script.
283  TestingProfile profile2;
284  std::string error;
285  scoped_refptr<Extension> extension = LoadExtension(
286      "content_script_yahoo.json", &error);
287  ASSERT_TRUE(extension.get());
288
289  content::NotificationService::current()->Notify(
290      chrome::NOTIFICATION_EXTENSION_LOADED,
291      content::Source<Profile>(&profile2),
292      content::Details<Extension>(extension.get()));
293
294  net::TestDelegate delegate;
295  net::TestURLRequestContext context;
296  scoped_ptr<net::TestURLRequest> request(
297      StartTestRequest(&delegate, kMatchingUrl, &context));
298  ASSERT_FALSE(request->is_pending());
299
300  // When the first profile's user scripts are ready, the request should still
301  // be blocked waiting for profile2.
302  content::NotificationService::current()->Notify(
303      chrome::NOTIFICATION_USER_SCRIPTS_UPDATED,
304      content::Source<Profile>(profile_.get()),
305      content::NotificationService::NoDetails());
306  base::MessageLoop::current()->RunUntilIdle();
307  ASSERT_FALSE(request->is_pending());
308  EXPECT_TRUE(delegate.data_received().empty());
309
310  // After profile2 is ready, the request should proceed.
311  content::NotificationService::current()->Notify(
312      chrome::NOTIFICATION_USER_SCRIPTS_UPDATED,
313      content::Source<Profile>(&profile2),
314      content::NotificationService::NoDetails());
315  base::MessageLoop::current()->RunUntilIdle();
316  EXPECT_EQ(kTestData, delegate.data_received());
317}
318
319// Test when the script updated notification occurs before the throttle's
320// WillStartRequest function is called.  This can occur when there are multiple
321// throttles.
322TEST_F(UserScriptListenerTest, ResumeBeforeStart) {
323  LoadTestExtension();
324  base::MessageLoop::current()->RunUntilIdle();
325  net::TestDelegate delegate;
326  net::TestURLRequestContext context;
327  GURL url(kMatchingUrl);
328  scoped_ptr<net::TestURLRequest> request(
329      new net::TestURLRequest(url, &delegate, &context, NULL));
330
331  ResourceThrottle* throttle =
332      listener_->CreateResourceThrottle(url, ResourceType::MAIN_FRAME);
333  ASSERT_TRUE(throttle);
334  request->SetUserData(NULL, new ThrottleController(request.get(), throttle));
335
336  ASSERT_FALSE(request->is_pending());
337
338  content::NotificationService::current()->Notify(
339      chrome::NOTIFICATION_USER_SCRIPTS_UPDATED,
340      content::Source<Profile>(profile_.get()),
341      content::NotificationService::NoDetails());
342  base::MessageLoop::current()->RunUntilIdle();
343
344  bool defer = false;
345  throttle->WillStartRequest(&defer);
346  ASSERT_FALSE(defer);
347}
348
349}  // namespace
350
351}  // namespace extensions
352