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