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