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