// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/extensions/extension_apitest.h" #include "base/base_switches.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/unpacked_installer.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/extensions/app_launch_params.h" #include "chrome/browser/ui/extensions/application_launch.h" #include "chrome/test/base/ui_test_utils.h" #include "content/public/common/content_switches.h" #include "extensions/browser/api/test/test_api.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_system.h" #include "extensions/common/constants.h" #include "extensions/common/extension.h" #include "extensions/common/extension_set.h" #include "extensions/test/result_catcher.h" #include "net/base/escape.h" #include "net/base/filename_util.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "net/test/embedded_test_server/http_request.h" #include "net/test/embedded_test_server/http_response.h" #include "net/test/spawned_test_server/spawned_test_server.h" namespace { const char kTestCustomArg[] = "customArg"; const char kTestServerPort[] = "testServer.port"; const char kTestDataDirectory[] = "testDataDirectory"; const char kTestWebSocketPort[] = "testWebSocketPort"; const char kSitePerProcess[] = "sitePerProcess"; const char kFtpServerPort[] = "ftpServer.port"; const char kSpawnedTestServerPort[] = "spawnedTestServer.port"; scoped_ptr HandleServerRedirectRequest( const net::test_server::HttpRequest& request) { if (!base::StartsWith(request.relative_url, "/server-redirect?", base::CompareCase::SENSITIVE)) return nullptr; size_t query_string_pos = request.relative_url.find('?'); std::string redirect_target = request.relative_url.substr(query_string_pos + 1); scoped_ptr http_response( new net::test_server::BasicHttpResponse); http_response->set_code(net::HTTP_MOVED_PERMANENTLY); http_response->AddCustomHeader("Location", redirect_target); return http_response.Pass(); } scoped_ptr HandleEchoHeaderRequest( const net::test_server::HttpRequest& request) { if (!base::StartsWith(request.relative_url, "/echoheader?", base::CompareCase::SENSITIVE)) return nullptr; size_t query_string_pos = request.relative_url.find('?'); std::string header_name = request.relative_url.substr(query_string_pos + 1); std::string header_value; std::map::const_iterator it = request.headers.find( header_name); if (it != request.headers.end()) header_value = it->second; scoped_ptr http_response( new net::test_server::BasicHttpResponse); http_response->set_code(net::HTTP_OK); http_response->set_content(header_value); return http_response.Pass(); } scoped_ptr HandleSetCookieRequest( const net::test_server::HttpRequest& request) { if (!base::StartsWith(request.relative_url, "/set-cookie?", base::CompareCase::SENSITIVE)) return nullptr; scoped_ptr http_response( new net::test_server::BasicHttpResponse); http_response->set_code(net::HTTP_OK); size_t query_string_pos = request.relative_url.find('?'); std::string cookie_value = request.relative_url.substr(query_string_pos + 1); for (const std::string& cookie : base::SplitString( cookie_value, "&", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) http_response->AddCustomHeader("Set-Cookie", cookie); return http_response.Pass(); } scoped_ptr HandleSetHeaderRequest( const net::test_server::HttpRequest& request) { if (!base::StartsWith(request.relative_url, "/set-header?", base::CompareCase::SENSITIVE)) return nullptr; size_t query_string_pos = request.relative_url.find('?'); std::string escaped_header = request.relative_url.substr(query_string_pos + 1); std::string header = net::UnescapeURLComponent(escaped_header, net::UnescapeRule::NORMAL | net::UnescapeRule::SPACES | net::UnescapeRule::URL_SPECIAL_CHARS); size_t colon_pos = header.find(':'); if (colon_pos == std::string::npos) return scoped_ptr(); std::string header_name = header.substr(0, colon_pos); // Skip space after colon. std::string header_value = header.substr(colon_pos + 2); scoped_ptr http_response( new net::test_server::BasicHttpResponse); http_response->set_code(net::HTTP_OK); http_response->AddCustomHeader(header_name, header_value); return http_response.Pass(); } }; // namespace ExtensionApiTest::ExtensionApiTest() { embedded_test_server()->RegisterRequestHandler( base::Bind(&HandleServerRedirectRequest)); embedded_test_server()->RegisterRequestHandler( base::Bind(&HandleEchoHeaderRequest)); embedded_test_server()->RegisterRequestHandler( base::Bind(&HandleSetCookieRequest)); embedded_test_server()->RegisterRequestHandler( base::Bind(&HandleSetHeaderRequest)); } ExtensionApiTest::~ExtensionApiTest() {} void ExtensionApiTest::SetUpInProcessBrowserTestFixture() { DCHECK(!test_config_.get()) << "Previous test did not clear config state."; test_config_.reset(new base::DictionaryValue()); test_config_->SetString(kTestDataDirectory, net::FilePathToFileURL(test_data_dir_).spec()); test_config_->SetInteger(kTestWebSocketPort, 0); test_config_->SetBoolean(kSitePerProcess, base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kSitePerProcess)); extensions::TestGetConfigFunction::set_test_config_state( test_config_.get()); } void ExtensionApiTest::TearDownInProcessBrowserTestFixture() { extensions::TestGetConfigFunction::set_test_config_state(NULL); test_config_.reset(NULL); } bool ExtensionApiTest::RunExtensionTest(const std::string& extension_name) { return RunExtensionTestImpl( extension_name, std::string(), NULL, kFlagEnableFileAccess); } bool ExtensionApiTest::RunExtensionTestIncognito( const std::string& extension_name) { return RunExtensionTestImpl(extension_name, std::string(), NULL, kFlagEnableIncognito | kFlagEnableFileAccess); } bool ExtensionApiTest::RunExtensionTestIgnoreManifestWarnings( const std::string& extension_name) { return RunExtensionTestImpl( extension_name, std::string(), NULL, kFlagIgnoreManifestWarnings); } bool ExtensionApiTest::RunExtensionTestAllowOldManifestVersion( const std::string& extension_name) { return RunExtensionTestImpl( extension_name, std::string(), NULL, kFlagEnableFileAccess | kFlagAllowOldManifestVersions); } bool ExtensionApiTest::RunComponentExtensionTest( const std::string& extension_name) { return RunExtensionTestImpl(extension_name, std::string(), NULL, kFlagEnableFileAccess | kFlagLoadAsComponent); } bool ExtensionApiTest::RunExtensionTestNoFileAccess( const std::string& extension_name) { return RunExtensionTestImpl(extension_name, std::string(), NULL, kFlagNone); } bool ExtensionApiTest::RunExtensionTestIncognitoNoFileAccess( const std::string& extension_name) { return RunExtensionTestImpl( extension_name, std::string(), NULL, kFlagEnableIncognito); } bool ExtensionApiTest::ExtensionSubtestsAreSkipped() { // See http://crbug.com/177163 for details. #if defined(OS_WIN) && !defined(NDEBUG) LOG(WARNING) << "Workaround for 177163, prematurely returning"; return true; #else return false; #endif } bool ExtensionApiTest::RunExtensionSubtest(const std::string& extension_name, const std::string& page_url) { return RunExtensionSubtest(extension_name, page_url, kFlagEnableFileAccess); } bool ExtensionApiTest::RunExtensionSubtest(const std::string& extension_name, const std::string& page_url, int flags) { DCHECK(!page_url.empty()) << "Argument page_url is required."; if (ExtensionSubtestsAreSkipped()) return true; return RunExtensionTestImpl(extension_name, page_url, NULL, flags); } bool ExtensionApiTest::RunPageTest(const std::string& page_url) { return RunExtensionSubtest(std::string(), page_url); } bool ExtensionApiTest::RunPageTest(const std::string& page_url, int flags) { return RunExtensionSubtest(std::string(), page_url, flags); } bool ExtensionApiTest::RunPlatformAppTest(const std::string& extension_name) { return RunExtensionTestImpl( extension_name, std::string(), NULL, kFlagLaunchPlatformApp); } bool ExtensionApiTest::RunPlatformAppTestWithArg( const std::string& extension_name, const char* custom_arg) { return RunExtensionTestImpl( extension_name, std::string(), custom_arg, kFlagLaunchPlatformApp); } bool ExtensionApiTest::RunPlatformAppTestWithFlags( const std::string& extension_name, int flags) { return RunExtensionTestImpl( extension_name, std::string(), NULL, flags | kFlagLaunchPlatformApp); } // Load |extension_name| extension and/or |page_url| and wait for // PASSED or FAILED notification. bool ExtensionApiTest::RunExtensionTestImpl(const std::string& extension_name, const std::string& page_url, const char* custom_arg, int flags) { bool load_as_component = (flags & kFlagLoadAsComponent) != 0; bool launch_platform_app = (flags & kFlagLaunchPlatformApp) != 0; bool use_incognito = (flags & kFlagUseIncognito) != 0; if (custom_arg && custom_arg[0]) test_config_->SetString(kTestCustomArg, custom_arg); extensions::ResultCatcher catcher; DCHECK(!extension_name.empty() || !page_url.empty()) << "extension_name and page_url cannot both be empty"; const extensions::Extension* extension = NULL; if (!extension_name.empty()) { base::FilePath extension_path = test_data_dir_.AppendASCII(extension_name); if (load_as_component) { extension = LoadExtensionAsComponent(extension_path); } else { int browser_test_flags = ExtensionBrowserTest::kFlagNone; if (flags & kFlagEnableIncognito) browser_test_flags |= ExtensionBrowserTest::kFlagEnableIncognito; if (flags & kFlagEnableFileAccess) browser_test_flags |= ExtensionBrowserTest::kFlagEnableFileAccess; if (flags & kFlagIgnoreManifestWarnings) browser_test_flags |= ExtensionBrowserTest::kFlagIgnoreManifestWarnings; if (flags & kFlagAllowOldManifestVersions) { browser_test_flags |= ExtensionBrowserTest::kFlagAllowOldManifestVersions; } extension = LoadExtensionWithFlags(extension_path, browser_test_flags); } if (!extension) { message_ = "Failed to load extension."; return false; } } // If there is a page_url to load, navigate it. if (!page_url.empty()) { GURL url = GURL(page_url); // Note: We use is_valid() here in the expectation that the provided url // may lack a scheme & host and thus be a relative url within the loaded // extension. if (!url.is_valid()) { DCHECK(!extension_name.empty()) << "Relative page_url given with no extension_name"; url = extension->GetResourceURL(page_url); } if (use_incognito) OpenURLOffTheRecord(browser()->profile(), url); else ui_test_utils::NavigateToURL(browser(), url); } else if (launch_platform_app) { AppLaunchParams params(browser()->profile(), extension, extensions::LAUNCH_CONTAINER_NONE, NEW_WINDOW, extensions::SOURCE_TEST); params.command_line = *base::CommandLine::ForCurrentProcess(); OpenApplication(params); } if (!catcher.GetNextResult()) { message_ = catcher.message(); return false; } return true; } // Test that exactly one extension is loaded, and return it. const extensions::Extension* ExtensionApiTest::GetSingleLoadedExtension() { extensions::ExtensionRegistry* registry = extensions::ExtensionRegistry::Get(browser()->profile()); const extensions::Extension* result = NULL; for (const scoped_refptr& extension : registry->enabled_extensions()) { // Ignore any component extensions. They are automatically loaded into all // profiles and aren't the extension we're looking for here. if (extension->location() == extensions::Manifest::COMPONENT) continue; if (result != NULL) { // TODO(yoz): this is misleading; it counts component extensions. message_ = base::StringPrintf( "Expected only one extension to be present. Found %u.", static_cast(registry->enabled_extensions().size())); return NULL; } result = extension.get(); } if (!result) { message_ = "extension pointer is NULL."; return NULL; } return result; } bool ExtensionApiTest::StartEmbeddedTestServer() { if (!embedded_test_server()->InitializeAndWaitUntilReady()) return false; // Build a dictionary of values that tests can use to build URLs that // access the test server and local file system. Tests can see these values // using the extension API function chrome.test.getConfig(). test_config_->SetInteger(kTestServerPort, embedded_test_server()->port()); return true; } bool ExtensionApiTest::StartWebSocketServer( const base::FilePath& root_directory) { websocket_server_.reset(new net::SpawnedTestServer( net::SpawnedTestServer::TYPE_WS, net::SpawnedTestServer::kLocalhost, root_directory)); if (!websocket_server_->Start()) return false; test_config_->SetInteger(kTestWebSocketPort, websocket_server_->host_port_pair().port()); return true; } bool ExtensionApiTest::StartFTPServer(const base::FilePath& root_directory) { ftp_server_.reset(new net::SpawnedTestServer( net::SpawnedTestServer::TYPE_FTP, net::SpawnedTestServer::kLocalhost, root_directory)); if (!ftp_server_->Start()) return false; test_config_->SetInteger(kFtpServerPort, ftp_server_->host_port_pair().port()); return true; } bool ExtensionApiTest::StartSpawnedTestServer() { if (!test_server()->Start()) return false; // Build a dictionary of values that tests can use to build URLs that // access the test server and local file system. Tests can see these values // using the extension API function chrome.test.getConfig(). test_config_->SetInteger(kSpawnedTestServerPort, test_server()->host_port_pair().port()); return true; } void ExtensionApiTest::SetUpCommandLine(base::CommandLine* command_line) { ExtensionBrowserTest::SetUpCommandLine(command_line); test_data_dir_ = test_data_dir_.AppendASCII("api_test"); // Backgrounded renderer processes run at a lower priority, causing the // tests to take more time to complete. Disable backgrounding so that the // tests don't time out. command_line->AppendSwitch(switches::kDisableRendererBackgrounding); }