#include "base/base64.h"
#include "base/basictypes.h"
+#include "base/bind.h"
#include "base/command_line.h"
-#include "base/file_util.h"
#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
#include "base/format_macros.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "base/values.h"
+#include "chrome/common/chrome_constants.h"
#include "chrome/test/chromedriver/chrome/chrome_android_impl.h"
#include "chrome/test/chromedriver/chrome/chrome_desktop_impl.h"
-#include "chrome/test/chromedriver/chrome/chrome_existing_impl.h"
#include "chrome/test/chromedriver/chrome/chrome_finder.h"
+#include "chrome/test/chromedriver/chrome/chrome_remote_impl.h"
#include "chrome/test/chromedriver/chrome/device_manager.h"
+#include "chrome/test/chromedriver/chrome/devtools_client_impl.h"
+#include "chrome/test/chromedriver/chrome/devtools_event_listener.h"
#include "chrome/test/chromedriver/chrome/devtools_http_client.h"
#include "chrome/test/chromedriver/chrome/embedded_automation_extension.h"
#include "chrome/test/chromedriver/chrome/status.h"
#include "chrome/test/chromedriver/chrome/user_data_dir.h"
#include "chrome/test/chromedriver/chrome/version.h"
#include "chrome/test/chromedriver/chrome/web_view.h"
-#include "chrome/test/chromedriver/chrome/zip.h"
#include "chrome/test/chromedriver/net/port_server.h"
#include "chrome/test/chromedriver/net/url_request_context_getter.h"
+#include "crypto/rsa_private_key.h"
#include "crypto/sha2.h"
+#include "third_party/zlib/google/zip.h"
#if defined(OS_POSIX)
#include <fcntl.h>
base::FilePath extension_zip = temp_dir.AppendASCII("internal.zip");
int size = static_cast<int>(decoded_extension.length());
- if (file_util::WriteFile(extension_zip, decoded_extension.c_str(), size)
+ if (base::WriteFile(extension_zip, decoded_extension.c_str(), size)
!= size) {
return Status(kUnknownError, "failed to write automation extension zip");
}
CommandLine command(program);
Switches switches;
- // TODO(chrisgao): Add "disable-sync" when chrome 30- is not supported.
- // For chrome 30-, it leads to crash when opening chrome://settings.
for (size_t i = 0; i < arraysize(kCommonSwitches); ++i)
switches.SetSwitch(kCommonSwitches[i]);
switches.SetSwitch("disable-hang-monitor");
switches.SetSwitch("disable-prompt-on-repost");
- switches.SetSwitch("full-memory-crash-report");
+ switches.SetSwitch("disable-sync");
switches.SetSwitch("no-first-run");
switches.SetSwitch("disable-background-networking");
switches.SetSwitch("disable-web-resources");
switches.SetSwitch("disable-component-update");
switches.SetSwitch("disable-default-apps");
switches.SetSwitch("enable-logging");
- switches.SetSwitch("logging-level", "1");
+ switches.SetSwitch("log-level", "0");
switches.SetSwitch("password-store", "basic");
switches.SetSwitch("use-mock-keychain");
switches.SetSwitch("remote-debugging-port", base::IntToString(port));
+ switches.SetSwitch("test-type", "webdriver");
for (std::set<std::string>::const_iterator iter =
capabilities.exclude_switches.begin();
const NetAddress& address,
URLRequestContextGetter* context_getter,
const SyncWebSocketFactory& socket_factory,
+ const Capabilities* capabilities,
scoped_ptr<DevToolsHttpClient>* user_client) {
+ scoped_ptr<DeviceMetrics> device_metrics;
+ if (capabilities && capabilities->device_metrics)
+ device_metrics.reset(new DeviceMetrics(*capabilities->device_metrics));
+
scoped_ptr<DevToolsHttpClient> client(new DevToolsHttpClient(
- address, context_getter, socket_factory));
+ address, context_getter, socket_factory, device_metrics.Pass()));
base::TimeTicks deadline =
- base::TimeTicks::Now() + base::TimeDelta::FromSeconds(20);
+ base::TimeTicks::Now() + base::TimeDelta::FromSeconds(60);
Status status = client->Init(deadline - base::TimeTicks::Now());
if (status.IsError())
return status;
- if (client->build_no() < kMinimumSupportedChromeBuildNo) {
+ if (client->browser_info()->build_no < kMinimumSupportedChromeBuildNo) {
return Status(kUnknownError, "Chrome version must be >= " +
GetMinimumSupportedChromeVersion());
}
return Status(kUnknownError, "unable to discover open pages");
}
-Status LaunchExistingChromeSession(
+Status CreateBrowserwideDevToolsClientAndConnect(
+ const NetAddress& address,
+ const PerfLoggingPrefs& perf_logging_prefs,
+ const SyncWebSocketFactory& socket_factory,
+ ScopedVector<DevToolsEventListener>& devtools_event_listeners,
+ scoped_ptr<DevToolsClient>* browser_client) {
+ scoped_ptr<DevToolsClient> client(new DevToolsClientImpl(
+ socket_factory, base::StringPrintf("ws://%s/devtools/browser/",
+ address.ToString().c_str()),
+ DevToolsClientImpl::kBrowserwideDevToolsClientId));
+ for (ScopedVector<DevToolsEventListener>::const_iterator it =
+ devtools_event_listeners.begin();
+ it != devtools_event_listeners.end();
+ ++it) {
+ // Only add listeners that subscribe to the browser-wide |DevToolsClient|.
+ // Otherwise, listeners will think this client is associated with a webview,
+ // and will send unrecognized commands to it.
+ if ((*it)->subscribes_to_browser())
+ client->AddListener(*it);
+ }
+ // Provide the client regardless of whether it connects, so that Chrome always
+ // has a valid |devtools_websocket_client_|. If not connected, no listeners
+ // will be notified, and client will just return kDisconnected errors if used.
+ *browser_client = client.Pass();
+ // To avoid unnecessary overhead, only connect if tracing is enabled, since
+ // the browser-wide client is currently only used for tracing.
+ if (!perf_logging_prefs.trace_categories.empty()) {
+ Status status = (*browser_client)->ConnectIfNecessary();
+ if (status.IsError())
+ return status;
+ }
+ return Status(kOk);
+}
+
+Status LaunchRemoteChromeSession(
URLRequestContextGetter* context_getter,
const SyncWebSocketFactory& socket_factory,
const Capabilities& capabilities,
ScopedVector<DevToolsEventListener>& devtools_event_listeners,
scoped_ptr<Chrome>* chrome) {
Status status(kOk);
- scoped_ptr<DevToolsHttpClient> devtools_client;
+ scoped_ptr<DevToolsHttpClient> devtools_http_client;
status = WaitForDevToolsAndCheckVersion(
capabilities.debugger_address, context_getter, socket_factory,
- &devtools_client);
+ NULL, &devtools_http_client);
if (status.IsError()) {
return Status(kUnknownError, "cannot connect to chrome at " +
capabilities.debugger_address.ToString(),
status);
}
- chrome->reset(new ChromeExistingImpl(devtools_client.Pass(),
- devtools_event_listeners));
+
+ scoped_ptr<DevToolsClient> devtools_websocket_client;
+ status = CreateBrowserwideDevToolsClientAndConnect(
+ capabilities.debugger_address, capabilities.perf_logging_prefs,
+ socket_factory, devtools_event_listeners, &devtools_websocket_client);
+ if (status.IsError()) {
+ LOG(WARNING) << "Browser-wide DevTools client failed to connect: "
+ << status.message();
+ }
+
+ chrome->reset(new ChromeRemoteImpl(devtools_http_client.Pass(),
+ devtools_websocket_client.Pass(),
+ devtools_event_listeners));
return Status(kOk);
}
if (!command.HasSwitch(kEnableCrashReport))
command.AppendSwitch(kEnableCrashReport);
}
+
+ // We need to allow new privileges so that chrome's setuid sandbox can run.
+ options.allow_new_privs = true;
#endif
#if !defined(OS_WIN)
#if defined(OS_POSIX)
base::FileHandleMappingVector no_stderr;
- int devnull = -1;
- file_util::ScopedFD scoped_devnull(&devnull);
+ base::ScopedFD devnull;
if (!CommandLine::ForCurrentProcess()->HasSwitch("verbose")) {
// Redirect stderr to /dev/null, so that Chrome log spew doesn't confuse
// users.
- devnull = open("/dev/null", O_WRONLY);
- if (devnull == -1)
+ devnull.reset(HANDLE_EINTR(open("/dev/null", O_WRONLY)));
+ if (!devnull.is_valid())
return Status(kUnknownError, "couldn't open /dev/null");
- no_stderr.push_back(std::make_pair(devnull, STDERR_FILENO));
+ no_stderr.push_back(std::make_pair(devnull.get(), STDERR_FILENO));
options.fds_to_remap = &no_stderr;
}
#endif
if (!base::LaunchProcess(command, options, &process))
return Status(kUnknownError, "chrome failed to start");
- scoped_ptr<DevToolsHttpClient> devtools_client;
+ scoped_ptr<DevToolsHttpClient> devtools_http_client;
status = WaitForDevToolsAndCheckVersion(
- NetAddress(port), context_getter, socket_factory, &devtools_client);
+ NetAddress(port), context_getter, socket_factory, &capabilities,
+ &devtools_http_client);
if (status.IsError()) {
int exit_code;
}
return status;
}
+
+ scoped_ptr<DevToolsClient> devtools_websocket_client;
+ status = CreateBrowserwideDevToolsClientAndConnect(
+ NetAddress(port), capabilities.perf_logging_prefs, socket_factory,
+ devtools_event_listeners, &devtools_websocket_client);
+ if (status.IsError()) {
+ LOG(WARNING) << "Browser-wide DevTools client failed to connect: "
+ << status.message();
+ }
+
scoped_ptr<ChromeDesktopImpl> chrome_desktop(
- new ChromeDesktopImpl(devtools_client.Pass(),
+ new ChromeDesktopImpl(devtools_http_client.Pass(),
+ devtools_websocket_client.Pass(),
devtools_event_listeners,
port_reservation.Pass(),
process,
status = device_manager->AcquireSpecificDevice(
capabilities.android_device_serial, &device);
}
- if (!status.IsOk())
+ if (status.IsError())
return status;
Switches switches(capabilities.switches);
switches.SetSwitch(kCommonSwitches[i]);
switches.SetSwitch("disable-fre");
switches.SetSwitch("enable-remote-debugging");
- status = device->StartApp(capabilities.android_package,
- capabilities.android_activity,
- capabilities.android_process,
- switches.ToString(), port);
- if (!status.IsOk()) {
- device->StopApp();
+ status = device->SetUp(capabilities.android_package,
+ capabilities.android_activity,
+ capabilities.android_process,
+ switches.ToString(),
+ capabilities.android_use_running_app,
+ port);
+ if (status.IsError()) {
+ device->TearDown();
return status;
}
- scoped_ptr<DevToolsHttpClient> devtools_client;
+ scoped_ptr<DevToolsHttpClient> devtools_http_client;
status = WaitForDevToolsAndCheckVersion(NetAddress(port),
context_getter,
socket_factory,
- &devtools_client);
- if (status.IsError())
+ &capabilities,
+ &devtools_http_client);
+ if (status.IsError()) {
+ device->TearDown();
return status;
+ }
- chrome->reset(new ChromeAndroidImpl(devtools_client.Pass(),
+ scoped_ptr<DevToolsClient> devtools_websocket_client;
+ status = CreateBrowserwideDevToolsClientAndConnect(
+ NetAddress(port), capabilities.perf_logging_prefs, socket_factory,
+ devtools_event_listeners, &devtools_websocket_client);
+ if (status.IsError()) {
+ LOG(WARNING) << "Browser-wide DevTools client failed to connect: "
+ << status.message();
+ }
+
+ chrome->reset(new ChromeAndroidImpl(devtools_http_client.Pass(),
+ devtools_websocket_client.Pass(),
devtools_event_listeners,
port_reservation.Pass(),
device.Pass()));
const Capabilities& capabilities,
ScopedVector<DevToolsEventListener>& devtools_event_listeners,
scoped_ptr<Chrome>* chrome) {
- if (capabilities.IsExistingBrowser()) {
- return LaunchExistingChromeSession(
+ if (capabilities.IsRemoteBrowser()) {
+ return LaunchRemoteChromeSession(
context_getter, socket_factory,
capabilities, devtools_event_listeners, chrome);
}
int port = 0;
scoped_ptr<PortReservation> port_reservation;
Status port_status(kOk);
- if (port_server)
- port_status = port_server->ReservePort(&port, &port_reservation);
- else
- port_status = port_manager->ReservePort(&port, &port_reservation);
- if (port_status.IsError())
- return Status(kUnknownError, "cannot reserve port for Chrome", port_status);
if (capabilities.IsAndroid()) {
+ port_status = port_manager->ReservePortFromPool(&port, &port_reservation);
+ if (port_status.IsError())
+ return Status(kUnknownError, "cannot reserve port for Chrome",
+ port_status);
return LaunchAndroidChrome(context_getter,
port,
port_reservation.Pass(),
device_manager,
chrome);
} else {
+ if (port_server)
+ port_status = port_server->ReservePort(&port, &port_reservation);
+ else
+ port_status = port_manager->ReservePort(&port, &port_reservation);
+ if (port_status.IsError())
+ return Status(kUnknownError, "cannot reserve port for Chrome",
+ port_status);
return LaunchDesktopChrome(context_getter,
port,
port_reservation.Pass(),
std::string GenerateExtensionId(const std::string& input) {
uint8 hash[16];
crypto::SHA256HashString(input, hash, sizeof(hash));
- std::string output = StringToLowerASCII(base::HexEncode(hash, sizeof(hash)));
+ std::string output =
+ base::StringToLowerASCII(base::HexEncode(hash, sizeof(hash)));
ConvertHexadecimalToIDAlphabet(&output);
return output;
}
// 'encoded lines be no more than 76 characters long'. Just remove any
// newlines.
std::string extension_base64;
- RemoveChars(extension, "\n", &extension_base64);
+ base::RemoveChars(extension, "\n", &extension_base64);
std::string decoded_extension;
if (!base::Base64Decode(extension_base64, &decoded_extension))
return Status(kUnknownError, "cannot base64 decode");
- // Get extension's ID from public key in crx file.
- // Assumes crx v2. See http://developer.chrome.com/extensions/crx.html.
- std::string key_len_str = decoded_extension.substr(8, 4);
- if (key_len_str.size() != 4)
- return Status(kUnknownError, "cannot extract public key length");
- uint32 key_len = *reinterpret_cast<const uint32*>(key_len_str.c_str());
- std::string public_key = decoded_extension.substr(16, key_len);
- if (key_len != public_key.size())
- return Status(kUnknownError, "invalid public key length");
+ // If the file is a crx file, extract the extension's ID from its public key.
+ // Otherwise generate a random public key and use its derived extension ID.
+ std::string public_key;
+ std::string magic_header = decoded_extension.substr(0, 4);
+ if (magic_header.size() != 4)
+ return Status(kUnknownError, "cannot extract magic number");
+
+ const bool is_crx_file = magic_header == "Cr24";
+
+ if (is_crx_file) {
+ // Assume a CRX v2 file - see https://developer.chrome.com/extensions/crx.
+ std::string key_len_str = decoded_extension.substr(8, 4);
+ if (key_len_str.size() != 4)
+ return Status(kUnknownError, "cannot extract public key length");
+ uint32 key_len = *reinterpret_cast<const uint32*>(key_len_str.c_str());
+ public_key = decoded_extension.substr(16, key_len);
+ if (key_len != public_key.size())
+ return Status(kUnknownError, "invalid public key length");
+ } else {
+ // Not a CRX file. Generate RSA keypair to get a valid extension id.
+ scoped_ptr<crypto::RSAPrivateKey> key_pair(
+ crypto::RSAPrivateKey::Create(2048));
+ if (!key_pair)
+ return Status(kUnknownError, "cannot generate RSA key pair");
+ std::vector<uint8> public_key_vector;
+ if (!key_pair->ExportPublicKey(&public_key_vector))
+ return Status(kUnknownError, "cannot extract public key");
+ public_key =
+ std::string(reinterpret_cast<char*>(&public_key_vector.front()),
+ public_key_vector.size());
+ }
std::string public_key_base64;
- if (!base::Base64Encode(public_key, &public_key_base64))
- return Status(kUnknownError, "cannot base64 encode public key");
+ base::Base64Encode(public_key, &public_key_base64);
std::string id = GenerateExtensionId(public_key);
// Unzip the crx file.
return Status(kUnknownError, "cannot create temp dir");
base::FilePath extension_crx = temp_crx_dir.path().AppendASCII("temp.crx");
int size = static_cast<int>(decoded_extension.length());
- if (file_util::WriteFile(extension_crx, decoded_extension.c_str(), size) !=
+ if (base::WriteFile(extension_crx, decoded_extension.c_str(), size) !=
size) {
return Status(kUnknownError, "cannot write file");
}
base::DictionaryValue* manifest;
if (!manifest_value || !manifest_value->GetAsDictionary(&manifest))
return Status(kUnknownError, "invalid manifest");
- if (!manifest->HasKey("key")) {
+
+ std::string manifest_key_base64;
+ if (manifest->GetString("key", &manifest_key_base64)) {
+ // If there is a key in both the header and the manifest, use the key in the
+ // manifest. This allows chromedriver users users who generate dummy crxs
+ // to set the manifest key and have a consistent ID.
+ std::string manifest_key;
+ if (!base::Base64Decode(manifest_key_base64, &manifest_key))
+ return Status(kUnknownError, "'key' in manifest is not base64 encoded");
+ std::string manifest_id = GenerateExtensionId(manifest_key);
+ if (id != manifest_id) {
+ if (is_crx_file) {
+ LOG(WARNING)
+ << "Public key in crx header is different from key in manifest"
+ << std::endl << "key from header: " << public_key_base64
+ << std::endl << "key from manifest: " << manifest_key_base64
+ << std::endl << "generated extension id from header key: " << id
+ << std::endl << "generated extension id from manifest key: "
+ << manifest_id;
+ }
+ id = manifest_id;
+ }
+ } else {
manifest->SetString("key", public_key_base64);
base::JSONWriter::Write(manifest, &manifest_data);
- if (file_util::WriteFile(
+ if (base::WriteFile(
manifest_path, manifest_data.c_str(), manifest_data.size()) !=
static_cast<int>(manifest_data.size())) {
return Status(kUnknownError, "cannot add 'key' to manifest");
base::JSONWriter::Write(prefs, &prefs_str);
VLOG(0) << "Populating " << path.BaseName().value()
<< " file: " << PrettyPrintValue(*prefs);
- if (static_cast<int>(prefs_str.length()) != file_util::WriteFile(
+ if (static_cast<int>(prefs_str.length()) != base::WriteFile(
path, prefs_str.c_str(), prefs_str.length())) {
return Status(kUnknownError, "failed to write prefs file");
}
const base::FilePath& user_data_dir,
const base::DictionaryValue* custom_prefs,
const base::DictionaryValue* custom_local_state) {
- base::FilePath default_dir = user_data_dir.AppendASCII("Default");
- if (!file_util::CreateDirectory(default_dir))
+ base::FilePath default_dir =
+ user_data_dir.AppendASCII(chrome::kInitialProfile);
+ if (!base::CreateDirectory(default_dir))
return Status(kUnknownError, "cannot create default profile directory");
- Status status = WritePrefsFile(
- kPreferences,
- custom_prefs,
- default_dir.AppendASCII("Preferences"));
+ Status status =
+ WritePrefsFile(kPreferences,
+ custom_prefs,
+ default_dir.Append(chrome::kPreferencesFilename));
if (status.IsError())
return status;
- status = WritePrefsFile(
- kLocalState,
- custom_local_state,
- user_data_dir.AppendASCII("Local State"));
+ status = WritePrefsFile(kLocalState,
+ custom_local_state,
+ user_data_dir.Append(chrome::kLocalStateFilename));
if (status.IsError())
return status;
// Write empty "First Run" file, otherwise Chrome will wipe the default
// profile that was written.
- if (file_util::WriteFile(
- user_data_dir.AppendASCII("First Run"), "", 0) != 0) {
+ if (base::WriteFile(
+ user_data_dir.Append(chrome::kFirstRunSentinel), "", 0) != 0) {
return Status(kUnknownError, "failed to write first run file");
}
return Status(kOk);