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 "chrome/browser/extensions/extension_apitest.h" 6 7 #include "base/strings/string_split.h" 8 #include "base/strings/string_util.h" 9 #include "base/strings/stringprintf.h" 10 #include "chrome/browser/chrome_notification_types.h" 11 #include "chrome/browser/extensions/api/test/test_api.h" 12 #include "chrome/browser/extensions/extension_service.h" 13 #include "chrome/browser/extensions/extension_system.h" 14 #include "chrome/browser/extensions/unpacked_installer.h" 15 #include "chrome/browser/profiles/profile.h" 16 #include "chrome/browser/ui/browser.h" 17 #include "chrome/browser/ui/extensions/application_launch.h" 18 #include "chrome/test/base/ui_test_utils.h" 19 #include "content/public/browser/notification_registrar.h" 20 #include "content/public/browser/notification_service.h" 21 #include "extensions/common/extension.h" 22 #include "net/base/escape.h" 23 #include "net/base/net_util.h" 24 #include "net/test/embedded_test_server/embedded_test_server.h" 25 #include "net/test/embedded_test_server/http_request.h" 26 #include "net/test/embedded_test_server/http_response.h" 27 #include "net/test/spawned_test_server/spawned_test_server.h" 28 29 namespace { 30 31 const char kTestCustomArg[] = "customArg"; 32 const char kTestServerPort[] = "testServer.port"; 33 const char kTestDataDirectory[] = "testDataDirectory"; 34 const char kTestWebSocketPort[] = "testWebSocketPort"; 35 const char kSpawnedTestServerPort[] = "spawnedTestServer.port"; 36 37 scoped_ptr<net::test_server::HttpResponse> HandleServerRedirectRequest( 38 const net::test_server::HttpRequest& request) { 39 if (!StartsWithASCII(request.relative_url, "/server-redirect?", true)) 40 return scoped_ptr<net::test_server::HttpResponse>(); 41 42 size_t query_string_pos = request.relative_url.find('?'); 43 std::string redirect_target = 44 request.relative_url.substr(query_string_pos + 1); 45 46 scoped_ptr<net::test_server::BasicHttpResponse> http_response( 47 new net::test_server::BasicHttpResponse); 48 http_response->set_code(net::HTTP_MOVED_PERMANENTLY); 49 http_response->AddCustomHeader("Location", redirect_target); 50 return http_response.PassAs<net::test_server::HttpResponse>(); 51 } 52 53 scoped_ptr<net::test_server::HttpResponse> HandleEchoHeaderRequest( 54 const net::test_server::HttpRequest& request) { 55 if (!StartsWithASCII(request.relative_url, "/echoheader?", true)) 56 return scoped_ptr<net::test_server::HttpResponse>(); 57 58 size_t query_string_pos = request.relative_url.find('?'); 59 std::string header_name = 60 request.relative_url.substr(query_string_pos + 1); 61 62 std::string header_value; 63 std::map<std::string, std::string>::const_iterator it = request.headers.find( 64 header_name); 65 if (it != request.headers.end()) 66 header_value = it->second; 67 68 scoped_ptr<net::test_server::BasicHttpResponse> http_response( 69 new net::test_server::BasicHttpResponse); 70 http_response->set_code(net::HTTP_OK); 71 http_response->set_content(header_value); 72 return http_response.PassAs<net::test_server::HttpResponse>(); 73 } 74 75 scoped_ptr<net::test_server::HttpResponse> HandleSetCookieRequest( 76 const net::test_server::HttpRequest& request) { 77 if (!StartsWithASCII(request.relative_url, "/set-cookie?", true)) 78 return scoped_ptr<net::test_server::HttpResponse>(); 79 80 scoped_ptr<net::test_server::BasicHttpResponse> http_response( 81 new net::test_server::BasicHttpResponse); 82 http_response->set_code(net::HTTP_OK); 83 84 size_t query_string_pos = request.relative_url.find('?'); 85 std::string cookie_value = 86 request.relative_url.substr(query_string_pos + 1); 87 88 std::vector<std::string> cookies; 89 base::SplitString(cookie_value, '&', &cookies); 90 91 for (size_t i = 0; i < cookies.size(); i++) 92 http_response->AddCustomHeader("Set-Cookie", cookies[i]); 93 94 return http_response.PassAs<net::test_server::HttpResponse>(); 95 } 96 97 scoped_ptr<net::test_server::HttpResponse> HandleSetHeaderRequest( 98 const net::test_server::HttpRequest& request) { 99 if (!StartsWithASCII(request.relative_url, "/set-header?", true)) 100 return scoped_ptr<net::test_server::HttpResponse>(); 101 102 size_t query_string_pos = request.relative_url.find('?'); 103 std::string escaped_header = 104 request.relative_url.substr(query_string_pos + 1); 105 106 std::string header = 107 net::UnescapeURLComponent(escaped_header, 108 net::UnescapeRule::NORMAL | 109 net::UnescapeRule::SPACES | 110 net::UnescapeRule::URL_SPECIAL_CHARS); 111 112 size_t colon_pos = header.find(':'); 113 if (colon_pos == std::string::npos) 114 return scoped_ptr<net::test_server::HttpResponse>(); 115 116 std::string header_name = header.substr(0, colon_pos); 117 // Skip space after colon. 118 std::string header_value = header.substr(colon_pos + 2); 119 120 scoped_ptr<net::test_server::BasicHttpResponse> http_response( 121 new net::test_server::BasicHttpResponse); 122 http_response->set_code(net::HTTP_OK); 123 http_response->AddCustomHeader(header_name, header_value); 124 return http_response.PassAs<net::test_server::HttpResponse>(); 125 } 126 127 }; // namespace 128 129 ExtensionApiTest::ExtensionApiTest() { 130 embedded_test_server()->RegisterRequestHandler( 131 base::Bind(&HandleServerRedirectRequest)); 132 embedded_test_server()->RegisterRequestHandler( 133 base::Bind(&HandleEchoHeaderRequest)); 134 embedded_test_server()->RegisterRequestHandler( 135 base::Bind(&HandleSetCookieRequest)); 136 embedded_test_server()->RegisterRequestHandler( 137 base::Bind(&HandleSetHeaderRequest)); 138 } 139 140 ExtensionApiTest::~ExtensionApiTest() {} 141 142 ExtensionApiTest::ResultCatcher::ResultCatcher() 143 : profile_restriction_(NULL), 144 waiting_(false) { 145 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_TEST_PASSED, 146 content::NotificationService::AllSources()); 147 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_TEST_FAILED, 148 content::NotificationService::AllSources()); 149 } 150 151 ExtensionApiTest::ResultCatcher::~ResultCatcher() { 152 } 153 154 bool ExtensionApiTest::ResultCatcher::GetNextResult() { 155 // Depending on the tests, multiple results can come in from a single call 156 // to RunMessageLoop(), so we maintain a queue of results and just pull them 157 // off as the test calls this, going to the run loop only when the queue is 158 // empty. 159 if (results_.empty()) { 160 waiting_ = true; 161 content::RunMessageLoop(); 162 waiting_ = false; 163 } 164 165 if (!results_.empty()) { 166 bool ret = results_.front(); 167 results_.pop_front(); 168 message_ = messages_.front(); 169 messages_.pop_front(); 170 return ret; 171 } 172 173 NOTREACHED(); 174 return false; 175 } 176 177 void ExtensionApiTest::ResultCatcher::Observe( 178 int type, const content::NotificationSource& source, 179 const content::NotificationDetails& details) { 180 if (profile_restriction_ && 181 content::Source<Profile>(source).ptr() != profile_restriction_) { 182 return; 183 } 184 185 switch (type) { 186 case chrome::NOTIFICATION_EXTENSION_TEST_PASSED: 187 VLOG(1) << "Got EXTENSION_TEST_PASSED notification."; 188 results_.push_back(true); 189 messages_.push_back(std::string()); 190 if (waiting_) 191 base::MessageLoopForUI::current()->Quit(); 192 break; 193 194 case chrome::NOTIFICATION_EXTENSION_TEST_FAILED: 195 VLOG(1) << "Got EXTENSION_TEST_FAILED notification."; 196 results_.push_back(false); 197 messages_.push_back(*(content::Details<std::string>(details).ptr())); 198 if (waiting_) 199 base::MessageLoopForUI::current()->Quit(); 200 break; 201 202 default: 203 NOTREACHED(); 204 } 205 } 206 207 void ExtensionApiTest::SetUpInProcessBrowserTestFixture() { 208 DCHECK(!test_config_.get()) << "Previous test did not clear config state."; 209 test_config_.reset(new DictionaryValue()); 210 test_config_->SetString(kTestDataDirectory, 211 net::FilePathToFileURL(test_data_dir_).spec()); 212 test_config_->SetInteger(kTestWebSocketPort, 0); 213 extensions::TestGetConfigFunction::set_test_config_state( 214 test_config_.get()); 215 } 216 217 void ExtensionApiTest::TearDownInProcessBrowserTestFixture() { 218 extensions::TestGetConfigFunction::set_test_config_state(NULL); 219 test_config_.reset(NULL); 220 } 221 222 bool ExtensionApiTest::RunExtensionTest(const std::string& extension_name) { 223 return RunExtensionTestImpl( 224 extension_name, std::string(), NULL, kFlagEnableFileAccess); 225 } 226 227 bool ExtensionApiTest::RunExtensionTestIncognito( 228 const std::string& extension_name) { 229 return RunExtensionTestImpl(extension_name, 230 std::string(), 231 NULL, 232 kFlagEnableIncognito | kFlagEnableFileAccess); 233 } 234 235 bool ExtensionApiTest::RunExtensionTestIgnoreManifestWarnings( 236 const std::string& extension_name) { 237 return RunExtensionTestImpl( 238 extension_name, std::string(), NULL, kFlagIgnoreManifestWarnings); 239 } 240 241 bool ExtensionApiTest::RunExtensionTestAllowOldManifestVersion( 242 const std::string& extension_name) { 243 return RunExtensionTestImpl( 244 extension_name, 245 std::string(), 246 NULL, 247 kFlagEnableFileAccess | kFlagAllowOldManifestVersions); 248 } 249 250 bool ExtensionApiTest::RunComponentExtensionTest( 251 const std::string& extension_name) { 252 return RunExtensionTestImpl(extension_name, 253 std::string(), 254 NULL, 255 kFlagEnableFileAccess | kFlagLoadAsComponent); 256 } 257 258 bool ExtensionApiTest::RunExtensionTestNoFileAccess( 259 const std::string& extension_name) { 260 return RunExtensionTestImpl(extension_name, std::string(), NULL, kFlagNone); 261 } 262 263 bool ExtensionApiTest::RunExtensionTestIncognitoNoFileAccess( 264 const std::string& extension_name) { 265 return RunExtensionTestImpl( 266 extension_name, std::string(), NULL, kFlagEnableIncognito); 267 } 268 269 bool ExtensionApiTest::RunExtensionSubtest(const std::string& extension_name, 270 const std::string& page_url) { 271 return RunExtensionSubtest(extension_name, page_url, kFlagEnableFileAccess); 272 } 273 274 bool ExtensionApiTest::RunExtensionSubtest(const std::string& extension_name, 275 const std::string& page_url, 276 int flags) { 277 DCHECK(!page_url.empty()) << "Argument page_url is required."; 278 // See http://crbug.com/177163 for details. 279 #if defined(OS_WIN) && !defined(NDEBUG) 280 LOG(WARNING) << "Workaround for 177163, prematurely returning"; 281 return true; 282 #endif 283 return RunExtensionTestImpl(extension_name, page_url, NULL, flags); 284 } 285 286 287 bool ExtensionApiTest::RunPageTest(const std::string& page_url) { 288 return RunExtensionSubtest(std::string(), page_url); 289 } 290 291 bool ExtensionApiTest::RunPageTest(const std::string& page_url, 292 int flags) { 293 return RunExtensionSubtest(std::string(), page_url, flags); 294 } 295 296 bool ExtensionApiTest::RunPlatformAppTest(const std::string& extension_name) { 297 return RunExtensionTestImpl( 298 extension_name, std::string(), NULL, kFlagLaunchPlatformApp); 299 } 300 301 bool ExtensionApiTest::RunPlatformAppTestWithArg( 302 const std::string& extension_name, const char* custom_arg) { 303 return RunExtensionTestImpl( 304 extension_name, std::string(), custom_arg, kFlagLaunchPlatformApp); 305 } 306 307 bool ExtensionApiTest::RunPlatformAppTestWithFlags( 308 const std::string& extension_name, int flags) { 309 return RunExtensionTestImpl( 310 extension_name, std::string(), NULL, flags | kFlagLaunchPlatformApp); 311 } 312 313 // Load |extension_name| extension and/or |page_url| and wait for 314 // PASSED or FAILED notification. 315 bool ExtensionApiTest::RunExtensionTestImpl(const std::string& extension_name, 316 const std::string& page_url, 317 const char* custom_arg, 318 int flags) { 319 bool load_as_component = (flags & kFlagLoadAsComponent) != 0; 320 bool launch_platform_app = (flags & kFlagLaunchPlatformApp) != 0; 321 bool use_incognito = (flags & kFlagUseIncognito) != 0; 322 323 if (custom_arg && custom_arg[0]) 324 test_config_->SetString(kTestCustomArg, custom_arg); 325 326 ResultCatcher catcher; 327 DCHECK(!extension_name.empty() || !page_url.empty()) << 328 "extension_name and page_url cannot both be empty"; 329 330 const extensions::Extension* extension = NULL; 331 if (!extension_name.empty()) { 332 base::FilePath extension_path = test_data_dir_.AppendASCII(extension_name); 333 if (load_as_component) { 334 extension = LoadExtensionAsComponent(extension_path); 335 } else { 336 int browser_test_flags = ExtensionBrowserTest::kFlagNone; 337 if (flags & kFlagEnableIncognito) 338 browser_test_flags |= ExtensionBrowserTest::kFlagEnableIncognito; 339 if (flags & kFlagEnableFileAccess) 340 browser_test_flags |= ExtensionBrowserTest::kFlagEnableFileAccess; 341 if (flags & kFlagIgnoreManifestWarnings) 342 browser_test_flags |= ExtensionBrowserTest::kFlagIgnoreManifestWarnings; 343 if (flags & kFlagAllowOldManifestVersions) { 344 browser_test_flags |= 345 ExtensionBrowserTest::kFlagAllowOldManifestVersions; 346 } 347 extension = LoadExtensionWithFlags(extension_path, browser_test_flags); 348 } 349 if (!extension) { 350 message_ = "Failed to load extension."; 351 return false; 352 } 353 } 354 355 // If there is a page_url to load, navigate it. 356 if (!page_url.empty()) { 357 GURL url = GURL(page_url); 358 359 // Note: We use is_valid() here in the expectation that the provided url 360 // may lack a scheme & host and thus be a relative url within the loaded 361 // extension. 362 if (!url.is_valid()) { 363 DCHECK(!extension_name.empty()) << 364 "Relative page_url given with no extension_name"; 365 366 url = extension->GetResourceURL(page_url); 367 } 368 369 if (use_incognito) 370 ui_test_utils::OpenURLOffTheRecord(browser()->profile(), url); 371 else 372 ui_test_utils::NavigateToURL(browser(), url); 373 } else if (launch_platform_app) { 374 AppLaunchParams params(browser()->profile(), 375 extension, 376 extensions::LAUNCH_CONTAINER_NONE, 377 NEW_WINDOW); 378 params.command_line = CommandLine::ForCurrentProcess(); 379 OpenApplication(params); 380 } 381 382 if (!catcher.GetNextResult()) { 383 message_ = catcher.message(); 384 return false; 385 } 386 387 return true; 388 } 389 390 // Test that exactly one extension is loaded, and return it. 391 const extensions::Extension* ExtensionApiTest::GetSingleLoadedExtension() { 392 ExtensionService* service = extensions::ExtensionSystem::Get( 393 browser()->profile())->extension_service(); 394 395 const extensions::Extension* extension = NULL; 396 for (ExtensionSet::const_iterator it = service->extensions()->begin(); 397 it != service->extensions()->end(); ++it) { 398 // Ignore any component extensions. They are automatically loaded into all 399 // profiles and aren't the extension we're looking for here. 400 if ((*it)->location() == extensions::Manifest::COMPONENT) 401 continue; 402 403 if (extension != NULL) { 404 // TODO(yoz): this is misleading; it counts component extensions. 405 message_ = base::StringPrintf( 406 "Expected only one extension to be present. Found %u.", 407 static_cast<unsigned>(service->extensions()->size())); 408 return NULL; 409 } 410 411 extension = it->get(); 412 } 413 414 if (!extension) { 415 message_ = "extension pointer is NULL."; 416 return NULL; 417 } 418 return extension; 419 } 420 421 bool ExtensionApiTest::StartEmbeddedTestServer() { 422 if (!embedded_test_server()->InitializeAndWaitUntilReady()) 423 return false; 424 425 // Build a dictionary of values that tests can use to build URLs that 426 // access the test server and local file system. Tests can see these values 427 // using the extension API function chrome.test.getConfig(). 428 test_config_->SetInteger(kTestServerPort, 429 embedded_test_server()->port()); 430 431 return true; 432 } 433 434 bool ExtensionApiTest::StartWebSocketServer( 435 const base::FilePath& root_directory) { 436 websocket_server_.reset(new net::SpawnedTestServer( 437 net::SpawnedTestServer::TYPE_WS, 438 net::SpawnedTestServer::kLocalhost, 439 root_directory)); 440 441 if (!websocket_server_->Start()) 442 return false; 443 444 test_config_->SetInteger(kTestWebSocketPort, 445 websocket_server_->host_port_pair().port()); 446 447 return true; 448 } 449 450 bool ExtensionApiTest::StartSpawnedTestServer() { 451 if (!test_server()->Start()) 452 return false; 453 454 // Build a dictionary of values that tests can use to build URLs that 455 // access the test server and local file system. Tests can see these values 456 // using the extension API function chrome.test.getConfig(). 457 test_config_->SetInteger(kSpawnedTestServerPort, 458 test_server()->host_port_pair().port()); 459 460 return true; 461 } 462 463 void ExtensionApiTest::SetUpCommandLine(CommandLine* command_line) { 464 ExtensionBrowserTest::SetUpCommandLine(command_line); 465 test_data_dir_ = test_data_dir_.AppendASCII("api_test"); 466 } 467