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/extensions/extension_service.h" 11 #include "chrome/browser/extensions/unpacked_installer.h" 12 #include "chrome/browser/profiles/profile.h" 13 #include "chrome/browser/ui/browser.h" 14 #include "chrome/browser/ui/extensions/application_launch.h" 15 #include "chrome/test/base/ui_test_utils.h" 16 #include "extensions/browser/api/test/test_api.h" 17 #include "extensions/browser/extension_system.h" 18 #include "extensions/common/extension.h" 19 #include "extensions/common/extension_set.h" 20 #include "extensions/test/result_catcher.h" 21 #include "net/base/escape.h" 22 #include "net/base/filename_util.h" 23 #include "net/test/embedded_test_server/embedded_test_server.h" 24 #include "net/test/embedded_test_server/http_request.h" 25 #include "net/test/embedded_test_server/http_response.h" 26 #include "net/test/spawned_test_server/spawned_test_server.h" 27 28 namespace { 29 30 const char kTestCustomArg[] = "customArg"; 31 const char kTestServerPort[] = "testServer.port"; 32 const char kTestDataDirectory[] = "testDataDirectory"; 33 const char kTestWebSocketPort[] = "testWebSocketPort"; 34 const char kFtpServerPort[] = "ftpServer.port"; 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 void ExtensionApiTest::SetUpInProcessBrowserTestFixture() { 143 DCHECK(!test_config_.get()) << "Previous test did not clear config state."; 144 test_config_.reset(new base::DictionaryValue()); 145 test_config_->SetString(kTestDataDirectory, 146 net::FilePathToFileURL(test_data_dir_).spec()); 147 test_config_->SetInteger(kTestWebSocketPort, 0); 148 extensions::TestGetConfigFunction::set_test_config_state( 149 test_config_.get()); 150 } 151 152 void ExtensionApiTest::TearDownInProcessBrowserTestFixture() { 153 extensions::TestGetConfigFunction::set_test_config_state(NULL); 154 test_config_.reset(NULL); 155 } 156 157 bool ExtensionApiTest::RunExtensionTest(const std::string& extension_name) { 158 return RunExtensionTestImpl( 159 extension_name, std::string(), NULL, kFlagEnableFileAccess); 160 } 161 162 bool ExtensionApiTest::RunExtensionTestIncognito( 163 const std::string& extension_name) { 164 return RunExtensionTestImpl(extension_name, 165 std::string(), 166 NULL, 167 kFlagEnableIncognito | kFlagEnableFileAccess); 168 } 169 170 bool ExtensionApiTest::RunExtensionTestIgnoreManifestWarnings( 171 const std::string& extension_name) { 172 return RunExtensionTestImpl( 173 extension_name, std::string(), NULL, kFlagIgnoreManifestWarnings); 174 } 175 176 bool ExtensionApiTest::RunExtensionTestAllowOldManifestVersion( 177 const std::string& extension_name) { 178 return RunExtensionTestImpl( 179 extension_name, 180 std::string(), 181 NULL, 182 kFlagEnableFileAccess | kFlagAllowOldManifestVersions); 183 } 184 185 bool ExtensionApiTest::RunComponentExtensionTest( 186 const std::string& extension_name) { 187 return RunExtensionTestImpl(extension_name, 188 std::string(), 189 NULL, 190 kFlagEnableFileAccess | kFlagLoadAsComponent); 191 } 192 193 bool ExtensionApiTest::RunExtensionTestNoFileAccess( 194 const std::string& extension_name) { 195 return RunExtensionTestImpl(extension_name, std::string(), NULL, kFlagNone); 196 } 197 198 bool ExtensionApiTest::RunExtensionTestIncognitoNoFileAccess( 199 const std::string& extension_name) { 200 return RunExtensionTestImpl( 201 extension_name, std::string(), NULL, kFlagEnableIncognito); 202 } 203 204 bool ExtensionApiTest::RunExtensionSubtest(const std::string& extension_name, 205 const std::string& page_url) { 206 return RunExtensionSubtest(extension_name, page_url, kFlagEnableFileAccess); 207 } 208 209 bool ExtensionApiTest::RunExtensionSubtest(const std::string& extension_name, 210 const std::string& page_url, 211 int flags) { 212 DCHECK(!page_url.empty()) << "Argument page_url is required."; 213 // See http://crbug.com/177163 for details. 214 #if defined(OS_WIN) && !defined(NDEBUG) 215 LOG(WARNING) << "Workaround for 177163, prematurely returning"; 216 return true; 217 #else 218 return RunExtensionTestImpl(extension_name, page_url, NULL, flags); 219 #endif 220 } 221 222 223 bool ExtensionApiTest::RunPageTest(const std::string& page_url) { 224 return RunExtensionSubtest(std::string(), page_url); 225 } 226 227 bool ExtensionApiTest::RunPageTest(const std::string& page_url, 228 int flags) { 229 return RunExtensionSubtest(std::string(), page_url, flags); 230 } 231 232 bool ExtensionApiTest::RunPlatformAppTest(const std::string& extension_name) { 233 return RunExtensionTestImpl( 234 extension_name, std::string(), NULL, kFlagLaunchPlatformApp); 235 } 236 237 bool ExtensionApiTest::RunPlatformAppTestWithArg( 238 const std::string& extension_name, const char* custom_arg) { 239 return RunExtensionTestImpl( 240 extension_name, std::string(), custom_arg, kFlagLaunchPlatformApp); 241 } 242 243 bool ExtensionApiTest::RunPlatformAppTestWithFlags( 244 const std::string& extension_name, int flags) { 245 return RunExtensionTestImpl( 246 extension_name, std::string(), NULL, flags | kFlagLaunchPlatformApp); 247 } 248 249 // Load |extension_name| extension and/or |page_url| and wait for 250 // PASSED or FAILED notification. 251 bool ExtensionApiTest::RunExtensionTestImpl(const std::string& extension_name, 252 const std::string& page_url, 253 const char* custom_arg, 254 int flags) { 255 bool load_as_component = (flags & kFlagLoadAsComponent) != 0; 256 bool launch_platform_app = (flags & kFlagLaunchPlatformApp) != 0; 257 bool use_incognito = (flags & kFlagUseIncognito) != 0; 258 259 if (custom_arg && custom_arg[0]) 260 test_config_->SetString(kTestCustomArg, custom_arg); 261 262 extensions::ResultCatcher catcher; 263 DCHECK(!extension_name.empty() || !page_url.empty()) << 264 "extension_name and page_url cannot both be empty"; 265 266 const extensions::Extension* extension = NULL; 267 if (!extension_name.empty()) { 268 base::FilePath extension_path = test_data_dir_.AppendASCII(extension_name); 269 if (load_as_component) { 270 extension = LoadExtensionAsComponent(extension_path); 271 } else { 272 int browser_test_flags = ExtensionBrowserTest::kFlagNone; 273 if (flags & kFlagEnableIncognito) 274 browser_test_flags |= ExtensionBrowserTest::kFlagEnableIncognito; 275 if (flags & kFlagEnableFileAccess) 276 browser_test_flags |= ExtensionBrowserTest::kFlagEnableFileAccess; 277 if (flags & kFlagIgnoreManifestWarnings) 278 browser_test_flags |= ExtensionBrowserTest::kFlagIgnoreManifestWarnings; 279 if (flags & kFlagAllowOldManifestVersions) { 280 browser_test_flags |= 281 ExtensionBrowserTest::kFlagAllowOldManifestVersions; 282 } 283 extension = LoadExtensionWithFlags(extension_path, browser_test_flags); 284 } 285 if (!extension) { 286 message_ = "Failed to load extension."; 287 return false; 288 } 289 } 290 291 // If there is a page_url to load, navigate it. 292 if (!page_url.empty()) { 293 GURL url = GURL(page_url); 294 295 // Note: We use is_valid() here in the expectation that the provided url 296 // may lack a scheme & host and thus be a relative url within the loaded 297 // extension. 298 if (!url.is_valid()) { 299 DCHECK(!extension_name.empty()) << 300 "Relative page_url given with no extension_name"; 301 302 url = extension->GetResourceURL(page_url); 303 } 304 305 if (use_incognito) 306 ui_test_utils::OpenURLOffTheRecord(browser()->profile(), url); 307 else 308 ui_test_utils::NavigateToURL(browser(), url); 309 } else if (launch_platform_app) { 310 AppLaunchParams params(browser()->profile(), 311 extension, 312 extensions::LAUNCH_CONTAINER_NONE, 313 NEW_WINDOW); 314 params.command_line = *CommandLine::ForCurrentProcess(); 315 OpenApplication(params); 316 } 317 318 if (!catcher.GetNextResult()) { 319 message_ = catcher.message(); 320 return false; 321 } 322 323 return true; 324 } 325 326 // Test that exactly one extension is loaded, and return it. 327 const extensions::Extension* ExtensionApiTest::GetSingleLoadedExtension() { 328 ExtensionService* service = extensions::ExtensionSystem::Get( 329 browser()->profile())->extension_service(); 330 331 const extensions::Extension* extension = NULL; 332 for (extensions::ExtensionSet::const_iterator it = 333 service->extensions()->begin(); 334 it != service->extensions()->end(); ++it) { 335 // Ignore any component extensions. They are automatically loaded into all 336 // profiles and aren't the extension we're looking for here. 337 if ((*it)->location() == extensions::Manifest::COMPONENT) 338 continue; 339 340 if (extension != NULL) { 341 // TODO(yoz): this is misleading; it counts component extensions. 342 message_ = base::StringPrintf( 343 "Expected only one extension to be present. Found %u.", 344 static_cast<unsigned>(service->extensions()->size())); 345 return NULL; 346 } 347 348 extension = it->get(); 349 } 350 351 if (!extension) { 352 message_ = "extension pointer is NULL."; 353 return NULL; 354 } 355 return extension; 356 } 357 358 bool ExtensionApiTest::StartEmbeddedTestServer() { 359 if (!embedded_test_server()->InitializeAndWaitUntilReady()) 360 return false; 361 362 // Build a dictionary of values that tests can use to build URLs that 363 // access the test server and local file system. Tests can see these values 364 // using the extension API function chrome.test.getConfig(). 365 test_config_->SetInteger(kTestServerPort, 366 embedded_test_server()->port()); 367 368 return true; 369 } 370 371 bool ExtensionApiTest::StartWebSocketServer( 372 const base::FilePath& root_directory) { 373 websocket_server_.reset(new net::SpawnedTestServer( 374 net::SpawnedTestServer::TYPE_WS, 375 net::SpawnedTestServer::kLocalhost, 376 root_directory)); 377 378 if (!websocket_server_->Start()) 379 return false; 380 381 test_config_->SetInteger(kTestWebSocketPort, 382 websocket_server_->host_port_pair().port()); 383 384 return true; 385 } 386 387 bool ExtensionApiTest::StartFTPServer(const base::FilePath& root_directory) { 388 ftp_server_.reset(new net::SpawnedTestServer( 389 net::SpawnedTestServer::TYPE_FTP, 390 net::SpawnedTestServer::kLocalhost, 391 root_directory)); 392 393 if (!ftp_server_->Start()) 394 return false; 395 396 test_config_->SetInteger(kFtpServerPort, 397 ftp_server_->host_port_pair().port()); 398 399 return true; 400 } 401 402 bool ExtensionApiTest::StartSpawnedTestServer() { 403 if (!test_server()->Start()) 404 return false; 405 406 // Build a dictionary of values that tests can use to build URLs that 407 // access the test server and local file system. Tests can see these values 408 // using the extension API function chrome.test.getConfig(). 409 test_config_->SetInteger(kSpawnedTestServerPort, 410 test_server()->host_port_pair().port()); 411 412 return true; 413 } 414 415 void ExtensionApiTest::SetUpCommandLine(CommandLine* command_line) { 416 ExtensionBrowserTest::SetUpCommandLine(command_line); 417 test_data_dir_ = test_data_dir_.AppendASCII("api_test"); 418 } 419