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