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